fix: improve probe target form feedback

This commit is contained in:
sagitchu
2026-05-01 23:46:56 +08:00
parent a40150b136
commit 727ef56c67
2 changed files with 118 additions and 12 deletions
@@ -1170,6 +1170,7 @@ export function TunnelMonitorView({
{tunnels.map((tunnel) => {
const quality = qualityMap[tunnel.id];
const isEnabled = tunnel.status === 1;
const targetLabel = probeTargetLabel(quality);
return (
<Card
@@ -1223,13 +1224,16 @@ export function TunnelMonitorView({
/>
</div>
<div className="space-y-1">
<div
className="text-[10px] text-default-500 flex items-center gap-1"
title={probeTargetLabel(quality)}
>
<div className="text-[10px] text-default-500 flex items-center gap-1">
<Globe className="w-3 h-3" />
出口→测试目标
</div>
<div
aria-label={`测试目标 ${targetLabel}`}
className="text-[10px] text-default-400 truncate"
>
{targetLabel}
</div>
<UptimeHistoryBar
history={qualityHistoryMap[tunnel.id]}
latestValue={quality?.exitToBingLatency}
@@ -1291,6 +1295,7 @@ export function TunnelMonitorView({
{tunnels.map((tunnel) => {
const quality = qualityMap[tunnel.id];
const isEnabled = tunnel.status === 1;
const targetLabel = probeTargetLabel(quality);
return (
<TableRow
@@ -1319,12 +1324,20 @@ export function TunnelMonitorView({
type="entryToExit"
/>
</TableCell>
<TableCell title={probeTargetLabel(quality)}>
<UptimeHistoryBar
history={qualityHistoryMap[tunnel.id]}
latestValue={quality?.exitToBingLatency}
type="exitToBing"
/>
<TableCell>
<div
aria-label={`出口到测试目标 ${targetLabel}`}
className="space-y-1"
>
<UptimeHistoryBar
history={qualityHistoryMap[tunnel.id]}
latestValue={quality?.exitToBingLatency}
type="exitToBing"
/>
<span className="block max-w-[160px] truncate text-[10px] text-default-400">
{targetLabel}
</span>
</div>
</TableCell>
<TableCell>
{quality?.timestamp ? (
+95 -2
View File
@@ -17,6 +17,93 @@ interface TunnelNodeInput {
status: number;
}
const isValidProbeIPv4 = (host: string) => {
const parts = host.split(".");
return (
parts.length === 4 &&
parts.every((part) => {
if (!/^\d+$/.test(part)) {
return false;
}
const value = Number(part);
return value >= 0 && value <= 255;
})
);
};
const isIPv4LikeProbeHost = (host: string) =>
/^[0-9.]+$/.test(host) && host.includes(".");
const isValidProbeIPv6 = (host: string) => {
let value = host;
if (host.startsWith("[") || host.endsWith("]")) {
if (!host.startsWith("[") || !host.endsWith("]")) {
return false;
}
value = host.slice(1, -1);
}
if (!value.includes(":") || value.includes("[") || value.includes("]")) {
return false;
}
try {
const url = new URL(`http://[${value}]`);
return url.hostname.length > 0;
} catch {
return false;
}
};
const isSchemeLikeProbeHost = (host: string) => {
if (isValidProbeIPv6(host)) {
return false;
}
const colonIndex = host.indexOf(":");
if (colonIndex <= 0) {
return false;
}
return /^[A-Za-z][A-Za-z0-9+.-]*$/.test(host.slice(0, colonIndex));
};
const isValidProbeDomain = (host: string) => {
if (!host || host.length > 253) {
return false;
}
return host.split(".").every((label) => {
if (
!label ||
label.length > 63 ||
label.startsWith("-") ||
label.endsWith("-")
) {
return false;
}
return /^[A-Za-z0-9-]+$/.test(label);
});
};
const isValidProbeTargetHost = (host: string) => {
if (isValidProbeIPv6(host) || isValidProbeIPv4(host)) {
return true;
}
if (host.includes(":") || isIPv4LikeProbeHost(host)) {
return false;
}
return isValidProbeDomain(host);
};
export const createTunnelFormDefaults = () => {
return {
name: "",
@@ -75,8 +162,14 @@ export const validateTunnelForm = (
if (probeHost || hasProbePort) {
if (!probeHost) {
errors.probeTargetHost = "请输入测试目标 Host";
} else if (probeHost.includes("://") || /[\s/?#]/.test(probeHost)) {
errors.probeTargetHost = "Host 不能包含协议、空格或路径";
} else if (
probeHost.includes("://") ||
/[\s/?#]/.test(probeHost) ||
isSchemeLikeProbeHost(probeHost)
) {
errors.probeTargetHost = "Host 不能包含协议、端口、空格或路径";
} else if (!isValidProbeTargetHost(probeHost)) {
errors.probeTargetHost = "测试目标 Host 格式无效";
}
if (!Number.isInteger(probePort) || probePort < 1 || probePort > 65535) {