refactor: update network tool components for improved user experience
- Translated UI text in DNS, Ping, TCPing, and Speed Test components from Chinese to English for better accessibility. - Enhanced user prompts and labels for clarity, including error messages and button texts. - Updated comments in the code to reflect the changes in language and improve code readability.
This commit is contained in:
@@ -18,16 +18,16 @@ interface DNSResponse {
|
||||
}
|
||||
|
||||
const DNS_RECORD_TYPES = [
|
||||
{ value: "1", label: "A", description: "IPv4 地址" },
|
||||
{ value: "28", label: "AAAA", description: "IPv6 地址" },
|
||||
{ value: "5", label: "CNAME", description: "规范名称" },
|
||||
{ value: "15", label: "MX", description: "邮件交换" },
|
||||
{ value: "2", label: "NS", description: "名称服务器" },
|
||||
{ value: "16", label: "TXT", description: "文本记录" },
|
||||
{ value: "6", label: "SOA", description: "授权起始" },
|
||||
{ value: "257", label: "CAA", description: "证书颁发机构授权" },
|
||||
{ value: "12", label: "PTR", description: "指针记录" },
|
||||
{ value: "33", label: "SRV", description: "服务记录" },
|
||||
{ value: "1", label: "A", description: "IPv4 Address" },
|
||||
{ value: "28", label: "AAAA", description: "IPv6 Address" },
|
||||
{ value: "5", label: "CNAME", description: "Canonical Name" },
|
||||
{ value: "15", label: "MX", description: "Mail Exchange" },
|
||||
{ value: "2", label: "NS", description: "Name Server" },
|
||||
{ value: "16", label: "TXT", description: "Text Record" },
|
||||
{ value: "6", label: "SOA", description: "Start of Authority" },
|
||||
{ value: "257", label: "CAA", description: "Certification Authority Authorization" },
|
||||
{ value: "12", label: "PTR", description: "Pointer Record" },
|
||||
{ value: "33", label: "SRV", description: "Service Record" },
|
||||
];
|
||||
|
||||
const getRecordTypeName = (type: number): string => {
|
||||
@@ -43,7 +43,7 @@ const Tool: FC = () => {
|
||||
|
||||
const queryDNS = async () => {
|
||||
if (!domain.trim()) {
|
||||
toast.error("请输入域名");
|
||||
toast.error("Please enter a domain name");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ const Tool: FC = () => {
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
// 并发查询所有记录类型
|
||||
// Query all record types concurrently
|
||||
const queries = DNS_RECORD_TYPES.map((recordType) =>
|
||||
fetch(
|
||||
`https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(
|
||||
@@ -80,11 +80,11 @@ const Tool: FC = () => {
|
||||
const endTime = performance.now();
|
||||
setQueryTime(endTime - startTime);
|
||||
|
||||
// 合并所有结果并去重
|
||||
// Merge and deduplicate results
|
||||
const combinedResults = allResults.flat();
|
||||
|
||||
if (combinedResults.length > 0) {
|
||||
// 按记录类型分组并去重
|
||||
// Group by record type and deduplicate
|
||||
const uniqueResults = Array.from(
|
||||
new Map(
|
||||
combinedResults.map((record) => [
|
||||
@@ -95,16 +95,16 @@ const Tool: FC = () => {
|
||||
);
|
||||
|
||||
setResults(uniqueResults);
|
||||
toast.success(`查询成功,找到 ${uniqueResults.length} 条记录`);
|
||||
toast.success(`Query successful, found ${uniqueResults.length} record(s)`);
|
||||
} else {
|
||||
setResults([]);
|
||||
toast.info("未找到记录");
|
||||
toast.info("No records found");
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
toast.error(`查询失败: ${error.message}`);
|
||||
toast.error(`Query failed: ${error.message}`);
|
||||
} else {
|
||||
toast.error("查询失败");
|
||||
toast.error("Query failed");
|
||||
}
|
||||
setResults([]);
|
||||
} finally {
|
||||
@@ -122,34 +122,34 @@ const Tool: FC = () => {
|
||||
<div className="flex flex-col gap-4 h-full">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium">域名</label>
|
||||
<label className="text-sm font-medium">Domain Name</label>
|
||||
<Input
|
||||
placeholder="例如: example.com"
|
||||
placeholder="e.g. example.com"
|
||||
value={domain}
|
||||
onChange={(e) => setDomain(e.target.value)}
|
||||
onKeyPress={handleKeyPress}
|
||||
disabled={loading}
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
将自动查询所有类型的 DNS 记录
|
||||
Will automatically query all DNS record types
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Button onClick={queryDNS} disabled={loading} className="w-full">
|
||||
{loading && <Loader2 className="mr-2 size-4 animate-spin" />}
|
||||
{loading ? "查询中..." : "查询所有记录"}
|
||||
{loading ? "Querying..." : "Query All Records"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{queryTime > 0 && (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
查询耗时: {queryTime.toFixed(2)} ms
|
||||
Query time: {queryTime.toFixed(2)} ms
|
||||
</div>
|
||||
)}
|
||||
|
||||
{results.length > 0 && (
|
||||
<div className="flex flex-col gap-3 flex-1 overflow-auto">
|
||||
<div className="text-sm font-medium">查询结果:</div>
|
||||
<div className="text-sm font-medium">Query Results:</div>
|
||||
<div className="space-y-2">
|
||||
{results.map((record, index) => (
|
||||
<div
|
||||
@@ -157,13 +157,13 @@ const Tool: FC = () => {
|
||||
className="border rounded-md p-3 bg-card text-card-foreground"
|
||||
>
|
||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||
<div className="text-muted-foreground">名称:</div>
|
||||
<div className="text-muted-foreground">Name:</div>
|
||||
<div className="font-mono break-all">{record.name}</div>
|
||||
<div className="text-muted-foreground">类型:</div>
|
||||
<div className="text-muted-foreground">Type:</div>
|
||||
<div>{getRecordTypeName(record.type)}</div>
|
||||
<div className="text-muted-foreground">TTL:</div>
|
||||
<div>{record.TTL} 秒</div>
|
||||
<div className="text-muted-foreground">数据:</div>
|
||||
<div>{record.TTL} seconds</div>
|
||||
<div className="text-muted-foreground">Data:</div>
|
||||
<div className="font-mono break-all">{record.data}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -38,14 +38,14 @@ const Tool: FC = () => {
|
||||
|
||||
const ping = async () => {
|
||||
if (!url.trim()) {
|
||||
toast.error("请输入 URL");
|
||||
toast.error("Please enter a URL");
|
||||
return;
|
||||
}
|
||||
|
||||
const seq = ++seqRef.current;
|
||||
let targetUrl = url.trim();
|
||||
|
||||
// 如果没有协议前缀,默认使用 https://
|
||||
// If no protocol prefix, default to https://
|
||||
if (!targetUrl.startsWith("http://") && !targetUrl.startsWith("https://")) {
|
||||
targetUrl = `https://${targetUrl}`;
|
||||
}
|
||||
@@ -75,7 +75,7 @@ const Tool: FC = () => {
|
||||
const time = endTime - startTime;
|
||||
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "请求失败";
|
||||
error instanceof Error ? error.message : "Request failed";
|
||||
|
||||
const newResult: PingResult = {
|
||||
seq,
|
||||
@@ -117,7 +117,7 @@ const Tool: FC = () => {
|
||||
|
||||
const startPing = () => {
|
||||
if (!url.trim()) {
|
||||
toast.error("请输入 URL");
|
||||
toast.error("Please enter a URL");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -133,10 +133,10 @@ const Tool: FC = () => {
|
||||
});
|
||||
seqRef.current = 0;
|
||||
|
||||
// 立即执行第一次 ping
|
||||
// Execute first ping immediately
|
||||
ping();
|
||||
|
||||
// 然后每秒执行一次
|
||||
// Then execute every second
|
||||
intervalRef.current = window.setInterval(ping, 1000);
|
||||
};
|
||||
|
||||
@@ -149,7 +149,7 @@ const Tool: FC = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// 自动滚动到底部
|
||||
// Auto-scroll to bottom
|
||||
if (resultsContainerRef.current) {
|
||||
resultsContainerRef.current.scrollTop =
|
||||
resultsContainerRef.current.scrollHeight;
|
||||
@@ -157,7 +157,7 @@ const Tool: FC = () => {
|
||||
}, [results]);
|
||||
|
||||
useEffect(() => {
|
||||
// 清理定时器
|
||||
// Cleanup timer
|
||||
return () => {
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
@@ -172,9 +172,9 @@ const Tool: FC = () => {
|
||||
<div className="flex flex-col gap-4 h-full">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium">目标 URL 或 IP</label>
|
||||
<label className="text-sm font-medium">Target URL or IP</label>
|
||||
<Input
|
||||
placeholder="例如: example.com 或 https://example.com"
|
||||
placeholder="e.g. example.com or https://example.com"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
disabled={running}
|
||||
@@ -184,11 +184,11 @@ const Tool: FC = () => {
|
||||
<div className="flex gap-2">
|
||||
{!running ? (
|
||||
<Button onClick={startPing} className="flex-1">
|
||||
开始 Ping
|
||||
Start Ping
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={stopPing} variant="destructive" className="flex-1">
|
||||
停止
|
||||
Stop
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -196,23 +196,23 @@ const Tool: FC = () => {
|
||||
|
||||
{stats.sent > 0 && (
|
||||
<div className="border rounded-md p-3 bg-card text-card-foreground">
|
||||
<div className="text-sm font-medium mb-2">统计信息</div>
|
||||
<div className="text-sm font-medium mb-2">Statistics</div>
|
||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||
<div className="text-muted-foreground">已发送:</div>
|
||||
<div>{stats.sent} 包</div>
|
||||
<div className="text-muted-foreground">已接收:</div>
|
||||
<div>{stats.received} 包</div>
|
||||
<div className="text-muted-foreground">丢失:</div>
|
||||
<div className="text-muted-foreground">Sent:</div>
|
||||
<div>{stats.sent} packets</div>
|
||||
<div className="text-muted-foreground">Received:</div>
|
||||
<div>{stats.received} packets</div>
|
||||
<div className="text-muted-foreground">Lost:</div>
|
||||
<div>
|
||||
{stats.lost} 包 ({lossRate}%)
|
||||
{stats.lost} packets ({lossRate}%)
|
||||
</div>
|
||||
{stats.received > 0 && (
|
||||
<>
|
||||
<div className="text-muted-foreground">最小延迟:</div>
|
||||
<div className="text-muted-foreground">Min Latency:</div>
|
||||
<div>{stats.min.toFixed(2)} ms</div>
|
||||
<div className="text-muted-foreground">最大延迟:</div>
|
||||
<div className="text-muted-foreground">Max Latency:</div>
|
||||
<div>{stats.max.toFixed(2)} ms</div>
|
||||
<div className="text-muted-foreground">平均延迟:</div>
|
||||
<div className="text-muted-foreground">Avg Latency:</div>
|
||||
<div>{stats.avg.toFixed(2)} ms</div>
|
||||
</>
|
||||
)}
|
||||
@@ -222,7 +222,7 @@ const Tool: FC = () => {
|
||||
|
||||
{results.length > 0 && (
|
||||
<div className="flex flex-col gap-2 flex-1 overflow-hidden">
|
||||
<div className="text-sm font-medium">Ping 结果:</div>
|
||||
<div className="text-sm font-medium">Ping Results:</div>
|
||||
<div
|
||||
ref={resultsContainerRef}
|
||||
className="flex-1 overflow-auto space-y-1 font-mono text-sm border rounded-md p-3 bg-card"
|
||||
@@ -238,7 +238,7 @@ const Tool: FC = () => {
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
seq={result.seq} 请求超时
|
||||
seq={result.seq} Request timeout
|
||||
{result.error && ` (${result.error})`}
|
||||
</>
|
||||
)}
|
||||
@@ -247,7 +247,7 @@ const Tool: FC = () => {
|
||||
{running && (
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<Loader2 className="size-3 animate-spin" />
|
||||
运行中...
|
||||
Running...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -68,7 +68,7 @@ const Tool: FC = () => {
|
||||
|
||||
return { performance: metrics };
|
||||
} else {
|
||||
// 如果没有详细的性能数据,只返回总时间
|
||||
// If no detailed performance data, only return total time
|
||||
return {
|
||||
performance: {
|
||||
dns: 0,
|
||||
@@ -144,13 +144,13 @@ const Tool: FC = () => {
|
||||
|
||||
const startTest = async () => {
|
||||
if (!url.trim()) {
|
||||
toast.error("请输入 URL");
|
||||
toast.error("Please enter a URL");
|
||||
return;
|
||||
}
|
||||
|
||||
let targetUrl = url.trim();
|
||||
|
||||
// 如果没有协议前缀,默认使用 https://
|
||||
// If no protocol prefix, default to https://
|
||||
if (!targetUrl.startsWith("http://") && !targetUrl.startsWith("https://")) {
|
||||
targetUrl = `https://${targetUrl}`;
|
||||
}
|
||||
@@ -164,24 +164,24 @@ const Tool: FC = () => {
|
||||
switch (testType) {
|
||||
case "performance":
|
||||
testResult = await testPerformance(targetUrl);
|
||||
toast.success("性能测试完成");
|
||||
toast.success("Performance test completed");
|
||||
break;
|
||||
case "download":
|
||||
testResult = await testDownloadSpeed(targetUrl);
|
||||
toast.success("下载速度测试完成");
|
||||
toast.success("Download speed test completed");
|
||||
break;
|
||||
case "upload":
|
||||
testResult = await testUploadSpeed(targetUrl);
|
||||
toast.success("上传速度测试完成");
|
||||
toast.success("Upload speed test completed");
|
||||
break;
|
||||
}
|
||||
|
||||
setResult(testResult);
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
toast.error(`测试失败: ${error.message}`);
|
||||
toast.error(`Test failed: ${error.message}`);
|
||||
} else {
|
||||
toast.error("测试失败");
|
||||
toast.error("Test failed");
|
||||
}
|
||||
} finally {
|
||||
setTesting(false);
|
||||
@@ -198,24 +198,24 @@ const Tool: FC = () => {
|
||||
<div className="flex flex-col gap-4 h-full">
|
||||
<div className="border rounded-md p-3 bg-yellow-500/10 border-yellow-500/50">
|
||||
<div className="text-sm font-medium text-yellow-600 dark:text-yellow-400 mb-1">
|
||||
⚠️ CORS 限制说明
|
||||
⚠️ CORS Restrictions
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground space-y-1">
|
||||
<p>由于浏览器的 CORS 安全策略,部分网站可能无法直接测试。</p>
|
||||
<p>建议测试以下类型的网站:</p>
|
||||
<p>Due to browser CORS security policies, some websites cannot be tested directly.</p>
|
||||
<p>Recommended websites to test:</p>
|
||||
<ul className="list-disc list-inside ml-2">
|
||||
<li>支持 CORS 的公共 API</li>
|
||||
<li>您自己控制的网站(可配置 CORS 头)</li>
|
||||
<li>使用 CORS 代理服务</li>
|
||||
<li>Public APIs with CORS support</li>
|
||||
<li>Your own websites (with configured CORS headers)</li>
|
||||
<li>Using CORS proxy services</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium">目标 URL</label>
|
||||
<label className="text-sm font-medium">Target URL</label>
|
||||
<Input
|
||||
placeholder="例如: https://example.com"
|
||||
placeholder="e.g. https://example.com"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
onKeyPress={handleKeyPress}
|
||||
@@ -224,7 +224,7 @@ const Tool: FC = () => {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium">测试类型</label>
|
||||
<label className="text-sm font-medium">Test Type</label>
|
||||
<select
|
||||
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
value={testType}
|
||||
@@ -233,58 +233,58 @@ const Tool: FC = () => {
|
||||
}
|
||||
disabled={testing}
|
||||
>
|
||||
<option value="performance">页面加载性能</option>
|
||||
<option value="download">下载速度</option>
|
||||
<option value="upload">上传速度</option>
|
||||
<option value="performance">Page Load Performance</option>
|
||||
<option value="download">Download Speed</option>
|
||||
<option value="upload">Upload Speed</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<Button onClick={startTest} disabled={testing} className="w-full">
|
||||
{testing && <Loader2 className="mr-2 size-4 animate-spin" />}
|
||||
{testing ? "测试中..." : "开始测试"}
|
||||
{testing ? "Testing..." : "Start Test"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{result && (
|
||||
<div className="flex flex-col gap-3 flex-1 overflow-auto">
|
||||
<div className="text-sm font-medium">测试结果:</div>
|
||||
<div className="text-sm font-medium">Test Results:</div>
|
||||
|
||||
{result.performance && (
|
||||
<div className="border rounded-md p-3 bg-card text-card-foreground">
|
||||
<div className="text-sm font-medium mb-3">页面加载性能</div>
|
||||
<div className="text-sm font-medium mb-3">Page Load Performance</div>
|
||||
<div className="space-y-2">
|
||||
{result.performance.dns > 0 && (
|
||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||
<div className="text-muted-foreground">DNS 查询:</div>
|
||||
<div className="text-muted-foreground">DNS Lookup:</div>
|
||||
<div>{result.performance.dns.toFixed(2)} ms</div>
|
||||
</div>
|
||||
)}
|
||||
{result.performance.tcp > 0 && (
|
||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||
<div className="text-muted-foreground">TCP 连接:</div>
|
||||
<div className="text-muted-foreground">TCP Connection:</div>
|
||||
<div>{result.performance.tcp.toFixed(2)} ms</div>
|
||||
</div>
|
||||
)}
|
||||
{result.performance.ssl > 0 && (
|
||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||
<div className="text-muted-foreground">SSL 握手:</div>
|
||||
<div className="text-muted-foreground">SSL Handshake:</div>
|
||||
<div>{result.performance.ssl.toFixed(2)} ms</div>
|
||||
</div>
|
||||
)}
|
||||
{result.performance.ttfb > 0 && (
|
||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||
<div className="text-muted-foreground">首字节时间 (TTFB):</div>
|
||||
<div className="text-muted-foreground">Time to First Byte (TTFB):</div>
|
||||
<div>{result.performance.ttfb.toFixed(2)} ms</div>
|
||||
</div>
|
||||
)}
|
||||
{result.performance.download > 0 && (
|
||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||
<div className="text-muted-foreground">内容下载:</div>
|
||||
<div className="text-muted-foreground">Content Download:</div>
|
||||
<div>{result.performance.download.toFixed(2)} ms</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="grid grid-cols-2 gap-2 text-sm border-t pt-2 mt-2">
|
||||
<div className="text-muted-foreground font-medium">总时间:</div>
|
||||
<div className="text-muted-foreground font-medium">Total Time:</div>
|
||||
<div className="font-medium">
|
||||
{result.performance.total.toFixed(2)} ms
|
||||
</div>
|
||||
@@ -295,7 +295,7 @@ const Tool: FC = () => {
|
||||
|
||||
{result.downloadSpeed !== undefined && (
|
||||
<div className="border rounded-md p-3 bg-card text-card-foreground">
|
||||
<div className="text-sm font-medium mb-3">下载速度</div>
|
||||
<div className="text-sm font-medium mb-3">Download Speed</div>
|
||||
<div className="text-2xl font-bold text-green-500">
|
||||
{result.downloadSpeed.toFixed(2)} Mbps
|
||||
</div>
|
||||
@@ -307,7 +307,7 @@ const Tool: FC = () => {
|
||||
|
||||
{result.uploadSpeed !== undefined && (
|
||||
<div className="border rounded-md p-3 bg-card text-card-foreground">
|
||||
<div className="text-sm font-medium mb-3">上传速度</div>
|
||||
<div className="text-sm font-medium mb-3">Upload Speed</div>
|
||||
<div className="text-2xl font-bold text-blue-500">
|
||||
{result.uploadSpeed.toFixed(2)} Mbps
|
||||
</div>
|
||||
@@ -321,13 +321,13 @@ const Tool: FC = () => {
|
||||
|
||||
{testType === "download" && (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
提示: 下载速度测试会下载目标 URL 的内容并计算速度
|
||||
Note: Download speed test will download content from the target URL and calculate speed
|
||||
</div>
|
||||
)}
|
||||
|
||||
{testType === "upload" && (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
提示: 上传速度测试会向目标 URL 发送 1MB 测试数据
|
||||
Note: Upload speed test will send 1MB of test data to the target URL
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -39,7 +39,7 @@ const Tool: FC = () => {
|
||||
|
||||
const tcping = async () => {
|
||||
if (!host.trim()) {
|
||||
toast.error("请输入主机名或 IP");
|
||||
toast.error("Please enter a hostname or IP");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ const Tool: FC = () => {
|
||||
const time = endTime - startTime;
|
||||
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "连接失败";
|
||||
error instanceof Error ? error.message : "Connection failed";
|
||||
|
||||
const newResult: TCPingResult = {
|
||||
seq,
|
||||
@@ -122,7 +122,7 @@ const Tool: FC = () => {
|
||||
|
||||
const startTCPing = () => {
|
||||
if (!host.trim()) {
|
||||
toast.error("请输入主机名或 IP");
|
||||
toast.error("Please enter a hostname or IP");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -177,9 +177,9 @@ const Tool: FC = () => {
|
||||
<div className="flex flex-col gap-4 h-full">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium">主机名或 IP</label>
|
||||
<label className="text-sm font-medium">Hostname or IP</label>
|
||||
<Input
|
||||
placeholder="例如: example.com 或 192.168.1.1"
|
||||
placeholder="e.g. example.com or 192.168.1.1"
|
||||
value={host}
|
||||
onChange={(e) => setHost(e.target.value)}
|
||||
disabled={running}
|
||||
@@ -187,10 +187,10 @@ const Tool: FC = () => {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium">端口</label>
|
||||
<label className="text-sm font-medium">Port</label>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="例如: 443"
|
||||
placeholder="e.g. 443"
|
||||
value={port}
|
||||
onChange={(e) => setPort(e.target.value)}
|
||||
disabled={running}
|
||||
@@ -202,7 +202,7 @@ const Tool: FC = () => {
|
||||
<div className="flex gap-2">
|
||||
{!running ? (
|
||||
<Button onClick={startTCPing} className="flex-1">
|
||||
开始测试
|
||||
Start Test
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
@@ -210,7 +210,7 @@ const Tool: FC = () => {
|
||||
variant="destructive"
|
||||
className="flex-1"
|
||||
>
|
||||
停止
|
||||
Stop
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -218,26 +218,26 @@ const Tool: FC = () => {
|
||||
|
||||
{stats.sent > 0 && (
|
||||
<div className="border rounded-md p-3 bg-card text-card-foreground">
|
||||
<div className="text-sm font-medium mb-2">统计信息</div>
|
||||
<div className="text-sm font-medium mb-2">Statistics</div>
|
||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||
<div className="text-muted-foreground">已发送:</div>
|
||||
<div>{stats.sent} 次</div>
|
||||
<div className="text-muted-foreground">成功:</div>
|
||||
<div>{stats.received} 次</div>
|
||||
<div className="text-muted-foreground">失败:</div>
|
||||
<div className="text-muted-foreground">Sent:</div>
|
||||
<div>{stats.sent} times</div>
|
||||
<div className="text-muted-foreground">Success:</div>
|
||||
<div>{stats.received} times</div>
|
||||
<div className="text-muted-foreground">Failed:</div>
|
||||
<div>
|
||||
{stats.lost} 次 ({lossRate}%)
|
||||
{stats.lost} times ({lossRate}%)
|
||||
</div>
|
||||
{stats.received > 0 && (
|
||||
<>
|
||||
<div className="text-muted-foreground">最小延迟:</div>
|
||||
<div className="text-muted-foreground">Min Latency:</div>
|
||||
<div>{stats.min.toFixed(2)} ms</div>
|
||||
<div className="text-muted-foreground">最大延迟:</div>
|
||||
<div className="text-muted-foreground">Max Latency:</div>
|
||||
<div>{stats.max.toFixed(2)} ms</div>
|
||||
<div className="text-muted-foreground">平均延迟:</div>
|
||||
<div className="text-muted-foreground">Avg Latency:</div>
|
||||
<div>{stats.avg.toFixed(2)} ms</div>
|
||||
<div className="text-muted-foreground">端口状态:</div>
|
||||
<div className="text-green-500 font-medium">开放</div>
|
||||
<div className="text-muted-foreground">Port Status:</div>
|
||||
<div className="text-green-500 font-medium">Open</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -246,7 +246,7 @@ const Tool: FC = () => {
|
||||
|
||||
{results.length > 0 && (
|
||||
<div className="flex flex-col gap-2 flex-1 overflow-hidden">
|
||||
<div className="text-sm font-medium">TCPing 结果:</div>
|
||||
<div className="text-sm font-medium">TCPing Results:</div>
|
||||
<div
|
||||
ref={resultsContainerRef}
|
||||
className="flex-1 overflow-auto space-y-1 font-mono text-sm border rounded-md p-3 bg-card"
|
||||
@@ -262,7 +262,7 @@ const Tool: FC = () => {
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
seq={result.seq} port={port} 连接失败
|
||||
seq={result.seq} port={port} Connection failed
|
||||
{result.error && ` (${result.error})`}
|
||||
</>
|
||||
)}
|
||||
@@ -271,7 +271,7 @@ const Tool: FC = () => {
|
||||
{running && (
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<Loader2 className="size-3 animate-spin" />
|
||||
运行中...
|
||||
Running...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user