mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-28 16:09:36 +00:00
feat(frontend): stream tab skeleton with TCP + KCP (Pattern A)
Opens the stream tab on the sibling-file rewrite. Tab visibility is driven by canEnableStream from lib/xray/protocol-capabilities — same gate the legacy modal used, now schema-aware. Transmission picker (network select) is hidden for HYSTERIA since that protocol's network is implicit. onNetworkChange clears any stale per-network settings keys (tcpSettings/kcpSettings/...) and seeds an empty object for the new branch so AntD Form.Items don't read from undefined nested paths. TCP section: acceptProxyProtocol Switch (literal-true-optional on the wire — the form stores true/false but Zod's strip behavior keeps false-as-omission round-trips clean) plus an HTTP-camouflage toggle that flips header.type between 'none' and 'http'. The full HTTP camouflage request/response sub-form lands in a follow-up commit. KCP section: six numeric knobs (mtu, tti, upCap, downCap, cwndMultiplier, maxSendingWindow). WS / gRPC / HTTPUpgrade / XHTTP / external-proxy / sockopt / hysteria stream / FinalMaskForm hookup all still pending.
This commit is contained in:
@@ -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<string, unknown>) ?? {};
|
||||
const cleaned: Record<string, unknown> = { ...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 && (
|
||||
<Form.Item label="Transmission">
|
||||
<Select
|
||||
value={network}
|
||||
style={{ width: '75%' }}
|
||||
onChange={onNetworkChange}
|
||||
>
|
||||
<Select.Option value="tcp">TCP (RAW)</Select.Option>
|
||||
<Select.Option value="kcp">mKCP</Select.Option>
|
||||
<Select.Option value="ws">WebSocket</Select.Option>
|
||||
<Select.Option value="grpc">gRPC</Select.Option>
|
||||
<Select.Option value="httpupgrade">HTTPUpgrade</Select.Option>
|
||||
<Select.Option value="xhttp">XHTTP</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{network === 'tcp' && (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'tcpSettings', 'acceptProxyProtocol']}
|
||||
label="Proxy Protocol"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item label={`HTTP ${t('camouflage')}`}>
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prev, curr) =>
|
||||
prev.streamSettings?.tcpSettings?.header?.type
|
||||
!== curr.streamSettings?.tcpSettings?.header?.type
|
||||
}
|
||||
>
|
||||
{({ getFieldValue, setFieldValue }) => {
|
||||
const headerType = getFieldValue(
|
||||
['streamSettings', 'tcpSettings', 'header', 'type'],
|
||||
) as string | undefined;
|
||||
return (
|
||||
<Switch
|
||||
checked={headerType === 'http'}
|
||||
onChange={(v) => {
|
||||
setFieldValue(
|
||||
['streamSettings', 'tcpSettings', 'header'],
|
||||
v ? { type: 'http' } : { type: 'none' },
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
{network === 'kcp' && (
|
||||
<>
|
||||
<Form.Item name={['streamSettings', 'kcpSettings', 'mtu']} label="MTU">
|
||||
<InputNumber min={576} max={1460} />
|
||||
</Form.Item>
|
||||
<Form.Item name={['streamSettings', 'kcpSettings', 'tti']} label="TTI (ms)">
|
||||
<InputNumber min={10} max={100} />
|
||||
</Form.Item>
|
||||
<Form.Item name={['streamSettings', 'kcpSettings', 'upCap']} label="Uplink (MB/s)">
|
||||
<InputNumber min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item name={['streamSettings', 'kcpSettings', 'downCap']} label="Downlink (MB/s)">
|
||||
<InputNumber min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'kcpSettings', 'cwndMultiplier']}
|
||||
label="CWND Multiplier"
|
||||
>
|
||||
<InputNumber min={1} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'kcpSettings', 'maxSendingWindow']}
|
||||
label="Max Sending Window"
|
||||
>
|
||||
<InputNumber min={0} />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
const sniffingTab = (
|
||||
<>
|
||||
<Form.Item name={['sniffing', 'enabled']} label={t('enable')} valuePropName="checked">
|
||||
@@ -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 },
|
||||
]} />
|
||||
</Form>
|
||||
|
||||
Reference in New Issue
Block a user