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.
This commit is contained in:
MHSanaei
2026-05-29 23:22:49 +02:00
parent 12afb862ff
commit 987a6dd1e5
28 changed files with 231 additions and 116 deletions
@@ -69,7 +69,7 @@ export default function AttachClientsModal({
if (!source) return [];
return (dbInbounds || [])
.filter((ib) => ib.id !== source.id && isInboundMultiUser(ib))
.map((ib) => ({ value: ib.id, label: `${ib.remark} (${ib.protocol}@${ib.port})` }));
.map((ib) => ({ value: ib.id, label: ib.tag ?? '' }));
}, [dbInbounds, source]);
const filteredRows = useMemo(() => {
@@ -150,7 +150,7 @@ export default function AttachClientsModal({
}}
okText={t('pages.inbounds.attachClients')}
cancelText={t('cancel')}
title={t('pages.inbounds.attachClientsTitle', { remark: source?.remark ?? '' })}
title={t('pages.inbounds.attachClientsTitle', { remark: source?.tag ?? '' })}
width={680}
>
{messageContextHolder}
@@ -139,7 +139,7 @@ export default function DetachClientsModal({
}}
okText={t('pages.inbounds.detachClients')}
cancelText={t('cancel')}
title={t('pages.inbounds.detachClientsTitle', { remark: source?.remark ?? '' })}
title={t('pages.inbounds.detachClientsTitle', { remark: source?.tag ?? '' })}
width={680}
>
{messageContextHolder}
@@ -480,7 +480,7 @@ export default function InboundInfoModal({
if (!dbInbound || !inbound) {
return (
<Modal open={open} onCancel={onClose} title={t('pages.inbounds.inboundData')} footer={null} width={640} />
<Modal open={open} onCancel={onClose} title={t('pages.inbounds.inboundInfo')} footer={null} width={640} />
);
}
@@ -1074,7 +1074,7 @@ export default function InboundInfoModal({
tabItems.push({ key: 'inbound', label: t('pages.xray.rules.inbound'), children: inboundTab });
return (
<Modal open={open} onCancel={onClose} title={t('pages.inbounds.inboundData')} footer={null} width={640} destroyOnHidden>
<Modal open={open} onCancel={onClose} title={t('pages.inbounds.inboundInfo')} footer={null} width={640} destroyOnHidden>
<Tabs activeKey={activeTab} onChange={setActiveTab} items={tabItems} />
</Modal>
);
+2 -2
View File
@@ -255,7 +255,7 @@ function buildRowActionsMenu({ record, subEnable, t, isMobile, hasClients }: { r
});
}
} else {
items.push({ key: 'showInfo', icon: <InfoCircleOutlined />, label: t('info') });
items.push({ key: 'showInfo', icon: <InfoCircleOutlined />, label: t('pages.inbounds.inboundInfo') });
}
items.push({ key: 'clipboard', icon: <CopyOutlined />, label: t('pages.inbounds.exportInbound') });
items.push({ key: 'resetTraffic', icon: <RetweetOutlined />, label: t('pages.inbounds.resetTraffic') });
@@ -626,7 +626,7 @@ export default function InboundList({
<span className="card-id">#{record.id}</span>
<span className="tag-name">{record.remark}</span>
<div className="card-actions" onClick={(e) => e.stopPropagation()}>
<Tooltip title={t('info')}>
<Tooltip title={t('pages.inbounds.inboundInfo')}>
<InfoCircleOutlined className="row-action-trigger" onClick={() => setStatsRecord(record)} />
</Tooltip>
<Switch