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)
protocol := strings.ToLower(strings.TrimSpace(tunnel.Protocol))
probeTarget := effectiveTunnelProbeTargetValues(tunnel.ProbeTargetHost, tunnel.ProbeTargetPort)
inNodes, chainHops, outNodes := splitChainNodeGroups(chainRows)
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)
workItems = append(workItems, diagnosisWorkItem{
fromNodeID: inNode.NodeID,
targetIP: "www.bing.com",
targetPort: 443,
targetIP: probeTarget.Host,
targetPort: probeTarget.Port,
description: description,
protocol: "tcp",
metadata: map[string]interface{}{
@@ -1079,8 +1080,8 @@ func (h *Handler) prepareTunnelDiagnosis(tunnelID int64) (string, string, []diag
description := fmt.Sprintf("出口(%s)->外网", outNode.NodeName)
workItems = append(workItems, diagnosisWorkItem{
fromNodeID: outNode.NodeID,
targetIP: "www.bing.com",
targetPort: 443,
targetIP: probeTarget.Host,
targetPort: probeTarget.Port,
description: description,
protocol: "tcp",
metadata: map[string]interface{}{
@@ -1093,8 +1094,8 @@ func (h *Handler) prepareTunnelDiagnosis(tunnelID int64) (string, string, []diag
description := fmt.Sprintf("入口(%s)->外网", inNode.NodeName)
workItems = append(workItems, diagnosisWorkItem{
fromNodeID: inNode.NodeID,
targetIP: "www.bing.com",
targetPort: 443,
targetIP: probeTarget.Host,
targetPort: probeTarget.Port,
description: description,
protocol: "tcp",
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 {
t.Helper()
r, err := repo.Open(filepath.Join(t.TempDir(), "panel.db"))
+73 -48
View File
@@ -46,6 +46,7 @@ import { Alert } from "@/shadcn-bridge/heroui/alert";
import { Checkbox } from "@/shadcn-bridge/heroui/checkbox";
import { Progress } from "@/shadcn-bridge/heroui/progress";
import { Radio, RadioGroup } from "@/shadcn-bridge/heroui/radio";
import { Accordion, AccordionItem } from "@/shadcn-bridge/heroui/accordion";
import {
Table,
TableHeader,
@@ -137,6 +138,14 @@ interface Tunnel {
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 {
id: number;
name: string;
@@ -902,6 +911,7 @@ export default function TunnelPage() {
const handleDiagnose = async (tunnel: Tunnel) => {
diagnosisAbortRef.current?.abort();
const abortController = new AbortController();
const diagnosisTarget = getTunnelDiagnosisTarget(tunnel);
diagnosisAbortRef.current = abortController;
@@ -1043,6 +1053,7 @@ export default function TunnelPage() {
tunnelType: tunnel.type,
description: "诊断失败",
message: response.msg || "诊断过程中发生错误",
...diagnosisTarget,
}),
);
setDiagnosisProgress({
@@ -1074,6 +1085,7 @@ export default function TunnelPage() {
tunnelType: tunnel.type,
description: "网络错误",
message: "无法连接到服务器",
...diagnosisTarget,
}),
);
setDiagnosisProgress({
@@ -2335,54 +2347,67 @@ export default function TunnelPage() {
</Select>
)}
<div className="rounded-xl border border-divider/60 bg-default-50/40 p-3 space-y-3">
<div>
<div className="text-sm font-medium">质量检测目标</div>
<p className="text-xs text-default-500 mt-0.5">
用于实时隧道质量检测和 best 最优出口评分,留空使用
www.bing.com:443
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-[1fr_140px] gap-3">
<Input
errorMessage={errors.probeTargetHost}
isInvalid={!!errors.probeTargetHost}
label="Host"
placeholder="www.bing.com"
value={form.probeTargetHost || ""}
variant="bordered"
onChange={(e) =>
setForm((prev) => ({
...prev,
probeTargetHost: e.target.value,
}))
}
/>
<Input
errorMessage={errors.probeTargetPort}
isInvalid={!!errors.probeTargetPort}
label="Port"
max={65535}
min={1}
placeholder="443"
type="number"
value={
form.probeTargetPort
? String(form.probeTargetPort)
: ""
}
variant="bordered"
onChange={(e) =>
setForm((prev) => ({
...prev,
probeTargetPort: e.target.value
? Number(e.target.value)
: 0,
}))
}
/>
</div>
</div>
<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 className="text-sm font-medium">质量检测目标</div>
<p className="text-xs text-default-500 mt-0.5">
用于实时隧道质量检测、诊断目标和 best
最优出口评分,留空使用 www.bing.com:443
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-[1fr_140px] gap-3">
<Input
errorMessage={errors.probeTargetHost}
isInvalid={!!errors.probeTargetHost}
label="Host"
placeholder="www.bing.com"
value={form.probeTargetHost || ""}
variant="bordered"
onChange={(e) =>
setForm((prev) => ({
...prev,
probeTargetHost: e.target.value,
}))
}
/>
<Input
errorMessage={errors.probeTargetPort}
isInvalid={!!errors.probeTargetPort}
label="Port"
max={65535}
min={1}
placeholder="443"
type="number"
value={
form.probeTargetPort
? String(form.probeTargetPort)
: ""
}
variant="bordered"
onChange={(e) =>
setForm((prev) => ({
...prev,
probeTargetPort: e.target.value
? Number(e.target.value)
: 0,
}))
}
/>
</div>
</div>
</AccordionItem>
</Accordion>
<Divider />
<h3 className="text-lg font-semibold">入口配置</h3>
+6 -2
View File
@@ -27,6 +27,8 @@ export interface DiagnosisFallbackInput {
tunnelType: number;
description: string;
message: string;
targetIp?: string;
targetPort?: number;
}
export const buildDiagnosisFallbackResult = ({
@@ -34,6 +36,8 @@ export const buildDiagnosisFallbackResult = ({
tunnelType,
description,
message,
targetIp = "-",
targetPort = 443,
}: DiagnosisFallbackInput): DiagnosisResult => {
return {
tunnelName,
@@ -45,8 +49,8 @@ export const buildDiagnosisFallbackResult = ({
description,
nodeName: "-",
nodeId: "-",
targetIp: "-",
targetPort: 443,
targetIp,
targetPort,
message,
},
],