From 40d17b5e598c72133ccc0794531c4fc7468bfaaf Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Tue, 26 May 2026 11:30:52 +0200 Subject: [PATCH] feat(frontend): security tab TLS certificates list (Pattern A) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes out the security tab: a Form.List of certificates that toggles between TlsCertFileSchema (certificateFile + keyFile string paths) and TlsCertInlineSchema (certificate + key as string arrays per the wire shape) via a per-row useFile boolean. useFile is a transient form-only field — not part of TlsCertSchema. Zod's default-strip behavior drops it during InboundFormSchema parse on submit, leaving only the matching wire branch's keys populated. Whichever side the user wasn't on stays empty, so Zod's union picks the populated branch. For inline certs the TextAreas use normalize + getValueProps to convert between the wire-side string[] and the multi-line text the user types. Each line becomes one array element, matching the legacy class's `cert.split('\n')` toJson convention. Per-row buildChain is conditionally rendered when usage === 'issue' — a shouldUpdate-closure watches the specific path so the toggle re-renders inline without listening to unrelated form changes. Security tab is now functionally complete. Advanced JSON tab, Fallbacks card, and the atomic swap in InboundsPage are next. --- .../pages/inbounds/InboundFormModal.new.tsx | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/frontend/src/pages/inbounds/InboundFormModal.new.tsx b/frontend/src/pages/inbounds/InboundFormModal.new.tsx index 9bae57f7..fee7c51e 100644 --- a/frontend/src/pages/inbounds/InboundFormModal.new.tsx +++ b/frontend/src/pages/inbounds/InboundFormModal.new.tsx @@ -8,6 +8,7 @@ import { Input, InputNumber, Modal, + Radio, Select, Space, Switch, @@ -46,6 +47,7 @@ import { TCP_CONGESTION_OPTION, TLS_CIPHER_OPTION, TLS_VERSION_OPTION, + USAGE_OPTION, UTLS_FINGERPRINT, } from '@/schemas/primitives'; import { SockoptStreamSettingsSchema } from '@/schemas/protocols/stream/sockopt'; @@ -53,6 +55,8 @@ import { TlsStreamSettingsSchema } from '@/schemas/protocols/security/tls'; import { RealityStreamSettingsSchema } from '@/schemas/protocols/security/reality'; import DateTimePicker from '@/components/DateTimePicker'; import InputAddon from '@/components/InputAddon'; + +const { TextArea } = Input; import type { DBInbound } from '@/models/dbinbound'; import type { NodeRecord } from '@/api/queries/useNodesQuery'; @@ -1556,6 +1560,157 @@ export default function InboundFormModalNew({ + + {(certFields, { add, remove }) => ( + <> + + + + {certFields.map((certField, idx) => ( +
+ + + + {t('pages.inbounds.certificatePath')} + + + {t('pages.inbounds.certificateContent')} + + + + {certFields.length > 1 && ( + + + + )} + + prev.streamSettings?.tlsSettings?.certificates?.[certField.name]?.useFile + !== curr.streamSettings?.tlsSettings?.certificates?.[certField.name]?.useFile + } + > + {({ getFieldValue }) => { + const useFile = getFieldValue([ + 'streamSettings', 'tlsSettings', 'certificates', + certField.name, 'useFile', + ]); + return useFile ? ( + <> + + + + + + + + ) : ( + <> + typeof v === 'string' + ? v.split('\n') + : v} + getValueProps={(v) => ({ + value: Array.isArray(v) ? v.join('\n') : v, + })} + > +