mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-25 13:56:10 +00:00
fix(xray): test UDP outbounds via xray probe (#4657) + Vision testseed & Flow form fixes
Outbound connection tester (#4657): UDP-based outbounds (wireguard, hysteria, kcp/quic transports) were probed with a raw UDP dial that treated the inevitable read timeout as success, so every one reported a fake ~5s 'alive'. Route them through the authoritative xray burstObservatory probe and drop the broken raw-UDP path. Test All now runs a parallel TCP lane and a serial HTTP lane so xray-probe outbounds don't collide on the test semaphore. Vision testseed: the [900, 500, 900, 256] default repeats 900, and a tags Select keys each tag by value -> 'two children with the same key, 900'. Render it as four InputNumbers (inbound + outbound forms); the field is a fixed 4-tuple where repeats are valid. Inbound form: drop the null-valued 'Local Panel' Select option (AntD rejects null option values; placeholder + allowClear already cover it). Outbound form: add an explicit 'None' option to the Flow selector.
This commit is contained in:
@@ -931,14 +931,11 @@ export default function InboundFormModal({
|
||||
disabled={mode === 'edit'}
|
||||
placeholder={t('pages.inbounds.localPanel')}
|
||||
allowClear
|
||||
options={[
|
||||
{ value: null, label: t('pages.inbounds.localPanel') },
|
||||
...selectableNodes.map((n) => ({
|
||||
value: n.id,
|
||||
label: `${n.name}${n.status === 'offline' ? ' (offline)' : ''}`,
|
||||
disabled: n.status === 'offline',
|
||||
})),
|
||||
]}
|
||||
options={selectableNodes.map((n) => ({
|
||||
value: n.id,
|
||||
label: `${n.name}${n.status === 'offline' ? ' (offline)' : ''}`,
|
||||
disabled: n.status === 'offline',
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
@@ -1498,16 +1495,15 @@ export default function InboundFormModal({
|
||||
{network === 'tcp' && (security === 'tls' || security === 'reality') && (
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.visionTestseed')}
|
||||
name={['settings', 'testseed']}
|
||||
initialValue={[900, 500, 900, 256]}
|
||||
normalize={(v: unknown) =>
|
||||
Array.isArray(v)
|
||||
? v.map((x) => Number(x)).filter((n) => Number.isInteger(n) && n > 0)
|
||||
: []
|
||||
}
|
||||
extra="Applies only to clients using the xtls-rprx-vision flow; ignored otherwise."
|
||||
>
|
||||
<Select mode="tags" tokenSeparators={[',', ' ']} placeholder="four positive integers" />
|
||||
<Space.Compact block>
|
||||
{[900, 500, 900, 256].map((def, i) => (
|
||||
<Form.Item key={i} name={['settings', 'testseed', i]} noStyle initialValue={def}>
|
||||
<InputNumber min={1} style={{ width: '25%' }} />
|
||||
</Form.Item>
|
||||
))}
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -191,8 +191,8 @@ export default function OutboundFormModal({
|
||||
const [linkInput, setLinkInput] = useState('');
|
||||
|
||||
// Parse a share link (vmess:// / vless:// / trojan:// / ss:// /
|
||||
// hysteria2://) and replace form state with the result. The current
|
||||
// tag is preserved when the parsed link doesn't carry one.
|
||||
// hysteria2:// / wireguard://) and replace form state with the result.
|
||||
// The current tag is preserved when the parsed link doesn't carry one.
|
||||
function importLink() {
|
||||
const link = linkInput.trim();
|
||||
if (!link) return;
|
||||
@@ -1743,7 +1743,7 @@ export default function OutboundFormModal({
|
||||
<Select
|
||||
allowClear
|
||||
placeholder={t('none')}
|
||||
options={FLOW_OPTIONS}
|
||||
options={[{ value: '', label: t('none') }, ...FLOW_OPTIONS]}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
@@ -1762,22 +1762,14 @@ export default function OutboundFormModal({
|
||||
<Form.Item label={t('pages.xray.outboundForm.visionTestpre')} name={['settings', 'testpre']}>
|
||||
<InputNumber min={0} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.visionTestseed')}
|
||||
name={['settings', 'testseed']}
|
||||
normalize={(v: unknown) =>
|
||||
Array.isArray(v)
|
||||
? v
|
||||
.map((x) => Number(x))
|
||||
.filter((n) => Number.isInteger(n) && n > 0)
|
||||
: []
|
||||
}
|
||||
>
|
||||
<Select
|
||||
mode="tags"
|
||||
tokenSeparators={[',', ' ']}
|
||||
placeholder="four positive integers"
|
||||
/>
|
||||
<Form.Item label={t('pages.inbounds.form.visionTestseed')}>
|
||||
<Space.Compact block>
|
||||
{[900, 500, 900, 256].map((def, i) => (
|
||||
<Form.Item key={i} name={['settings', 'testseed', i]} noStyle initialValue={def}>
|
||||
<InputNumber min={1} style={{ width: '25%' }} />
|
||||
</Form.Item>
|
||||
))}
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
@@ -2215,7 +2207,7 @@ export default function OutboundFormModal({
|
||||
<Space orientation="vertical" size={10} style={{ width: '100%', marginTop: 10 }}>
|
||||
<Input.Search
|
||||
value={linkInput}
|
||||
placeholder="vmess:// vless:// trojan:// ss:// hysteria2://"
|
||||
placeholder="vmess:// vless:// trojan:// ss:// hysteria2:// wireguard://"
|
||||
enterButton="Import"
|
||||
onChange={(e) => setLinkInput(e.target.value)}
|
||||
onSearch={importLink}
|
||||
|
||||
@@ -36,6 +36,7 @@ import type { ColumnsType } from 'antd/es/table';
|
||||
import { SizeFormatter } from '@/utils';
|
||||
import { OutboundProtocols as Protocols } from '@/schemas/primitives';
|
||||
import OutboundFormModal from './OutboundFormModal';
|
||||
import { isUdpOutbound } from '@/hooks/useXraySetting';
|
||||
import type { XraySettingsValue, SetTemplate, OutboundTestState, OutboundTrafficRow } from '@/hooks/useXraySetting';
|
||||
import './OutboundsTab.css';
|
||||
|
||||
@@ -361,7 +362,7 @@ export default function OutboundsTab({
|
||||
align: 'center',
|
||||
width: 80,
|
||||
render: (_v, record, index) => (
|
||||
<Tooltip title={`${t('check')} (${testMode.toUpperCase()})`}>
|
||||
<Tooltip title={`${t('check')} (${(isUdpOutbound(record) ? 'http' : testMode).toUpperCase()})`}>
|
||||
<Button
|
||||
type="primary"
|
||||
shape="circle"
|
||||
|
||||
Reference in New Issue
Block a user