From 42c5492c1dab8cc872aeebf34b659a89980baa38 Mon Sep 17 00:00:00 2001 From: sagitchu Date: Fri, 1 May 2026 23:36:14 +0800 Subject: [PATCH] feat: add tunnel probe target UI --- vite-frontend/src/api/types.ts | 2 + .../src/pages/node/tunnel-monitor-view.tsx | 45 +++++++++++--- vite-frontend/src/pages/tunnel.tsx | 61 +++++++++++++++++++ vite-frontend/src/pages/tunnel/form.ts | 19 ++++++ 4 files changed, 117 insertions(+), 10 deletions(-) diff --git a/vite-frontend/src/api/types.ts b/vite-frontend/src/api/types.ts index 39cfcbd..f69f7af 100644 --- a/vite-frontend/src/api/types.ts +++ b/vite-frontend/src/api/types.ts @@ -525,6 +525,8 @@ export interface TunnelQualityApiItem { exitToBingLatency: number; entryToExitLoss: number; exitToBingLoss: number; + probeTargetHost?: string; + probeTargetPort?: number; success: boolean; errorMessage?: string; timestamp: number; diff --git a/vite-frontend/src/pages/node/tunnel-monitor-view.tsx b/vite-frontend/src/pages/node/tunnel-monitor-view.tsx index 59e9db3..3eec8a3 100644 --- a/vite-frontend/src/pages/node/tunnel-monitor-view.tsx +++ b/vite-frontend/src/pages/node/tunnel-monitor-view.tsx @@ -63,6 +63,19 @@ const MONITOR_TUNNEL_QUALITY_ENABLED_CONFIG_KEY = "monitor_tunnel_quality_enabled"; const MONITOR_TUNNEL_QUALITY_ENABLED_EVENT = "monitorTunnelQualityEnabledChanged"; +const DEFAULT_PROBE_TARGET_LABEL = "www.bing.com:443"; + +const probeTargetLabel = (quality?: TunnelQualityApiItem | null) => { + if (!quality?.probeTargetHost || !quality.probeTargetPort) { + return DEFAULT_PROBE_TARGET_LABEL; + } + + const host = quality.probeTargetHost.includes(":") + ? `[${quality.probeTargetHost}]` + : quality.probeTargetHost; + + return `${host}:${quality.probeTargetPort}`; +}; const formatTimestamp = (ts: number, rangeMs?: number): string => { const date = new Date(ts); @@ -177,7 +190,7 @@ function UptimeHistoryBar({ } const displayLatency = latency >= 0 ? `${latency.toFixed(0)}ms` : "-"; - const tooltip = `${timeStr} | ${displayLatency} | ${statusText}`; + const tooltip = `${timeStr} | ${displayLatency} | ${statusText} | 测试目标: ${probeTargetLabel(q)}`; return (
- + - 出口 → Bing 延迟 + 出口 → 测试目标 延迟 - - 出口 → Bing 丢包 + + 出口 → 测试目标 丢包 0 ? "text-warning" : ""}`} @@ -1054,6 +1073,9 @@ export function TunnelMonitorView({ 实时隧道质量检测已关闭 )} + + · 测试目标: {probeTargetLabel(quality)} + {quality?.timestamp && ( · 最近更新:{" "} @@ -1201,9 +1223,12 @@ export function TunnelMonitorView({ />
-
+
- 出口→Bing + 出口→测试目标
状态 名称 入口→出口 - 出口→Bing + 出口→测试目标 更新时间 @@ -1294,7 +1319,7 @@ export function TunnelMonitorView({ type="entryToExit" /> - + ip.trim()) .filter((ip) => ip) .join(","); + const probeTargetHost = (form.probeTargetHost || "").trim(); + const probeTargetPort = probeTargetHost + ? Number(form.probeTargetPort || 0) + : 0; const data = { ...form, inIp: inIpString, outNodeId: cleanedOutNodeId, chainNodes: cleanedChainNodes, + probeTargetHost, + probeTargetPort, }; const response = isEdit @@ -2323,6 +2335,55 @@ export default function TunnelPage() { )} +
+
+
质量检测目标
+

+ 用于实时隧道质量检测和 best 最优出口评分,留空使用 + www.bing.com:443 +

+
+
+ + setForm((prev) => ({ + ...prev, + probeTargetHost: e.target.value, + })) + } + /> + + setForm((prev) => ({ + ...prev, + probeTargetPort: e.target.value + ? Number(e.target.value) + : 0, + })) + } + /> +
+
+

入口配置

diff --git a/vite-frontend/src/pages/tunnel/form.ts b/vite-frontend/src/pages/tunnel/form.ts index c69ea75..6c560fc 100644 --- a/vite-frontend/src/pages/tunnel/form.ts +++ b/vite-frontend/src/pages/tunnel/form.ts @@ -8,6 +8,8 @@ interface TunnelFormInput { inNodeId: TunnelChainNode[]; outNodeId?: TunnelChainNode[]; trafficRatio: number; + probeTargetHost?: string; + probeTargetPort?: number; } interface TunnelNodeInput { @@ -26,6 +28,8 @@ export const createTunnelFormDefaults = () => { trafficRatio: 1.0, inIp: "", ipPreference: "", + probeTargetHost: "", + probeTargetPort: 0, status: 1, }; }; @@ -63,6 +67,21 @@ export const validateTunnelForm = ( errors.trafficRatio = "流量倍率须大于0,支持小数(如 0.5)"; } + const probeHost = (form.probeTargetHost || "").trim(); + const probePort = Number(form.probeTargetPort || 0); + + if (probeHost || probePort > 0) { + if (!probeHost) { + errors.probeTargetHost = "请输入测试目标 Host"; + } else if (probeHost.includes("://") || /[\s/?#]/.test(probeHost)) { + errors.probeTargetHost = "Host 不能包含协议、空格或路径"; + } + + if (!Number.isInteger(probePort) || probePort < 1 || probePort > 65535) { + errors.probeTargetPort = "端口必须是 1-65535"; + } + } + if (form.type === 2) { if (!form.outNodeId || form.outNodeId.length === 0) { errors.outNodeId = "请至少选择一个出口节点";