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"))
+73 -48
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,54 +2347,67 @@ 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">
<div> <AccordionItem
<div className="text-sm font-medium">质量检测目标</div> key="advanced"
<p className="text-xs text-default-500 mt-0.5"> aria-label="高级设置"
用于实时隧道质量检测和 best 最优出口评分,留空使用 className="border-b-0 [&_[data-slot=accordion-trigger]]:no-underline [&_[data-slot=accordion-trigger]]:hover:no-underline"
www.bing.com:443 title={
</p> <span className="text-small text-default-500 font-medium">
</div> 高级设置
<div className="grid grid-cols-1 md:grid-cols-[1fr_140px] gap-3"> </span>
<Input }
errorMessage={errors.probeTargetHost} >
isInvalid={!!errors.probeTargetHost} <div className="space-y-4 pb-2">
label="Host" <div>
placeholder="www.bing.com" <div className="text-sm font-medium">质量检测目标</div>
value={form.probeTargetHost || ""} <p className="text-xs text-default-500 mt-0.5">
variant="bordered" 用于实时隧道质量检测、诊断目标和 best
onChange={(e) => 最优出口评分,留空使用 www.bing.com:443
setForm((prev) => ({ </p>
...prev, </div>
probeTargetHost: e.target.value, <div className="grid grid-cols-1 md:grid-cols-[1fr_140px] gap-3">
})) <Input
} errorMessage={errors.probeTargetHost}
/> isInvalid={!!errors.probeTargetHost}
<Input label="Host"
errorMessage={errors.probeTargetPort} placeholder="www.bing.com"
isInvalid={!!errors.probeTargetPort} value={form.probeTargetHost || ""}
label="Port" variant="bordered"
max={65535} onChange={(e) =>
min={1} setForm((prev) => ({
placeholder="443" ...prev,
type="number" probeTargetHost: e.target.value,
value={ }))
form.probeTargetPort }
? String(form.probeTargetPort) />
: "" <Input
} errorMessage={errors.probeTargetPort}
variant="bordered" isInvalid={!!errors.probeTargetPort}
onChange={(e) => label="Port"
setForm((prev) => ({ max={65535}
...prev, min={1}
probeTargetPort: e.target.value placeholder="443"
? Number(e.target.value) type="number"
: 0, value={
})) form.probeTargetPort
} ? String(form.probeTargetPort)
/> : ""
</div> }
</div> variant="bordered"
onChange={(e) =>
setForm((prev) => ({
...prev,
probeTargetPort: e.target.value
? Number(e.target.value)
: 0,
}))
}
/>
</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,
}, },
], ],