diff --git a/frontend/src/pages/inbounds/InboundFormModal.tsx b/frontend/src/pages/inbounds/InboundFormModal.tsx index dd1c43c4..fb7d477b 100644 --- a/frontend/src/pages/inbounds/InboundFormModal.tsx +++ b/frontend/src/pages/inbounds/InboundFormModal.tsx @@ -60,6 +60,7 @@ import FinalMaskForm from '@/components/FinalMaskForm'; import DateTimePicker from '@/components/DateTimePicker'; import JsonEditor from '@/components/JsonEditor'; import type { NodeRecord } from '@/api/queries/useNodesQuery'; +import { InboundFormSchema } from '@/schemas/inbound'; import './InboundFormModal.css'; const { TextArea } = Input; @@ -931,6 +932,19 @@ export default function InboundFormModal({ settings = compactAdvancedJson(advancedTextRef.current.settings, ib.settings.toString(), t('pages.inbounds.advanced.settings')); } catch { return; } + const baseCheck = InboundFormSchema.safeParse({ + remark: form.remark ?? '', + enable: !!form.enable, + port: Number(ib.port), + listen: ib.listen ?? '', + protocol: ib.protocol ?? '', + }); + if (!baseCheck.success) { + const issue = baseCheck.error.issues[0]; + messageApi.error(t(issue?.message ?? 'somethingWentWrong', { defaultValue: issue?.message ?? 'invalid' })); + return; + } + const payload: Record = { up: form.up || 0, down: form.down || 0, @@ -967,7 +981,7 @@ export default function InboundFormModal({ } finally { setSaving(false); } - }, [canEnableStream, compactAdvancedJson, t, mode, dbInbound, isFallbackHost, saveFallbacks, onSaved, onClose]); + }, [canEnableStream, compactAdvancedJson, t, messageApi, mode, dbInbound, isFallbackHost, saveFallbacks, onSaved, onClose]); const protocolSnapshot = inboundRef.current?.protocol; const streamSnapshot = JSON.stringify(inboundRef.current?.stream?.toJson?.() || {}); diff --git a/frontend/src/pages/xray/OutboundFormModal.tsx b/frontend/src/pages/xray/OutboundFormModal.tsx index 8d2b2006..4d628b57 100644 --- a/frontend/src/pages/xray/OutboundFormModal.tsx +++ b/frontend/src/pages/xray/OutboundFormModal.tsx @@ -35,6 +35,7 @@ import { } from '@/models/outbound'; import FinalMaskForm from '@/components/FinalMaskForm'; import JsonEditor from '@/components/JsonEditor'; +import { OutboundTagSchema } from '@/schemas/xray'; import './OutboundFormModal.css'; interface OutboundFormModalProps { @@ -223,8 +224,10 @@ export default function OutboundFormModal({ function onOk() { if (!ob) return; if (activeKey === '2' && !applyAdvancedJsonToForm()) return; - if (!ob.tag?.trim()) { - messageApi.error('Tag is required'); + const tagOk = OutboundTagSchema.safeParse(ob.tag); + if (!tagOk.success) { + const msgKey = tagOk.error.issues[0]?.message ?? 'Tag is required'; + messageApi.error(t(msgKey, { defaultValue: 'Tag is required' })); return; } if (duplicateTag) { diff --git a/frontend/src/schemas/inbound.ts b/frontend/src/schemas/inbound.ts index d0325029..d3c5126f 100644 --- a/frontend/src/schemas/inbound.ts +++ b/frontend/src/schemas/inbound.ts @@ -14,6 +14,19 @@ export const InboundDetailSchema = z.object({ export const LastOnlineMapSchema = z.record(z.string(), z.number()); +export const InboundFormSchema = z.object({ + remark: z.string(), + enable: z.boolean(), + port: z + .number({ error: 'pages.inbounds.toasts.portRequired' }) + .int() + .min(1, 'pages.inbounds.toasts.portRange') + .max(65535, 'pages.inbounds.toasts.portRange'), + listen: z.string(), + protocol: z.string().min(1, 'pages.inbounds.toasts.protocolRequired'), +}); + export type SlimInbound = z.infer; export type InboundDetail = z.infer; export type LastOnlineMap = z.infer; +export type InboundFormValues = z.infer; diff --git a/frontend/src/schemas/xray.ts b/frontend/src/schemas/xray.ts index 66d66407..baea7604 100644 --- a/frontend/src/schemas/xray.ts +++ b/frontend/src/schemas/xray.ts @@ -114,6 +114,11 @@ export const BalancerFormSchema = z.object({ fallbackTag: z.string(), }); +export const OutboundTagSchema = z + .string() + .trim() + .min(1, 'pages.xray.outboundTagRequired'); + export type BalancerFormValues = z.infer; export type RuleFormValues = z.infer; export type CustomGeoFormValues = z.infer;