From d2f3a7baa79a7ea9a615638f3c4fd92a2356308d Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Tue, 26 May 2026 01:21:30 +0200 Subject: [PATCH] feat(frontend): InboundFormValues schema for Pattern A rewrite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Foundation for the InboundFormModal rewrite. Mirrors the wire Inbound shape (intersection of core fields + protocol settings DU + stream/security DUs) plus the DB-side fields (up/down/total/trafficReset/nodeId/...) that flow through DBInbound rather than the xray config slice. InboundStreamFormSchema is exported separately so individual sub-form sections can rule against just the stream portion when needed. FallbackRowSchema is co-located here even though fallbacks save via a distinct endpoint after the main POST — they belong to the same form state from the user's perspective. No modal changes in this commit. Foundation only; subsequent turns swap the modal's `inboundRef`/`dbFormRef` mutable-class state for Form.useForm(). --- frontend/src/schemas/forms/inbound-form.ts | 83 ++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 frontend/src/schemas/forms/inbound-form.ts diff --git a/frontend/src/schemas/forms/inbound-form.ts b/frontend/src/schemas/forms/inbound-form.ts new file mode 100644 index 00000000..960db1eb --- /dev/null +++ b/frontend/src/schemas/forms/inbound-form.ts @@ -0,0 +1,83 @@ +import { z } from 'zod'; + +import { PortSchema, SniffingSchema } from '@/schemas/primitives'; +import { InboundSettingsSchema } from '@/schemas/protocols/inbound'; +import { SecuritySettingsSchema } from '@/schemas/protocols/security'; +import { NetworkSettingsSchema, StreamExtrasSchema } from '@/schemas/protocols/stream'; + +// InboundFormValues = the values shape Form.useForm() carries in +// InboundFormModal. Mirrors the wire shape (so submission can hand +// values straight to Schema.parse + POST) plus the DB-side fields that +// the panel's /panel/api/inbounds/add endpoint expects alongside. +// +// Differences from schemas/api/inbound.ts InboundSchema: +// - settings/streamSettings/sniffing are nested OBJECTS here, not the +// JSON strings the endpoint accepts. The form holds typed data; the +// submit handler stringifies right before POSTing. +// - Adds DB fields not in InboundSchema: up, down, total, trafficReset, +// lastTrafficResetTime, nodeId. These flow through the DBInbound row, +// not the xray-config slice. + +export const InboundStreamFormSchema = NetworkSettingsSchema + .and(SecuritySettingsSchema) + .and(StreamExtrasSchema); +export type InboundStreamFormValues = z.infer; + +export const TrafficResetSchema = z.enum(['never', 'hourly', 'daily', 'weekly', 'monthly']); +export type TrafficReset = z.infer; + +// Db-side fields layered on top of the xray slice. These mirror the +// DBInbound model — they live in the SQL row, not in xray's config. +export const InboundDbFieldsSchema = z.object({ + up: z.number().int().min(0).default(0), + down: z.number().int().min(0).default(0), + total: z.number().int().min(0).default(0), + trafficReset: TrafficResetSchema.default('never'), + lastTrafficResetTime: z.number().int().default(0), + nodeId: z.number().int().nullable().optional(), +}); +export type InboundDbFields = z.infer; + +// Base fields that apply to every inbound regardless of protocol or +// transport. The protocol-specific `settings` and the transport-specific +// `streamSettings` are layered on via intersection below. +export const InboundFormBaseSchema = z.object({ + remark: z.string().default(''), + enable: z.boolean().default(true), + port: PortSchema, + listen: z.string().default(''), + tag: z.string().default(''), + expiryTime: z.number().int().default(0), + clientStats: z.string().optional(), + sniffing: SniffingSchema.default({ + enabled: false, + destOverride: ['http', 'tls', 'quic', 'fakedns'], + metadataOnly: false, + routeOnly: false, + ipsExcluded: [], + domainsExcluded: [], + }), + streamSettings: InboundStreamFormSchema.optional(), +}); +export type InboundFormBase = z.infer; + +// Full form values = base + db fields + protocol-discriminated settings. +// Consumers narrow on `.protocol` to access the matching settings branch. +export const InboundFormSchema = InboundFormBaseSchema + .and(InboundDbFieldsSchema) + .and(InboundSettingsSchema); +export type InboundFormValues = z.infer; + +// Fallback rows ride alongside the inbound submission for VLESS/Trojan +// hosts. They're saved via a separate endpoint after the main inbound +// POST returns, so the schema lives here but is not part of the wire +// inbound payload. +export const FallbackRowSchema = z.object({ + rowKey: z.string(), + childId: z.number().int().nullable(), + name: z.string().default(''), + alpn: z.string().default(''), + path: z.string().default(''), + xver: z.number().int().min(0).max(2).default(0), +}); +export type FallbackRow = z.infer;