mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-05 17:46:37 +08:00
fix: improve probe target form feedback
This commit is contained in:
@@ -1170,6 +1170,7 @@ export function TunnelMonitorView({
|
|||||||
{tunnels.map((tunnel) => {
|
{tunnels.map((tunnel) => {
|
||||||
const quality = qualityMap[tunnel.id];
|
const quality = qualityMap[tunnel.id];
|
||||||
const isEnabled = tunnel.status === 1;
|
const isEnabled = tunnel.status === 1;
|
||||||
|
const targetLabel = probeTargetLabel(quality);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
@@ -1223,13 +1224,16 @@ export function TunnelMonitorView({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div
|
<div className="text-[10px] text-default-500 flex items-center gap-1">
|
||||||
className="text-[10px] text-default-500 flex items-center gap-1"
|
|
||||||
title={probeTargetLabel(quality)}
|
|
||||||
>
|
|
||||||
<Globe className="w-3 h-3" />
|
<Globe className="w-3 h-3" />
|
||||||
出口→测试目标
|
出口→测试目标
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
aria-label={`测试目标 ${targetLabel}`}
|
||||||
|
className="text-[10px] text-default-400 truncate"
|
||||||
|
>
|
||||||
|
{targetLabel}
|
||||||
|
</div>
|
||||||
<UptimeHistoryBar
|
<UptimeHistoryBar
|
||||||
history={qualityHistoryMap[tunnel.id]}
|
history={qualityHistoryMap[tunnel.id]}
|
||||||
latestValue={quality?.exitToBingLatency}
|
latestValue={quality?.exitToBingLatency}
|
||||||
@@ -1291,6 +1295,7 @@ export function TunnelMonitorView({
|
|||||||
{tunnels.map((tunnel) => {
|
{tunnels.map((tunnel) => {
|
||||||
const quality = qualityMap[tunnel.id];
|
const quality = qualityMap[tunnel.id];
|
||||||
const isEnabled = tunnel.status === 1;
|
const isEnabled = tunnel.status === 1;
|
||||||
|
const targetLabel = probeTargetLabel(quality);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
@@ -1319,12 +1324,20 @@ export function TunnelMonitorView({
|
|||||||
type="entryToExit"
|
type="entryToExit"
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell title={probeTargetLabel(quality)}>
|
<TableCell>
|
||||||
<UptimeHistoryBar
|
<div
|
||||||
history={qualityHistoryMap[tunnel.id]}
|
aria-label={`出口到测试目标 ${targetLabel}`}
|
||||||
latestValue={quality?.exitToBingLatency}
|
className="space-y-1"
|
||||||
type="exitToBing"
|
>
|
||||||
/>
|
<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>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{quality?.timestamp ? (
|
{quality?.timestamp ? (
|
||||||
|
|||||||
@@ -17,6 +17,93 @@ interface TunnelNodeInput {
|
|||||||
status: number;
|
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 = () => {
|
export const createTunnelFormDefaults = () => {
|
||||||
return {
|
return {
|
||||||
name: "",
|
name: "",
|
||||||
@@ -75,8 +162,14 @@ export const validateTunnelForm = (
|
|||||||
if (probeHost || hasProbePort) {
|
if (probeHost || hasProbePort) {
|
||||||
if (!probeHost) {
|
if (!probeHost) {
|
||||||
errors.probeTargetHost = "请输入测试目标 Host";
|
errors.probeTargetHost = "请输入测试目标 Host";
|
||||||
} else if (probeHost.includes("://") || /[\s/?#]/.test(probeHost)) {
|
} else if (
|
||||||
errors.probeTargetHost = "Host 不能包含协议、空格或路径";
|
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) {
|
if (!Number.isInteger(probePort) || probePort < 1 || probePort > 65535) {
|
||||||
|
|||||||
Reference in New Issue
Block a user