From d6854076fe3de2afb496b4f6caba78e025f7ae4c Mon Sep 17 00:00:00 2001 From: web Date: Tue, 16 Sep 2025 04:50:05 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix:=20Add=20protocol-related=20?= =?UTF-8?q?constants,=20default=20configurations,=20field=20definitions,?= =?UTF-8?q?=20and=20validation=20modes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../servers/form-schema/constants.ts | 99 +++++ .../dashboard/servers/form-schema/defaults.ts | 83 ++++ .../{form-schema.ts => form-schema/fields.ts} | 390 +----------------- .../dashboard/servers/form-schema/index.ts | 30 ++ .../dashboard/servers/form-schema/schemas.ts | 175 ++++++++ .../dashboard/servers/form-schema/types.ts | 27 ++ .../app/dashboard/servers/server-form.tsx | 4 +- 7 files changed, 434 insertions(+), 374 deletions(-) create mode 100644 apps/admin/app/dashboard/servers/form-schema/constants.ts create mode 100644 apps/admin/app/dashboard/servers/form-schema/defaults.ts rename apps/admin/app/dashboard/servers/{form-schema.ts => form-schema/fields.ts} (63%) create mode 100644 apps/admin/app/dashboard/servers/form-schema/index.ts create mode 100644 apps/admin/app/dashboard/servers/form-schema/schemas.ts create mode 100644 apps/admin/app/dashboard/servers/form-schema/types.ts diff --git a/apps/admin/app/dashboard/servers/form-schema/constants.ts b/apps/admin/app/dashboard/servers/form-schema/constants.ts new file mode 100644 index 0000000..7a967d2 --- /dev/null +++ b/apps/admin/app/dashboard/servers/form-schema/constants.ts @@ -0,0 +1,99 @@ +export const protocols = [ + 'shadowsocks', + 'vmess', + 'vless', + 'trojan', + 'hysteria2', + 'tuic', + 'anytls', + 'socks', + 'naive', + 'http', + 'mieru', +] as const; + +// Global label map for display; fallback to raw value if missing +export const LABELS = { + // transport + 'tcp': 'TCP', + 'udp': 'UDP', + 'websocket': 'WebSocket', + 'grpc': 'gRPC', + 'mkcp': 'mKCP', + 'httpupgrade': 'HTTP Upgrade', + 'xhttp': 'XHTTP', + // security + 'none': 'NONE', + 'tls': 'TLS', + 'reality': 'Reality', + // fingerprint + 'chrome': 'Chrome', + 'firefox': 'Firefox', + 'safari': 'Safari', + 'ios': 'IOS', + 'android': 'Android', + 'edge': 'edge', + '360': '360', + 'qq': 'QQ', + // multiplex + 'low': 'Low', + 'middle': 'Middle', + 'high': 'High', +} as const; + +// Flat arrays for enum-like sets +export const SS_CIPHERS = [ + 'aes-128-gcm', + 'aes-192-gcm', + 'aes-256-gcm', + 'chacha20-ietf-poly1305', + '2022-blake3-aes-128-gcm', + '2022-blake3-aes-256-gcm', + '2022-blake3-chacha20-poly1305', +] as const; + +export const TRANSPORTS = { + vmess: ['tcp', 'websocket', 'grpc'] as const, + vless: ['tcp', 'websocket', 'grpc', 'mkcp', 'httpupgrade', 'xhttp'] as const, + trojan: ['tcp', 'websocket', 'grpc'] as const, + mieru: ['tcp', 'udp'] as const, +} as const; + +export const SECURITY = { + vmess: ['none', 'tls'] as const, + vless: ['none', 'tls', 'reality'] as const, + trojan: ['tls'] as const, + hysteria2: ['tls'] as const, + tuic: ['tls'] as const, + anytls: ['tls'] as const, + naive: ['none', 'tls'] as const, + http: ['none', 'tls'] as const, +} as const; + +export const FLOWS = { + vless: ['none', 'xtls-rprx-direct', 'xtls-rprx-splice', 'xtls-rprx-vision'] as const, +} as const; + +export const TUIC_UDP_RELAY_MODES = ['native', 'quic'] as const; +export const TUIC_CONGESTION = ['bbr', 'cubic', 'new_reno'] as const; +export const XHTTP_MODES = ['auto', 'packet-up', 'stream-up', 'stream-one'] as const; +export const ENCRYPTION_TYPES = ['none', 'mlkem768x25519plus'] as const; +export const ENCRYPTION_MODES = ['native', 'xorpub', 'random'] as const; +export const ENCRYPTION_RTT = ['0rtt', '1rtt'] as const; +export const FINGERPRINTS = [ + 'chrome', + 'firefox', + 'safari', + 'ios', + 'android', + 'edge', + '360', + 'qq', +] as const; + +export const multiplexLevels = ['none', 'low', 'middle', 'high'] as const; + +export function getLabel(value: string): string { + const label = (LABELS as Record)[value]; + return label ?? value.toUpperCase(); +} diff --git a/apps/admin/app/dashboard/servers/form-schema/defaults.ts b/apps/admin/app/dashboard/servers/form-schema/defaults.ts new file mode 100644 index 0000000..1dc8e1b --- /dev/null +++ b/apps/admin/app/dashboard/servers/form-schema/defaults.ts @@ -0,0 +1,83 @@ +import type { ProtocolType } from './types'; + +export function getProtocolDefaultConfig(proto: ProtocolType) { + switch (proto) { + case 'shadowsocks': + return { + type: 'shadowsocks', + port: null, + cipher: 'chacha20-ietf-poly1305', + server_key: null, + obfs: 'none', + obfs_host: null, + obfs_path: null, + } as any; + case 'vmess': + return { type: 'vmess', port: null, transport: 'tcp', security: 'none' } as any; + case 'vless': + return { type: 'vless', port: null, transport: 'tcp', security: 'none', flow: 'none' } as any; + case 'trojan': + return { type: 'trojan', port: null, transport: 'tcp', security: 'tls' } as any; + case 'hysteria2': + return { + type: 'hysteria2', + port: null, + hop_ports: null, + hop_interval: null, + obfs: 'none', + obfs_password: null, + security: 'tls', + up_mbps: null, + down_mbps: null, + } as any; + case 'tuic': + return { + type: 'tuic', + port: null, + disable_sni: false, + reduce_rtt: false, + udp_relay_mode: 'native', + congestion_controller: 'bbr', + security: 'tls', + sni: null, + allow_insecure: false, + fingerprint: 'chrome', + } as any; + case 'socks': + return { + type: 'socks', + port: null, + } as any; + case 'naive': + return { + type: 'naive', + port: null, + security: 'none', + } as any; + case 'http': + return { + type: 'http', + port: null, + security: 'none', + } as any; + case 'mieru': + return { + type: 'mieru', + port: null, + multiplex: 'none', + transport: 'tcp', + } as any; + case 'anytls': + return { + type: 'anytls', + port: null, + security: 'tls', + padding_scheme: null, + sni: null, + allow_insecure: false, + fingerprint: 'chrome', + } as any; + default: + return {} as any; + } +} diff --git a/apps/admin/app/dashboard/servers/form-schema.ts b/apps/admin/app/dashboard/servers/form-schema/fields.ts similarity index 63% rename from apps/admin/app/dashboard/servers/form-schema.ts rename to apps/admin/app/dashboard/servers/form-schema/fields.ts index 84b8f5f..2f06c97 100644 --- a/apps/admin/app/dashboard/servers/form-schema.ts +++ b/apps/admin/app/dashboard/servers/form-schema/fields.ts @@ -1,378 +1,24 @@ -import { z } from 'zod'; import { generateMLKEM768KeyPair, generatePassword, generateRealityKeyPair, generateRealityShortId, -} from './generate'; - -export const protocols = [ - 'shadowsocks', - 'vmess', - 'vless', - 'trojan', - 'hysteria2', - 'tuic', - 'anytls', - 'socks', - 'naive', - 'http', - 'meru', -] as const; - -export type FieldConfig = { - name: string; - type: 'input' | 'select' | 'switch' | 'number' | 'textarea'; - label: string; - placeholder?: string | ((t: (key: string) => string, protocol: any) => string); - options?: readonly string[]; - defaultValue?: any; - min?: number; - max?: number; - step?: number; - suffix?: string; - generate?: { - function: () => any; - updateFields?: Record; - }; - condition?: (protocol: any, values: any) => boolean; - group?: 'basic' | 'transport' | 'security' | 'reality' | 'obfs' | 'encryption'; - gridSpan?: 1 | 2; -}; - -// Global label map for display; fallback to raw value if missing -export const LABELS = { - // transport - 'tcp': 'TCP', - 'udp': 'UDP', - 'websocket': 'WebSocket', - 'grpc': 'gRPC', - 'mkcp': 'mKCP', - 'httpupgrade': 'HTTP Upgrade', - 'xhttp': 'XHTTP', - // security - 'none': 'NONE', - 'tls': 'TLS', - 'reality': 'Reality', - // fingerprint - 'chrome': 'Chrome', - 'firefox': 'Firefox', - 'safari': 'Safari', - 'ios': 'IOS', - 'android': 'Android', - 'edge': 'edge', - '360': '360', - 'qq': 'QQ', - // multiplex - 'low': 'Low', - 'middle': 'Middle', - 'high': 'High', -} as const; - -// Flat arrays for enum-like sets -export const SS_CIPHERS = [ - 'aes-128-gcm', - 'aes-192-gcm', - 'aes-256-gcm', - 'chacha20-ietf-poly1305', - '2022-blake3-aes-128-gcm', - '2022-blake3-aes-256-gcm', - '2022-blake3-chacha20-poly1305', -] as const; - -export const TRANSPORTS = { - vmess: ['tcp', 'websocket', 'grpc'] as const, - vless: ['tcp', 'websocket', 'grpc', 'mkcp', 'httpupgrade', 'xhttp'] as const, - trojan: ['tcp', 'websocket', 'grpc'] as const, - meru: ['tcp', 'udp'] as const, -} as const; - -export const SECURITY = { - vmess: ['none', 'tls'] as const, - vless: ['none', 'tls', 'reality'] as const, - trojan: ['tls'] as const, - hysteria2: ['tls'] as const, - tuic: ['tls'] as const, - anytls: ['tls'] as const, - naive: ['none', 'tls'] as const, - http: ['none', 'tls'] as const, -} as const; - -export const FLOWS = { - vless: ['none', 'xtls-rprx-direct', 'xtls-rprx-splice', 'xtls-rprx-vision'] as const, -} as const; - -export const TUIC_UDP_RELAY_MODES = ['native', 'quic'] as const; -export const TUIC_CONGESTION = ['bbr', 'cubic', 'new_reno'] as const; -export const XHTTP_MODES = ['auto', 'packet-up', 'stream-up', 'stream-one'] as const; -export const ENCRYPTION_TYPES = ['none', 'mlkem768x25519plus'] as const; -export const ENCRYPTION_MODES = ['native', 'xorpub', 'random'] as const; -export const ENCRYPTION_RTT = ['0rtt', '1rtt'] as const; -export const FINGERPRINTS = [ - 'chrome', - 'firefox', - 'safari', - 'ios', - 'android', - 'edge', - '360', - 'qq', -] as const; - -export const multiplexLevels = ['none', 'low', 'middle', 'high'] as const; - -export function getLabel(value: string): string { - const label = (LABELS as Record)[value]; - return label ?? value.toUpperCase(); -} - -const nullableString = z.string().nullish(); -const nullableBool = z.boolean().nullish(); -const nullablePort = z.number().int().min(0).max(65535).nullish(); - -const ss = z.object({ - type: z.literal('shadowsocks'), - host: nullableString, - port: nullablePort, - cipher: z.enum(SS_CIPHERS as any).nullish(), - server_key: nullableString, - obfs: z.enum(['none', 'http', 'tls'] as const).nullish(), - obfs_host: nullableString, - obfs_path: nullableString, -}); - -const vmess = z.object({ - type: z.literal('vmess'), - host: nullableString, - port: nullablePort, - transport: z.enum(TRANSPORTS.vmess as any).nullish(), - security: z.enum(SECURITY.vmess as any).nullish(), - path: nullableString, - service_name: nullableString, - sni: nullableString, - allow_insecure: nullableBool, - fingerprint: nullableString, -}); - -const vless = z.object({ - type: z.literal('vless'), - host: nullableString, - port: nullablePort, - transport: z.enum(TRANSPORTS.vless as any).nullish(), - security: z.enum(SECURITY.vless as any).nullish(), - path: nullableString, - service_name: nullableString, - flow: z.enum(FLOWS.vless as any).nullish(), - sni: nullableString, - allow_insecure: nullableBool, - fingerprint: nullableString, - reality_server_addr: nullableString, - reality_server_port: nullablePort, - reality_private_key: nullableString, - reality_public_key: nullableString, - reality_short_id: nullableString, - mode: nullableString, - extra: nullableString, - encryption: z.enum(ENCRYPTION_TYPES as any).nullish(), - encryption_mode: z.enum(ENCRYPTION_MODES as any).nullish(), - encryption_rtt: z.enum(ENCRYPTION_RTT as any).nullish(), - encryption_ticket: nullableString, - encryption_server_padding: nullableString, - encryption_private_key: nullableString, - encryption_client_padding: nullableString, - encryption_password: nullableString, -}); - -const trojan = z.object({ - type: z.literal('trojan'), - host: nullableString, - port: nullablePort, - transport: z.enum(TRANSPORTS.trojan as any).nullish(), - security: z.enum(SECURITY.trojan as any).nullish(), - path: nullableString, - service_name: nullableString, - sni: nullableString, - allow_insecure: nullableBool, - fingerprint: nullableString, -}); - -const hysteria2 = z.object({ - type: z.literal('hysteria2'), - hop_ports: nullableString, - hop_interval: z.number().nullish(), - obfs_password: nullableString, - obfs: z.enum(['none', 'salamander'] as const).nullish(), - port: nullablePort, - security: z.enum(SECURITY.hysteria2 as any).nullish(), - sni: nullableString, - allow_insecure: nullableBool, - fingerprint: nullableString, - up_mbps: z.number().nullish(), - down_mbps: z.number().nullish(), -}); - -const tuic = z.object({ - type: z.literal('tuic'), - host: nullableString, - port: nullablePort, - disable_sni: z.boolean().nullish(), - reduce_rtt: z.boolean().nullish(), - udp_relay_mode: z.enum(TUIC_UDP_RELAY_MODES as any).nullish(), - congestion_controller: z.enum(TUIC_CONGESTION as any).nullish(), - security: z.enum(SECURITY.tuic as any).nullish(), - sni: nullableString, - allow_insecure: nullableBool, - fingerprint: nullableString, -}); - -const anytls = z.object({ - type: z.literal('anytls'), - port: nullablePort, - security: z.enum(SECURITY.anytls as any).nullish(), - sni: nullableString, - allow_insecure: nullableBool, - fingerprint: nullableString, - padding_scheme: nullableString, -}); - -const socks = z.object({ - type: z.literal('socks'), - port: nullablePort, -}); - -const naive = z.object({ - type: z.literal('naive'), - port: nullablePort, - security: z.enum(SECURITY.naive as any).nullish(), - sni: nullableString, - allow_insecure: nullableBool, - fingerprint: nullableString, -}); - -const http = z.object({ - type: z.literal('http'), - port: nullablePort, - security: z.enum(SECURITY.http as any).nullish(), - sni: nullableString, - allow_insecure: nullableBool, - fingerprint: nullableString, -}); - -const meru = z.object({ - type: z.literal('meru'), - port: nullablePort, - multiplex: z.enum(multiplexLevels).nullish(), - transport: z.enum(TRANSPORTS.meru as any).nullish(), -}); - -export const protocolApiScheme = z.discriminatedUnion('type', [ - ss, - vmess, - vless, - trojan, - hysteria2, - tuic, - anytls, - socks, - naive, - http, - meru, -]); - -export const formSchema = z.object({ - name: z.string().min(1), - address: z.string().min(1), - country: z.string().optional(), - city: z.string().optional(), - ratio: z.number().default(1), - protocols: z.array(protocolApiScheme), -}); - -export type ServerFormValues = z.infer; - -export type ProtocolType = (typeof protocols)[number]; - -export function getProtocolDefaultConfig(proto: ProtocolType) { - switch (proto) { - case 'shadowsocks': - return { - type: 'shadowsocks', - port: null, - cipher: 'chacha20-ietf-poly1305', - server_key: null, - obfs: 'none', - obfs_host: null, - obfs_path: null, - } as any; - case 'vmess': - return { type: 'vmess', port: null, transport: 'tcp', security: 'none' } as any; - case 'vless': - return { type: 'vless', port: null, transport: 'tcp', security: 'none', flow: 'none' } as any; - case 'trojan': - return { type: 'trojan', port: null, transport: 'tcp', security: 'tls' } as any; - case 'hysteria2': - return { - type: 'hysteria2', - port: null, - hop_ports: null, - hop_interval: null, - obfs: 'none', - obfs_password: null, - security: 'tls', - up_mbps: null, - down_mbps: null, - } as any; - case 'tuic': - return { - type: 'tuic', - port: null, - disable_sni: false, - reduce_rtt: false, - udp_relay_mode: 'native', - congestion_controller: 'bbr', - security: 'tls', - sni: null, - allow_insecure: false, - fingerprint: 'chrome', - } as any; - case 'socks': - return { - type: 'socks', - port: null, - } as any; - case 'naive': - return { - type: 'naive', - port: null, - security: 'none', - } as any; - case 'http': - return { - type: 'http', - port: null, - security: 'none', - } as any; - case 'meru': - return { - type: 'meru', - port: null, - multiplex: 'none', - transport: 'tcp', - } as any; - case 'anytls': - return { - type: 'anytls', - port: null, - security: 'tls', - padding_scheme: null, - sni: null, - allow_insecure: false, - fingerprint: 'chrome', - } as any; - default: - return {} as any; - } -} +} from '../generate'; +import { + ENCRYPTION_MODES, + ENCRYPTION_RTT, + ENCRYPTION_TYPES, + FINGERPRINTS, + FLOWS, + multiplexLevels, + SECURITY, + SS_CIPHERS, + TRANSPORTS, + TUIC_CONGESTION, + TUIC_UDP_RELAY_MODES, + XHTTP_MODES, +} from './constants'; +import type { FieldConfig } from './types'; export const PROTOCOL_FIELDS: Record = { shadowsocks: [ @@ -1010,7 +656,7 @@ export const PROTOCOL_FIELDS: Record = { condition: (p) => p.security !== 'none', }, ], - meru: [ + mieru: [ { name: 'port', type: 'number', @@ -1032,7 +678,7 @@ export const PROTOCOL_FIELDS: Record = { name: 'transport', type: 'select', label: 'transport', - options: TRANSPORTS.meru, + options: TRANSPORTS.mieru, defaultValue: 'tcp', group: 'transport', }, diff --git a/apps/admin/app/dashboard/servers/form-schema/index.ts b/apps/admin/app/dashboard/servers/form-schema/index.ts new file mode 100644 index 0000000..e7ba0f0 --- /dev/null +++ b/apps/admin/app/dashboard/servers/form-schema/index.ts @@ -0,0 +1,30 @@ +// Re-export all constants +export { + ENCRYPTION_MODES, + ENCRYPTION_RTT, + ENCRYPTION_TYPES, + FINGERPRINTS, + FLOWS, + LABELS, + SECURITY, + SS_CIPHERS, + TRANSPORTS, + TUIC_CONGESTION, + TUIC_UDP_RELAY_MODES, + XHTTP_MODES, + getLabel, + multiplexLevels, + protocols, +} from './constants'; + +// Re-export all types +export type { FieldConfig, ProtocolType, ServerFormValues } from './types'; + +// Re-export all schemas +export { formSchema, protocolApiScheme } from './schemas'; + +// Re-export defaults +export { getProtocolDefaultConfig } from './defaults'; + +// Re-export fields +export { PROTOCOL_FIELDS } from './fields'; diff --git a/apps/admin/app/dashboard/servers/form-schema/schemas.ts b/apps/admin/app/dashboard/servers/form-schema/schemas.ts new file mode 100644 index 0000000..911ea0f --- /dev/null +++ b/apps/admin/app/dashboard/servers/form-schema/schemas.ts @@ -0,0 +1,175 @@ +import { z } from 'zod'; +import { + ENCRYPTION_MODES, + ENCRYPTION_RTT, + ENCRYPTION_TYPES, + FLOWS, + multiplexLevels, + SECURITY, + SS_CIPHERS, + TRANSPORTS, + TUIC_CONGESTION, + TUIC_UDP_RELAY_MODES, +} from './constants'; + +const nullableString = z.string().nullish(); +const nullableBool = z.boolean().nullish(); +const nullablePort = z.number().int().min(0).max(65535).nullish(); + +const ss = z.object({ + type: z.literal('shadowsocks'), + host: nullableString, + port: nullablePort, + cipher: z.enum(SS_CIPHERS as any).nullish(), + server_key: nullableString, + obfs: z.enum(['none', 'http', 'tls'] as const).nullish(), + obfs_host: nullableString, + obfs_path: nullableString, +}); + +const vmess = z.object({ + type: z.literal('vmess'), + host: nullableString, + port: nullablePort, + transport: z.enum(TRANSPORTS.vmess as any).nullish(), + security: z.enum(SECURITY.vmess as any).nullish(), + path: nullableString, + service_name: nullableString, + sni: nullableString, + allow_insecure: nullableBool, + fingerprint: nullableString, +}); + +const vless = z.object({ + type: z.literal('vless'), + host: nullableString, + port: nullablePort, + transport: z.enum(TRANSPORTS.vless as any).nullish(), + security: z.enum(SECURITY.vless as any).nullish(), + path: nullableString, + service_name: nullableString, + flow: z.enum(FLOWS.vless as any).nullish(), + sni: nullableString, + allow_insecure: nullableBool, + fingerprint: nullableString, + reality_server_addr: nullableString, + reality_server_port: nullablePort, + reality_private_key: nullableString, + reality_public_key: nullableString, + reality_short_id: nullableString, + mode: nullableString, + extra: nullableString, + encryption: z.enum(ENCRYPTION_TYPES as any).nullish(), + encryption_mode: z.enum(ENCRYPTION_MODES as any).nullish(), + encryption_rtt: z.enum(ENCRYPTION_RTT as any).nullish(), + encryption_ticket: nullableString, + encryption_server_padding: nullableString, + encryption_private_key: nullableString, + encryption_client_padding: nullableString, + encryption_password: nullableString, +}); + +const trojan = z.object({ + type: z.literal('trojan'), + host: nullableString, + port: nullablePort, + transport: z.enum(TRANSPORTS.trojan as any).nullish(), + security: z.enum(SECURITY.trojan as any).nullish(), + path: nullableString, + service_name: nullableString, + sni: nullableString, + allow_insecure: nullableBool, + fingerprint: nullableString, +}); + +const hysteria2 = z.object({ + type: z.literal('hysteria2'), + hop_ports: nullableString, + hop_interval: z.number().nullish(), + obfs_password: nullableString, + obfs: z.enum(['none', 'salamander'] as const).nullish(), + port: nullablePort, + security: z.enum(SECURITY.hysteria2 as any).nullish(), + sni: nullableString, + allow_insecure: nullableBool, + fingerprint: nullableString, + up_mbps: z.number().nullish(), + down_mbps: z.number().nullish(), +}); + +const tuic = z.object({ + type: z.literal('tuic'), + host: nullableString, + port: nullablePort, + disable_sni: z.boolean().nullish(), + reduce_rtt: z.boolean().nullish(), + udp_relay_mode: z.enum(TUIC_UDP_RELAY_MODES as any).nullish(), + congestion_controller: z.enum(TUIC_CONGESTION as any).nullish(), + security: z.enum(SECURITY.tuic as any).nullish(), + sni: nullableString, + allow_insecure: nullableBool, + fingerprint: nullableString, +}); + +const anytls = z.object({ + type: z.literal('anytls'), + port: nullablePort, + security: z.enum(SECURITY.anytls as any).nullish(), + sni: nullableString, + allow_insecure: nullableBool, + fingerprint: nullableString, + padding_scheme: nullableString, +}); + +const socks = z.object({ + type: z.literal('socks'), + port: nullablePort, +}); + +const naive = z.object({ + type: z.literal('naive'), + port: nullablePort, + security: z.enum(SECURITY.naive as any).nullish(), + sni: nullableString, + allow_insecure: nullableBool, + fingerprint: nullableString, +}); + +const http = z.object({ + type: z.literal('http'), + port: nullablePort, + security: z.enum(SECURITY.http as any).nullish(), + sni: nullableString, + allow_insecure: nullableBool, + fingerprint: nullableString, +}); + +const mieru = z.object({ + type: z.literal('mieru'), + port: nullablePort, + multiplex: z.enum(multiplexLevels).nullish(), + transport: z.enum(TRANSPORTS.mieru as any).nullish(), +}); + +export const protocolApiScheme = z.discriminatedUnion('type', [ + ss, + vmess, + vless, + trojan, + hysteria2, + tuic, + anytls, + socks, + naive, + http, + mieru, +]); + +export const formSchema = z.object({ + name: z.string().min(1), + address: z.string().min(1), + country: z.string().optional(), + city: z.string().optional(), + ratio: z.number().default(1), + protocols: z.array(protocolApiScheme), +}); diff --git a/apps/admin/app/dashboard/servers/form-schema/types.ts b/apps/admin/app/dashboard/servers/form-schema/types.ts new file mode 100644 index 0000000..0a2459d --- /dev/null +++ b/apps/admin/app/dashboard/servers/form-schema/types.ts @@ -0,0 +1,27 @@ +import { z } from 'zod'; +import { protocols } from './constants'; +import { formSchema } from './schemas'; + +export type FieldConfig = { + name: string; + type: 'input' | 'select' | 'switch' | 'number' | 'textarea'; + label: string; + placeholder?: string | ((t: (key: string) => string, protocol: any) => string); + options?: readonly string[]; + defaultValue?: any; + min?: number; + max?: number; + step?: number; + suffix?: string; + generate?: { + function: () => Promise> | string | Record; + updateFields?: Record; + }; + condition?: (protocol: any, values: any) => boolean; + group?: 'basic' | 'transport' | 'security' | 'reality' | 'obfs' | 'encryption'; + gridSpan?: 1 | 2; +}; + +export type ServerFormValues = z.infer; + +export type ProtocolType = (typeof protocols)[number]; diff --git a/apps/admin/app/dashboard/servers/server-form.tsx b/apps/admin/app/dashboard/servers/server-form.tsx index 882b0db..2f53ae8 100644 --- a/apps/admin/app/dashboard/servers/server-form.tsx +++ b/apps/admin/app/dashboard/servers/server-form.tsx @@ -102,8 +102,8 @@ function DynamicField({