From 987a6dd1e554b64453ac83c534c546ebb6fbda35 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Fri, 29 May 2026 23:22:49 +0200 Subject: [PATCH] feat(clients/inbounds): IP log popups, clearer titles, tag-based inbound labels Add an IP Log popup (view list + refresh + clear) to the client edit form and the Client Information modal, with IPs stacked vertically. Identify inbounds by their xray tag (not remark/protocol:port) across every picker and chip: attach/detach modals, the attached-inbounds column and field, the filter drawer, and bulk-add. Add the tag field to the InboundOption schema (the backend already returned it). Clarify modal titles/labels: Client Information (was More Information) and Inbound Information (was Inbound's Data); Client Information / QR Code titles now include the client email. i18n: rename keys moreInformation->clientInfo and inboundData->inboundInfo with proper translations in all languages; addTitle->addClient, editTitle->editClient, addToGroupPlaceholder->groupName. --- frontend/src/models/setting.ts | 2 +- .../src/pages/clients/BulkAddToGroupModal.tsx | 2 +- .../pages/clients/BulkAttachInboundsModal.tsx | 2 +- .../pages/clients/BulkDetachInboundsModal.tsx | 2 +- .../src/pages/clients/ClientBulkAddModal.tsx | 2 +- .../src/pages/clients/ClientFormModal.tsx | 74 +++++++++---- .../src/pages/clients/ClientInfoModal.tsx | 103 ++++++++++++++++-- frontend/src/pages/clients/ClientQrModal.tsx | 2 +- frontend/src/pages/clients/ClientsPage.tsx | 9 +- frontend/src/pages/clients/FilterDrawer.tsx | 4 +- .../src/pages/inbounds/AttachClientsModal.tsx | 4 +- .../src/pages/inbounds/DetachClientsModal.tsx | 2 +- .../src/pages/inbounds/InboundInfoModal.tsx | 4 +- frontend/src/pages/inbounds/InboundList.tsx | 4 +- frontend/src/schemas/client.ts | 1 + web/translation/ar-EG.json | 10 +- web/translation/en-US.json | 10 +- web/translation/es-ES.json | 10 +- web/translation/fa-IR.json | 10 +- web/translation/id-ID.json | 10 +- web/translation/ja-JP.json | 10 +- web/translation/pt-BR.json | 10 +- web/translation/ru-RU.json | 10 +- web/translation/tr-TR.json | 10 +- web/translation/uk-UA.json | 10 +- web/translation/vi-VN.json | 10 +- web/translation/zh-CN.json | 10 +- web/translation/zh-TW.json | 10 +- 28 files changed, 231 insertions(+), 116 deletions(-) diff --git a/frontend/src/models/setting.ts b/frontend/src/models/setting.ts index ff9d1d7f..24a515af 100644 --- a/frontend/src/models/setting.ts +++ b/frontend/src/models/setting.ts @@ -40,7 +40,7 @@ export class AllSetting { subPort = 2096; subPath = '/sub/'; subJsonPath = '/json/'; - subClashEnable = true; + subClashEnable = false; subClashPath = '/clash/'; subDomain = ''; externalTrafficInformEnable = false; diff --git a/frontend/src/pages/clients/BulkAddToGroupModal.tsx b/frontend/src/pages/clients/BulkAddToGroupModal.tsx index 538ad4c3..bfa8cd4c 100644 --- a/frontend/src/pages/clients/BulkAddToGroupModal.tsx +++ b/frontend/src/pages/clients/BulkAddToGroupModal.tsx @@ -63,7 +63,7 @@ export default function BulkAddToGroupModal({ > ({ value: g }))} onChange={(v) => setValue(v ?? '')} filterOption={(input, option) => diff --git a/frontend/src/pages/clients/BulkAttachInboundsModal.tsx b/frontend/src/pages/clients/BulkAttachInboundsModal.tsx index 7087e80e..8e31bf0e 100644 --- a/frontend/src/pages/clients/BulkAttachInboundsModal.tsx +++ b/frontend/src/pages/clients/BulkAttachInboundsModal.tsx @@ -36,7 +36,7 @@ export default function BulkAttachInboundsModal({ .filter((ib) => MULTI_USER_PROTOCOLS.has((ib.protocol || '').toLowerCase())) .map((ib) => ({ value: ib.id, - label: `${ib.remark ?? ''} (${ib.protocol ?? ''}@${ib.port ?? ''})`, + label: ib.tag, })); }, [inbounds]); diff --git a/frontend/src/pages/clients/BulkDetachInboundsModal.tsx b/frontend/src/pages/clients/BulkDetachInboundsModal.tsx index 0dd6d5fe..2f5a5717 100644 --- a/frontend/src/pages/clients/BulkDetachInboundsModal.tsx +++ b/frontend/src/pages/clients/BulkDetachInboundsModal.tsx @@ -36,7 +36,7 @@ export default function BulkDetachInboundsModal({ .filter((ib) => MULTI_USER_PROTOCOLS.has((ib.protocol || '').toLowerCase())) .map((ib) => ({ value: ib.id, - label: `${ib.remark ?? ''} (${ib.protocol ?? ''}@${ib.port ?? ''})`, + label: ib.tag, })); }, [inbounds]); diff --git a/frontend/src/pages/clients/ClientBulkAddModal.tsx b/frontend/src/pages/clients/ClientBulkAddModal.tsx index 9b4d8c42..d86e44a9 100644 --- a/frontend/src/pages/clients/ClientBulkAddModal.tsx +++ b/frontend/src/pages/clients/ClientBulkAddModal.tsx @@ -100,7 +100,7 @@ export default function ClientBulkAddModal({ () => (inbounds || []) .filter((ib) => MULTI_CLIENT_PROTOCOLS.has(ib.protocol || '')) .map((ib) => ({ - label: `${ib.remark || `#${ib.id}`} · ${ib.protocol}:${ib.port}`, + label: ib.tag ?? '', value: ib.id, })), [inbounds], diff --git a/frontend/src/pages/clients/ClientFormModal.tsx b/frontend/src/pages/clients/ClientFormModal.tsx index 873e749f..4f1f575e 100644 --- a/frontend/src/pages/clients/ClientFormModal.tsx +++ b/frontend/src/pages/clients/ClientFormModal.tsx @@ -15,7 +15,7 @@ import { Tag, message, } from 'antd'; -import { ReloadOutlined } from '@ant-design/icons'; +import { EyeOutlined, ReloadOutlined } from '@ant-design/icons'; import dayjs from 'dayjs'; import type { Dayjs } from 'dayjs'; @@ -148,6 +148,7 @@ export default function ClientFormModal({ const [clientIps, setClientIps] = useState([]); const [ipsLoading, setIpsLoading] = useState(false); const [ipsClearing, setIpsClearing] = useState(false); + const [ipsModalOpen, setIpsModalOpen] = useState(false); function update(key: K, value: FormState[K]) { setForm((prev) => ({ ...prev, [key]: value })); @@ -155,6 +156,7 @@ export default function ClientFormModal({ useEffect(() => { if (!open) return; + setIpsModalOpen(false); if (isEdit && client) { const et = Number(client.expiryTime) || 0; @@ -259,9 +261,9 @@ export default function ClientFormModal({ () => (inbounds || []) .filter((ib) => MULTI_CLIENT_PROTOCOLS.has(ib.protocol || '')) .map((ib) => ({ - label: `${ib.remark || `#${ib.id}`} · ${ib.protocol}:${ib.port}`, + label: ib.tag ?? '', value: ib.id, - title: `${ib.remark || ''} (${ib.protocol}:${ib.port})`, + title: ib.tag ?? '', })), [inbounds], ); @@ -279,6 +281,11 @@ export default function ClientFormModal({ } } + function openIpsModal() { + setIpsModalOpen(true); + if (clientIps.length === 0) void loadIps(); + } + async function clearIps() { if (!isEdit || !client?.email) return; setIpsClearing(true); @@ -376,7 +383,7 @@ export default function ClientFormModal({ {messageContextHolder} - - - - - {clientIps.length > 0 ? ( -
- {clientIps.map((ip, idx) => ( - {ip} - ))} -
- ) : ( - {t('tgbot.noIpRecord')} - )} + )}
+ + setIpsModalOpen(false)} + footer={[ + , + , + , + ]} + > + {clientIps.length > 0 ? ( +
+ {clientIps.map((ip, idx) => ( + + {ip} + + ))} +
+ ) : ( + {t('tgbot.noIpRecord')} + )} +
); } diff --git a/frontend/src/pages/clients/ClientInfoModal.tsx b/frontend/src/pages/clients/ClientInfoModal.tsx index b0049bef..f422064e 100644 --- a/frontend/src/pages/clients/ClientInfoModal.tsx +++ b/frontend/src/pages/clients/ClientInfoModal.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, Divider, Modal, Popover, Tag, Tooltip, message } from 'antd'; -import { CopyOutlined, QrcodeOutlined } from '@ant-design/icons'; +import { CopyOutlined, EyeOutlined, QrcodeOutlined, ReloadOutlined } from '@ant-design/icons'; import { ClipboardManager, HttpUtil, IntlUtil, SizeFormatter } from '@/utils'; import { useDatepicker } from '@/hooks/useDatepicker'; @@ -145,10 +145,16 @@ export default function ClientInfoModal({ const dateLabel = (ts?: number) => (!ts || ts <= 0 ? '-' : IntlUtil.formatDate(ts, datepicker)); const [messageApi, messageContextHolder] = message.useMessage(); const [links, setLinks] = useState([]); + const [clientIps, setClientIps] = useState([]); + const [ipsLoading, setIpsLoading] = useState(false); + const [ipsClearing, setIpsClearing] = useState(false); + const [ipsModalOpen, setIpsModalOpen] = useState(false); useEffect(() => { if (!open) { setLinks([]); + setClientIps([]); + setIpsModalOpen(false); return; } if (!client?.subId) return; @@ -197,12 +203,41 @@ export default function ClientInfoModal({ if (ok) messageApi.success(t('copied')); } + async function loadIps() { + if (!client?.email) return; + setIpsLoading(true); + try { + const msg = await HttpUtil.post(`/panel/api/clients/ips/${encodeURIComponent(client.email)}`) as ApiMsg; + if (!msg?.success) { setClientIps([]); return; } + const arr = Array.isArray(msg.obj) ? msg.obj : []; + setClientIps(arr.filter((x): x is string => typeof x === 'string' && x.length > 0)); + } finally { + setIpsLoading(false); + } + } + + async function clearIps() { + if (!client?.email) return; + setIpsClearing(true); + try { + const msg = await HttpUtil.post(`/panel/api/clients/clearIps/${encodeURIComponent(client.email)}`) as ApiMsg; + if (msg?.success) setClientIps([]); + } finally { + setIpsClearing(false); + } + } + + function openIpsModal() { + setIpsModalOpen(true); + if (clientIps.length === 0) void loadIps(); + } + return ( <> {messageContextHolder} onOpenChange(false)} @@ -313,6 +348,14 @@ export default function ClientInfoModal({ {t('pages.clients.ipLimit')} {!client.limitIp ? : {client.limitIp}} + + {t('pages.inbounds.IPLimitlog')} + + + + {t('pages.inbounds.createdAt')} {dateLabel(client.createdAt)} @@ -335,30 +378,27 @@ export default function ClientInfoModal({ if (ids.length === 0) return ; const visible = ids.slice(0, INBOUND_CHIP_LIMIT); const overflow = ids.slice(INBOUND_CHIP_LIMIT); - const inboundChip = (id: number, compact: boolean) => { + const inboundChip = (id: number) => { const ib = inboundsById[id]; const proto = (ib?.protocol || '').toLowerCase(); const color = INBOUND_PROTOCOL_COLORS[proto] ?? 'default'; - const fullLabel = ib - ? `${ib.remark || `#${id}`} (${ib.protocol}:${ib.port})` - : `#${id}`; - const compactLabel = ib ? `${ib.protocol}:${ib.port}` : `#${id}`; + const label = ib?.tag ?? ''; return ( - - {compact ? compactLabel : fullLabel} + + {label} ); }; return (
- {visible.map((id) => inboundChip(id, true))} + {visible.map((id) => inboundChip(id))} {overflow.length > 0 && ( - {overflow.map((id) => inboundChip(id, false))} + {overflow.map((id) => inboundChip(id))}
} > @@ -510,6 +550,47 @@ export default function ClientInfoModal({ )}
+ + setIpsModalOpen(false)} + footer={[ + , + , + , + ]} + > + {clientIps.length > 0 ? ( +
+ {clientIps.map((ip, idx) => ( + + {ip} + + ))} +
+ ) : ( + {t('tgbot.noIpRecord')} + )} +
); } diff --git a/frontend/src/pages/clients/ClientQrModal.tsx b/frontend/src/pages/clients/ClientQrModal.tsx index 14f2c950..0cd9e56c 100644 --- a/frontend/src/pages/clients/ClientQrModal.tsx +++ b/frontend/src/pages/clients/ClientQrModal.tsx @@ -117,7 +117,7 @@ export default function ClientQrModal({ return ( { @@ -589,7 +588,7 @@ export default function ClientsPage() {