mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 12:44:22 +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:
parent
b1ccf915db
commit
985e647d6e
1 changed files with 109 additions and 1 deletions
|
|
@ -24,7 +24,7 @@ import {
|
||||||
formValuesToWirePayload,
|
formValuesToWirePayload,
|
||||||
} from '@/lib/xray/inbound-form-adapter';
|
} from '@/lib/xray/inbound-form-adapter';
|
||||||
import { createDefaultInboundSettings } from '@/lib/xray/inbound-defaults';
|
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 { SSMethodSchema } from '@/schemas/protocols/inbound/shadowsocks';
|
||||||
import {
|
import {
|
||||||
InboundFormBaseSchema,
|
InboundFormBaseSchema,
|
||||||
|
|
@ -105,6 +105,8 @@ export default function InboundFormModalNew({
|
||||||
settings: typeof ssMethod === 'string' ? { method: ssMethod } : {},
|
settings: typeof ssMethod === 'string' ? { method: ssMethod } : {},
|
||||||
});
|
});
|
||||||
const mixedUdpOn = Form.useWatch(['settings', 'udp'], form) ?? false;
|
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 wgSecretKey = Form.useWatch(['settings', 'secretKey'], form);
|
||||||
const wgPubKey = typeof wgSecretKey === 'string' && wgSecretKey.length > 0
|
const wgPubKey = typeof wgSecretKey === 'string' && wgSecretKey.length > 0
|
||||||
? Wireguard.generateKeypair(wgSecretKey).publicKey
|
? 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 = (
|
const sniffingTab = (
|
||||||
<>
|
<>
|
||||||
<Form.Item name={['sniffing', 'enabled']} label={t('enable')} valuePropName="checked">
|
<Form.Item name={['sniffing', 'enabled']} label={t('enable')} valuePropName="checked">
|
||||||
|
|
@ -839,6 +944,9 @@ export default function InboundFormModalNew({
|
||||||
] as string[]).includes(protocol)
|
] as string[]).includes(protocol)
|
||||||
? [{ key: 'protocol', label: t('pages.inbounds.protocol'), children: protocolTab }]
|
? [{ 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 },
|
{ key: 'sniffing', label: t('pages.inbounds.sniffingTab'), children: sniffingTab },
|
||||||
]} />
|
]} />
|
||||||
</Form>
|
</Form>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue