fix: use custom probe targets for diagnostics

Move custom probe target controls into the tunnel advanced settings and reuse the configured target in tunnel diagnosis output.
This commit is contained in:
sagitchu
2026-05-02 14:23:51 +08:00
parent 791773fd62
commit cdcdfd8ff0
4 changed files with 102 additions and 56 deletions
@@ -978,6 +978,7 @@ func (h *Handler) prepareTunnelDiagnosis(tunnelID int64) (string, string, []diag
ipPreference := h.repo.GetTunnelIPPreference(tunnelID) ipPreference := h.repo.GetTunnelIPPreference(tunnelID)
protocol := strings.ToLower(strings.TrimSpace(tunnel.Protocol)) protocol := strings.ToLower(strings.TrimSpace(tunnel.Protocol))
probeTarget := effectiveTunnelProbeTargetValues(tunnel.ProbeTargetHost, tunnel.ProbeTargetPort)
inNodes, chainHops, outNodes := splitChainNodeGroups(chainRows) inNodes, chainHops, outNodes := splitChainNodeGroups(chainRows)
workItems := make([]diagnosisWorkItem, 0, len(chainRows)*2) workItems := make([]diagnosisWorkItem, 0, len(chainRows)*2)
@@ -987,8 +988,8 @@ func (h *Handler) prepareTunnelDiagnosis(tunnelID int64) (string, string, []diag
description := fmt.Sprintf("入口(%s)->外网", inNode.NodeName) description := fmt.Sprintf("入口(%s)->外网", inNode.NodeName)
workItems = append(workItems, diagnosisWorkItem{ workItems = append(workItems, diagnosisWorkItem{
fromNodeID: inNode.NodeID, fromNodeID: inNode.NodeID,
targetIP: "www.bing.com", targetIP: probeTarget.Host,
targetPort: 443, targetPort: probeTarget.Port,
description: description, description: description,
protocol: "tcp", protocol: "tcp",
metadata: map[string]interface{}{ metadata: map[string]interface{}{
@@ -1079,8 +1080,8 @@ func (h *Handler) prepareTunnelDiagnosis(tunnelID int64) (string, string, []diag
description := fmt.Sprintf("出口(%s)->外网", outNode.NodeName) description := fmt.Sprintf("出口(%s)->外网", outNode.NodeName)
workItems = append(workItems, diagnosisWorkItem{ workItems = append(workItems, diagnosisWorkItem{
fromNodeID: outNode.NodeID, fromNodeID: outNode.NodeID,
targetIP: "www.bing.com", targetIP: probeTarget.Host,
targetPort: 443, targetPort: probeTarget.Port,
description: description, description: description,
protocol: "tcp", protocol: "tcp",
metadata: map[string]interface{}{ metadata: map[string]interface{}{
@@ -1093,8 +1094,8 @@ func (h *Handler) prepareTunnelDiagnosis(tunnelID int64) (string, string, []diag
description := fmt.Sprintf("入口(%s)->外网", inNode.NodeName) description := fmt.Sprintf("入口(%s)->外网", inNode.NodeName)
workItems = append(workItems, diagnosisWorkItem{ workItems = append(workItems, diagnosisWorkItem{
fromNodeID: inNode.NodeID, fromNodeID: inNode.NodeID,
targetIP: "www.bing.com", targetIP: probeTarget.Host,
targetPort: 443, targetPort: probeTarget.Port,
description: description, description: description,
protocol: "tcp", protocol: "tcp",
metadata: map[string]interface{}{ metadata: map[string]interface{}{
@@ -209,6 +209,22 @@ func TestTunnelUpdateInvalidProbeTargetDoesNotCleanFederationBindings(t *testing
} }
} }
func TestTunnelDiagnosisUsesConfiguredProbeTarget(t *testing.T) {
h := setupProbeTargetTunnelHandler(t)
seedProbeTargetTunnel(t, h, 90, "diagnosis-target", "speed.example.com", 8443)
_, _, workItems, err := h.prepareTunnelDiagnosis(90)
if err != nil {
t.Fatalf("prepare tunnel diagnosis: %v", err)
}
if len(workItems) != 1 {
t.Fatalf("expected one diagnosis item, got %d", len(workItems))
}
if workItems[0].targetIP != "speed.example.com" || workItems[0].targetPort != 8443 {
t.Fatalf("expected custom diagnosis target speed.example.com:8443, got %s:%d", workItems[0].targetIP, workItems[0].targetPort)
}
}
func setupProbeTargetTunnelHandler(t *testing.T) *Handler { func setupProbeTargetTunnelHandler(t *testing.T) *Handler {
t.Helper() t.Helper()
r, err := repo.Open(filepath.Join(t.TempDir(), "panel.db")) r, err := repo.Open(filepath.Join(t.TempDir(), "panel.db"))
+28 -3
View File
@@ -46,6 +46,7 @@ import { Alert } from "@/shadcn-bridge/heroui/alert";
import { Checkbox } from "@/shadcn-bridge/heroui/checkbox"; import { Checkbox } from "@/shadcn-bridge/heroui/checkbox";
import { Progress } from "@/shadcn-bridge/heroui/progress"; import { Progress } from "@/shadcn-bridge/heroui/progress";
import { Radio, RadioGroup } from "@/shadcn-bridge/heroui/radio"; import { Radio, RadioGroup } from "@/shadcn-bridge/heroui/radio";
import { Accordion, AccordionItem } from "@/shadcn-bridge/heroui/accordion";
import { import {
Table, Table,
TableHeader, TableHeader,
@@ -137,6 +138,14 @@ interface Tunnel {
createdTime: string; createdTime: string;
} }
const DEFAULT_PROBE_TARGET_HOST = "www.bing.com";
const DEFAULT_PROBE_TARGET_PORT = 443;
const getTunnelDiagnosisTarget = (tunnel: Tunnel) => ({
targetIp: tunnel.probeTargetHost || DEFAULT_PROBE_TARGET_HOST,
targetPort: tunnel.probeTargetPort || DEFAULT_PROBE_TARGET_PORT,
});
interface Node { interface Node {
id: number; id: number;
name: string; name: string;
@@ -902,6 +911,7 @@ export default function TunnelPage() {
const handleDiagnose = async (tunnel: Tunnel) => { const handleDiagnose = async (tunnel: Tunnel) => {
diagnosisAbortRef.current?.abort(); diagnosisAbortRef.current?.abort();
const abortController = new AbortController(); const abortController = new AbortController();
const diagnosisTarget = getTunnelDiagnosisTarget(tunnel);
diagnosisAbortRef.current = abortController; diagnosisAbortRef.current = abortController;
@@ -1043,6 +1053,7 @@ export default function TunnelPage() {
tunnelType: tunnel.type, tunnelType: tunnel.type,
description: "诊断失败", description: "诊断失败",
message: response.msg || "诊断过程中发生错误", message: response.msg || "诊断过程中发生错误",
...diagnosisTarget,
}), }),
); );
setDiagnosisProgress({ setDiagnosisProgress({
@@ -1074,6 +1085,7 @@ export default function TunnelPage() {
tunnelType: tunnel.type, tunnelType: tunnel.type,
description: "网络错误", description: "网络错误",
message: "无法连接到服务器", message: "无法连接到服务器",
...diagnosisTarget,
}), }),
); );
setDiagnosisProgress({ setDiagnosisProgress({
@@ -2335,12 +2347,23 @@ export default function TunnelPage() {
</Select> </Select>
)} )}
<div className="rounded-xl border border-divider/60 bg-default-50/40 p-3 space-y-3"> <Accordion className="px-0" variant="light">
<AccordionItem
key="advanced"
aria-label="高级设置"
className="border-b-0 [&_[data-slot=accordion-trigger]]:no-underline [&_[data-slot=accordion-trigger]]:hover:no-underline"
title={
<span className="text-small text-default-500 font-medium">
高级设置
</span>
}
>
<div className="space-y-4 pb-2">
<div> <div>
<div className="text-sm font-medium">质量检测目标</div> <div className="text-sm font-medium">质量检测目标</div>
<p className="text-xs text-default-500 mt-0.5"> <p className="text-xs text-default-500 mt-0.5">
用于实时隧道质量检测和 best 最优出口评分,留空使用 用于实时隧道质量检测、诊断目标和 best
www.bing.com:443 最优出口评分,留空使用 www.bing.com:443
</p> </p>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-[1fr_140px] gap-3"> <div className="grid grid-cols-1 md:grid-cols-[1fr_140px] gap-3">
@@ -2383,6 +2406,8 @@ export default function TunnelPage() {
/> />
</div> </div>
</div> </div>
</AccordionItem>
</Accordion>
<Divider /> <Divider />
<h3 className="text-lg font-semibold">入口配置</h3> <h3 className="text-lg font-semibold">入口配置</h3>
+6 -2
View File
@@ -27,6 +27,8 @@ export interface DiagnosisFallbackInput {
tunnelType: number; tunnelType: number;
description: string; description: string;
message: string; message: string;
targetIp?: string;
targetPort?: number;
} }
export const buildDiagnosisFallbackResult = ({ export const buildDiagnosisFallbackResult = ({
@@ -34,6 +36,8 @@ export const buildDiagnosisFallbackResult = ({
tunnelType, tunnelType,
description, description,
message, message,
targetIp = "-",
targetPort = 443,
}: DiagnosisFallbackInput): DiagnosisResult => { }: DiagnosisFallbackInput): DiagnosisResult => {
return { return {
tunnelName, tunnelName,
@@ -45,8 +49,8 @@ export const buildDiagnosisFallbackResult = ({
description, description,
nodeName: "-", nodeName: "-",
nodeId: "-", nodeId: "-",
targetIp: "-", targetIp,
targetPort: 443, targetPort,
message, message,
}, },
], ],