mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 20:54:14 +00:00
refactor(frontend): extract OutboundFormModal xhttp transport form
Move the xhttp transport block into transport/xhttp.tsx (takes form + onXmuxToggle prop); drop now-unused HeaderMapEditor and MODE_OPTIONS imports from the modal. OutboundFormModal.tsx down to ~1001 lines (from 2238 originally). Verbatim relocation; outbound snapshots unchanged -> no behavior change. typecheck/lint/build green.
This commit is contained in:
parent
3271374401
commit
543ede63aa
3 changed files with 358 additions and 341 deletions
|
|
@ -14,7 +14,6 @@ import {
|
|||
message,
|
||||
} from 'antd';
|
||||
import { FinalMaskForm } from '@/lib/xray/forms/transport';
|
||||
import { HeaderMapEditor } from '@/components/form';
|
||||
import { HysteriaMasqueradeForm } from '@/lib/xray/forms/protocols/shared';
|
||||
import { JsonEditor } from '@/components/form';
|
||||
import { Wireguard } from '@/utils';
|
||||
|
|
@ -50,7 +49,6 @@ import {
|
|||
ALPN_OPTIONS,
|
||||
FLOW_OPTIONS,
|
||||
HYSTERIA_NETWORK_OPTION,
|
||||
MODE_OPTIONS,
|
||||
NETWORK_OPTIONS,
|
||||
PROTOCOL_OPTIONS,
|
||||
SERVER_PROTOCOLS,
|
||||
|
|
@ -82,6 +80,7 @@ import {
|
|||
KcpForm,
|
||||
RawForm,
|
||||
WsForm,
|
||||
XhttpForm,
|
||||
} from './transport';
|
||||
import './OutboundFormModal.css';
|
||||
|
||||
|
|
@ -524,345 +523,7 @@ export default function OutboundFormModal({
|
|||
|
||||
{network === 'httpupgrade' && <HttpUpgradeForm />}
|
||||
|
||||
{network === 'xhttp' && (
|
||||
<>
|
||||
<Form.Item
|
||||
label={t('host')}
|
||||
name={['streamSettings', 'xhttpSettings', 'host']}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('path')}
|
||||
name={['streamSettings', 'xhttpSettings', 'path']}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.info.mode')}
|
||||
name={['streamSettings', 'xhttpSettings', 'mode']}
|
||||
>
|
||||
<Select options={MODE_OPTIONS} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.paddingBytes')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingBytes']}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.headers')}
|
||||
name={['streamSettings', 'xhttpSettings', 'headers']}
|
||||
>
|
||||
<HeaderMapEditor mode="v1" />
|
||||
</Form.Item>
|
||||
|
||||
{/* Padding obfs sub-section: gated by a Switch.
|
||||
When on, four extra knobs (key/header/placement/
|
||||
method) tune how Xray injects random padding to
|
||||
disguise the post body shape. */}
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.paddingObfsMode')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingObfsMode']}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() => {
|
||||
const obfs = !!form.getFieldValue([
|
||||
'streamSettings', 'xhttpSettings', 'xPaddingObfsMode',
|
||||
]);
|
||||
if (!obfs) return null;
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.paddingKey')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingKey']}
|
||||
>
|
||||
<Input placeholder="x_padding" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.paddingHeader')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingHeader']}
|
||||
>
|
||||
<Input placeholder="X-Padding" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.paddingPlacement')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingPlacement']}
|
||||
>
|
||||
<Select
|
||||
options={[
|
||||
{ value: '', label: 'Default (queryInHeader)' },
|
||||
{ value: 'queryInHeader', label: 'queryInHeader' },
|
||||
{ value: 'header', label: 'header' },
|
||||
{ value: 'cookie', label: 'cookie' },
|
||||
{ value: 'query', label: 'query' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.paddingMethod')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingMethod']}
|
||||
>
|
||||
<Select
|
||||
options={[
|
||||
{ value: '', label: 'Default (repeat-x)' },
|
||||
{ value: 'repeat-x', label: 'repeat-x' },
|
||||
{ value: 'tokenish', label: 'tokenish' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prev, curr) =>
|
||||
prev?.streamSettings?.xhttpSettings?.mode !==
|
||||
curr?.streamSettings?.xhttpSettings?.mode
|
||||
}
|
||||
>
|
||||
{() => {
|
||||
const mode = form.getFieldValue([
|
||||
'streamSettings', 'xhttpSettings', 'mode',
|
||||
]);
|
||||
return (
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.uplinkHttpMethod')}
|
||||
name={['streamSettings', 'xhttpSettings', 'uplinkHTTPMethod']}
|
||||
>
|
||||
<Select
|
||||
placeholder="Default (POST)"
|
||||
options={[
|
||||
{ value: '', label: 'Default (POST)' },
|
||||
{ value: 'POST', label: 'POST' },
|
||||
{ value: 'PUT', label: 'PUT' },
|
||||
{ value: 'GET', label: 'GET (packet-up only)', disabled: mode !== 'packet-up' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
{/* Session + sequence + uplinkData placements:
|
||||
three orthogonal slots Xray uses to thread
|
||||
request metadata through the transport
|
||||
(path / header / cookie / query). Key field
|
||||
only matters when placement is not 'path'. */}
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.sessionPlacement')}
|
||||
name={['streamSettings', 'xhttpSettings', 'sessionPlacement']}
|
||||
>
|
||||
<Select
|
||||
placeholder="Default (path)"
|
||||
options={[
|
||||
{ value: '', label: 'Default (path)' },
|
||||
{ value: 'path', label: 'path' },
|
||||
{ value: 'header', label: 'header' },
|
||||
{ value: 'cookie', label: 'cookie' },
|
||||
{ value: 'query', label: 'query' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() => {
|
||||
const placement = form.getFieldValue([
|
||||
'streamSettings', 'xhttpSettings', 'sessionPlacement',
|
||||
]);
|
||||
if (!placement || placement === 'path') return null;
|
||||
return (
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.sessionKey')}
|
||||
name={['streamSettings', 'xhttpSettings', 'sessionKey']}
|
||||
>
|
||||
<Input placeholder="x_session" />
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.sequencePlacement')}
|
||||
name={['streamSettings', 'xhttpSettings', 'seqPlacement']}
|
||||
>
|
||||
<Select
|
||||
placeholder="Default (path)"
|
||||
options={[
|
||||
{ value: '', label: 'Default (path)' },
|
||||
{ value: 'path', label: 'path' },
|
||||
{ value: 'header', label: 'header' },
|
||||
{ value: 'cookie', label: 'cookie' },
|
||||
{ value: 'query', label: 'query' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() => {
|
||||
const placement = form.getFieldValue([
|
||||
'streamSettings', 'xhttpSettings', 'seqPlacement',
|
||||
]);
|
||||
if (!placement || placement === 'path') return null;
|
||||
return (
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.sequenceKey')}
|
||||
name={['streamSettings', 'xhttpSettings', 'seqKey']}
|
||||
>
|
||||
<Input placeholder="x_seq" />
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
{/* Mode-conditional sub-sections. */}
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() => {
|
||||
const mode = form.getFieldValue([
|
||||
'streamSettings', 'xhttpSettings', 'mode',
|
||||
]);
|
||||
if (mode !== 'packet-up') return null;
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.minUploadInterval')}
|
||||
name={['streamSettings', 'xhttpSettings', 'scMinPostsIntervalMs']}
|
||||
>
|
||||
<Input placeholder="30" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.maxUploadSizeBytes')}
|
||||
name={['streamSettings', 'xhttpSettings', 'scMaxEachPostBytes']}
|
||||
>
|
||||
<Input placeholder="1000000" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.uplinkDataPlacement')}
|
||||
name={['streamSettings', 'xhttpSettings', 'uplinkDataPlacement']}
|
||||
>
|
||||
<Select
|
||||
options={[
|
||||
{ value: '', label: 'Default (body)' },
|
||||
{ value: 'body', label: 'body' },
|
||||
{ value: 'header', label: 'header' },
|
||||
{ value: 'cookie', label: 'cookie' },
|
||||
{ value: 'query', label: 'query' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() => {
|
||||
const place = form.getFieldValue([
|
||||
'streamSettings', 'xhttpSettings', 'uplinkDataPlacement',
|
||||
]);
|
||||
if (!place || place === 'body') return null;
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.uplinkDataKey')}
|
||||
name={['streamSettings', 'xhttpSettings', 'uplinkDataKey']}
|
||||
>
|
||||
<Input placeholder="x_data" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.uplinkChunkSize')}
|
||||
name={['streamSettings', 'xhttpSettings', 'uplinkChunkSize']}
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
placeholder="0 (unlimited)"
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() => {
|
||||
const mode = form.getFieldValue([
|
||||
'streamSettings', 'xhttpSettings', 'mode',
|
||||
]);
|
||||
if (mode !== 'stream-up' && mode !== 'stream-one') return null;
|
||||
return (
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.noGrpcHeader')}
|
||||
name={['streamSettings', 'xhttpSettings', 'noGRPCHeader']}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
{/* XMUX is the connection-multiplexing layer
|
||||
xHTTP uses to fan out parallel requests over
|
||||
a small pool of upstream connections. UI-only
|
||||
toggle (enableXmux) hides the 6 nested knobs
|
||||
when off. */}
|
||||
<Form.Item
|
||||
label="XMUX"
|
||||
name={['streamSettings', 'xhttpSettings', 'enableXmux']}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch onChange={onXmuxToggle} />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() => {
|
||||
if (!form.getFieldValue([
|
||||
'streamSettings', 'xhttpSettings', 'enableXmux',
|
||||
])) return null;
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.maxConcurrency')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xmux', 'maxConcurrency']}
|
||||
>
|
||||
<Input placeholder="16-32" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.maxConnections')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xmux', 'maxConnections']}
|
||||
>
|
||||
<Input placeholder="0" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.maxReuseTimes')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xmux', 'cMaxReuseTimes']}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.maxRequestTimes')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xmux', 'hMaxRequestTimes']}
|
||||
>
|
||||
<Input placeholder="600-900" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.maxReusableSecs')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xmux', 'hMaxReusableSecs']}
|
||||
>
|
||||
<Input placeholder="1800-3000" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.keepAlivePeriod')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xmux', 'hKeepAlivePeriod']}
|
||||
>
|
||||
<InputNumber min={0} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
{network === 'xhttp' && <XhttpForm form={form} onXmuxToggle={onXmuxToggle} />}
|
||||
|
||||
{network === 'hysteria' && (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@ export { default as KcpForm } from './kcp';
|
|||
export { default as WsForm } from './ws';
|
||||
export { default as GrpcForm } from './grpc';
|
||||
export { default as HttpUpgradeForm } from './httpupgrade';
|
||||
export { default as XhttpForm } from './xhttp';
|
||||
|
|
|
|||
355
frontend/src/pages/xray/outbounds/transport/xhttp.tsx
Normal file
355
frontend/src/pages/xray/outbounds/transport/xhttp.tsx
Normal file
|
|
@ -0,0 +1,355 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, Input, InputNumber, Select, Switch, type FormInstance } from 'antd';
|
||||
|
||||
import { HeaderMapEditor } from '@/components/form';
|
||||
import type { OutboundFormValues } from '@/schemas/forms/outbound-form';
|
||||
|
||||
import { MODE_OPTIONS } from '../outbound-form-constants';
|
||||
|
||||
interface XhttpFormProps {
|
||||
form: FormInstance<OutboundFormValues>;
|
||||
onXmuxToggle: (checked: boolean) => void;
|
||||
}
|
||||
|
||||
export default function XhttpForm({ form, onXmuxToggle }: XhttpFormProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label={t('host')}
|
||||
name={['streamSettings', 'xhttpSettings', 'host']}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('path')}
|
||||
name={['streamSettings', 'xhttpSettings', 'path']}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.info.mode')}
|
||||
name={['streamSettings', 'xhttpSettings', 'mode']}
|
||||
>
|
||||
<Select options={MODE_OPTIONS} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.paddingBytes')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingBytes']}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.headers')}
|
||||
name={['streamSettings', 'xhttpSettings', 'headers']}
|
||||
>
|
||||
<HeaderMapEditor mode="v1" />
|
||||
</Form.Item>
|
||||
|
||||
{/* Padding obfs sub-section: gated by a Switch.
|
||||
When on, four extra knobs (key/header/placement/
|
||||
method) tune how Xray injects random padding to
|
||||
disguise the post body shape. */}
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.paddingObfsMode')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingObfsMode']}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() => {
|
||||
const obfs = !!form.getFieldValue([
|
||||
'streamSettings', 'xhttpSettings', 'xPaddingObfsMode',
|
||||
]);
|
||||
if (!obfs) return null;
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.paddingKey')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingKey']}
|
||||
>
|
||||
<Input placeholder="x_padding" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.paddingHeader')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingHeader']}
|
||||
>
|
||||
<Input placeholder="X-Padding" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.paddingPlacement')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingPlacement']}
|
||||
>
|
||||
<Select
|
||||
options={[
|
||||
{ value: '', label: 'Default (queryInHeader)' },
|
||||
{ value: 'queryInHeader', label: 'queryInHeader' },
|
||||
{ value: 'header', label: 'header' },
|
||||
{ value: 'cookie', label: 'cookie' },
|
||||
{ value: 'query', label: 'query' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.paddingMethod')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingMethod']}
|
||||
>
|
||||
<Select
|
||||
options={[
|
||||
{ value: '', label: 'Default (repeat-x)' },
|
||||
{ value: 'repeat-x', label: 'repeat-x' },
|
||||
{ value: 'tokenish', label: 'tokenish' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prev, curr) =>
|
||||
prev?.streamSettings?.xhttpSettings?.mode !==
|
||||
curr?.streamSettings?.xhttpSettings?.mode
|
||||
}
|
||||
>
|
||||
{() => {
|
||||
const mode = form.getFieldValue([
|
||||
'streamSettings', 'xhttpSettings', 'mode',
|
||||
]);
|
||||
return (
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.uplinkHttpMethod')}
|
||||
name={['streamSettings', 'xhttpSettings', 'uplinkHTTPMethod']}
|
||||
>
|
||||
<Select
|
||||
placeholder="Default (POST)"
|
||||
options={[
|
||||
{ value: '', label: 'Default (POST)' },
|
||||
{ value: 'POST', label: 'POST' },
|
||||
{ value: 'PUT', label: 'PUT' },
|
||||
{ value: 'GET', label: 'GET (packet-up only)', disabled: mode !== 'packet-up' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
{/* Session + sequence + uplinkData placements:
|
||||
three orthogonal slots Xray uses to thread
|
||||
request metadata through the transport
|
||||
(path / header / cookie / query). Key field
|
||||
only matters when placement is not 'path'. */}
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.sessionPlacement')}
|
||||
name={['streamSettings', 'xhttpSettings', 'sessionPlacement']}
|
||||
>
|
||||
<Select
|
||||
placeholder="Default (path)"
|
||||
options={[
|
||||
{ value: '', label: 'Default (path)' },
|
||||
{ value: 'path', label: 'path' },
|
||||
{ value: 'header', label: 'header' },
|
||||
{ value: 'cookie', label: 'cookie' },
|
||||
{ value: 'query', label: 'query' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() => {
|
||||
const placement = form.getFieldValue([
|
||||
'streamSettings', 'xhttpSettings', 'sessionPlacement',
|
||||
]);
|
||||
if (!placement || placement === 'path') return null;
|
||||
return (
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.sessionKey')}
|
||||
name={['streamSettings', 'xhttpSettings', 'sessionKey']}
|
||||
>
|
||||
<Input placeholder="x_session" />
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.sequencePlacement')}
|
||||
name={['streamSettings', 'xhttpSettings', 'seqPlacement']}
|
||||
>
|
||||
<Select
|
||||
placeholder="Default (path)"
|
||||
options={[
|
||||
{ value: '', label: 'Default (path)' },
|
||||
{ value: 'path', label: 'path' },
|
||||
{ value: 'header', label: 'header' },
|
||||
{ value: 'cookie', label: 'cookie' },
|
||||
{ value: 'query', label: 'query' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() => {
|
||||
const placement = form.getFieldValue([
|
||||
'streamSettings', 'xhttpSettings', 'seqPlacement',
|
||||
]);
|
||||
if (!placement || placement === 'path') return null;
|
||||
return (
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.sequenceKey')}
|
||||
name={['streamSettings', 'xhttpSettings', 'seqKey']}
|
||||
>
|
||||
<Input placeholder="x_seq" />
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
{/* Mode-conditional sub-sections. */}
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() => {
|
||||
const mode = form.getFieldValue([
|
||||
'streamSettings', 'xhttpSettings', 'mode',
|
||||
]);
|
||||
if (mode !== 'packet-up') return null;
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.minUploadInterval')}
|
||||
name={['streamSettings', 'xhttpSettings', 'scMinPostsIntervalMs']}
|
||||
>
|
||||
<Input placeholder="30" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.maxUploadSizeBytes')}
|
||||
name={['streamSettings', 'xhttpSettings', 'scMaxEachPostBytes']}
|
||||
>
|
||||
<Input placeholder="1000000" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.uplinkDataPlacement')}
|
||||
name={['streamSettings', 'xhttpSettings', 'uplinkDataPlacement']}
|
||||
>
|
||||
<Select
|
||||
options={[
|
||||
{ value: '', label: 'Default (body)' },
|
||||
{ value: 'body', label: 'body' },
|
||||
{ value: 'header', label: 'header' },
|
||||
{ value: 'cookie', label: 'cookie' },
|
||||
{ value: 'query', label: 'query' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() => {
|
||||
const place = form.getFieldValue([
|
||||
'streamSettings', 'xhttpSettings', 'uplinkDataPlacement',
|
||||
]);
|
||||
if (!place || place === 'body') return null;
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.uplinkDataKey')}
|
||||
name={['streamSettings', 'xhttpSettings', 'uplinkDataKey']}
|
||||
>
|
||||
<Input placeholder="x_data" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.uplinkChunkSize')}
|
||||
name={['streamSettings', 'xhttpSettings', 'uplinkChunkSize']}
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
placeholder="0 (unlimited)"
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() => {
|
||||
const mode = form.getFieldValue([
|
||||
'streamSettings', 'xhttpSettings', 'mode',
|
||||
]);
|
||||
if (mode !== 'stream-up' && mode !== 'stream-one') return null;
|
||||
return (
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.noGrpcHeader')}
|
||||
name={['streamSettings', 'xhttpSettings', 'noGRPCHeader']}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
{/* XMUX is the connection-multiplexing layer
|
||||
xHTTP uses to fan out parallel requests over
|
||||
a small pool of upstream connections. UI-only
|
||||
toggle (enableXmux) hides the 6 nested knobs
|
||||
when off. */}
|
||||
<Form.Item
|
||||
label="XMUX"
|
||||
name={['streamSettings', 'xhttpSettings', 'enableXmux']}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch onChange={onXmuxToggle} />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() => {
|
||||
if (!form.getFieldValue([
|
||||
'streamSettings', 'xhttpSettings', 'enableXmux',
|
||||
])) return null;
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.maxConcurrency')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xmux', 'maxConcurrency']}
|
||||
>
|
||||
<Input placeholder="16-32" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.maxConnections')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xmux', 'maxConnections']}
|
||||
>
|
||||
<Input placeholder="0" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.maxReuseTimes')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xmux', 'cMaxReuseTimes']}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.maxRequestTimes')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xmux', 'hMaxRequestTimes']}
|
||||
>
|
||||
<Input placeholder="600-900" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.maxReusableSecs')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xmux', 'hMaxReusableSecs']}
|
||||
>
|
||||
<Input placeholder="1800-3000" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.xray.outboundForm.keepAlivePeriod')}
|
||||
name={['streamSettings', 'xhttpSettings', 'xmux', 'hKeepAlivePeriod']}
|
||||
>
|
||||
<InputNumber min={0} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue