'use client'; import { getNodeGroupList } from '@/services/admin/server'; import { zodResolver } from '@hookform/resolvers/zod'; import { useQuery } from '@tanstack/react-query'; import { Button } from '@workspace/ui/components/button'; import { Card, CardContent, CardHeader, CardTitle } from '@workspace/ui/components/card'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from '@workspace/ui/components/form'; import { ScrollArea } from '@workspace/ui/components/scroll-area'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@workspace/ui/components/select'; import { Sheet, SheetContent, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, } from '@workspace/ui/components/sheet'; import { Switch } from '@workspace/ui/components/switch'; import { Tabs, TabsList, TabsTrigger } from '@workspace/ui/components/tabs'; import { Combobox } from '@workspace/ui/custom-components/combobox'; import { ArrayInput } from '@workspace/ui/custom-components/dynamic-Inputs'; import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input'; import { Icon } from '@workspace/ui/custom-components/icon'; import TagInput from '@workspace/ui/custom-components/tag-input'; import { cn } from '@workspace/ui/lib/utils'; import { unitConversion } from '@workspace/ui/utils'; import { useTranslations } from 'next-intl'; import { useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { toast } from 'sonner'; import { formSchema, protocols } from './form-schema'; interface NodeFormProps { onSubmit: (data: T) => Promise | boolean; initialValues?: T; loading?: boolean; trigger: string; title: string; } export default function NodeForm({ onSubmit, initialValues, loading, trigger, title, }: Readonly>) { const t = useTranslations('server.node'); const [open, setOpen] = useState(false); const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { tags: [], traffic_ratio: 1, protocol: 'shadowsocks', ...initialValues, config: { security: 'none', transport: 'tcp', ...initialValues?.config, }, } as any, }); const protocol = form.watch('protocol'); const transport = form.watch('config.transport'); const security = form.watch('config.security'); const relayMode = form.watch('relay_mode'); const method = form.watch('config.method'); useEffect(() => { form?.reset(initialValues); }, [form, initialValues]); async function handleSubmit(data: { [x: string]: any }) { const bool = await onSubmit(data as unknown as T); if (bool) setOpen(false); } const { data: groups } = useQuery({ queryKey: ['getNodeGroupList'], queryFn: async () => { const { data } = await getNodeGroupList(); return (data.data?.list || []) as API.ServerGroup[]; }, }); return ( {title}
( {t('form.name')} { form.setValue(field.name, value); }} /> )} /> ( {t('form.groupId')} placeholder={t('form.selectNodeGroup')} {...field} options={groups?.map((item) => ({ value: item.id, label: item.name, }))} onChange={(value) => { form.setValue(field.name, value || 0); }} /> )} />
( {t('form.tags')} form.setValue(field.name, value)} /> )} /> ( {t('form.country')} { form.setValue(field.name, value); }} /> )} /> ( {t('form.city')} { form.setValue(field.name, value); }} /> )} />
( {t('form.serverAddr')} { form.setValue(field.name, value); }} /> )} /> ( {t('form.speedLimit')} unitConversion('bitsToMb', value)} formatOutput={(value) => unitConversion('mbToBits', value)} onValueChange={(value) => { form.setValue(field.name, value); }} suffix='Mbps' /> )} /> ( {t('form.trafficRatio')} { form.setValue(field.name, value); }} suffix='X' /> )} />
( {t('form.protocol')} { form.setValue(field.name, value); if (['trojan', 'hysteria2', 'tuic'].includes(value)) { form.setValue('config.security', 'tls'); } }} > {protocols.map((proto) => ( {proto.charAt(0).toUpperCase() + proto.slice(1)} ))} )} /> {protocol === 'shadowsocks' && (
( {t('form.encryptionMethod')} )} /> ( {t('form.port')} { form.setValue(field.name, value); }} /> )} /> {[ '2022-blake3-aes-128-gcm', '2022-blake3-aes-256-gcm', '2022-blake3-chacha20-poly1305', ].includes(method) && ( ( {t('form.serverKey')} { form.setValue(field.name, value); }} /> )} /> )}
)} {['vmess', 'vless', 'trojan', 'hysteria2', 'tuic'].includes(protocol) && (
( {t('form.port')} { form.setValue(field.name, value); }} /> )} /> {protocol === 'vless' && ( ( {t('form.flow')} )} /> )} {protocol === 'hysteria2' && ( <> ( {t('form.obfsPassword')} { form.setValue(field.name, value); }} /> )} /> ( {t('form.hopPorts')} { form.setValue(field.name, value); }} /> )} /> ( {t('form.hopInterval')} { form.setValue(field.name, value); }} suffix='S' /> )} /> )}
{['vmess', 'vless', 'trojan'].includes(protocol) && ( {t('form.transportConfig')} ( )} /> {transport !== 'tcp' && ( {['websocket', 'http2', 'httpupgrade'].includes(transport) && ( <> ( PATH { form.setValue(field.name, value); }} /> )} /> ( HOST { form.setValue(field.name, value); }} /> )} /> )} {['grpc'].includes(transport) && ( ( Service Name { form.setValue(field.name, value); }} /> )} /> )} )} )} {t('form.securityConfig')} ( )} /> {security !== 'none' && ( ( Server Name(SNI) { form.setValue(field.name, value); }} /> )} /> {protocol === 'vless' && security === 'reality' && ( <> ( {t('form.security_config.serverAddress')} { form.setValue(field.name, value); }} /> )} /> ( {t('form.security_config.serverPort')} { form.setValue(field.name, value); }} /> )} /> ( {t('form.security_config.privateKey')} { form.setValue(field.name, value); }} /> )} /> ( {t('form.security_config.publicKey')} { form.setValue(field.name, value); }} /> )} /> ( {t('form.security_config.shortId')} { form.setValue(field.name, value); }} /> )} /> )} {protocol === 'vless' && ( ( {t('form.security_config.fingerprint')} )} /> )} ( Allow Insecure
{ form.setValue(field.name, value); }} />
)} />
)}
)} {t('form.relayMode')} ( )} /> {relayMode !== 'none' && ( ( { form.setValue(field.name, value); }} /> )} /> )}
); }