diff --git a/frontend/src/pages/inbounds/InboundFormModal.new.tsx b/frontend/src/pages/inbounds/InboundFormModal.new.tsx index fee7c51e..2e9c32e7 100644 --- a/frontend/src/pages/inbounds/InboundFormModal.new.tsx +++ b/frontend/src/pages/inbounds/InboundFormModal.new.tsx @@ -55,6 +55,9 @@ import { TlsStreamSettingsSchema } from '@/schemas/protocols/security/tls'; import { RealityStreamSettingsSchema } from '@/schemas/protocols/security/reality'; import DateTimePicker from '@/components/DateTimePicker'; import InputAddon from '@/components/InputAddon'; +import JsonEditor from '@/components/JsonEditor'; +import type { FormInstance } from 'antd'; +import type { NamePath } from 'antd/es/form/interface'; const { TextArea } = Input; import type { DBInbound } from '@/models/dbinbound'; @@ -67,6 +70,44 @@ import type { NodeRecord } from '@/api/queries/useNodesQuery'; const { Text } = Typography; +// Sub-editor for one slice of the form (settings, streamSettings, sniffing). +// Holds a local text buffer so the user can type freely; on every keystroke +// we try to JSON.parse and forward the result to form state. Invalid JSON +// is held in the buffer until the next valid moment — no panic on partial +// input. The buffer seeds once on mount; the modal's destroyOnHidden makes +// each open a fresh editor instance, so we don't need to re-sync on outer +// form changes. +function AdvancedSliceEditor({ + form, + path, + minHeight, + maxHeight, +}: { + form: FormInstance; + path: NamePath; + minHeight?: string; + maxHeight?: string; +}) { + const [text, setText] = useState(() => + JSON.stringify(form.getFieldValue(path) ?? {}, null, 2), + ); + return ( + { + setText(next); + try { + form.setFieldValue(path, JSON.parse(next)); + } catch { + + } + }} + /> + ); +} + const PROTOCOL_OPTIONS = Object.values(Protocols).map((p) => ({ value: p, label: p })); const TRAFFIC_RESETS = ['never', 'hourly', 'daily', 'weekly', 'monthly'] as const; const NODE_ELIGIBLE_PROTOCOLS = new Set([ @@ -1855,6 +1896,51 @@ export default function InboundFormModalNew({ ); + const advancedTab = ( + + ), + }, + ...(streamEnabled + ? [{ + key: 'stream', + label: t('pages.inbounds.advanced.stream'), + children: ( + + ), + }] + : []), + { + key: 'sniffing', + label: t('pages.inbounds.advanced.sniffing'), + children: ( + + ), + }, + ]} + /> + ); + const sniffingTab = ( <> @@ -1957,6 +2043,7 @@ export default function InboundFormModalNew({ ] : []), { key: 'sniffing', label: t('pages.inbounds.sniffingTab'), children: sniffingTab }, + { key: 'advanced', label: t('pages.xray.advancedTemplate'), children: advancedTab }, ]} />