mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 20:54:14 +00:00
refactor(frontend): extract inbound transport forms into transport/ folder
Move the six inbound stream-transport blocks (tcp/raw, ws, grpc, xhttp, httpupgrade, kcp) out of InboundFormModal into presentational components under inbounds/form/transport/. XhttpForm takes the form instance and re-derives its mode/obfs/placement watches internally; the rest are declarative. InboundFormModal drops from 2566 to 2105 lines. No behavior change — per-protocol field-label snapshots unchanged.
This commit is contained in:
parent
4fd8a884cc
commit
57d66ec9ff
8 changed files with 539 additions and 475 deletions
|
|
@ -75,7 +75,6 @@ import { HttpUpgradeStreamSettingsSchema } from '@/schemas/protocols/stream/http
|
|||
import { XHttpStreamSettingsSchema } from '@/schemas/protocols/stream/xhttp';
|
||||
import { DateTimePicker } from '@/components/form';
|
||||
import { FinalMaskForm } from '@/lib/xray/forms/transport';
|
||||
import { HeaderMapEditor } from '@/components/form';
|
||||
import { InputAddon } from '@/components/ui';
|
||||
import './InboundFormModal.css';
|
||||
|
||||
|
|
@ -90,6 +89,14 @@ import {
|
|||
VlessFields,
|
||||
WireguardFields,
|
||||
} from './protocols';
|
||||
import {
|
||||
GrpcForm,
|
||||
HttpUpgradeForm,
|
||||
KcpForm,
|
||||
RawForm,
|
||||
WsForm,
|
||||
XhttpForm,
|
||||
} from './transport';
|
||||
|
||||
const { TextArea } = Input;
|
||||
import { coerceInboundJsonField, type DBInbound } from '@/models/dbinbound';
|
||||
|
|
@ -503,11 +510,6 @@ export default function InboundFormModal({
|
|||
}
|
||||
}
|
||||
};
|
||||
const xhttpMode = Form.useWatch(['streamSettings', 'xhttpSettings', 'mode'], form);
|
||||
const xhttpObfsMode = Form.useWatch(['streamSettings', 'xhttpSettings', 'xPaddingObfsMode'], form) ?? false;
|
||||
const xhttpSessionPlacement = Form.useWatch(['streamSettings', 'xhttpSettings', 'sessionPlacement'], form);
|
||||
const xhttpSeqPlacement = Form.useWatch(['streamSettings', 'xhttpSettings', 'seqPlacement'], form);
|
||||
const xhttpUplinkPlacement = Form.useWatch(['streamSettings', 'xhttpSettings', 'uplinkDataPlacement'], form);
|
||||
|
||||
const toggleExternalProxy = (on: boolean) => {
|
||||
if (on) {
|
||||
|
|
@ -1048,480 +1050,17 @@ export default function InboundFormModal({
|
|||
HTTP server when probed. */}
|
||||
{protocol === Protocols.HYSTERIA && <HysteriaFields form={form} />}
|
||||
|
||||
{network === 'tcp' && (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'tcpSettings', 'acceptProxyProtocol']}
|
||||
label={t('pages.inbounds.form.proxyProtocol')}
|
||||
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',
|
||||
request: {
|
||||
version: '1.1',
|
||||
method: 'GET',
|
||||
path: ['/'],
|
||||
headers: {},
|
||||
},
|
||||
response: {
|
||||
version: '1.1',
|
||||
status: '200',
|
||||
reason: 'OK',
|
||||
headers: {},
|
||||
},
|
||||
}
|
||||
: { type: 'none' },
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
{/* Per Xray docs (transports/raw.html#httpheaderobject), the
|
||||
`request` object is honored only by outbound proxies; the
|
||||
inbound listener reads `response`. Showing Host / Path /
|
||||
Method / Version / request-headers on the inbound side was
|
||||
a regression from this modal's earlier iteration — those
|
||||
inputs wrote to the wire but xray-core ignored them. The
|
||||
inbound modal now only exposes the response side. */}
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prev, curr) =>
|
||||
prev.streamSettings?.tcpSettings?.header?.type
|
||||
!== curr.streamSettings?.tcpSettings?.header?.type
|
||||
}
|
||||
>
|
||||
{({ getFieldValue }) => {
|
||||
const headerType = getFieldValue(
|
||||
['streamSettings', 'tcpSettings', 'header', 'type'],
|
||||
) as string | undefined;
|
||||
if (headerType !== 'http') return null;
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.requestVersion')}
|
||||
name={[
|
||||
'streamSettings', 'tcpSettings', 'header',
|
||||
'request', 'version',
|
||||
]}
|
||||
>
|
||||
<Input placeholder="1.1" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.requestMethod')}
|
||||
name={[
|
||||
'streamSettings', 'tcpSettings', 'header',
|
||||
'request', 'method',
|
||||
]}
|
||||
>
|
||||
<Input placeholder="GET" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.requestPath')}
|
||||
name={[
|
||||
'streamSettings', 'tcpSettings', 'header',
|
||||
'request', 'path',
|
||||
]}
|
||||
getValueProps={(v) => ({ value: Array.isArray(v) ? v.join(',') : v })}
|
||||
getValueFromEvent={(e) => {
|
||||
const raw = (e?.target?.value ?? '') as string;
|
||||
const parts = raw.split(',').map((s) => s.trim()).filter(Boolean);
|
||||
return parts.length > 0 ? parts : ['/'];
|
||||
}}
|
||||
>
|
||||
<Input placeholder="/" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.requestHeaders')}
|
||||
name={[
|
||||
'streamSettings', 'tcpSettings', 'header',
|
||||
'request', 'headers',
|
||||
]}
|
||||
>
|
||||
<HeaderMapEditor mode="v2" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.responseVersion')}
|
||||
name={[
|
||||
'streamSettings', 'tcpSettings', 'header',
|
||||
'response', 'version',
|
||||
]}
|
||||
>
|
||||
<Input placeholder="1.1" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.responseStatus')}
|
||||
name={[
|
||||
'streamSettings', 'tcpSettings', 'header',
|
||||
'response', 'status',
|
||||
]}
|
||||
>
|
||||
<Input placeholder="200" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.responseReason')}
|
||||
name={[
|
||||
'streamSettings', 'tcpSettings', 'header',
|
||||
'response', 'reason',
|
||||
]}
|
||||
>
|
||||
<Input placeholder="OK" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.responseHeaders')}
|
||||
name={[
|
||||
'streamSettings', 'tcpSettings', 'header',
|
||||
'response', 'headers',
|
||||
]}
|
||||
>
|
||||
<HeaderMapEditor mode="v2" />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
{network === 'tcp' && <RawForm />}
|
||||
|
||||
{network === 'ws' && (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'wsSettings', 'acceptProxyProtocol']}
|
||||
label={t('pages.inbounds.form.proxyProtocol')}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name={['streamSettings', 'wsSettings', 'host']} label={t('host')}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={['streamSettings', 'wsSettings', 'path']} label={t('path')}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'wsSettings', 'heartbeatPeriod']}
|
||||
label={t('pages.inbounds.form.heartbeatPeriod')}
|
||||
>
|
||||
<InputNumber min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.headers')}
|
||||
name={['streamSettings', 'wsSettings', 'headers']}
|
||||
>
|
||||
<HeaderMapEditor mode="v1" />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
{network === 'ws' && <WsForm />}
|
||||
|
||||
{network === 'grpc' && (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'grpcSettings', 'serviceName']}
|
||||
label={t('pages.inbounds.form.serviceName')}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'grpcSettings', 'authority']}
|
||||
label={t('pages.inbounds.form.authority')}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'grpcSettings', 'multiMode']}
|
||||
label={t('pages.inbounds.form.multiMode')}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
{network === 'grpc' && <GrpcForm />}
|
||||
|
||||
{network === 'xhttp' && (
|
||||
<>
|
||||
<Form.Item name={['streamSettings', 'xhttpSettings', 'host']} label={t('host')}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={['streamSettings', 'xhttpSettings', 'path']} label={t('path')}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={['streamSettings', 'xhttpSettings', 'mode']} label={t('pages.inbounds.info.mode')}>
|
||||
<Select
|
||||
style={{ width: '50%' }}
|
||||
options={(['auto', 'packet-up', 'stream-up', 'stream-one'] as const).map((m) => ({
|
||||
value: m,
|
||||
label: m,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
{xhttpMode === 'packet-up' && (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'scMaxBufferedPosts']}
|
||||
label={t('pages.inbounds.form.maxBufferedUpload')}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'scMaxEachPostBytes']}
|
||||
label={t('pages.inbounds.form.maxUploadSize')}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
{xhttpMode === 'stream-up' && (
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'scStreamUpServerSecs']}
|
||||
label={t('pages.inbounds.form.streamUpServer')}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'serverMaxHeaderBytes']}
|
||||
label={t('pages.inbounds.form.serverMaxHeaderBytes')}
|
||||
>
|
||||
<InputNumber min={0} placeholder="0 (default)" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingBytes']}
|
||||
label={t('pages.inbounds.form.paddingBytes')}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'headers']}
|
||||
label={t('pages.inbounds.form.headers')}
|
||||
>
|
||||
<HeaderMapEditor mode="v1" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'uplinkHTTPMethod']}
|
||||
label={t('pages.inbounds.form.uplinkHttpMethod')}
|
||||
>
|
||||
<Select
|
||||
options={[
|
||||
{ value: '', label: 'Default (POST)' },
|
||||
{ value: 'POST', label: 'POST' },
|
||||
{ value: 'PUT', label: 'PUT' },
|
||||
{
|
||||
value: 'GET',
|
||||
label: 'GET (packet-up only)',
|
||||
disabled: xhttpMode !== 'packet-up',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingObfsMode']}
|
||||
label={t('pages.inbounds.form.paddingObfsMode')}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
{xhttpObfsMode && (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingKey']}
|
||||
label={t('pages.inbounds.form.paddingKey')}
|
||||
>
|
||||
<Input placeholder="x_padding" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingHeader']}
|
||||
label={t('pages.inbounds.form.paddingHeader')}
|
||||
>
|
||||
<Input placeholder="X-Padding" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingPlacement']}
|
||||
label={t('pages.inbounds.form.paddingPlacement')}
|
||||
>
|
||||
<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
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingMethod']}
|
||||
label={t('pages.inbounds.form.paddingMethod')}
|
||||
>
|
||||
<Select
|
||||
options={[
|
||||
{ value: '', label: 'Default (repeat-x)' },
|
||||
{ value: 'repeat-x', label: 'repeat-x' },
|
||||
{ value: 'tokenish', label: 'tokenish' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'sessionPlacement']}
|
||||
label={t('pages.inbounds.form.sessionPlacement')}
|
||||
>
|
||||
<Select
|
||||
options={[
|
||||
{ value: '', label: 'Default (path)' },
|
||||
{ value: 'path', label: 'path' },
|
||||
{ value: 'header', label: 'header' },
|
||||
{ value: 'cookie', label: 'cookie' },
|
||||
{ value: 'query', label: 'query' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
{xhttpSessionPlacement && xhttpSessionPlacement !== 'path' && (
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'sessionKey']}
|
||||
label={t('pages.inbounds.form.sessionKey')}
|
||||
>
|
||||
<Input placeholder="x_session" />
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'seqPlacement']}
|
||||
label={t('pages.inbounds.form.sequencePlacement')}
|
||||
>
|
||||
<Select
|
||||
options={[
|
||||
{ value: '', label: 'Default (path)' },
|
||||
{ value: 'path', label: 'path' },
|
||||
{ value: 'header', label: 'header' },
|
||||
{ value: 'cookie', label: 'cookie' },
|
||||
{ value: 'query', label: 'query' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
{xhttpSeqPlacement && xhttpSeqPlacement !== 'path' && (
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'seqKey']}
|
||||
label={t('pages.inbounds.form.sequenceKey')}
|
||||
>
|
||||
<Input placeholder="x_seq" />
|
||||
</Form.Item>
|
||||
)}
|
||||
{xhttpMode === 'packet-up' && (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'uplinkDataPlacement']}
|
||||
label={t('pages.inbounds.form.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>
|
||||
{xhttpUplinkPlacement && xhttpUplinkPlacement !== 'body' && (
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'uplinkDataKey']}
|
||||
label={t('pages.inbounds.form.uplinkDataKey')}
|
||||
>
|
||||
<Input placeholder="x_data" />
|
||||
</Form.Item>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'noSSEHeader']}
|
||||
label={t('pages.inbounds.form.noSseHeader')}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
{network === 'xhttp' && <XhttpForm form={form} />}
|
||||
|
||||
{network === 'httpupgrade' && (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'httpupgradeSettings', 'acceptProxyProtocol']}
|
||||
label={t('pages.inbounds.form.proxyProtocol')}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'httpupgradeSettings', 'host']}
|
||||
label={t('host')}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'httpupgradeSettings', 'path']}
|
||||
label={t('path')}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.headers')}
|
||||
name={['streamSettings', 'httpupgradeSettings', 'headers']}
|
||||
>
|
||||
<HeaderMapEditor mode="v1" />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
{network === 'httpupgrade' && <HttpUpgradeForm />}
|
||||
|
||||
{network === 'kcp' && (
|
||||
<>
|
||||
<Form.Item name={['streamSettings', 'kcpSettings', 'mtu']} label="MTU">
|
||||
<InputNumber min={576} max={1460} />
|
||||
</Form.Item>
|
||||
<Form.Item name={['streamSettings', 'kcpSettings', 'tti']} label={t('pages.inbounds.form.ttiMs')}>
|
||||
<InputNumber min={10} max={100} />
|
||||
</Form.Item>
|
||||
<Form.Item name={['streamSettings', 'kcpSettings', 'uplinkCapacity']} label={t('pages.inbounds.form.uplinkMbps')}>
|
||||
<InputNumber min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item name={['streamSettings', 'kcpSettings', 'downlinkCapacity']} label={t('pages.inbounds.form.downlinkMbps')}>
|
||||
<InputNumber min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'kcpSettings', 'cwndMultiplier']}
|
||||
label={t('pages.inbounds.form.cwndMultiplier')}
|
||||
>
|
||||
<InputNumber min={1} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'kcpSettings', 'maxSendingWindow']}
|
||||
label={t('pages.inbounds.form.maxSendingWindow')}
|
||||
>
|
||||
<InputNumber min={0} />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
{network === 'kcp' && <KcpForm />}
|
||||
|
||||
<Form.Item
|
||||
noStyle
|
||||
|
|
|
|||
29
frontend/src/pages/inbounds/form/transport/grpc.tsx
Normal file
29
frontend/src/pages/inbounds/form/transport/grpc.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, Input, Switch } from 'antd';
|
||||
|
||||
export default function GrpcForm() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'grpcSettings', 'serviceName']}
|
||||
label={t('pages.inbounds.form.serviceName')}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'grpcSettings', 'authority']}
|
||||
label={t('pages.inbounds.form.authority')}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'grpcSettings', 'multiMode']}
|
||||
label={t('pages.inbounds.form.multiMode')}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}
|
||||
37
frontend/src/pages/inbounds/form/transport/httpupgrade.tsx
Normal file
37
frontend/src/pages/inbounds/form/transport/httpupgrade.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, Input, Switch } from 'antd';
|
||||
|
||||
import { HeaderMapEditor } from '@/components/form';
|
||||
|
||||
export default function HttpUpgradeForm() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'httpupgradeSettings', 'acceptProxyProtocol']}
|
||||
label={t('pages.inbounds.form.proxyProtocol')}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'httpupgradeSettings', 'host']}
|
||||
label={t('host')}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'httpupgradeSettings', 'path']}
|
||||
label={t('path')}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.headers')}
|
||||
name={['streamSettings', 'httpupgradeSettings', 'headers']}
|
||||
>
|
||||
<HeaderMapEditor mode="v1" />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}
|
||||
6
frontend/src/pages/inbounds/form/transport/index.ts
Normal file
6
frontend/src/pages/inbounds/form/transport/index.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export { default as RawForm } from './raw';
|
||||
export { default as WsForm } from './ws';
|
||||
export { default as GrpcForm } from './grpc';
|
||||
export { default as XhttpForm } from './xhttp';
|
||||
export { default as HttpUpgradeForm } from './httpupgrade';
|
||||
export { default as KcpForm } from './kcp';
|
||||
34
frontend/src/pages/inbounds/form/transport/kcp.tsx
Normal file
34
frontend/src/pages/inbounds/form/transport/kcp.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, InputNumber } from 'antd';
|
||||
|
||||
export default function KcpForm() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Form.Item name={['streamSettings', 'kcpSettings', 'mtu']} label="MTU">
|
||||
<InputNumber min={576} max={1460} />
|
||||
</Form.Item>
|
||||
<Form.Item name={['streamSettings', 'kcpSettings', 'tti']} label={t('pages.inbounds.form.ttiMs')}>
|
||||
<InputNumber min={10} max={100} />
|
||||
</Form.Item>
|
||||
<Form.Item name={['streamSettings', 'kcpSettings', 'uplinkCapacity']} label={t('pages.inbounds.form.uplinkMbps')}>
|
||||
<InputNumber min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item name={['streamSettings', 'kcpSettings', 'downlinkCapacity']} label={t('pages.inbounds.form.downlinkMbps')}>
|
||||
<InputNumber min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'kcpSettings', 'cwndMultiplier']}
|
||||
label={t('pages.inbounds.form.cwndMultiplier')}
|
||||
>
|
||||
<InputNumber min={1} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'kcpSettings', 'maxSendingWindow']}
|
||||
label={t('pages.inbounds.form.maxSendingWindow')}
|
||||
>
|
||||
<InputNumber min={0} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}
|
||||
164
frontend/src/pages/inbounds/form/transport/raw.tsx
Normal file
164
frontend/src/pages/inbounds/form/transport/raw.tsx
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, Input, Switch } from 'antd';
|
||||
|
||||
import { HeaderMapEditor } from '@/components/form';
|
||||
|
||||
export default function RawForm() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'tcpSettings', 'acceptProxyProtocol']}
|
||||
label={t('pages.inbounds.form.proxyProtocol')}
|
||||
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',
|
||||
request: {
|
||||
version: '1.1',
|
||||
method: 'GET',
|
||||
path: ['/'],
|
||||
headers: {},
|
||||
},
|
||||
response: {
|
||||
version: '1.1',
|
||||
status: '200',
|
||||
reason: 'OK',
|
||||
headers: {},
|
||||
},
|
||||
}
|
||||
: { type: 'none' },
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
{/* Per Xray docs (transports/raw.html#httpheaderobject), the
|
||||
`request` object is honored only by outbound proxies; the
|
||||
inbound listener reads `response`. Showing Host / Path /
|
||||
Method / Version / request-headers on the inbound side was
|
||||
a regression from this modal's earlier iteration — those
|
||||
inputs wrote to the wire but xray-core ignored them. The
|
||||
inbound modal now only exposes the response side. */}
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prev, curr) =>
|
||||
prev.streamSettings?.tcpSettings?.header?.type
|
||||
!== curr.streamSettings?.tcpSettings?.header?.type
|
||||
}
|
||||
>
|
||||
{({ getFieldValue }) => {
|
||||
const headerType = getFieldValue(
|
||||
['streamSettings', 'tcpSettings', 'header', 'type'],
|
||||
) as string | undefined;
|
||||
if (headerType !== 'http') return null;
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.requestVersion')}
|
||||
name={[
|
||||
'streamSettings', 'tcpSettings', 'header',
|
||||
'request', 'version',
|
||||
]}
|
||||
>
|
||||
<Input placeholder="1.1" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.requestMethod')}
|
||||
name={[
|
||||
'streamSettings', 'tcpSettings', 'header',
|
||||
'request', 'method',
|
||||
]}
|
||||
>
|
||||
<Input placeholder="GET" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.requestPath')}
|
||||
name={[
|
||||
'streamSettings', 'tcpSettings', 'header',
|
||||
'request', 'path',
|
||||
]}
|
||||
getValueProps={(v) => ({ value: Array.isArray(v) ? v.join(',') : v })}
|
||||
getValueFromEvent={(e) => {
|
||||
const raw = (e?.target?.value ?? '') as string;
|
||||
const parts = raw.split(',').map((s) => s.trim()).filter(Boolean);
|
||||
return parts.length > 0 ? parts : ['/'];
|
||||
}}
|
||||
>
|
||||
<Input placeholder="/" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.requestHeaders')}
|
||||
name={[
|
||||
'streamSettings', 'tcpSettings', 'header',
|
||||
'request', 'headers',
|
||||
]}
|
||||
>
|
||||
<HeaderMapEditor mode="v2" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.responseVersion')}
|
||||
name={[
|
||||
'streamSettings', 'tcpSettings', 'header',
|
||||
'response', 'version',
|
||||
]}
|
||||
>
|
||||
<Input placeholder="1.1" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.responseStatus')}
|
||||
name={[
|
||||
'streamSettings', 'tcpSettings', 'header',
|
||||
'response', 'status',
|
||||
]}
|
||||
>
|
||||
<Input placeholder="200" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.responseReason')}
|
||||
name={[
|
||||
'streamSettings', 'tcpSettings', 'header',
|
||||
'response', 'reason',
|
||||
]}
|
||||
>
|
||||
<Input placeholder="OK" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.responseHeaders')}
|
||||
name={[
|
||||
'streamSettings', 'tcpSettings', 'header',
|
||||
'response', 'headers',
|
||||
]}
|
||||
>
|
||||
<HeaderMapEditor mode="v2" />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}
|
||||
37
frontend/src/pages/inbounds/form/transport/ws.tsx
Normal file
37
frontend/src/pages/inbounds/form/transport/ws.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, Input, InputNumber, Switch } from 'antd';
|
||||
|
||||
import { HeaderMapEditor } from '@/components/form';
|
||||
|
||||
export default function WsForm() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'wsSettings', 'acceptProxyProtocol']}
|
||||
label={t('pages.inbounds.form.proxyProtocol')}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name={['streamSettings', 'wsSettings', 'host']} label={t('host')}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={['streamSettings', 'wsSettings', 'path']} label={t('path')}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'wsSettings', 'heartbeatPeriod']}
|
||||
label={t('pages.inbounds.form.heartbeatPeriod')}
|
||||
>
|
||||
<InputNumber min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.headers')}
|
||||
name={['streamSettings', 'wsSettings', 'headers']}
|
||||
>
|
||||
<HeaderMapEditor mode="v1" />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}
|
||||
218
frontend/src/pages/inbounds/form/transport/xhttp.tsx
Normal file
218
frontend/src/pages/inbounds/form/transport/xhttp.tsx
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, Input, InputNumber, Select, Switch, type FormInstance } from 'antd';
|
||||
|
||||
import { HeaderMapEditor } from '@/components/form';
|
||||
import type { InboundFormValues } from '@/schemas/forms/inbound-form';
|
||||
|
||||
export default function XhttpForm({ form }: { form: FormInstance<InboundFormValues> }) {
|
||||
const { t } = useTranslation();
|
||||
const xhttpMode = Form.useWatch(['streamSettings', 'xhttpSettings', 'mode'], form);
|
||||
const xhttpObfsMode = Form.useWatch(['streamSettings', 'xhttpSettings', 'xPaddingObfsMode'], form) ?? false;
|
||||
const xhttpSessionPlacement = Form.useWatch(['streamSettings', 'xhttpSettings', 'sessionPlacement'], form);
|
||||
const xhttpSeqPlacement = Form.useWatch(['streamSettings', 'xhttpSettings', 'seqPlacement'], form);
|
||||
const xhttpUplinkPlacement = Form.useWatch(['streamSettings', 'xhttpSettings', 'uplinkDataPlacement'], form);
|
||||
return (
|
||||
<>
|
||||
<Form.Item name={['streamSettings', 'xhttpSettings', 'host']} label={t('host')}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={['streamSettings', 'xhttpSettings', 'path']} label={t('path')}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={['streamSettings', 'xhttpSettings', 'mode']} label={t('pages.inbounds.info.mode')}>
|
||||
<Select
|
||||
style={{ width: '50%' }}
|
||||
options={(['auto', 'packet-up', 'stream-up', 'stream-one'] as const).map((m) => ({
|
||||
value: m,
|
||||
label: m,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
{xhttpMode === 'packet-up' && (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'scMaxBufferedPosts']}
|
||||
label={t('pages.inbounds.form.maxBufferedUpload')}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'scMaxEachPostBytes']}
|
||||
label={t('pages.inbounds.form.maxUploadSize')}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
{xhttpMode === 'stream-up' && (
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'scStreamUpServerSecs']}
|
||||
label={t('pages.inbounds.form.streamUpServer')}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'serverMaxHeaderBytes']}
|
||||
label={t('pages.inbounds.form.serverMaxHeaderBytes')}
|
||||
>
|
||||
<InputNumber min={0} placeholder="0 (default)" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingBytes']}
|
||||
label={t('pages.inbounds.form.paddingBytes')}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'headers']}
|
||||
label={t('pages.inbounds.form.headers')}
|
||||
>
|
||||
<HeaderMapEditor mode="v1" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'uplinkHTTPMethod']}
|
||||
label={t('pages.inbounds.form.uplinkHttpMethod')}
|
||||
>
|
||||
<Select
|
||||
options={[
|
||||
{ value: '', label: 'Default (POST)' },
|
||||
{ value: 'POST', label: 'POST' },
|
||||
{ value: 'PUT', label: 'PUT' },
|
||||
{
|
||||
value: 'GET',
|
||||
label: 'GET (packet-up only)',
|
||||
disabled: xhttpMode !== 'packet-up',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingObfsMode']}
|
||||
label={t('pages.inbounds.form.paddingObfsMode')}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
{xhttpObfsMode && (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingKey']}
|
||||
label={t('pages.inbounds.form.paddingKey')}
|
||||
>
|
||||
<Input placeholder="x_padding" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingHeader']}
|
||||
label={t('pages.inbounds.form.paddingHeader')}
|
||||
>
|
||||
<Input placeholder="X-Padding" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingPlacement']}
|
||||
label={t('pages.inbounds.form.paddingPlacement')}
|
||||
>
|
||||
<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
|
||||
name={['streamSettings', 'xhttpSettings', 'xPaddingMethod']}
|
||||
label={t('pages.inbounds.form.paddingMethod')}
|
||||
>
|
||||
<Select
|
||||
options={[
|
||||
{ value: '', label: 'Default (repeat-x)' },
|
||||
{ value: 'repeat-x', label: 'repeat-x' },
|
||||
{ value: 'tokenish', label: 'tokenish' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'sessionPlacement']}
|
||||
label={t('pages.inbounds.form.sessionPlacement')}
|
||||
>
|
||||
<Select
|
||||
options={[
|
||||
{ value: '', label: 'Default (path)' },
|
||||
{ value: 'path', label: 'path' },
|
||||
{ value: 'header', label: 'header' },
|
||||
{ value: 'cookie', label: 'cookie' },
|
||||
{ value: 'query', label: 'query' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
{xhttpSessionPlacement && xhttpSessionPlacement !== 'path' && (
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'sessionKey']}
|
||||
label={t('pages.inbounds.form.sessionKey')}
|
||||
>
|
||||
<Input placeholder="x_session" />
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'seqPlacement']}
|
||||
label={t('pages.inbounds.form.sequencePlacement')}
|
||||
>
|
||||
<Select
|
||||
options={[
|
||||
{ value: '', label: 'Default (path)' },
|
||||
{ value: 'path', label: 'path' },
|
||||
{ value: 'header', label: 'header' },
|
||||
{ value: 'cookie', label: 'cookie' },
|
||||
{ value: 'query', label: 'query' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
{xhttpSeqPlacement && xhttpSeqPlacement !== 'path' && (
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'seqKey']}
|
||||
label={t('pages.inbounds.form.sequenceKey')}
|
||||
>
|
||||
<Input placeholder="x_seq" />
|
||||
</Form.Item>
|
||||
)}
|
||||
{xhttpMode === 'packet-up' && (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'uplinkDataPlacement']}
|
||||
label={t('pages.inbounds.form.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>
|
||||
{xhttpUplinkPlacement && xhttpUplinkPlacement !== 'body' && (
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'uplinkDataKey']}
|
||||
label={t('pages.inbounds.form.uplinkDataKey')}
|
||||
>
|
||||
<Input placeholder="x_data" />
|
||||
</Form.Item>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Form.Item
|
||||
name={['streamSettings', 'xhttpSettings', 'noSSEHeader']}
|
||||
label={t('pages.inbounds.form.noSseHeader')}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue