import { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Alert, Button, Col, Input, InputNumber, Row, Select, Space, Tag } from 'antd'; import { AimOutlined } from '@ant-design/icons'; import { HttpUtil } from '@/utils'; import { useInboundOptions } from '@/api/queries/useInboundOptions'; import { buildRemarkByTag, formatInboundTag } from './helpers'; interface RouteTesterProps { inboundTags: string[]; isMobile: boolean; } // Mirror of the /xray/routeTest response (RoutingService.TestRoute). interface RouteTestResult { matched: boolean; outboundTag: string; groupTags?: string[]; } const PROTOCOL_OPTIONS = ['http', 'tls', 'quic', 'bittorrent'].map((p) => ({ label: p, value: p })); export default function RouteTester({ inboundTags, isMobile }: RouteTesterProps) { const { t } = useTranslation(); const { data: inboundOptions } = useInboundOptions(); const remarkByTag = useMemo(() => buildRemarkByTag(inboundOptions || []), [inboundOptions]); const [dest, setDest] = useState(''); const [port, setPort] = useState(443); const [network, setNetwork] = useState('tcp'); const [inboundTag, setInboundTag] = useState(undefined); const [protocol, setProtocol] = useState(undefined); const [testing, setTesting] = useState(false); const [result, setResult] = useState(null); async function run() { const value = dest.trim(); if (!value) return; // Domains never contain ':' and a pure dotted-quad is an IPv4 address; // everything else is treated as a domain. const isIp = /^(\d{1,3}\.){3}\d{1,3}$/.test(value) || value.includes(':'); setTesting(true); setResult(null); try { const msg = await HttpUtil.post('/panel/api/xray/routeTest', { domain: isIp ? '' : value, ip: isIp ? value : '', port: port ?? 0, network, inboundTag: inboundTag || '', protocol: protocol || '', }); if (msg?.success && msg.obj && typeof msg.obj === 'object') { setResult(msg.obj as RouteTestResult); } } finally { setTesting(false); } } const fieldSpan = isMobile ? 24 : undefined; return ( setDest(e.target.value)} onPressEnter={run} allowClear /> setPort(v)} /> ({ label: formatInboundTag(tag, remarkByTag), value: tag }))} />