diff --git a/frontend/src/pages/xray/outbounds/OutboundFormModal.tsx b/frontend/src/pages/xray/outbounds/OutboundFormModal.tsx
index 0e340ddb..51f3bb73 100644
--- a/frontend/src/pages/xray/outbounds/OutboundFormModal.tsx
+++ b/frontend/src/pages/xray/outbounds/OutboundFormModal.tsx
@@ -1,7 +1,6 @@
import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
- Button,
Form,
Input,
InputNumber,
@@ -13,7 +12,7 @@ import {
Tabs,
message,
} from 'antd';
-import { FinalMaskForm, HysteriaMasqueradeForm } from '@/lib/xray/forms/transport';
+import { FinalMaskForm } from '@/lib/xray/forms/transport';
import { JsonEditor } from '@/components/form';
import { Wireguard } from '@/utils';
import {
@@ -26,15 +25,7 @@ import {
OutboundFormBaseSchema,
type OutboundFormValues,
} from '@/schemas/forms/outbound-form';
-import {
- DOMAIN_STRATEGY_OPTION,
- SNIFFING_OPTION,
- TCP_CONGESTION_OPTION,
-} from '@/schemas/primitives';
-import {
- HappyEyeballsSchema,
- SockoptStreamSettingsSchema,
-} from '@/schemas/protocols/stream/sockopt';
+import { SNIFFING_OPTION } from '@/schemas/primitives';
import {
canEnableReality,
canEnableStream,
@@ -44,7 +35,6 @@ import {
import { antdRule } from '@/utils/zodForm';
import {
- ADDRESS_PORT_STRATEGY_OPTIONS,
FLOW_OPTIONS,
HYSTERIA_NETWORK_OPTION,
NETWORK_OPTIONS,
@@ -54,7 +44,6 @@ import {
import {
buildAddModeValues,
hysteriaStreamSlice,
- isMuxAllowed,
newStreamSlice,
} from './outbound-form-helpers';
import {
@@ -74,8 +63,11 @@ import {
import {
GrpcForm,
HttpUpgradeForm,
+ HysteriaForm,
KcpForm,
+ MuxForm,
RawForm,
+ SockoptForm,
WsForm,
XhttpForm,
} from './transport';
@@ -523,29 +515,7 @@ export default function OutboundFormModal({
{network === 'xhttp' && }
- {network === 'hysteria' && (
- <>
-
-
-
-
-
-
-
-
-
-
- >
- )}
+ {network === 'hysteria' && }
>
)}
@@ -605,270 +575,7 @@ export default function OutboundFormModal({
{security === 'reality' && realityAllowed && }
- {((streamAllowed && network) || !streamAllowed) && (
-
- {() => {
- const hasSockopt = !!form.getFieldValue([
- 'streamSettings',
- 'sockopt',
- ]);
- return (
- <>
-
- {
- form.setFieldValue(
- ['streamSettings', 'sockopt'],
- checked ? SockoptStreamSettingsSchema.parse({}) : undefined,
- );
- }}
- />
-
- {hasSockopt && (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {() => {
- const he = form.getFieldValue([
- 'streamSettings', 'sockopt', 'happyEyeballs',
- ]);
- const hasHe = he != null;
- return (
- <>
-
- {
- form.setFieldValue(
- ['streamSettings', 'sockopt', 'happyEyeballs'],
- v ? HappyEyeballsSchema.parse({}) : undefined,
- );
- }}
- />
-
- {hasHe && (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
- >
- )}
- >
- );
- }}
-
-
- {(fields, { add, remove }) => (
- <>
-
-
-
- {fields.map((field) => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ))}
- >
- )}
-
- >
- )}
- >
- );
- }}
-
- )}
+ {((streamAllowed && network) || !streamAllowed) && }
- {(() => {
- const flow = (form.getFieldValue(['settings', 'flow']) ?? '') as string;
- if (!isMuxAllowed(protocol, flow, network)) return null;
- return (
-
- {() => {
- const muxEnabled = !!form.getFieldValue(['mux', 'enabled']);
- return (
- <>
-
-
-
- {muxEnabled && (
- <>
-
-
-
-
-
-
-
-
- >
- )}
- >
- );
- }}
-
- );
- })()}
+
>
),
},
diff --git a/frontend/src/pages/xray/outbounds/transport/hysteria.tsx b/frontend/src/pages/xray/outbounds/transport/hysteria.tsx
new file mode 100644
index 00000000..38f0c171
--- /dev/null
+++ b/frontend/src/pages/xray/outbounds/transport/hysteria.tsx
@@ -0,0 +1,32 @@
+import { useTranslation } from 'react-i18next';
+import { Form, Input, InputNumber, type FormInstance } from 'antd';
+
+import { HysteriaMasqueradeForm } from '@/lib/xray/forms/transport';
+import type { OutboundFormValues } from '@/schemas/forms/outbound-form';
+
+export default function HysteriaForm({ form }: { form: FormInstance }) {
+ const { t } = useTranslation();
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/pages/xray/outbounds/transport/index.ts b/frontend/src/pages/xray/outbounds/transport/index.ts
index dfe6673f..38ce4d5e 100644
--- a/frontend/src/pages/xray/outbounds/transport/index.ts
+++ b/frontend/src/pages/xray/outbounds/transport/index.ts
@@ -4,3 +4,6 @@ export { default as WsForm } from './ws';
export { default as GrpcForm } from './grpc';
export { default as HttpUpgradeForm } from './httpupgrade';
export { default as XhttpForm } from './xhttp';
+export { default as HysteriaForm } from './hysteria';
+export { default as SockoptForm } from './sockopt';
+export { default as MuxForm } from './mux';
diff --git a/frontend/src/pages/xray/outbounds/transport/mux.tsx b/frontend/src/pages/xray/outbounds/transport/mux.tsx
new file mode 100644
index 00000000..e9a51283
--- /dev/null
+++ b/frontend/src/pages/xray/outbounds/transport/mux.tsx
@@ -0,0 +1,63 @@
+import { useTranslation } from 'react-i18next';
+import { Form, InputNumber, Select, Switch, type FormInstance } from 'antd';
+
+import type { OutboundFormValues } from '@/schemas/forms/outbound-form';
+
+import { isMuxAllowed } from '../outbound-form-helpers';
+
+interface MuxFormProps {
+ form: FormInstance;
+ protocol: string;
+ network: string;
+}
+
+export default function MuxForm({ form, protocol, network }: MuxFormProps) {
+ const { t } = useTranslation();
+ const flow = (form.getFieldValue(['settings', 'flow']) ?? '') as string;
+ if (!isMuxAllowed(protocol, flow, network)) return null;
+ return (
+
+ {() => {
+ const muxEnabled = !!form.getFieldValue(['mux', 'enabled']);
+ return (
+ <>
+
+
+
+ {muxEnabled && (
+ <>
+
+
+
+
+
+
+
+
+ >
+ )}
+ >
+ );
+ }}
+
+ );
+}
diff --git a/frontend/src/pages/xray/outbounds/transport/sockopt.tsx b/frontend/src/pages/xray/outbounds/transport/sockopt.tsx
new file mode 100644
index 00000000..fc869cdd
--- /dev/null
+++ b/frontend/src/pages/xray/outbounds/transport/sockopt.tsx
@@ -0,0 +1,276 @@
+import { useTranslation } from 'react-i18next';
+import { Button, Form, Input, InputNumber, Select, Space, Switch, type FormInstance } from 'antd';
+
+import { DOMAIN_STRATEGY_OPTION, TCP_CONGESTION_OPTION } from '@/schemas/primitives';
+import { HappyEyeballsSchema, SockoptStreamSettingsSchema } from '@/schemas/protocols/stream/sockopt';
+import type { OutboundFormValues } from '@/schemas/forms/outbound-form';
+
+import { ADDRESS_PORT_STRATEGY_OPTIONS } from '../outbound-form-constants';
+
+export default function SockoptForm({ form }: { form: FormInstance }) {
+ const { t } = useTranslation();
+ return (
+
+ {() => {
+ const hasSockopt = !!form.getFieldValue([
+ 'streamSettings',
+ 'sockopt',
+ ]);
+ return (
+ <>
+
+ {
+ form.setFieldValue(
+ ['streamSettings', 'sockopt'],
+ checked ? SockoptStreamSettingsSchema.parse({}) : undefined,
+ );
+ }}
+ />
+
+ {hasSockopt && (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {() => {
+ const he = form.getFieldValue([
+ 'streamSettings', 'sockopt', 'happyEyeballs',
+ ]);
+ const hasHe = he != null;
+ return (
+ <>
+
+ {
+ form.setFieldValue(
+ ['streamSettings', 'sockopt', 'happyEyeballs'],
+ v ? HappyEyeballsSchema.parse({}) : undefined,
+ );
+ }}
+ />
+
+ {hasHe && (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
+ >
+ );
+ }}
+
+
+ {(fields, { add, remove }) => (
+ <>
+
+
+
+ {fields.map((field) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+ >
+ )}
+
+ >
+ )}
+ >
+ );
+ }}
+
+ );
+}