diff --git a/frontend/src/pages/xray/OutboundFormModal.new.tsx b/frontend/src/pages/xray/OutboundFormModal.new.tsx index 369891f8..02876046 100644 --- a/frontend/src/pages/xray/OutboundFormModal.new.tsx +++ b/frontend/src/pages/xray/OutboundFormModal.new.tsx @@ -6,6 +6,7 @@ import { Input, InputNumber, Modal, + Radio, Select, Space, Switch, @@ -30,6 +31,7 @@ import { type OutboundFormValues, } from '@/schemas/forms/outbound-form'; import { + ALPN_OPTION, DNSRuleActions, MODE_OPTION, OutboundDomainStrategies, @@ -37,12 +39,14 @@ import { SNIFFING_OPTION, TLS_FLOW_CONTROL, USERS_SECURITY, + UTLS_FINGERPRINT, WireguardDomainStrategy, } from '@/schemas/primitives'; import { canEnableReality, canEnableStream, canEnableTls, + canEnableTlsFlow, } from '@/lib/xray/protocol-capabilities'; import { SSMethodSchema } from '@/schemas/protocols/inbound/shadowsocks'; import { antdRule } from '@/utils/zodForm'; @@ -66,6 +70,8 @@ const SECURITY_OPTIONS = Object.values(USERS_SECURITY).map((v) => ({ value: v, l const FLOW_OPTIONS = Object.values(TLS_FLOW_CONTROL).map((v) => ({ value: v, label: v })); const SS_METHOD_OPTIONS = SSMethodSchema.options.map((v) => ({ value: v, label: v })); const MODE_OPTIONS = Object.values(MODE_OPTION).map((v) => ({ value: v, label: v })); +const UTLS_OPTIONS = Object.values(UTLS_FINGERPRINT).map((v) => ({ value: v, label: v })); +const ALPN_OPTIONS = Object.values(ALPN_OPTION).map((v) => ({ value: v, label: v })); const NETWORK_OPTIONS: { value: string; label: string }[] = [ { value: 'tcp', label: 'TCP (RAW)' }, @@ -165,8 +171,12 @@ export default function OutboundFormModalNew({ const tag = Form.useWatch('tag', form) ?? ''; const protocol = (Form.useWatch('protocol', form) ?? 'vless') as string; const network = (Form.useWatch(['streamSettings', 'network'], form) ?? '') as string; + const security = (Form.useWatch(['streamSettings', 'security'], form) ?? 'none') as string; const streamAllowed = canEnableStream({ protocol }); + const tlsAllowed = canEnableTls({ protocol, streamSettings: { network, security } }); + const realityAllowed = canEnableReality({ protocol, streamSettings: { network, security } }); + const tlsFlowAllowed = canEnableTlsFlow({ protocol, streamSettings: { network, security } }); // Seed streamSettings when the user picks a protocol that supports // streams but the form does not yet have a stream slice (new outbound, @@ -190,6 +200,37 @@ export default function OutboundFormModalNew({ } } + // Security change cascade: swap the security sub-key so the DU branch + // matches. Seed default field values when entering tls/reality so the + // sub-forms render without `undefined` field references. + function onSecurityChange(next: string) { + const stream = form.getFieldValue('streamSettings') ?? {}; + const cleaned = { ...stream } as Record; + delete cleaned.tlsSettings; + delete cleaned.realitySettings; + if (next === 'tls') { + cleaned.tlsSettings = { + serverName: '', + alpn: [], + fingerprint: '', + echConfigList: '', + verifyPeerCertByName: '', + pinnedPeerCertSha256: '', + }; + } else if (next === 'reality') { + cleaned.realitySettings = { + publicKey: '', + fingerprint: 'chrome', + serverName: '', + shortId: '', + spiderX: '', + mldsa65Verify: '', + }; + } + cleaned.security = next; + form.setFieldValue('streamSettings', cleaned); + } + // Network change cascade: swap the per-network sub-key (tcpSettings, // wsSettings, etc.) so the DU branch matches. Preserve security if // the new network supports it, otherwise force back to 'none'. @@ -369,13 +410,6 @@ export default function OutboundFormModalNew({ > - - @@ -1145,6 +1179,116 @@ export default function OutboundFormModalNew({ )} )} + + {tlsFlowAllowed && ( + + + + + + + + + + + + + + + + + )} + + {security === 'reality' && realityAllowed && ( + <> + + + + + + + + + + + + + + + + + )} ), },