import { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Alert, Button, Col, Form, Input, InputNumber, Modal, Row, Select, Switch, message, } from 'antd'; import type { NodeRecord } from '@/api/queries/useNodesQuery'; import type { Msg } from '@/utils'; import { NodeFormSchema, type NodeFormValues, type ProbeResult } from '@/schemas/node'; import { antdRule } from '@/utils/zodForm'; import './NodeFormModal.css'; type Mode = 'add' | 'edit'; interface NodeFormModalProps { open: boolean; mode: Mode; node: NodeRecord | null; testConnection: (payload: Partial) => Promise>; save: (payload: Partial) => Promise>; onOpenChange: (open: boolean) => void; } function defaultValues(): NodeFormValues { return { id: 0, name: '', remark: '', scheme: 'https', address: '', port: 2053, basePath: '/', apiToken: '', enable: true, allowPrivateAddress: false, }; } export default function NodeFormModal({ open, mode, node, testConnection, save, onOpenChange, }: NodeFormModalProps) { const { t } = useTranslation(); const [form] = Form.useForm(); const [messageApi, messageContextHolder] = message.useMessage(); const [submitting, setSubmitting] = useState(false); const [testing, setTesting] = useState(false); const [testResult, setTestResult] = useState(null); useEffect(() => { if (!open) return; const base = defaultValues(); const next: NodeFormValues = mode === 'edit' && node ? { ...base, ...(node as unknown as Partial), id: node.id, scheme: (node.scheme as 'http' | 'https') || base.scheme, } : base; form.resetFields(); form.setFieldsValue(next); setTestResult(null); }, [open, mode, node, form]); const title = useMemo( () => (mode === 'edit' ? t('pages.nodes.editNode') : t('pages.nodes.addNode')), [mode, t], ); function buildPayload(values: NodeFormValues): Partial { return { id: values.id || 0, name: values.name.trim(), remark: values.remark?.trim() || '', scheme: values.scheme, address: values.address.trim(), port: values.port, basePath: values.basePath.trim() || '/', apiToken: values.apiToken.trim(), enable: values.enable, allowPrivateAddress: values.allowPrivateAddress, }; } async function onTest() { try { await form.validateFields(['address', 'port']); } catch { return; } setTesting(true); setTestResult(null); try { const payload = buildPayload(form.getFieldsValue(true)); const msg = await testConnection(payload); if (msg?.success && msg.obj) { setTestResult(msg.obj); } else { setTestResult({ status: 'offline', error: msg?.msg || 'unknown error' }); } } finally { setTesting(false); } } async function onFinish(values: NodeFormValues) { const result = NodeFormSchema.safeParse(values); if (!result.success) { messageApi.error(t(result.error.issues[0]?.message ?? 'pages.nodes.toasts.fillRequired')); return; } setSubmitting(true); try { const msg = await save(buildPayload(result.data)); if (msg?.success) { onOpenChange(false); } } finally { setSubmitting(false); } } function close() { if (!submitting) onOpenChange(false); } return ( <> {messageContextHolder} form.submit()} onCancel={close} >
{testResult && (
{testResult.status === 'online' ? ( ) : ( )}
)}
); }