From a3857cff6a804238bc32e806786e4ce27618fd57 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Tue, 26 May 2026 12:04:57 +0200 Subject: [PATCH] feat(frontend): OutboundFormModal.new.tsx vmess/vless/trojan/ss sections - Shared connect-target sub-block (address + port) for the six protocols whose form schema carries them flat at settings root. - VMess: id + security Select (USERS_SECURITY). - VLESS: id + encryption + flow + reverseTag (reverse-sniffing slice and Vision testpre/testseed come in a later commit). - Trojan: password. - Shadowsocks: password + method Select (SSMethodSchema) + UoT switch + UoT version. onValuesChange cascade: when the user picks a different protocol, the adapter re-seeds the settings sub-object to the new protocol's defaults so leftover fields from the previous protocol do not bleed through. --- .../src/pages/xray/OutboundFormModal.new.tsx | 148 ++++++++++++++++-- 1 file changed, 139 insertions(+), 9 deletions(-) diff --git a/frontend/src/pages/xray/OutboundFormModal.new.tsx b/frontend/src/pages/xray/OutboundFormModal.new.tsx index a3e4f824..e7ab6bd4 100644 --- a/frontend/src/pages/xray/OutboundFormModal.new.tsx +++ b/frontend/src/pages/xray/OutboundFormModal.new.tsx @@ -1,14 +1,26 @@ import { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Form, Input, Modal, Select, Space, Tabs, message } from 'antd'; +import { Form, Input, InputNumber, Modal, Select, Space, Switch, Tabs, message } from 'antd'; import JsonEditor from '@/components/JsonEditor'; import { formValuesToWirePayload, rawOutboundToFormValues, } from '@/lib/xray/outbound-form-adapter'; -import { OutboundFormBaseSchema, type OutboundFormValues } from '@/schemas/forms/outbound-form'; -import { OutboundProtocols as Protocols } from '@/schemas/primitives'; +import { + OutboundFormBaseSchema, + ShadowsocksOutboundFormSettingsSchema, + TrojanOutboundFormSettingsSchema, + VlessOutboundFormSettingsSchema, + VmessOutboundFormSettingsSchema, + type OutboundFormValues, +} from '@/schemas/forms/outbound-form'; +import { + OutboundProtocols as Protocols, + TLS_FLOW_CONTROL, + USERS_SECURITY, +} from '@/schemas/primitives'; +import { SSMethodSchema } from '@/schemas/protocols/inbound/shadowsocks'; import { antdRule } from '@/utils/zodForm'; import './OutboundFormModal.css'; @@ -26,6 +38,17 @@ interface OutboundFormModalProps { } const PROTOCOL_OPTIONS = Object.values(Protocols).map((p) => ({ value: p, label: p })); +const SECURITY_OPTIONS = Object.values(USERS_SECURITY).map((v) => ({ value: v, label: v })); +const FLOW_OPTIONS = Object.values(TLS_FLOW_CONTROL).map((v) => ({ value: v, label: v })); +const SS_METHOD_OPTIONS = SSMethodSchema.options.map((v) => ({ value: v, label: v })); + +// Protocols whose form schema carries a flat connect target — these all +// get the shared "server" sub-block (address + port) at the top of the +// protocol section. Wireguard has an address but no port. DNS/freedom/ +// blackhole/loopback have no connect target. +const SERVER_PROTOCOLS = new Set([ + 'vmess', 'vless', 'trojan', 'shadowsocks', 'socks', 'http', 'hysteria', +]); function buildAddModeValues(): OutboundFormValues { return rawOutboundToFormValues({}); @@ -66,6 +89,18 @@ export default function OutboundFormModalNew({ const tag = Form.useWatch('tag', form) ?? ''; const protocol = (Form.useWatch('protocol', form) ?? 'vless') as string; + // Switching protocol resets the settings sub-object to fresh defaults + // so leftover fields from the previous protocol do not bleed through. + // The adapter's rawOutboundToFormValues seeds whatever the new protocol + // expects (vless flat shape, vmess flat shape, wireguard with secretKey + // placeholder, etc.). + function onValuesChange(changed: Partial) { + if ('protocol' in changed && changed.protocol) { + const next = rawOutboundToFormValues({ protocol: changed.protocol }); + form.setFieldValue('settings', next.settings); + } + } + const duplicateTag = useMemo(() => { const myTag = tag.trim(); if (!myTag) return false; @@ -145,6 +180,7 @@ export default function OutboundFormModalNew({ colon={false} labelCol={{ md: { span: 8 } }} wrapperCol={{ md: { span: 14 } }} + onValuesChange={onValuesChange} > - {/* Protocol-specific sub-forms come in subsequent commits. */} -
- Protocol-specific fields for {protocol} are still being - migrated. Use the JSON tab to edit settings until the - relevant section lands. -
+ {/* Shared connect target (address + port) for protocols + whose form schema carries them flat at settings root. + Hidden for freedom/blackhole/dns/loopback/wireguard. */} + {SERVER_PROTOCOLS.has(protocol) && ( + <> + + + + + + + + )} + + {(protocol === 'vmess' || protocol === 'vless') && ( + + + + )} + {protocol === 'vmess' && ( + + + + + + + + )} + + {(protocol === 'trojan' || protocol === 'shadowsocks') && ( + + + + )} + + {protocol === 'shadowsocks' && ( + <> + +