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
No known key found for this signature in database
GPG key ID: 7E4060F2FBE5AB7A
28 changed files with 231 additions and 116 deletions

View file

@ -40,7 +40,7 @@ export class AllSetting {
subPort = 2096; subPort = 2096;
subPath = '/sub/'; subPath = '/sub/';
subJsonPath = '/json/'; subJsonPath = '/json/';
subClashEnable = true; subClashEnable = false;
subClashPath = '/clash/'; subClashPath = '/clash/';
subDomain = ''; subDomain = '';
externalTrafficInformEnable = false; externalTrafficInformEnable = false;

View file

@ -63,7 +63,7 @@ export default function BulkAddToGroupModal({
> >
<AutoComplete <AutoComplete
value={value} value={value}
placeholder={t('pages.clients.addToGroupPlaceholder')} placeholder={t('pages.clients.groupName')}
options={groups.map((g) => ({ value: g }))} options={groups.map((g) => ({ value: g }))}
onChange={(v) => setValue(v ?? '')} onChange={(v) => setValue(v ?? '')}
filterOption={(input, option) => filterOption={(input, option) =>

View file

@ -36,7 +36,7 @@ export default function BulkAttachInboundsModal({
.filter((ib) => MULTI_USER_PROTOCOLS.has((ib.protocol || '').toLowerCase())) .filter((ib) => MULTI_USER_PROTOCOLS.has((ib.protocol || '').toLowerCase()))
.map((ib) => ({ .map((ib) => ({
value: ib.id, value: ib.id,
label: `${ib.remark ?? ''} (${ib.protocol ?? ''}@${ib.port ?? ''})`, label: ib.tag,
})); }));
}, [inbounds]); }, [inbounds]);

View file

@ -36,7 +36,7 @@ export default function BulkDetachInboundsModal({
.filter((ib) => MULTI_USER_PROTOCOLS.has((ib.protocol || '').toLowerCase())) .filter((ib) => MULTI_USER_PROTOCOLS.has((ib.protocol || '').toLowerCase()))
.map((ib) => ({ .map((ib) => ({
value: ib.id, value: ib.id,
label: `${ib.remark ?? ''} (${ib.protocol ?? ''}@${ib.port ?? ''})`, label: ib.tag,
})); }));
}, [inbounds]); }, [inbounds]);

View file

@ -100,7 +100,7 @@ export default function ClientBulkAddModal({
() => (inbounds || []) () => (inbounds || [])
.filter((ib) => MULTI_CLIENT_PROTOCOLS.has(ib.protocol || '')) .filter((ib) => MULTI_CLIENT_PROTOCOLS.has(ib.protocol || ''))
.map((ib) => ({ .map((ib) => ({
label: `${ib.remark || `#${ib.id}`} · ${ib.protocol}:${ib.port}`, label: ib.tag ?? '',
value: ib.id, value: ib.id,
})), })),
[inbounds], [inbounds],

View file

@ -15,7 +15,7 @@ import {
Tag, Tag,
message, message,
} from 'antd'; } from 'antd';
import { ReloadOutlined } from '@ant-design/icons'; import { EyeOutlined, ReloadOutlined } from '@ant-design/icons';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import type { Dayjs } from 'dayjs'; import type { Dayjs } from 'dayjs';
@ -148,6 +148,7 @@ export default function ClientFormModal({
const [clientIps, setClientIps] = useState<string[]>([]); const [clientIps, setClientIps] = useState<string[]>([]);
const [ipsLoading, setIpsLoading] = useState(false); const [ipsLoading, setIpsLoading] = useState(false);
const [ipsClearing, setIpsClearing] = useState(false); const [ipsClearing, setIpsClearing] = useState(false);
const [ipsModalOpen, setIpsModalOpen] = useState(false);
function update<K extends keyof FormState>(key: K, value: FormState[K]) { function update<K extends keyof FormState>(key: K, value: FormState[K]) {
setForm((prev) => ({ ...prev, [key]: value })); setForm((prev) => ({ ...prev, [key]: value }));
@ -155,6 +156,7 @@ export default function ClientFormModal({
useEffect(() => { useEffect(() => {
if (!open) return; if (!open) return;
setIpsModalOpen(false);
if (isEdit && client) { if (isEdit && client) {
const et = Number(client.expiryTime) || 0; const et = Number(client.expiryTime) || 0;
@ -259,9 +261,9 @@ export default function ClientFormModal({
() => (inbounds || []) () => (inbounds || [])
.filter((ib) => MULTI_CLIENT_PROTOCOLS.has(ib.protocol || '')) .filter((ib) => MULTI_CLIENT_PROTOCOLS.has(ib.protocol || ''))
.map((ib) => ({ .map((ib) => ({
label: `${ib.remark || `#${ib.id}`} · ${ib.protocol}:${ib.port}`, label: ib.tag ?? '',
value: ib.id, value: ib.id,
title: `${ib.remark || ''} (${ib.protocol}:${ib.port})`, title: ib.tag ?? '',
})), })),
[inbounds], [inbounds],
); );
@ -279,6 +281,11 @@ export default function ClientFormModal({
} }
} }
function openIpsModal() {
setIpsModalOpen(true);
if (clientIps.length === 0) void loadIps();
}
async function clearIps() { async function clearIps() {
if (!isEdit || !client?.email) return; if (!isEdit || !client?.email) return;
setIpsClearing(true); setIpsClearing(true);
@ -376,7 +383,7 @@ export default function ClientFormModal({
{messageContextHolder} {messageContextHolder}
<Modal <Modal
open={open} open={open}
title={isEdit ? t('pages.clients.editTitle') : t('pages.clients.addTitle')} title={isEdit ? t('pages.clients.editClient') : t('pages.clients.addClient')}
destroyOnHidden destroyOnHidden
okText={isEdit ? t('save') : t('create')} okText={isEdit ? t('save') : t('create')}
cancelText={t('cancel')} cancelText={t('cancel')}
@ -584,24 +591,53 @@ export default function ClientFormModal({
{isEdit && ipLimitEnable && ( {isEdit && ipLimitEnable && (
<Form.Item label={t('pages.clients.ipLog')}> <Form.Item label={t('pages.clients.ipLog')}>
<Space style={{ marginBottom: 8 }}> <Button icon={<EyeOutlined />} loading={ipsLoading} onClick={openIpsModal}>
<Button size="small" loading={ipsLoading} onClick={loadIps}>{t('refresh')}</Button> {clientIps.length > 0 ? clientIps.length : ''}
<Button size="small" danger loading={ipsClearing} disabled={clientIps.length === 0} onClick={clearIps}>
{t('pages.clients.clearAll')}
</Button> </Button>
</Space> </Form.Item>
)}
</Form>
</Modal>
<Modal
open={ipsModalOpen}
title={`${t('pages.clients.ipLog')}${client?.email ? `${client.email}` : ''}`}
width={440}
onCancel={() => setIpsModalOpen(false)}
footer={[
<Button key="refresh" icon={<ReloadOutlined />} loading={ipsLoading} onClick={loadIps}>
{t('refresh')}
</Button>,
<Button key="clear" danger loading={ipsClearing} disabled={clientIps.length === 0} onClick={clearIps}>
{t('pages.clients.clearAll')}
</Button>,
<Button key="close" type="primary" onClick={() => setIpsModalOpen(false)}>
{t('close')}
</Button>,
]}
>
{clientIps.length > 0 ? ( {clientIps.length > 0 ? (
<div> <div style={{ maxHeight: 360, overflowY: 'auto' }}>
{clientIps.map((ip, idx) => ( {clientIps.map((ip, idx) => (
<Tag key={idx} color="blue" style={{ marginBottom: 4 }}>{ip}</Tag> <Tag
key={idx}
color="blue"
style={{
display: 'block',
width: 'fit-content',
maxWidth: '100%',
marginBottom: 6,
padding: '2px 8px',
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
}}
>
{ip}
</Tag>
))} ))}
</div> </div>
) : ( ) : (
<Tag>{t('tgbot.noIpRecord')}</Tag> <Tag>{t('tgbot.noIpRecord')}</Tag>
)} )}
</Form.Item>
)}
</Form>
</Modal> </Modal>
</> </>
); );

View file

@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button, Divider, Modal, Popover, Tag, Tooltip, message } from 'antd'; 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 { ClipboardManager, HttpUtil, IntlUtil, SizeFormatter } from '@/utils';
import { useDatepicker } from '@/hooks/useDatepicker'; 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 dateLabel = (ts?: number) => (!ts || ts <= 0 ? '-' : IntlUtil.formatDate(ts, datepicker));
const [messageApi, messageContextHolder] = message.useMessage(); const [messageApi, messageContextHolder] = message.useMessage();
const [links, setLinks] = useState<string[]>([]); const [links, setLinks] = useState<string[]>([]);
const [clientIps, setClientIps] = useState<string[]>([]);
const [ipsLoading, setIpsLoading] = useState(false);
const [ipsClearing, setIpsClearing] = useState(false);
const [ipsModalOpen, setIpsModalOpen] = useState(false);
useEffect(() => { useEffect(() => {
if (!open) { if (!open) {
setLinks([]); setLinks([]);
setClientIps([]);
setIpsModalOpen(false);
return; return;
} }
if (!client?.subId) return; if (!client?.subId) return;
@ -197,12 +203,41 @@ export default function ClientInfoModal({
if (ok) messageApi.success(t('copied')); 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<unknown[]>;
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 ( return (
<> <>
{messageContextHolder} {messageContextHolder}
<Modal <Modal
open={open} open={open}
title={client ? client.email : t('info')} title={client ? `${t('pages.clients.clientInfo')}${client.email}` : t('pages.clients.clientInfo')}
footer={null} footer={null}
width={640} width={640}
onCancel={() => onOpenChange(false)} onCancel={() => onOpenChange(false)}
@ -313,6 +348,14 @@ export default function ClientInfoModal({
<td>{t('pages.clients.ipLimit')}</td> <td>{t('pages.clients.ipLimit')}</td>
<td>{!client.limitIp ? <Tag></Tag> : <Tag>{client.limitIp}</Tag>}</td> <td>{!client.limitIp ? <Tag></Tag> : <Tag>{client.limitIp}</Tag>}</td>
</tr> </tr>
<tr>
<td>{t('pages.inbounds.IPLimitlog')}</td>
<td>
<Button size="small" icon={<EyeOutlined />} loading={ipsLoading} onClick={openIpsModal}>
{clientIps.length > 0 ? clientIps.length : ''}
</Button>
</td>
</tr>
<tr> <tr>
<td>{t('pages.inbounds.createdAt')}</td> <td>{t('pages.inbounds.createdAt')}</td>
<td><Tag>{dateLabel(client.createdAt)}</Tag></td> <td><Tag>{dateLabel(client.createdAt)}</Tag></td>
@ -335,30 +378,27 @@ export default function ClientInfoModal({
if (ids.length === 0) return <span className="hint"></span>; if (ids.length === 0) return <span className="hint"></span>;
const visible = ids.slice(0, INBOUND_CHIP_LIMIT); const visible = ids.slice(0, INBOUND_CHIP_LIMIT);
const overflow = ids.slice(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 ib = inboundsById[id];
const proto = (ib?.protocol || '').toLowerCase(); const proto = (ib?.protocol || '').toLowerCase();
const color = INBOUND_PROTOCOL_COLORS[proto] ?? 'default'; const color = INBOUND_PROTOCOL_COLORS[proto] ?? 'default';
const fullLabel = ib const label = ib?.tag ?? '';
? `${ib.remark || `#${id}`} (${ib.protocol}:${ib.port})`
: `#${id}`;
const compactLabel = ib ? `${ib.protocol}:${ib.port}` : `#${id}`;
return ( return (
<Tooltip key={id} title={fullLabel}> <Tooltip key={id} title={label}>
<Tag color={color}>{compact ? compactLabel : fullLabel}</Tag> <Tag color={color}>{label}</Tag>
</Tooltip> </Tooltip>
); );
}; };
return ( return (
<div className="chips"> <div className="chips">
{visible.map((id) => inboundChip(id, true))} {visible.map((id) => inboundChip(id))}
{overflow.length > 0 && ( {overflow.length > 0 && (
<Popover <Popover
trigger="click" trigger="click"
placement="bottomRight" placement="bottomRight"
content={ content={
<div className="chips chips-stack"> <div className="chips chips-stack">
{overflow.map((id) => inboundChip(id, false))} {overflow.map((id) => inboundChip(id))}
</div> </div>
} }
> >
@ -510,6 +550,47 @@ export default function ClientInfoModal({
</> </>
)} )}
</Modal> </Modal>
<Modal
open={ipsModalOpen}
title={`${t('pages.inbounds.IPLimitlog')}${client?.email ? `${client.email}` : ''}`}
width={440}
onCancel={() => setIpsModalOpen(false)}
footer={[
<Button key="refresh" icon={<ReloadOutlined />} loading={ipsLoading} onClick={loadIps}>
{t('refresh')}
</Button>,
<Button key="clear" danger loading={ipsClearing} disabled={clientIps.length === 0} onClick={clearIps}>
{t('pages.clients.clearAll')}
</Button>,
<Button key="close" type="primary" onClick={() => setIpsModalOpen(false)}>
{t('close')}
</Button>,
]}
>
{clientIps.length > 0 ? (
<div style={{ maxHeight: 360, overflowY: 'auto' }}>
{clientIps.map((ip, idx) => (
<Tag
key={idx}
color="blue"
style={{
display: 'block',
width: 'fit-content',
maxWidth: '100%',
marginBottom: 6,
padding: '2px 8px',
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
}}
>
{ip}
</Tag>
))}
</div>
) : (
<Tag>{t('tgbot.noIpRecord')}</Tag>
)}
</Modal>
</> </>
); );
} }

View file

@ -117,7 +117,7 @@ export default function ClientQrModal({
return ( return (
<Modal <Modal
open={open} open={open}
title={client ? client.email : t('qrCode')} title={client ? `${t('qrCode')}${client.email}` : t('qrCode')}
footer={null} footer={null}
width={520} width={520}
centered centered

View file

@ -299,8 +299,7 @@ export default function ClientsPage() {
function inboundLabel(id: number) { function inboundLabel(id: number) {
const ib = inboundsById[id]; const ib = inboundsById[id];
if (!ib) return `#${id}`; return ib?.tag ?? '';
return ib.remark ? `${ib.remark} (${ib.protocol}:${ib.port})` : `${ib.protocol}:${ib.port}`;
} }
const clientBucket = useCallback((row: ClientRecord | null | undefined): Bucket | null => { const clientBucket = useCallback((row: ClientRecord | null | undefined): Bucket | null => {
@ -589,7 +588,7 @@ export default function ClientsPage() {
<Tooltip title={t('pages.clients.qrCode')}> <Tooltip title={t('pages.clients.qrCode')}>
<Button size="small" type="text" icon={<QrcodeOutlined />} onClick={() => onShowQr(record)} /> <Button size="small" type="text" icon={<QrcodeOutlined />} onClick={() => onShowQr(record)} />
</Tooltip> </Tooltip>
<Tooltip title={t('pages.clients.moreInformation')}> <Tooltip title={t('pages.clients.clientInfo')}>
<Button size="small" type="text" icon={<InfoCircleOutlined />} onClick={() => onShowInfo(record)} /> <Button size="small" type="text" icon={<InfoCircleOutlined />} onClick={() => onShowInfo(record)} />
</Tooltip> </Tooltip>
<Tooltip title={t('pages.inbounds.resetTraffic')}> <Tooltip title={t('pages.inbounds.resetTraffic')}>
@ -678,7 +677,7 @@ export default function ClientsPage() {
const ib = inboundsById[id]; const ib = inboundsById[id];
const proto = (ib?.protocol || '').toLowerCase(); const proto = (ib?.protocol || '').toLowerCase();
const color = INBOUND_PROTOCOL_COLORS[proto] ?? 'default'; const color = INBOUND_PROTOCOL_COLORS[proto] ?? 'default';
const compactLabel = ib ? `${ib.protocol}:${ib.port}` : `#${id}`; const compactLabel = ib?.tag ?? '';
return ( return (
<Tooltip key={id} title={inboundLabel(id)}> <Tooltip key={id} title={inboundLabel(id)}>
<Tag color={color} style={{ margin: 2 }}> <Tag color={color} style={{ margin: 2 }}>
@ -1118,7 +1117,7 @@ export default function ClientsPage() {
{bucket === 'depleted' && <Tag color="red" className="status-tag">{t('depleted')}</Tag>} {bucket === 'depleted' && <Tag color="red" className="status-tag">{t('depleted')}</Tag>}
{bucket === 'expiring' && <Tag color="orange" className="status-tag">{t('depletingSoon')}</Tag>} {bucket === 'expiring' && <Tag color="orange" className="status-tag">{t('depletingSoon')}</Tag>}
<div className="card-actions" onClick={(e) => e.stopPropagation()}> <div className="card-actions" onClick={(e) => e.stopPropagation()}>
<Tooltip title={t('pages.clients.moreInformation')}> <Tooltip title={t('pages.clients.clientInfo')}>
<InfoCircleOutlined className="row-action-trigger" onClick={() => onShowInfo(row)} /> <InfoCircleOutlined className="row-action-trigger" onClick={() => onShowInfo(row)} />
</Tooltip> </Tooltip>
<Switch <Switch

View file

@ -50,9 +50,7 @@ export default function FilterDrawer({
const inboundOptions = useMemo( const inboundOptions = useMemo(
() => inbounds.map((ib) => ({ () => inbounds.map((ib) => ({
value: ib.id, value: ib.id,
label: ib.remark label: ib.tag ?? '',
? `${ib.remark} (${ib.protocol || ''}${ib.port ? `:${ib.port}` : ''})`
: `#${ib.id} ${ib.protocol || ''}${ib.port ? `:${ib.port}` : ''}`,
})), })),
[inbounds], [inbounds],
); );

View file

@ -69,7 +69,7 @@ export default function AttachClientsModal({
if (!source) return []; if (!source) return [];
return (dbInbounds || []) return (dbInbounds || [])
.filter((ib) => ib.id !== source.id && isInboundMultiUser(ib)) .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]); }, [dbInbounds, source]);
const filteredRows = useMemo(() => { const filteredRows = useMemo(() => {
@ -150,7 +150,7 @@ export default function AttachClientsModal({
}} }}
okText={t('pages.inbounds.attachClients')} okText={t('pages.inbounds.attachClients')}
cancelText={t('cancel')} cancelText={t('cancel')}
title={t('pages.inbounds.attachClientsTitle', { remark: source?.remark ?? '' })} title={t('pages.inbounds.attachClientsTitle', { remark: source?.tag ?? '' })}
width={680} width={680}
> >
{messageContextHolder} {messageContextHolder}

View file

@ -139,7 +139,7 @@ export default function DetachClientsModal({
}} }}
okText={t('pages.inbounds.detachClients')} okText={t('pages.inbounds.detachClients')}
cancelText={t('cancel')} cancelText={t('cancel')}
title={t('pages.inbounds.detachClientsTitle', { remark: source?.remark ?? '' })} title={t('pages.inbounds.detachClientsTitle', { remark: source?.tag ?? '' })}
width={680} width={680}
> >
{messageContextHolder} {messageContextHolder}

View file

@ -480,7 +480,7 @@ export default function InboundInfoModal({
if (!dbInbound || !inbound) { if (!dbInbound || !inbound) {
return ( 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 }); tabItems.push({ key: 'inbound', label: t('pages.xray.rules.inbound'), children: inboundTab });
return ( 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} /> <Tabs activeKey={activeTab} onChange={setActiveTab} items={tabItems} />
</Modal> </Modal>
); );

View file

@ -255,7 +255,7 @@ function buildRowActionsMenu({ record, subEnable, t, isMobile, hasClients }: { r
}); });
} }
} else { } 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: 'clipboard', icon: <CopyOutlined />, label: t('pages.inbounds.exportInbound') });
items.push({ key: 'resetTraffic', icon: <RetweetOutlined />, label: t('pages.inbounds.resetTraffic') }); 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="card-id">#{record.id}</span>
<span className="tag-name">{record.remark}</span> <span className="tag-name">{record.remark}</span>
<div className="card-actions" onClick={(e) => e.stopPropagation()}> <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)} /> <InfoCircleOutlined className="row-action-trigger" onClick={() => setStatsRecord(record)} />
</Tooltip> </Tooltip>
<Switch <Switch

View file

@ -39,6 +39,7 @@ export const ClientRecordSchema = z.object({
export const InboundOptionSchema = z.object({ export const InboundOptionSchema = z.object({
id: z.number(), id: z.number(),
remark: z.string().optional(), remark: z.string().optional(),
tag: z.string().optional(),
protocol: z.string().optional(), protocol: z.string().optional(),
port: z.number().optional(), port: z.number().optional(),
tlsFlowCapable: z.boolean().optional(), tlsFlowCapable: z.boolean().optional(),

View file

@ -400,7 +400,7 @@
"telegramDesc": "ادخل ID شات Telegram. (استخدم '/id' في البوت) أو ({'@'}userinfobot)", "telegramDesc": "ادخل ID شات Telegram. (استخدم '/id' في البوت) أو ({'@'}userinfobot)",
"subscriptionDesc": "عشان تلاقي رابط الاشتراك، ادخل على 'التفاصيل'. وكمان ممكن تستخدم نفس الاسم لعدة عملاء.", "subscriptionDesc": "عشان تلاقي رابط الاشتراك، ادخل على 'التفاصيل'. وكمان ممكن تستخدم نفس الاسم لعدة عملاء.",
"same": "نفسه", "same": "نفسه",
"inboundData": "بيانات الإدخال", "inboundInfo": "معلومات الإدخال",
"exportInbound": "تصدير الإدخال", "exportInbound": "تصدير الإدخال",
"import": "استيراد", "import": "استيراد",
"importInbound": "استيراد إدخال", "importInbound": "استيراد إدخال",
@ -652,12 +652,12 @@
"comment": "ملاحظة", "comment": "ملاحظة",
"traffic": "حركة المرور", "traffic": "حركة المرور",
"offline": "غير متصل", "offline": "غير متصل",
"addTitle": "إضافة عميل", "addClient": "إضافة عميل",
"qrCode": "رمز QR", "qrCode": "رمز QR",
"moreInformation": "مزيد من المعلومات", "clientInfo": "معلومات العميل",
"delete": "حذف", "delete": "حذف",
"reset": "إعادة ضبط حركة المرور", "reset": "إعادة ضبط حركة المرور",
"editTitle": "تعديل العميل", "editClient": "تعديل العميل",
"client": "العميل", "client": "العميل",
"enabled": "مفعّل", "enabled": "مفعّل",
"remaining": "المتبقي", "remaining": "المتبقي",
@ -679,7 +679,7 @@
"subLinksSelected": "روابط الاشتراك ({count})", "subLinksSelected": "روابط الاشتراك ({count})",
"addToGroupTitle": "إضافة {count} عميل إلى مجموعة", "addToGroupTitle": "إضافة {count} عميل إلى مجموعة",
"addToGroupTooltip": "اختر مجموعة موجودة أو أدخل اسماً جديداً. استخدم Ungroup لإزالة العملاء من مجموعتهم الحالية.", "addToGroupTooltip": "اختر مجموعة موجودة أو أدخل اسماً جديداً. استخدم Ungroup لإزالة العملاء من مجموعتهم الحالية.",
"addToGroupPlaceholder": "اسم المجموعة", "groupName": "اسم المجموعة",
"addToGroupSuccessToast": "تمت إضافة {count} عميل إلى {group}", "addToGroupSuccessToast": "تمت إضافة {count} عميل إلى {group}",
"ungroupSuccessToast": "تم مسح المجموعة من {count} عميل", "ungroupSuccessToast": "تم مسح المجموعة من {count} عميل",
"ungroup": "إزالة من المجموعة", "ungroup": "إزالة من المجموعة",

View file

@ -400,7 +400,7 @@
"telegramDesc": "Please provide Telegram Chat ID. (use '/id' command in the bot) or ({'@'}userinfobot)", "telegramDesc": "Please provide Telegram Chat ID. (use '/id' command in the bot) or ({'@'}userinfobot)",
"subscriptionDesc": "To find your subscription URL, navigate to the 'Details'. Additionally, you can use the same name for several clients.", "subscriptionDesc": "To find your subscription URL, navigate to the 'Details'. Additionally, you can use the same name for several clients.",
"same": "Same", "same": "Same",
"inboundData": "Inbound's Data", "inboundInfo": "Inbound Information",
"exportInbound": "Export Inbound", "exportInbound": "Export Inbound",
"import": "Import", "import": "Import",
"importInbound": "Import an Inbound", "importInbound": "Import an Inbound",
@ -652,12 +652,12 @@
"comment": "Comment", "comment": "Comment",
"traffic": "Traffic", "traffic": "Traffic",
"offline": "Offline", "offline": "Offline",
"addTitle": "Add Client", "addClient": "Add Client",
"qrCode": "QR Code", "qrCode": "QR Code",
"moreInformation": "More Information", "clientInfo": "Client Information",
"delete": "Delete", "delete": "Delete",
"reset": "Reset Traffic", "reset": "Reset Traffic",
"editTitle": "Edit Client", "editClient": "Edit Client",
"client": "Client", "client": "Client",
"enabled": "Enabled", "enabled": "Enabled",
"remaining": "Remaining", "remaining": "Remaining",
@ -679,7 +679,7 @@
"subLinksSelected": "Sub links ({count})", "subLinksSelected": "Sub links ({count})",
"addToGroupTitle": "Add {count} client(s) to a group", "addToGroupTitle": "Add {count} client(s) to a group",
"addToGroupTooltip": "Pick an existing group or type a new name. Use the Ungroup action to remove clients from their current group.", "addToGroupTooltip": "Pick an existing group or type a new name. Use the Ungroup action to remove clients from their current group.",
"addToGroupPlaceholder": "Group name", "groupName": "Group name",
"addToGroupSuccessToast": "Added {count} client(s) to {group}", "addToGroupSuccessToast": "Added {count} client(s) to {group}",
"ungroupSuccessToast": "Cleared group from {count} client(s)", "ungroupSuccessToast": "Cleared group from {count} client(s)",
"ungroup": "Ungroup", "ungroup": "Ungroup",

View file

@ -400,7 +400,7 @@
"telegramDesc": "Por favor, proporciona el ID de Chat de Telegram. (usa el comando '/id' en el bot) o ({'@'}userinfobot)", "telegramDesc": "Por favor, proporciona el ID de Chat de Telegram. (usa el comando '/id' en el bot) o ({'@'}userinfobot)",
"subscriptionDesc": "Puedes encontrar tu enlace de suscripción en Detalles, también puedes usar el mismo nombre para varias configuraciones.", "subscriptionDesc": "Puedes encontrar tu enlace de suscripción en Detalles, también puedes usar el mismo nombre para varias configuraciones.",
"same": "misma", "same": "misma",
"inboundData": "Datos de entrada", "inboundInfo": "Información de entrada",
"exportInbound": "Exportación entrante", "exportInbound": "Exportación entrante",
"import": "Importar", "import": "Importar",
"importInbound": "Importar un entrante", "importInbound": "Importar un entrante",
@ -652,12 +652,12 @@
"comment": "Comentario", "comment": "Comentario",
"traffic": "Tráfico", "traffic": "Tráfico",
"offline": "Sin conexión", "offline": "Sin conexión",
"addTitle": "Añadir cliente", "addClient": "Añadir cliente",
"qrCode": "Código QR", "qrCode": "Código QR",
"moreInformation": "Más información", "clientInfo": "Información del cliente",
"delete": "Eliminar", "delete": "Eliminar",
"reset": "Restablecer tráfico", "reset": "Restablecer tráfico",
"editTitle": "Editar cliente", "editClient": "Editar cliente",
"client": "Cliente", "client": "Cliente",
"enabled": "Habilitado", "enabled": "Habilitado",
"remaining": "Restante", "remaining": "Restante",
@ -679,7 +679,7 @@
"subLinksSelected": "Enlaces sub ({count})", "subLinksSelected": "Enlaces sub ({count})",
"addToGroupTitle": "Añadir {count} cliente(s) a un grupo", "addToGroupTitle": "Añadir {count} cliente(s) a un grupo",
"addToGroupTooltip": "Selecciona un grupo existente o escribe un nombre nuevo. Usa Ungroup para quitar clientes de su grupo actual.", "addToGroupTooltip": "Selecciona un grupo existente o escribe un nombre nuevo. Usa Ungroup para quitar clientes de su grupo actual.",
"addToGroupPlaceholder": "Nombre del grupo", "groupName": "Nombre del grupo",
"addToGroupSuccessToast": "Se añadieron {count} cliente(s) a {group}", "addToGroupSuccessToast": "Se añadieron {count} cliente(s) a {group}",
"ungroupSuccessToast": "Grupo limpiado de {count} cliente(s)", "ungroupSuccessToast": "Grupo limpiado de {count} cliente(s)",
"ungroup": "Desagrupar", "ungroup": "Desagrupar",

View file

@ -400,7 +400,7 @@
"telegramDesc": "لطفا شناسه گفتگوی تلگرام را وارد کنید. (از دستور '/id' در ربات استفاده کنید) یا ({'@'}userinfobot)", "telegramDesc": "لطفا شناسه گفتگوی تلگرام را وارد کنید. (از دستور '/id' در ربات استفاده کنید) یا ({'@'}userinfobot)",
"subscriptionDesc": "شما می‌توانید لینک سابسکربپشن خودرا در 'جزئیات' پیدا کنید، همچنین می‌توانید از همین نام برای چندین کاربر استفاده‌کنید", "subscriptionDesc": "شما می‌توانید لینک سابسکربپشن خودرا در 'جزئیات' پیدا کنید، همچنین می‌توانید از همین نام برای چندین کاربر استفاده‌کنید",
"same": "همسان", "same": "همسان",
"inboundData": "داده‌های ورودی", "inboundInfo": "اطلاعات ورودی",
"exportInbound": "استخراج ورودی", "exportInbound": "استخراج ورودی",
"import": "افزودن", "import": "افزودن",
"importInbound": "افزودن یک ورودی", "importInbound": "افزودن یک ورودی",
@ -652,12 +652,12 @@
"comment": "توضیحات", "comment": "توضیحات",
"traffic": "ترافیک", "traffic": "ترافیک",
"offline": "آفلاین", "offline": "آفلاین",
"addTitle": "افزودن کلاینت", "addClient": "افزودن کلاینت",
"qrCode": "کد QR", "qrCode": "کد QR",
"moreInformation": "اطلاعات بیشتر", "clientInfo": "اطلاعات کلاینت",
"delete": "حذف", "delete": "حذف",
"reset": "بازنشانی ترافیک", "reset": "بازنشانی ترافیک",
"editTitle": "ویرایش کلاینت", "editClient": "ویرایش کلاینت",
"client": "کلاینت", "client": "کلاینت",
"enabled": "فعال", "enabled": "فعال",
"remaining": "باقی‌مانده", "remaining": "باقی‌مانده",
@ -679,7 +679,7 @@
"subLinksSelected": "لینک‌های اشتراک ({count})", "subLinksSelected": "لینک‌های اشتراک ({count})",
"addToGroupTitle": "افزودن {count} کاربر به یک گروه", "addToGroupTitle": "افزودن {count} کاربر به یک گروه",
"addToGroupTooltip": "یک گروه موجود را انتخاب کنید یا نام جدیدی تایپ کنید. برای حذف کاربران از گروه فعلی، از Ungroup استفاده کنید.", "addToGroupTooltip": "یک گروه موجود را انتخاب کنید یا نام جدیدی تایپ کنید. برای حذف کاربران از گروه فعلی، از Ungroup استفاده کنید.",
"addToGroupPlaceholder": "نام گروه", "groupName": "نام گروه",
"addToGroupSuccessToast": "{count} کاربر به {group} اضافه شد", "addToGroupSuccessToast": "{count} کاربر به {group} اضافه شد",
"ungroupSuccessToast": "گروه از {count} کاربر پاک شد", "ungroupSuccessToast": "گروه از {count} کاربر پاک شد",
"ungroup": "خارج از گروه", "ungroup": "خارج از گروه",

View file

@ -400,7 +400,7 @@
"telegramDesc": "Harap berikan ID Obrolan Telegram. (gunakan perintah '/id' di bot) atau ({'@'}userinfobot)", "telegramDesc": "Harap berikan ID Obrolan Telegram. (gunakan perintah '/id' di bot) atau ({'@'}userinfobot)",
"subscriptionDesc": "Untuk menemukan URL langganan Anda, buka 'Rincian'. Selain itu, Anda dapat menggunakan nama yang sama untuk beberapa klien.", "subscriptionDesc": "Untuk menemukan URL langganan Anda, buka 'Rincian'. Selain itu, Anda dapat menggunakan nama yang sama untuk beberapa klien.",
"same": "Sama", "same": "Sama",
"inboundData": "Data Masuk", "inboundInfo": "Informasi Inbound",
"exportInbound": "Ekspor Masuk", "exportInbound": "Ekspor Masuk",
"import": "Impor", "import": "Impor",
"importInbound": "Impor Masuk", "importInbound": "Impor Masuk",
@ -652,12 +652,12 @@
"comment": "Komentar", "comment": "Komentar",
"traffic": "Lalu lintas", "traffic": "Lalu lintas",
"offline": "Offline", "offline": "Offline",
"addTitle": "Tambah klien", "addClient": "Tambah klien",
"qrCode": "Kode QR", "qrCode": "Kode QR",
"moreInformation": "Informasi lebih lanjut", "clientInfo": "Informasi Klien",
"delete": "Hapus", "delete": "Hapus",
"reset": "Reset lalu lintas", "reset": "Reset lalu lintas",
"editTitle": "Ubah klien", "editClient": "Ubah klien",
"client": "Klien", "client": "Klien",
"enabled": "Aktif", "enabled": "Aktif",
"remaining": "Sisa", "remaining": "Sisa",
@ -679,7 +679,7 @@
"subLinksSelected": "Tautan sub ({count})", "subLinksSelected": "Tautan sub ({count})",
"addToGroupTitle": "Tambahkan {count} klien ke grup", "addToGroupTitle": "Tambahkan {count} klien ke grup",
"addToGroupTooltip": "Pilih grup yang ada atau ketik nama baru. Gunakan Ungroup untuk menghapus klien dari grup saat ini.", "addToGroupTooltip": "Pilih grup yang ada atau ketik nama baru. Gunakan Ungroup untuk menghapus klien dari grup saat ini.",
"addToGroupPlaceholder": "Nama grup", "groupName": "Nama grup",
"addToGroupSuccessToast": "{count} klien ditambahkan ke {group}", "addToGroupSuccessToast": "{count} klien ditambahkan ke {group}",
"ungroupSuccessToast": "Grup dihapus dari {count} klien", "ungroupSuccessToast": "Grup dihapus dari {count} klien",
"ungroup": "Lepaskan grup", "ungroup": "Lepaskan grup",

View file

@ -400,7 +400,7 @@
"telegramDesc": "TelegramチャットIDを提供してください。ボットで'/id'コマンドを使用)または({'@'}userinfobot", "telegramDesc": "TelegramチャットIDを提供してください。ボットで'/id'コマンドを使用)または({'@'}userinfobot",
"subscriptionDesc": "サブスクリプションURLを見つけるには、“詳細情報”に移動してください。また、複数のクライアントに同じ名前を使用することができます。", "subscriptionDesc": "サブスクリプションURLを見つけるには、“詳細情報”に移動してください。また、複数のクライアントに同じ名前を使用することができます。",
"same": "同じ", "same": "同じ",
"inboundData": "インバウンドデータ", "inboundInfo": "インバウンド情報",
"exportInbound": "インバウンドルールをエクスポート", "exportInbound": "インバウンドルールをエクスポート",
"import": "インポート", "import": "インポート",
"importInbound": "インバウンドルールをインポート", "importInbound": "インバウンドルールをインポート",
@ -652,12 +652,12 @@
"comment": "コメント", "comment": "コメント",
"traffic": "トラフィック", "traffic": "トラフィック",
"offline": "オフライン", "offline": "オフライン",
"addTitle": "クライアントを追加", "addClient": "クライアントを追加",
"qrCode": "QR コード", "qrCode": "QR コード",
"moreInformation": "詳細情報", "clientInfo": "クライアント情報",
"delete": "削除", "delete": "削除",
"reset": "トラフィックをリセット", "reset": "トラフィックをリセット",
"editTitle": "クライアントを編集", "editClient": "クライアントを編集",
"client": "クライアント", "client": "クライアント",
"enabled": "有効", "enabled": "有効",
"remaining": "残量", "remaining": "残量",
@ -679,7 +679,7 @@
"subLinksSelected": "サブリンク ({count})", "subLinksSelected": "サブリンク ({count})",
"addToGroupTitle": "{count} クライアントをグループに追加", "addToGroupTitle": "{count} クライアントをグループに追加",
"addToGroupTooltip": "既存のグループを選ぶか新しい名前を入力してください。Ungroup で現在のグループから外せます。", "addToGroupTooltip": "既存のグループを選ぶか新しい名前を入力してください。Ungroup で現在のグループから外せます。",
"addToGroupPlaceholder": "グループ名", "groupName": "グループ名",
"addToGroupSuccessToast": "{count} クライアントを {group} に追加しました", "addToGroupSuccessToast": "{count} クライアントを {group} に追加しました",
"ungroupSuccessToast": "{count} クライアントのグループをクリアしました", "ungroupSuccessToast": "{count} クライアントのグループをクリアしました",
"ungroup": "グループ解除", "ungroup": "グループ解除",

View file

@ -400,7 +400,7 @@
"telegramDesc": "Por favor, forneça o ID do Chat do Telegram. (use o comando '/id' no bot) ou ({'@'}userinfobot)", "telegramDesc": "Por favor, forneça o ID do Chat do Telegram. (use o comando '/id' no bot) ou ({'@'}userinfobot)",
"subscriptionDesc": "Para encontrar seu URL de assinatura, navegue até 'Detalhes'. Além disso, você pode usar o mesmo nome para vários clientes.", "subscriptionDesc": "Para encontrar seu URL de assinatura, navegue até 'Detalhes'. Além disso, você pode usar o mesmo nome para vários clientes.",
"same": "Igual", "same": "Igual",
"inboundData": "Dados do Inbound", "inboundInfo": "Informações do Inbound",
"exportInbound": "Exportar Inbound", "exportInbound": "Exportar Inbound",
"import": "Importar", "import": "Importar",
"importInbound": "Importar um Inbound", "importInbound": "Importar um Inbound",
@ -652,12 +652,12 @@
"comment": "Comentário", "comment": "Comentário",
"traffic": "Tráfego", "traffic": "Tráfego",
"offline": "Offline", "offline": "Offline",
"addTitle": "Adicionar cliente", "addClient": "Adicionar cliente",
"qrCode": "Código QR", "qrCode": "Código QR",
"moreInformation": "Mais informações", "clientInfo": "Informações do cliente",
"delete": "Excluir", "delete": "Excluir",
"reset": "Redefinir tráfego", "reset": "Redefinir tráfego",
"editTitle": "Editar cliente", "editClient": "Editar cliente",
"client": "Cliente", "client": "Cliente",
"enabled": "Habilitado", "enabled": "Habilitado",
"remaining": "Restante", "remaining": "Restante",
@ -679,7 +679,7 @@
"subLinksSelected": "Links sub ({count})", "subLinksSelected": "Links sub ({count})",
"addToGroupTitle": "Adicionar {count} cliente(s) a um grupo", "addToGroupTitle": "Adicionar {count} cliente(s) a um grupo",
"addToGroupTooltip": "Escolha um grupo existente ou digite um novo nome. Use Ungroup para remover clientes do grupo atual.", "addToGroupTooltip": "Escolha um grupo existente ou digite um novo nome. Use Ungroup para remover clientes do grupo atual.",
"addToGroupPlaceholder": "Nome do grupo", "groupName": "Nome do grupo",
"addToGroupSuccessToast": "{count} cliente(s) adicionado(s) a {group}", "addToGroupSuccessToast": "{count} cliente(s) adicionado(s) a {group}",
"ungroupSuccessToast": "Grupo limpo de {count} cliente(s)", "ungroupSuccessToast": "Grupo limpo de {count} cliente(s)",
"ungroup": "Desagrupar", "ungroup": "Desagrupar",

View file

@ -400,7 +400,7 @@
"telegramDesc": "Пожалуйста, укажите Chat ID Telegram. (используйте команду '/id' в боте) или ({'@'}userinfobot)", "telegramDesc": "Пожалуйста, укажите Chat ID Telegram. (используйте команду '/id' в боте) или ({'@'}userinfobot)",
"subscriptionDesc": "Вы можете найти свою ссылку подписки в разделе 'Подробнее'", "subscriptionDesc": "Вы можете найти свою ссылку подписки в разделе 'Подробнее'",
"same": "Тот же", "same": "Тот же",
"inboundData": "Данные подключений", "inboundInfo": "Информация о подключении",
"exportInbound": "Экспорт подключений", "exportInbound": "Экспорт подключений",
"import": "Импортировать", "import": "Импортировать",
"importInbound": "Импорт подключений", "importInbound": "Импорт подключений",
@ -652,12 +652,12 @@
"comment": "Комментарий", "comment": "Комментарий",
"traffic": "Трафик", "traffic": "Трафик",
"offline": "Не в сети", "offline": "Не в сети",
"addTitle": "Добавить клиента", "addClient": "Добавить клиента",
"qrCode": "QR-код", "qrCode": "QR-код",
"moreInformation": "Подробнее", "clientInfo": "Информация о клиенте",
"delete": "Удалить", "delete": "Удалить",
"reset": "Сбросить трафик", "reset": "Сбросить трафик",
"editTitle": "Изменить клиента", "editClient": "Изменить клиента",
"client": "Клиент", "client": "Клиент",
"enabled": "Включён", "enabled": "Включён",
"remaining": "Остаток", "remaining": "Остаток",
@ -679,7 +679,7 @@
"subLinksSelected": "Sub-ссылки ({count})", "subLinksSelected": "Sub-ссылки ({count})",
"addToGroupTitle": "Добавить {count} клиент(ов) в группу", "addToGroupTitle": "Добавить {count} клиент(ов) в группу",
"addToGroupTooltip": "Выберите существующую группу или введите новое имя. Используйте Ungroup, чтобы удалить клиентов из их текущей группы.", "addToGroupTooltip": "Выберите существующую группу или введите новое имя. Используйте Ungroup, чтобы удалить клиентов из их текущей группы.",
"addToGroupPlaceholder": "Имя группы", "groupName": "Имя группы",
"addToGroupSuccessToast": "{count} клиент(ов) добавлено в {group}", "addToGroupSuccessToast": "{count} клиент(ов) добавлено в {group}",
"ungroupSuccessToast": "Группа очищена у {count} клиент(ов)", "ungroupSuccessToast": "Группа очищена у {count} клиент(ов)",
"ungroup": "Разгруппировать", "ungroup": "Разгруппировать",

View file

@ -400,7 +400,7 @@
"telegramDesc": "Lütfen Telegram Sohbet Kimliği sağlayın. (botta '/id' komutunu kullanın) veya ({'@'}userinfobot)", "telegramDesc": "Lütfen Telegram Sohbet Kimliği sağlayın. (botta '/id' komutunu kullanın) veya ({'@'}userinfobot)",
"subscriptionDesc": "Abonelik URL'inizi bulmak için 'Detaylar'a gidin. Ayrıca, aynı adı birden fazla müşteri için kullanabilirsiniz.", "subscriptionDesc": "Abonelik URL'inizi bulmak için 'Detaylar'a gidin. Ayrıca, aynı adı birden fazla müşteri için kullanabilirsiniz.",
"same": "Aynı", "same": "Aynı",
"inboundData": "Gelenin Verileri", "inboundInfo": "Gelen Bilgileri",
"exportInbound": "Geleni Dışa Aktar", "exportInbound": "Geleni Dışa Aktar",
"import": "İçe Aktar", "import": "İçe Aktar",
"importInbound": "Bir Gelen İçe Aktar", "importInbound": "Bir Gelen İçe Aktar",
@ -652,12 +652,12 @@
"comment": "Yorum", "comment": "Yorum",
"traffic": "Trafik", "traffic": "Trafik",
"offline": "Çevrimdışı", "offline": "Çevrimdışı",
"addTitle": "İstemci ekle", "addClient": "İstemci ekle",
"qrCode": "QR kodu", "qrCode": "QR kodu",
"moreInformation": "Daha fazla bilgi", "clientInfo": "İstemci Bilgileri",
"delete": "Sil", "delete": "Sil",
"reset": "Trafiği sıfırla", "reset": "Trafiği sıfırla",
"editTitle": "İstemciyi düzenle", "editClient": "İstemciyi düzenle",
"client": "İstemci", "client": "İstemci",
"enabled": "Etkin", "enabled": "Etkin",
"remaining": "Kalan", "remaining": "Kalan",
@ -679,7 +679,7 @@
"subLinksSelected": "Abonelik bağlantıları ({count})", "subLinksSelected": "Abonelik bağlantıları ({count})",
"addToGroupTitle": "{count} istemciyi bir gruba ekle", "addToGroupTitle": "{count} istemciyi bir gruba ekle",
"addToGroupTooltip": "Mevcut bir grubu seçin veya yeni ad girin. İstemcileri mevcut gruplarından çıkarmak için Ungroup'u kullanın.", "addToGroupTooltip": "Mevcut bir grubu seçin veya yeni ad girin. İstemcileri mevcut gruplarından çıkarmak için Ungroup'u kullanın.",
"addToGroupPlaceholder": "Grup adı", "groupName": "Grup adı",
"addToGroupSuccessToast": "{count} istemci {group} grubuna eklendi", "addToGroupSuccessToast": "{count} istemci {group} grubuna eklendi",
"ungroupSuccessToast": "{count} istemcinin grubu temizlendi", "ungroupSuccessToast": "{count} istemcinin grubu temizlendi",
"ungroup": "Gruptan çıkar", "ungroup": "Gruptan çıkar",

View file

@ -400,7 +400,7 @@
"telegramDesc": "Будь ласка, вкажіть ID чату Telegram. (використовуйте команду '/id' у боті) або ({'@'}userinfobot)", "telegramDesc": "Будь ласка, вкажіть ID чату Telegram. (використовуйте команду '/id' у боті) або ({'@'}userinfobot)",
"subscriptionDesc": "Щоб знайти URL-адресу вашої підписки, перейдіть до «Деталі». Крім того, ви можете використовувати одне ім'я для кількох клієнтів.", "subscriptionDesc": "Щоб знайти URL-адресу вашої підписки, перейдіть до «Деталі». Крім того, ви можете використовувати одне ім'я для кількох клієнтів.",
"same": "Те саме", "same": "Те саме",
"inboundData": "Вхідні дані", "inboundInfo": "Інформація про підключення",
"exportInbound": "Експортувати вхідні", "exportInbound": "Експортувати вхідні",
"import": "Імпорт", "import": "Імпорт",
"importInbound": "Імпортувати вхідний", "importInbound": "Імпортувати вхідний",
@ -652,12 +652,12 @@
"comment": "Коментар", "comment": "Коментар",
"traffic": "Трафік", "traffic": "Трафік",
"offline": "Не в мережі", "offline": "Не в мережі",
"addTitle": "Додати клієнта", "addClient": "Додати клієнта",
"qrCode": "QR-код", "qrCode": "QR-код",
"moreInformation": "Докладніше", "clientInfo": "Інформація про клієнта",
"delete": "Видалити", "delete": "Видалити",
"reset": "Скинути трафік", "reset": "Скинути трафік",
"editTitle": "Редагувати клієнта", "editClient": "Редагувати клієнта",
"client": "Клієнт", "client": "Клієнт",
"enabled": "Увімкнено", "enabled": "Увімкнено",
"remaining": "Залишок", "remaining": "Залишок",
@ -679,7 +679,7 @@
"subLinksSelected": "Sub-посилання ({count})", "subLinksSelected": "Sub-посилання ({count})",
"addToGroupTitle": "Додати {count} клієнт(ів) до групи", "addToGroupTitle": "Додати {count} клієнт(ів) до групи",
"addToGroupTooltip": "Виберіть існуючу групу або введіть нову назву. Використовуйте Ungroup, щоб вилучити клієнтів із поточної групи.", "addToGroupTooltip": "Виберіть існуючу групу або введіть нову назву. Використовуйте Ungroup, щоб вилучити клієнтів із поточної групи.",
"addToGroupPlaceholder": "Назва групи", "groupName": "Назва групи",
"addToGroupSuccessToast": "{count} клієнт(ів) додано до {group}", "addToGroupSuccessToast": "{count} клієнт(ів) додано до {group}",
"ungroupSuccessToast": "Групу очищено у {count} клієнт(ів)", "ungroupSuccessToast": "Групу очищено у {count} клієнт(ів)",
"ungroup": "Розгрупувати", "ungroup": "Розгрупувати",

View file

@ -400,7 +400,7 @@
"telegramDesc": "Vui lòng cung cấp ID Trò chuyện Telegram. (sử dụng lệnh '/id' trong bot) hoặc ({'@'}userinfobot)", "telegramDesc": "Vui lòng cung cấp ID Trò chuyện Telegram. (sử dụng lệnh '/id' trong bot) hoặc ({'@'}userinfobot)",
"subscriptionDesc": "Bạn có thể tìm liên kết gói đăng ký của mình trong Chi tiết, cũng như bạn có thể sử dụng cùng tên cho nhiều cấu hình khác nhau", "subscriptionDesc": "Bạn có thể tìm liên kết gói đăng ký của mình trong Chi tiết, cũng như bạn có thể sử dụng cùng tên cho nhiều cấu hình khác nhau",
"same": "Giống nhau", "same": "Giống nhau",
"inboundData": "Dữ liệu gửi đến", "inboundInfo": "Thông tin Inbound",
"exportInbound": "Xuất nhập khẩu", "exportInbound": "Xuất nhập khẩu",
"import": "Nhập", "import": "Nhập",
"importInbound": "Nhập inbound", "importInbound": "Nhập inbound",
@ -652,12 +652,12 @@
"comment": "Ghi chú", "comment": "Ghi chú",
"traffic": "Lưu lượng", "traffic": "Lưu lượng",
"offline": "Ngoại tuyến", "offline": "Ngoại tuyến",
"addTitle": "Thêm khách hàng", "addClient": "Thêm khách hàng",
"qrCode": "Mã QR", "qrCode": "Mã QR",
"moreInformation": "Thông tin thêm", "clientInfo": "Thông tin khách hàng",
"delete": "Xóa", "delete": "Xóa",
"reset": "Đặt lại lưu lượng", "reset": "Đặt lại lưu lượng",
"editTitle": "Chỉnh sửa khách hàng", "editClient": "Chỉnh sửa khách hàng",
"client": "Khách hàng", "client": "Khách hàng",
"enabled": "Đã bật", "enabled": "Đã bật",
"remaining": "Còn lại", "remaining": "Còn lại",
@ -679,7 +679,7 @@
"subLinksSelected": "Liên kết sub ({count})", "subLinksSelected": "Liên kết sub ({count})",
"addToGroupTitle": "Thêm {count} client vào một nhóm", "addToGroupTitle": "Thêm {count} client vào một nhóm",
"addToGroupTooltip": "Chọn nhóm có sẵn hoặc nhập tên mới. Dùng Ungroup để xóa client khỏi nhóm hiện tại.", "addToGroupTooltip": "Chọn nhóm có sẵn hoặc nhập tên mới. Dùng Ungroup để xóa client khỏi nhóm hiện tại.",
"addToGroupPlaceholder": "Tên nhóm", "groupName": "Tên nhóm",
"addToGroupSuccessToast": "Đã thêm {count} client vào {group}", "addToGroupSuccessToast": "Đã thêm {count} client vào {group}",
"ungroupSuccessToast": "Đã xóa nhóm khỏi {count} client", "ungroupSuccessToast": "Đã xóa nhóm khỏi {count} client",
"ungroup": "Bỏ nhóm", "ungroup": "Bỏ nhóm",

View file

@ -400,7 +400,7 @@
"telegramDesc": "请提供Telegram聊天ID。在机器人中使用'/id'命令)或({'@'}userinfobot", "telegramDesc": "请提供Telegram聊天ID。在机器人中使用'/id'命令)或({'@'}userinfobot",
"subscriptionDesc": "要找到你的订阅 URL请导航到“详细信息”。此外你可以为多个客户端使用相同的名称。", "subscriptionDesc": "要找到你的订阅 URL请导航到“详细信息”。此外你可以为多个客户端使用相同的名称。",
"same": "相同", "same": "相同",
"inboundData": "入站数据", "inboundInfo": "入站信息",
"exportInbound": "导出入站规则", "exportInbound": "导出入站规则",
"import": "导入", "import": "导入",
"importInbound": "导入入站规则", "importInbound": "导入入站规则",
@ -652,12 +652,12 @@
"comment": "备注", "comment": "备注",
"traffic": "流量", "traffic": "流量",
"offline": "离线", "offline": "离线",
"addTitle": "添加客户端", "addClient": "添加客户端",
"qrCode": "二维码", "qrCode": "二维码",
"moreInformation": "更多信息", "clientInfo": "客户端信息",
"delete": "删除", "delete": "删除",
"reset": "重置流量", "reset": "重置流量",
"editTitle": "编辑客户端", "editClient": "编辑客户端",
"client": "客户端", "client": "客户端",
"enabled": "已启用", "enabled": "已启用",
"remaining": "剩余", "remaining": "剩余",
@ -679,7 +679,7 @@
"subLinksSelected": "订阅链接 ({count})", "subLinksSelected": "订阅链接 ({count})",
"addToGroupTitle": "将 {count} 个客户端添加到分组", "addToGroupTitle": "将 {count} 个客户端添加到分组",
"addToGroupTooltip": "选择现有分组或输入新名称。使用 Ungroup 操作从当前分组移除客户端。", "addToGroupTooltip": "选择现有分组或输入新名称。使用 Ungroup 操作从当前分组移除客户端。",
"addToGroupPlaceholder": "分组名称", "groupName": "分组名称",
"addToGroupSuccessToast": "已将 {count} 个客户端添加到 {group}", "addToGroupSuccessToast": "已将 {count} 个客户端添加到 {group}",
"ungroupSuccessToast": "已清除 {count} 个客户端的分组", "ungroupSuccessToast": "已清除 {count} 个客户端的分组",
"ungroup": "取消分组", "ungroup": "取消分组",

View file

@ -400,7 +400,7 @@
"telegramDesc": "請提供Telegram聊天ID。在機器人中使用'/id'命令)或({'@'}userinfobot", "telegramDesc": "請提供Telegram聊天ID。在機器人中使用'/id'命令)或({'@'}userinfobot",
"subscriptionDesc": "要找到你的訂閱 URL請導航到“詳細資訊”。此外你可以為多個客戶端使用相同的名稱。", "subscriptionDesc": "要找到你的訂閱 URL請導航到“詳細資訊”。此外你可以為多個客戶端使用相同的名稱。",
"same": "相同", "same": "相同",
"inboundData": "入站資料", "inboundInfo": "入站資訊",
"exportInbound": "匯出入站規則", "exportInbound": "匯出入站規則",
"import": "匯入", "import": "匯入",
"importInbound": "匯入入站規則", "importInbound": "匯入入站規則",
@ -652,12 +652,12 @@
"comment": "備註", "comment": "備註",
"traffic": "流量", "traffic": "流量",
"offline": "離線", "offline": "離線",
"addTitle": "新增客戶端", "addClient": "新增客戶端",
"qrCode": "QR 碼", "qrCode": "QR 碼",
"moreInformation": "更多資訊", "clientInfo": "客戶端資訊",
"delete": "刪除", "delete": "刪除",
"reset": "重設流量", "reset": "重設流量",
"editTitle": "編輯客戶端", "editClient": "編輯客戶端",
"client": "客戶端", "client": "客戶端",
"enabled": "已啟用", "enabled": "已啟用",
"remaining": "剩餘", "remaining": "剩餘",
@ -679,7 +679,7 @@
"subLinksSelected": "訂閱連結 ({count})", "subLinksSelected": "訂閱連結 ({count})",
"addToGroupTitle": "將 {count} 個客戶端加入群組", "addToGroupTitle": "將 {count} 個客戶端加入群組",
"addToGroupTooltip": "選擇現有群組或輸入新名稱。使用 Ungroup 操作從當前群組移除客戶端。", "addToGroupTooltip": "選擇現有群組或輸入新名稱。使用 Ungroup 操作從當前群組移除客戶端。",
"addToGroupPlaceholder": "群組名稱", "groupName": "群組名稱",
"addToGroupSuccessToast": "已將 {count} 個客戶端加入 {group}", "addToGroupSuccessToast": "已將 {count} 個客戶端加入 {group}",
"ungroupSuccessToast": "已清除 {count} 個客戶端的群組", "ungroupSuccessToast": "已清除 {count} 個客戶端的群組",
"ungroup": "取消群組", "ungroup": "取消群組",