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 = "请至少选择一个出口节点";