diff --git a/frontend/src/pages/inbounds/form/InboundFormModal.tsx b/frontend/src/pages/inbounds/form/InboundFormModal.tsx
index 02908bd9..8e2fc459 100644
--- a/frontend/src/pages/inbounds/form/InboundFormModal.tsx
+++ b/frontend/src/pages/inbounds/form/InboundFormModal.tsx
@@ -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 && }
- {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',
- request: {
- version: '1.1',
- method: 'GET',
- path: ['/'],
- headers: {},
- },
- response: {
- version: '1.1',
- status: '200',
- reason: 'OK',
- headers: {},
- },
- }
- : { type: 'none' },
- );
- }}
- />
- );
- }}
-
-
- {/* 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. */}
-
- 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 (
- <>
-
-
-
-
-
-
- ({ 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 : ['/'];
- }}
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
- }}
-
- >
- )}
+ {network === 'tcp' && }
- {network === 'ws' && (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- )}
+ {network === 'ws' && }
- {network === 'grpc' && (
- <>
-
-
-
-
-
-
-
-
-
- >
- )}
+ {network === 'grpc' && }
- {network === 'xhttp' && (
- <>
-
-
-
-
-
-
-
-
- {xhttpMode === 'packet-up' && (
- <>
-
-
-
-
-
-
- >
- )}
- {xhttpMode === 'stream-up' && (
-
-
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {xhttpObfsMode && (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
- >
- )}
-
-
-
- {xhttpSessionPlacement && xhttpSessionPlacement !== 'path' && (
-
-
-
- )}
-
-
-
- {xhttpSeqPlacement && xhttpSeqPlacement !== 'path' && (
-
-
-
- )}
- {xhttpMode === 'packet-up' && (
- <>
-
-
-
- {xhttpUplinkPlacement && xhttpUplinkPlacement !== 'body' && (
-
-
-
- )}
- >
- )}
-
-
-
- >
- )}
+ {network === 'xhttp' && }
- {network === 'httpupgrade' && (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
- >
- )}
+ {network === 'httpupgrade' && }
- {network === 'kcp' && (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- )}
+ {network === 'kcp' && }
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/pages/inbounds/form/transport/httpupgrade.tsx b/frontend/src/pages/inbounds/form/transport/httpupgrade.tsx
new file mode 100644
index 00000000..cb56e1ad
--- /dev/null
+++ b/frontend/src/pages/inbounds/form/transport/httpupgrade.tsx
@@ -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 (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/pages/inbounds/form/transport/index.ts b/frontend/src/pages/inbounds/form/transport/index.ts
new file mode 100644
index 00000000..ebcfb747
--- /dev/null
+++ b/frontend/src/pages/inbounds/form/transport/index.ts
@@ -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';
diff --git a/frontend/src/pages/inbounds/form/transport/kcp.tsx b/frontend/src/pages/inbounds/form/transport/kcp.tsx
new file mode 100644
index 00000000..e688cd50
--- /dev/null
+++ b/frontend/src/pages/inbounds/form/transport/kcp.tsx
@@ -0,0 +1,34 @@
+import { useTranslation } from 'react-i18next';
+import { Form, InputNumber } from 'antd';
+
+export default function KcpForm() {
+ const { t } = useTranslation();
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/pages/inbounds/form/transport/raw.tsx b/frontend/src/pages/inbounds/form/transport/raw.tsx
new file mode 100644
index 00000000..b1731514
--- /dev/null
+++ b/frontend/src/pages/inbounds/form/transport/raw.tsx
@@ -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 (
+ <>
+
+
+
+
+
+ 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',
+ request: {
+ version: '1.1',
+ method: 'GET',
+ path: ['/'],
+ headers: {},
+ },
+ response: {
+ version: '1.1',
+ status: '200',
+ reason: 'OK',
+ headers: {},
+ },
+ }
+ : { type: 'none' },
+ );
+ }}
+ />
+ );
+ }}
+
+
+ {/* 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. */}
+
+ 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 (
+ <>
+
+
+
+
+
+
+ ({ 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 : ['/'];
+ }}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+ }}
+
+ >
+ );
+}
diff --git a/frontend/src/pages/inbounds/form/transport/ws.tsx b/frontend/src/pages/inbounds/form/transport/ws.tsx
new file mode 100644
index 00000000..86b8a3af
--- /dev/null
+++ b/frontend/src/pages/inbounds/form/transport/ws.tsx
@@ -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 (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/pages/inbounds/form/transport/xhttp.tsx b/frontend/src/pages/inbounds/form/transport/xhttp.tsx
new file mode 100644
index 00000000..eb6ba700
--- /dev/null
+++ b/frontend/src/pages/inbounds/form/transport/xhttp.tsx
@@ -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 }) {
+ 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 (
+ <>
+
+
+
+
+
+
+
+
+ {xhttpMode === 'packet-up' && (
+ <>
+
+
+
+
+
+
+ >
+ )}
+ {xhttpMode === 'stream-up' && (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {xhttpObfsMode && (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+
+
+ {xhttpSessionPlacement && xhttpSessionPlacement !== 'path' && (
+
+
+
+ )}
+
+
+
+ {xhttpSeqPlacement && xhttpSeqPlacement !== 'path' && (
+
+
+
+ )}
+ {xhttpMode === 'packet-up' && (
+ <>
+
+
+
+ {xhttpUplinkPlacement && xhttpUplinkPlacement !== 'body' && (
+
+
+
+ )}
+ >
+ )}
+
+
+
+ >
+ );
+}