diff --git a/frontend/src/lib/xray/outbound-form-adapter.ts b/frontend/src/lib/xray/outbound-form-adapter.ts index bf331fca..0b2c5bf6 100644 --- a/frontend/src/lib/xray/outbound-form-adapter.ts +++ b/frontend/src/lib/xray/outbound-form-adapter.ts @@ -20,13 +20,6 @@ import type { WireguardOutboundFormSettings, } from '@/schemas/forms/outbound-form'; -// Adapter between the wire-shape outbound JSON the panel stores in -// templateSettings.outbounds[] and the typed OutboundFormValues the modal -// holds in Form.useForm. No dependency on the legacy Outbound class -// hierarchy — the modal hands a wire-shape object in, takes typed values -// out, and on submit calls formValuesToWirePayload() to get a plain JS -// object ready to pass to onConfirm(). - type Raw = Record; function asObject(value: unknown): Raw { @@ -348,20 +341,12 @@ export interface RawOutboundRow { mux?: unknown; } -// Convert wire-shape outbound (the object stored in -// templateSettings.outbounds[]) into typed form values. Stream + mux are -// minimal placeholders for now — the modal will fold the real stream sub- -// form in when those sections come online. export function rawOutboundToFormValues(raw: RawOutboundRow): OutboundFormValues { const protocol = asString(raw.protocol, 'vless'); const settings = asObject(raw.settings); const tag = asString(raw.tag); const sendThrough = asString(raw.sendThrough); const mux = muxFromWire(raw.mux); - // Leave streamSettings undefined when missing or empty — the modal's - // stream tab seeds it when the user opens the relevant section. This - // keeps Form.useForm from receiving a value that doesn't match the - // NetworkSettings DU. const hasStream = raw.streamSettings && typeof raw.streamSettings === 'object' && Object.keys(raw.streamSettings as Raw).length > 0; @@ -554,18 +539,22 @@ function loopbackToWire(s: LoopbackOutboundFormSettings) { const MUX_PROTOCOLS = new Set(['vmess', 'vless', 'trojan', 'shadowsocks', 'http', 'socks']); const STREAM_PROTOCOLS = new Set(['vmess', 'vless', 'trojan', 'shadowsocks', 'hysteria']); -// Strip UI-only fields the form layered into streamSettings (e.g. the -// XHTTP modal's enableXmux toggle that controls section visibility but -// has no meaning on the wire). xray-core would ignore unknown fields -// anyway but the panel reads back its own emitted JSON, so we keep -// the wire shape clean. +function dropEmptyStrings(obj: Raw): Raw { + const out: Raw = {}; + for (const [k, v] of Object.entries(obj)) { + if (v === '') continue; + out[k] = v; + } + return out; +} + function stripUiOnlyStreamFields(stream: unknown): Raw { const next = { ...(stream as Raw) }; const xh = next.xhttpSettings; if (xh && typeof xh === 'object') { const cleaned = { ...(xh as Raw) }; delete cleaned.enableXmux; - next.xhttpSettings = cleaned; + next.xhttpSettings = dropEmptyStrings(cleaned); } return next; } diff --git a/frontend/src/pages/xray/OutboundFormModal.tsx b/frontend/src/pages/xray/OutboundFormModal.tsx index 55e61088..eae3724c 100644 --- a/frontend/src/pages/xray/OutboundFormModal.tsx +++ b/frontend/src/pages/xray/OutboundFormModal.tsx @@ -1473,16 +1473,23 @@ export default function OutboundFormModal({ + prev?.streamSettings?.xhttpSettings?.mode !== + curr?.streamSettings?.xhttpSettings?.mode + } > - - {() => { - const mode = form.getFieldValue([ - 'streamSettings', 'xhttpSettings', 'mode', - ]); - return ( + {() => { + const mode = form.getFieldValue([ + 'streamSettings', 'xhttpSettings', 'mode', + ]); + return ( +