From 936158dd328e68fee05194f0929f084aa1c972fd Mon Sep 17 00:00:00 2001 From: root Date: Sun, 1 Feb 2026 12:34:31 +0000 Subject: [PATCH] =?UTF-8?q?=E8=8A=82=E7=82=B9=E5=9C=B0=E5=9D=80=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E6=94=AF=E6=8C=81=E5=9F=9F=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vite-frontend/src/pages/node.tsx | 87 +++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 25 deletions(-) diff --git a/vite-frontend/src/pages/node.tsx b/vite-frontend/src/pages/node.tsx index 35c4ba9..cc14f46 100644 --- a/vite-frontend/src/pages/node.tsx +++ b/vite-frontend/src/pages/node.tsx @@ -72,6 +72,7 @@ interface Node { interface NodeForm { id: number | null; name: string; + serverHost: string; serverIpV4: string; serverIpV6: string; port: string; @@ -99,6 +100,7 @@ export default function NodePage() { const [form, setForm] = useState({ id: null, name: '', + serverHost: '', serverIpV4: '', serverIpV6: '', port: '1000-65535', @@ -409,6 +411,15 @@ export default function NodePage() { const validateIpv4Literal = (ip: string): boolean => ipv4Regex.test(ip.trim()); const validateIpv6Literal = (ip: string): boolean => ipv6Regex.test(ip.trim()); + // Hostname/domain validation (no scheme/port) + const hostnameRegex = /^(?=.{1,253}$)(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)(?:\.(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?))*$/; + const validateHostname = (host: string): boolean => { + const v = host.trim(); + if (!v) return false; + if (v === 'localhost') return true; + return hostnameRegex.test(v); + }; + // 验证端口格式:支持 80,443,100-600 const validatePort = (portStr: string): { valid: boolean; error?: string } => { if (!portStr || !portStr.trim()) { @@ -474,10 +485,13 @@ export default function NodePage() { const v4 = form.serverIpV4.trim(); const v6 = form.serverIpV6.trim(); + const host = form.serverHost.trim(); - if (!v4 && !v6) { - newErrors.serverIpV4 = '请至少填写一个IPv4或IPv6地址'; - newErrors.serverIpV6 = '请至少填写一个IPv4或IPv6地址'; + if (!v4 && !v6 && !host) { + const msg = '请至少填写一个 IPv4/IPv6 地址或域名'; + newErrors.serverIpV4 = msg; + newErrors.serverIpV6 = msg; + newErrors.serverHost = msg; } else { if (v4 && !validateIpv4Literal(v4)) { newErrors.serverIpV4 = '请输入有效的IPv4地址'; @@ -485,6 +499,9 @@ export default function NodePage() { if (v6 && !validateIpv6Literal(v6)) { newErrors.serverIpV6 = '请输入有效的IPv6地址'; } + if (host && !validateHostname(host)) { + newErrors.serverHost = '请输入有效的域名/主机名'; + } } const portValidation = validatePort(form.port); @@ -510,11 +527,18 @@ export default function NodePage() { const handleEdit = (node: Node) => { setDialogTitle('编辑节点'); setIsEdit(true); + + const legacy = (node.serverIp || '').trim(); + const normalizedV4 = node.serverIpV4?.trim() || (validateIpv4Literal(legacy) ? legacy : ''); + const normalizedV6 = node.serverIpV6?.trim() || (validateIpv6Literal(legacy) ? legacy : ''); + const normalizedHost = (!normalizedV4 && !normalizedV6 && legacy) ? legacy : ''; + setForm({ id: node.id, name: node.name, - serverIpV4: node.serverIpV4?.trim() || (validateIpv4Literal((node.serverIp || '').trim()) ? (node.serverIp || '').trim() : ''), - serverIpV6: node.serverIpV6?.trim() || (validateIpv6Literal((node.serverIp || '').trim()) ? (node.serverIp || '').trim() : ''), + serverHost: normalizedHost, + serverIpV4: normalizedV4, + serverIpV6: normalizedV6, port: node.port || '1000-65535', tcpListenAddr: node.tcpListenAddr || '[::]', udpListenAddr: node.udpListenAddr || '[::]', @@ -605,9 +629,10 @@ export default function NodePage() { try { const apiCall = isEdit ? updateNode : createNode; - const data = { - ...form, - serverIp: form.serverIpV4?.trim() || form.serverIpV6?.trim() || '' + const { serverHost: _serverHost, ...rest } = form; + const data = { + ...rest, + serverIp: form.serverIpV4?.trim() || form.serverIpV6?.trim() || form.serverHost?.trim() || '' }; const res = await apiCall(data); @@ -616,22 +641,22 @@ export default function NodePage() { setDialogVisible(false); if (isEdit) { - setNodeList(prev => prev.map(n => - n.id === form.id ? { - ...n, - name: form.name, - serverIp: form.serverIpV4?.trim() || form.serverIpV6?.trim() || '', - serverIpV4: form.serverIpV4, - serverIpV6: form.serverIpV6, - port: form.port, - tcpListenAddr: form.tcpListenAddr, - udpListenAddr: form.udpListenAddr, - interfaceName: form.interfaceName, - http: form.http, - tls: form.tls, - socks: form.socks - } : n - )); + setNodeList(prev => prev.map(n => + n.id === form.id ? { + ...n, + name: form.name, + serverIp: form.serverIpV4?.trim() || form.serverIpV6?.trim() || form.serverHost?.trim() || '', + serverIpV4: form.serverIpV4, + serverIpV6: form.serverIpV6, + port: form.port, + tcpListenAddr: form.tcpListenAddr, + udpListenAddr: form.udpListenAddr, + interfaceName: form.interfaceName, + http: form.http, + tls: form.tls, + socks: form.socks + } : n + )); } else { loadNodes(); } @@ -650,6 +675,7 @@ export default function NodePage() { setForm({ id: null, name: '', + serverHost: '', serverIpV4: '', serverIpV6: '', port: '1000-65535', @@ -1059,6 +1085,17 @@ export default function NodePage() { variant="bordered" /> + setForm(prev => ({ ...prev, serverHost: e.target.value }))} + isInvalid={!!errors.serverHost} + errorMessage={errors.serverHost} + variant="bordered" + description="可选:不带协议、不带端口。至少填写一个 IPv4/IPv6/域名" + /> +