From 9372a974fdbdfb295e2a413aadafe2443df05f4e Mon Sep 17 00:00:00 2001 From: imengying Date: Thu, 10 Sep 2026 17:58:28 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=BB=B6=E8=BF=9F=E6=A3=80?= =?UTF-8?q?=E6=B5=8B=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/LatencyManager.tsx | 36 ++++++++++++++-------- frontend/src/styles/admin.css | 2 ++ 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/LatencyManager.tsx b/frontend/src/components/LatencyManager.tsx index 1c07c27..5f1a303 100644 --- a/frontend/src/components/LatencyManager.tsx +++ b/frontend/src/components/LatencyManager.tsx @@ -5,11 +5,12 @@ import type { AdminServer, LatencyTask, LatencyTaskInput } from "../types"; import { Checkbox } from "./Checkbox"; import { useDialog } from "./useDialog"; -const emptyTask: LatencyTaskInput = { +type TaskForm = Omit; + +const emptyTask: TaskForm = { name: "", task_type: "icmp", target: "", - port: null, interval_seconds: 60, default_enabled: false, server_ids: [], @@ -35,6 +36,15 @@ function validHost(value: string) { return labels.every((part) => /^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$/.test(part)); } +function parseTarget(value: string, type: LatencyTask["task_type"]): Pick | null { + const target = value.trim(); + if (type === "icmp") return validHost(target) ? { target, port: null } : null; + const match = /^([^:\s]+)(?::(\d{1,5}))?$/.exec(target); + if (!match || !validHost(match[1])) return null; + const port = Number(match[2] ?? 80); + return port >= 1 && port <= 65535 ? { target: match[1], port } : null; +} + export function LatencyManager({ servers, onError, @@ -47,7 +57,7 @@ export function LatencyManager({ const [tasks, setTasks] = useState([]); const [busy, setBusy] = useState(true); const [editing, setEditing] = useState(null); - const [form, setForm] = useState(emptyTask); + const [form, setForm] = useState(emptyTask); const [query, setQuery] = useState(""); const editorDialog = useDialog(editing !== null, () => setEditing(null)); @@ -76,8 +86,7 @@ export function LatencyManager({ setForm(task ? { name: task.name, task_type: task.task_type, - target: task.target, - port: task.port, + target: task.task_type === "tcp" ? `${task.target}:${task.port ?? 80}` : task.target, interval_seconds: task.interval_seconds, default_enabled: task.default_enabled, server_ids: [...task.server_ids], @@ -97,15 +106,17 @@ export function LatencyManager({ async function save(event: FormEvent) { event.preventDefault(); - if (!validHost(form.target) || (form.task_type === "tcp" && (!form.port || form.port < 1 || form.port > 65535)) || (form.task_type === "icmp" && form.port !== null)) { - onError(form.task_type === "icmp" ? "ICMP 节点应为公网域名或公网 IPv4,不使用端口" : "TCP 节点应为公网域名或公网 IPv4,并填写 1 至 65535 的端口"); + const target = parseTarget(form.target, form.task_type); + if (!target) { + onError(form.task_type === "icmp" ? "ICMP 节点应为公网域名或公网 IPv4,不使用端口" : "TCP 节点格式应为域名:端口或公网 IPv4:端口,端口范围为 1 至 65535"); return; } + const input: LatencyTaskInput = { ...form, ...target }; setBusy(true); onError(""); try { - if (editing === "new") await api.createLatencyTask(form); - else if (editing) await api.updateLatencyTask(editing.id, form); + if (editing === "new") await api.createLatencyTask(input); + else if (editing) await api.updateLatencyTask(editing.id, input); setEditing(null); await load(); onNotice(editing === "new" ? "延迟任务已添加" : "延迟任务已更新"); @@ -143,7 +154,7 @@ export function LatencyManager({ {task.task_type.toUpperCase()}
{task.name}{task.target}{task.port ? `:${task.port}` : ""}
{task.interval_seconds}s - {task.server_ids.length} 个节点{task.default_enabled ? " · 默认" : ""} + {task.server_ids.length} 个节点
)} {!tasks.length && !busy ?
暂无延迟任务
: null} @@ -152,9 +163,8 @@ export function LatencyManager({ {editing ?
延迟检测

{editing === "new" ? "添加任务" : `编辑 · ${editing.name}`}

-
-
{form.task_type === "tcp" ? :
}
- +
+
服务器已选 {form.server_ids.length} / 共 {servers.length}
setQuery(event.target.value)} />
diff --git a/frontend/src/styles/admin.css b/frontend/src/styles/admin.css index 6167892..7d7d131 100644 --- a/frontend/src/styles/admin.css +++ b/frontend/src/styles/admin.css @@ -249,6 +249,8 @@ .latency-editor { display: flex; width: min(620px, 100%); max-height: calc(100dvh - 48px); overflow: auto; flex-direction: column; gap: 16px; border: 1px solid var(--admin-border); border-radius: 8px; padding: 22px; color: var(--admin-text); background: var(--admin-surface); } .latency-editor > header { display: flex; align-items: center; justify-content: space-between; gap: 16px; } .latency-editor > * { flex-shrink: 0; } +.latency-target-row { grid-template-columns: minmax(0, 1fr) 140px; } +.latency-target-row > label { min-width: 0; } .password-field { position: relative; min-width: 0; } .password-field input { padding-right: 44px; } .password-field .icon-btn { position: absolute; right: 4px; top: 50%; transform: translateY(-50%); width: 32px; height: 32px; }