From 36afdf53afdc949c794c12436240fffc45445e6a Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Tue, 26 May 2026 16:08:52 +0200 Subject: [PATCH] fix(frontend): FinalMaskForm TCP Mask sub-forms + Advanced JSON wrap (B10/B11) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit B10 — FinalMaskForm TCP Mask: after adding a mask and picking a Type (Fragment/Header Custom/Sudoku), the type-specific sub-forms didn't render. TcpMaskItem read `type` via Form.useWatch on a path inside Form.List, which doesn't re-fire reliably in AntD 6.4.3 — same root cause as the earlier B1/B2/B5 reactivity issues. Replaced with a wrapper that reads `type` via getFieldValue inside the render prop. B11 — Advanced sub-tabs (settings / streamSettings / sniffing) showed just the inner value (e.g. `{clients:[],decryption:"none",...}`), but the legacy modal wrapped each slice with its key envelope (e.g. `{settings:{...}}`) so the JSON matches the wire shape's slice and round-trips cleanly from copy-pasted inbound configs. Added a `wrapKey` prop to AdvancedSliceEditor that wraps/unwraps the value on render/write; the three sub-tabs now pass settings / streamSettings / sniffing as their wrapKey. --- frontend/src/components/FinalMaskForm.tsx | 96 +++++++++++-------- .../src/pages/inbounds/InboundFormModal.tsx | 33 +++++-- 2 files changed, 77 insertions(+), 52 deletions(-) diff --git a/frontend/src/components/FinalMaskForm.tsx b/frontend/src/components/FinalMaskForm.tsx index 6f4c86e2..bd2840b6 100644 --- a/frontend/src/components/FinalMaskForm.tsx +++ b/frontend/src/components/FinalMaskForm.tsx @@ -156,7 +156,6 @@ function TcpMaskItem({ onRemove: () => void; }) { const path = [...base, 'tcp', index]; - const type = Form.useWatch([...path, 'type'], form) as string | undefined; return (
@@ -176,47 +175,60 @@ function TcpMaskItem({ /> - {type === 'fragment' && ( - <> - - - - - - - - - - - )} - - {type === 'sudoku' && ( - <> - - - - - - - - - - - - )} - - {type === 'header-custom' && ( - - )} + + (prev as Record)[String(path[0])] !== (curr as Record)[String(path[0])] + } + > + {({ getFieldValue }) => { + const type = getFieldValue([...path, 'type']) as string | undefined; + if (type === 'fragment') { + return ( + <> + + + + + + + + + + + ); + } + if (type === 'sudoku') { + return ( + <> + + + + + + + + + + + + ); + } + if (type === 'header-custom') { + return ; + } + return null; + }} +
); } diff --git a/frontend/src/pages/inbounds/InboundFormModal.tsx b/frontend/src/pages/inbounds/InboundFormModal.tsx index 60f09c60..4765b2d1 100644 --- a/frontend/src/pages/inbounds/InboundFormModal.tsx +++ b/frontend/src/pages/inbounds/InboundFormModal.tsx @@ -93,33 +93,40 @@ const { Text } = Typography; function AdvancedSliceEditor({ form, path, + wrapKey, minHeight, maxHeight, }: { form: FormInstance; path: NamePath; + // When set, the editor wraps the inner value with `{ [wrapKey]: ... }` so + // the JSON the user sees matches the wire shape's slice envelope (e.g. + // `{ "settings": { ... } }`). Edits unwrap the outer key before writing + // back to the form. Mirrors the legacy modal's wrappedConfigValue. + wrapKey?: string; minHeight?: string; maxHeight?: string; }) { - // The editor keeps a local text buffer so partial / invalid JSON typing - // doesn't clobber the form. lastEmitRef tracks the serialized form value - // at the moment we last accepted a write — if useWatch later fires with - // a different value than that, the form was changed from elsewhere - // (Stream tab toggle, sibling JSON tab edit), and we re-sync. + const serialize = (value: unknown): string => { + const inner = value ?? {}; + return JSON.stringify(wrapKey ? { [wrapKey]: inner } : inner, null, 2); + }; + const watched = Form.useWatch(path, form); const lastEmitRef = useRef(''); const [text, setText] = useState(() => { - const initial = JSON.stringify(form.getFieldValue(path) ?? {}, null, 2); + const initial = serialize(form.getFieldValue(path)); lastEmitRef.current = initial; return initial; }); useEffect(() => { - const formStr = JSON.stringify(watched ?? {}, null, 2); + const formStr = serialize(watched); if (formStr === lastEmitRef.current) return; setText(formStr); lastEmitRef.current = formStr; - }, [watched]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [watched, wrapKey]); return ( )[wrapKey] ?? {} + : parsed; + form.setFieldValue(path, toWrite); + lastEmitRef.current = JSON.stringify(wrapKey ? { [wrapKey]: toWrite } : toWrite, null, 2); } catch { // invalid JSON; keep buffer, don't push to form } @@ -2621,6 +2631,7 @@ export default function InboundFormModal({ @@ -2640,6 +2651,7 @@ export default function InboundFormModal({ @@ -2659,6 +2671,7 @@ export default function InboundFormModal({