diff --git a/frontend/src/pages/inbounds/form/InboundFormModal.tsx b/frontend/src/pages/inbounds/form/InboundFormModal.tsx
index 571a7286..02908bd9 100644
--- a/frontend/src/pages/inbounds/form/InboundFormModal.tsx
+++ b/frontend/src/pages/inbounds/form/InboundFormModal.tsx
@@ -16,7 +16,6 @@ import {
Switch,
Tabs,
Tooltip,
- Typography,
message,
} from 'antd';
import {
@@ -77,12 +76,20 @@ 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 { HysteriaMasqueradeForm } from '@/lib/xray/forms/protocols/shared';
import { InputAddon } from '@/components/ui';
import './InboundFormModal.css';
import { AdvancedAllEditor, AdvancedSliceEditor } from './advanced-editors';
-import { ShadowsocksFields, TunFields, TunnelFields, WireguardFields } from './protocols';
+import {
+ HttpFields,
+ HysteriaFields,
+ MixedFields,
+ ShadowsocksFields,
+ TunFields,
+ TunnelFields,
+ VlessFields,
+ WireguardFields,
+} from './protocols';
const { TextArea } = Input;
import { coerceInboundJsonField, type DBInbound } from '@/models/dbinbound';
@@ -93,8 +100,6 @@ import type { NodeRecord } from '@/api/queries/useNodesQuery';
// InboundsPage continues to render the old InboundFormModal.tsx until the
// atomic swap at the end (Core Decision 7).
-const { Text } = Typography;
-
const PROTOCOL_OPTIONS = Object.values(Protocols).map((p) => ({ value: p, label: p }));
const TRAFFIC_RESETS = ['never', 'hourly', 'daily', 'weekly', 'monthly'] as const;
@@ -952,119 +957,12 @@ export default function InboundFormModal({
{protocol === Protocols.TUNNEL && }
- {(protocol === Protocols.HTTP || protocol === Protocols.MIXED) && (
- <>
-
- {(fields, { add, remove }) => (
- <>
-
-
-
- {fields.length > 0 && (
-
- {fields.map((field, idx) => (
-
- {String(idx + 1)}
-
-
-
-
-
-
-
-
- ))}
-
- )}
- >
- )}
-
- {protocol === Protocols.HTTP && (
-
-
-
- )}
- {protocol === Protocols.MIXED && (
- <>
-
-
-
-
-
-
- {mixedUdpOn && (
-
-
-
- )}
- >
- )}
- >
- )}
+ {protocol === Protocols.HTTP && }
+ {protocol === Protocols.MIXED && }
{protocol === Protocols.SHADOWSOCKS && }
- {protocol === Protocols.VLESS && (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
- {t('pages.inbounds.vlessAuthSelected', { auth: selectedVlessAuth })}
-
-
- {network === 'tcp' && (security === 'tls' || security === 'reality') && (
-
-
- {[900, 500, 900, 256].map((def, i) => (
-
-
-
- ))}
-
-
- )}
- >
- )}
+ {protocol === Protocols.VLESS && }
{isFallbackHost && fallbacksCard}
>
@@ -1148,24 +1046,7 @@ export default function InboundFormModal({
auth + udpIdleTimeout are required, masquerade is an optional
sub-object that lets xray-core disguise the listener as an
HTTP server when probed. */}
- {protocol === Protocols.HYSTERIA && (
- <>
-
-
-
-
-
-
-
-
- >
- )}
+ {protocol === Protocols.HYSTERIA && }
{network === 'tcp' && (
<>
diff --git a/frontend/src/pages/inbounds/form/protocols/accounts-list.tsx b/frontend/src/pages/inbounds/form/protocols/accounts-list.tsx
new file mode 100644
index 00000000..850dd3ba
--- /dev/null
+++ b/frontend/src/pages/inbounds/form/protocols/accounts-list.tsx
@@ -0,0 +1,47 @@
+import { useTranslation } from 'react-i18next';
+import { Button, Form, Input, Space } from 'antd';
+import { MinusOutlined, PlusOutlined } from '@ant-design/icons';
+
+import { RandomUtil } from '@/utils';
+import { InputAddon } from '@/components/ui';
+
+export default function AccountsList() {
+ const { t } = useTranslation();
+ return (
+
+ {(fields, { add, remove }) => (
+ <>
+
+
+
+ {fields.length > 0 && (
+
+ {fields.map((field, idx) => (
+
+ {String(idx + 1)}
+
+
+
+
+
+
+
+
+ ))}
+
+ )}
+ >
+ )}
+
+ );
+}
diff --git a/frontend/src/pages/inbounds/form/protocols/http.tsx b/frontend/src/pages/inbounds/form/protocols/http.tsx
new file mode 100644
index 00000000..11806fae
--- /dev/null
+++ b/frontend/src/pages/inbounds/form/protocols/http.tsx
@@ -0,0 +1,20 @@
+import { useTranslation } from 'react-i18next';
+import { Form, Switch } from 'antd';
+
+import AccountsList from './accounts-list';
+
+export default function HttpFields() {
+ const { t } = useTranslation();
+ return (
+ <>
+
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/pages/inbounds/form/protocols/hysteria.tsx b/frontend/src/pages/inbounds/form/protocols/hysteria.tsx
new file mode 100644
index 00000000..a0b78c24
--- /dev/null
+++ b/frontend/src/pages/inbounds/form/protocols/hysteria.tsx
@@ -0,0 +1,27 @@
+import { useTranslation } from 'react-i18next';
+import { Form, InputNumber, type FormInstance } from 'antd';
+
+import { HysteriaMasqueradeForm } from '@/lib/xray/forms/protocols/shared';
+import type { InboundFormValues } from '@/schemas/forms/inbound-form';
+
+export default function HysteriaFields({ form }: { form: FormInstance }) {
+ const { t } = useTranslation();
+ return (
+ <>
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/pages/inbounds/form/protocols/index.ts b/frontend/src/pages/inbounds/form/protocols/index.ts
index f44af7eb..a92744e6 100644
--- a/frontend/src/pages/inbounds/form/protocols/index.ts
+++ b/frontend/src/pages/inbounds/form/protocols/index.ts
@@ -2,3 +2,7 @@ export { default as TunFields } from './tun';
export { default as TunnelFields } from './tunnel';
export { default as ShadowsocksFields } from './shadowsocks';
export { default as WireguardFields } from './wireguard';
+export { default as HysteriaFields } from './hysteria';
+export { default as HttpFields } from './http';
+export { default as MixedFields } from './mixed';
+export { default as VlessFields } from './vless';
diff --git a/frontend/src/pages/inbounds/form/protocols/mixed.tsx b/frontend/src/pages/inbounds/form/protocols/mixed.tsx
new file mode 100644
index 00000000..274aa03d
--- /dev/null
+++ b/frontend/src/pages/inbounds/form/protocols/mixed.tsx
@@ -0,0 +1,33 @@
+import { useTranslation } from 'react-i18next';
+import { Form, Input, Select, Switch } from 'antd';
+
+import AccountsList from './accounts-list';
+
+export default function MixedFields({ mixedUdpOn }: { mixedUdpOn: boolean }) {
+ const { t } = useTranslation();
+ return (
+ <>
+
+
+
+
+
+
+
+ {mixedUdpOn && (
+
+
+
+ )}
+ >
+ );
+}
diff --git a/frontend/src/pages/inbounds/form/protocols/vless.tsx b/frontend/src/pages/inbounds/form/protocols/vless.tsx
new file mode 100644
index 00000000..d924c880
--- /dev/null
+++ b/frontend/src/pages/inbounds/form/protocols/vless.tsx
@@ -0,0 +1,60 @@
+import { useTranslation } from 'react-i18next';
+import { Button, Form, Input, InputNumber, Space, Typography } from 'antd';
+
+interface VlessFieldsProps {
+ saving: boolean;
+ selectedVlessAuth: string;
+ network: string;
+ security: string;
+ getNewVlessEnc: (kind: 'x25519' | 'mlkem768') => void;
+ clearVlessEnc: () => void;
+}
+
+export default function VlessFields({
+ saving,
+ selectedVlessAuth,
+ network,
+ security,
+ getNewVlessEnc,
+ clearVlessEnc,
+}: VlessFieldsProps) {
+ const { t } = useTranslation();
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {t('pages.inbounds.vlessAuthSelected', { auth: selectedVlessAuth })}
+
+
+ {network === 'tcp' && (security === 'tls' || security === 'reality') && (
+
+
+ {[900, 500, 900, 256].map((def, i) => (
+
+
+
+ ))}
+
+
+ )}
+ >
+ );
+}