diff --git a/frontend/src/pages/inbounds/InboundFormModal.new.tsx b/frontend/src/pages/inbounds/InboundFormModal.new.tsx index dc1cb908..fcb23311 100644 --- a/frontend/src/pages/inbounds/InboundFormModal.new.tsx +++ b/frontend/src/pages/inbounds/InboundFormModal.new.tsx @@ -24,7 +24,7 @@ import { formValuesToWirePayload, } from '@/lib/xray/inbound-form-adapter'; import { createDefaultInboundSettings } from '@/lib/xray/inbound-defaults'; -import { isSS2022 } from '@/lib/xray/protocol-capabilities'; +import { canEnableStream, isSS2022 } from '@/lib/xray/protocol-capabilities'; import { SSMethodSchema } from '@/schemas/protocols/inbound/shadowsocks'; import { InboundFormBaseSchema, @@ -105,6 +105,8 @@ export default function InboundFormModalNew({ settings: typeof ssMethod === 'string' ? { method: ssMethod } : {}, }); const mixedUdpOn = Form.useWatch(['settings', 'udp'], form) ?? false; + const network = Form.useWatch(['streamSettings', 'network'], form) ?? ''; + const streamEnabled = canEnableStream({ protocol }); const wgSecretKey = Form.useWatch(['settings', 'secretKey'], form); const wgPubKey = typeof wgSecretKey === 'string' && wgSecretKey.length > 0 ? Wireguard.generateKeypair(wgSecretKey).publicKey @@ -744,6 +746,109 @@ export default function InboundFormModalNew({ ); + // Switching `network` swaps which per-network key (tcpSettings, wsSettings, + // grpcSettings, ...) appears on the wire. We clear the previously selected + // network's settings blob and seed a default empty object for the new one + // so AntD's Form.Items aren't pointed at undefined nested paths. + const onNetworkChange = (next: string) => { + const ALL = ['tcpSettings', 'kcpSettings', 'wsSettings', 'grpcSettings', 'httpupgradeSettings', 'xhttpSettings']; + const current = (form.getFieldValue('streamSettings') as Record) ?? {}; + const cleaned: Record = { ...current, network: next }; + for (const k of ALL) { + if (k !== `${next}Settings`) delete cleaned[k]; + } + cleaned[`${next}Settings`] = {}; + form.setFieldValue('streamSettings', cleaned); + }; + + const streamTab = ( + <> + {protocol !== Protocols.HYSTERIA && ( + + + + )} + + {network === 'tcp' && ( + <> + + + + + + prev.streamSettings?.tcpSettings?.header?.type + !== curr.streamSettings?.tcpSettings?.header?.type + } + > + {({ getFieldValue, setFieldValue }) => { + const headerType = getFieldValue( + ['streamSettings', 'tcpSettings', 'header', 'type'], + ) as string | undefined; + return ( + { + setFieldValue( + ['streamSettings', 'tcpSettings', 'header'], + v ? { type: 'http' } : { type: 'none' }, + ); + }} + /> + ); + }} + + + + )} + + {network === 'kcp' && ( + <> + + + + + + + + + + + + + + + + + + + + )} + + ); + const sniffingTab = ( <> @@ -839,6 +944,9 @@ export default function InboundFormModalNew({ ] as string[]).includes(protocol) ? [{ key: 'protocol', label: t('pages.inbounds.protocol'), children: protocolTab }] : []), + ...(streamEnabled + ? [{ key: 'stream', label: t('pages.inbounds.streamTab'), children: streamTab }] + : []), { key: 'sniffing', label: t('pages.inbounds.sniffingTab'), children: sniffingTab }, ]} />