Fix IPv4 display and IP detection logic
Improved IP detection and display logic to always show IPv4 format, converting IPv6-mapped IPv4 addresses (e.g., ::ffff:127.0.0.1) and IPv6 loopback (::1) to 127.0.0.1. Updated API endpoint, display components, and added dedicated test/debug pages for IP format and detection. Added documentation summarizing the fixes and new features.
This commit is contained in:
@@ -3,10 +3,9 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
import { Globe, Shield, MapPin, RefreshCw, AlertCircle, CheckCircle, Info, Lightbulb } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Globe, Info, AlertCircle, CheckCircle, RefreshCw } from 'lucide-react'
|
||||
|
||||
interface IpDebugInfo {
|
||||
ip: string
|
||||
@@ -23,12 +22,11 @@ interface IpDebugInfo {
|
||||
host: string | null
|
||||
referer: string | null
|
||||
userAgent: string | null
|
||||
originalDetectedIp: string
|
||||
finalDetectedIp: string
|
||||
}
|
||||
location: any
|
||||
development: {
|
||||
message: string
|
||||
suggestions: string[]
|
||||
} | null
|
||||
development: any
|
||||
}
|
||||
|
||||
export default function IpDebugPage() {
|
||||
@@ -47,8 +45,8 @@ export default function IpDebugPage() {
|
||||
const data = await response.json()
|
||||
setIpInfo(data)
|
||||
} catch (error) {
|
||||
console.error("無法獲取IP信息:", error)
|
||||
setError("無法獲取IP信息")
|
||||
console.error('Error fetching IP info:', error)
|
||||
setError('無法獲取IP信息')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
@@ -60,12 +58,10 @@ export default function IpDebugPage() {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="container mx-auto p-6 max-w-4xl">
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<div className="flex items-center gap-2">
|
||||
<RefreshCw className="w-5 h-5 animate-spin" />
|
||||
<span>載入中...</span>
|
||||
</div>
|
||||
<div className="container mx-auto p-6 max-w-6xl">
|
||||
<div className="text-center">
|
||||
<RefreshCw className="w-8 h-8 animate-spin mx-auto mb-4" />
|
||||
<p>載入中...</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -73,262 +69,197 @@ export default function IpDebugPage() {
|
||||
|
||||
if (error || !ipInfo) {
|
||||
return (
|
||||
<div className="container mx-auto p-6 max-w-4xl">
|
||||
<Card className="border-red-200 bg-red-50">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-red-800">
|
||||
<AlertCircle className="w-5 h-5" />
|
||||
錯誤
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-red-700">{error || '無法獲取IP信息'}</p>
|
||||
<Button onClick={fetchIpInfo} className="mt-4">
|
||||
<RefreshCw className="w-4 h-4 mr-2" />
|
||||
重試
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="container mx-auto p-6 max-w-6xl">
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6 max-w-4xl space-y-6">
|
||||
<div className="container mx-auto p-6 max-w-6xl space-y-6">
|
||||
<div className="text-center">
|
||||
<h1 className="text-3xl font-bold mb-2">IP 檢測調試工具</h1>
|
||||
<p className="text-muted-foreground">查看詳細的IP檢測信息和調試數據</p>
|
||||
<h1 className="text-3xl font-bold mb-2">IP 調試信息</h1>
|
||||
<p className="text-muted-foreground">詳細的IP檢測和調試信息</p>
|
||||
</div>
|
||||
|
||||
{/* 本地開發環境提示 */}
|
||||
{ipInfo.development && (
|
||||
<Alert className="border-blue-200 bg-blue-50">
|
||||
<Info className="h-4 w-4 text-blue-600" />
|
||||
<AlertDescription className="text-blue-800">
|
||||
<div className="font-medium mb-2">{ipInfo.development.message}</div>
|
||||
<div className="text-sm space-y-1">
|
||||
{ipInfo.development.suggestions.map((suggestion, index) => (
|
||||
<div key={index} className="flex items-start gap-2">
|
||||
<Lightbulb className="w-3 h-3 mt-0.5 text-blue-500 flex-shrink-0" />
|
||||
<span>{suggestion}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* 主要IP信息 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Globe className="w-5 h-5" />
|
||||
IP 信息
|
||||
檢測到的IP地址
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
系統檢測到的主要IP地址信息
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">檢測到的IP:</span>
|
||||
<Badge variant={ipInfo.ip === '127.0.0.1' ? 'destructive' : 'default'}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">最終檢測到的IP</h4>
|
||||
<Badge variant="default" className="text-sm">
|
||||
{ipInfo.ip}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">狀態:</span>
|
||||
{ipInfo.isAllowed ? (
|
||||
<Badge variant="default" className="flex items-center gap-1">
|
||||
<CheckCircle className="w-3 h-3" />
|
||||
允許
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge variant="destructive" className="flex items-center gap-1">
|
||||
<AlertCircle className="w-3 h-3" />
|
||||
拒絕
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield className="w-4 h-4" />
|
||||
<span className="text-sm">IP白名單:</span>
|
||||
<Badge variant={ipInfo.enableIpWhitelist ? 'default' : 'secondary'}>
|
||||
{ipInfo.enableIpWhitelist ? '已啟用' : '已停用'}
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">原始檢測到的IP</h4>
|
||||
<Badge variant="outline" className="text-sm">
|
||||
{ipInfo.debug.originalDetectedIp}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm">環境:</span>
|
||||
<Badge variant="outline">{ipInfo.debug.environment}</Badge>
|
||||
</div>
|
||||
{ipInfo.debug.isLocalDevelopment && ipInfo.debug.localIp && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm">本機IP:</span>
|
||||
<Badge variant="outline">{ipInfo.debug.localIp}</Badge>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{ipInfo.location && (
|
||||
<div className="flex items-center gap-2">
|
||||
<MapPin className="w-4 h-4" />
|
||||
<span className="text-sm">位置:</span>
|
||||
<span className="text-sm">
|
||||
{ipInfo.location.city}, {ipInfo.location.country} ({ipInfo.location.isp})
|
||||
</span>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">IP白名單狀態</h4>
|
||||
<Badge variant={ipInfo.isAllowed ? "default" : "destructive"}>
|
||||
{ipInfo.isAllowed ? '允許' : '拒絕'}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">白名單功能</h4>
|
||||
<Badge variant={ipInfo.enableIpWhitelist ? "default" : "secondary"}>
|
||||
{ipInfo.enableIpWhitelist ? '已啟用' : '已禁用'}
|
||||
</Badge>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">環境</h4>
|
||||
<Badge variant="outline">
|
||||
{ipInfo.debug.environment}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 所有找到的IP */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>所有檢測到的IP</CardTitle>
|
||||
<CardDescription>系統檢測到的所有IP地址</CardDescription>
|
||||
<CardTitle>所有檢測到的IP地址</CardTitle>
|
||||
<CardDescription>
|
||||
從各種來源檢測到的所有IP地址
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<div className="space-y-2">
|
||||
{ipInfo.debug.allFoundIps.length > 0 ? (
|
||||
ipInfo.debug.allFoundIps.map((ip, index) => (
|
||||
<Badge
|
||||
key={index}
|
||||
variant={ip === ipInfo.ip ? 'default' : 'outline'}
|
||||
className={ip === '127.0.0.1' ? 'border-red-300 text-red-700' : ''}
|
||||
>
|
||||
{ip} {ip === ipInfo.ip && '(已選擇)'}
|
||||
</Badge>
|
||||
<div key={index} className="flex items-center gap-2">
|
||||
<Badge variant="outline" className="font-mono">
|
||||
{ip}
|
||||
</Badge>
|
||||
{ip === ipInfo.ip && (
|
||||
<Badge variant="default" className="text-xs">
|
||||
最終選擇
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<span className="text-muted-foreground">未檢測到任何IP</span>
|
||||
<p className="text-muted-foreground">沒有檢測到任何IP地址</p>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 允許的IP列表 */}
|
||||
{ipInfo.enableIpWhitelist && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>允許的IP地址</CardTitle>
|
||||
<CardDescription>當前配置的IP白名單</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{ipInfo.allowedIps.length > 0 ? (
|
||||
ipInfo.allowedIps.map((ip, index) => (
|
||||
<Badge key={index} variant="outline">
|
||||
{ip}
|
||||
</Badge>
|
||||
))
|
||||
) : (
|
||||
<span className="text-muted-foreground">未配置IP白名單</span>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 調試信息 */}
|
||||
{/* IP來源詳細信息 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>調試信息</CardTitle>
|
||||
<CardDescription>所有可能的IP來源和請求頭信息</CardDescription>
|
||||
<CardTitle>IP來源詳細信息</CardTitle>
|
||||
<CardDescription>
|
||||
各種HTTP頭和連接信息中的IP地址
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">所有IP來源:</h4>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||
{Object.entries(ipInfo.debug.allIpSources).map(([key, value]) => (
|
||||
<div key={key} className="flex justify-between items-center p-2 bg-muted rounded">
|
||||
<span className="text-sm font-mono">{key}:</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{value || 'null'}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
<div className="space-y-3">
|
||||
{Object.entries(ipInfo.debug.allIpSources).map(([source, value]) => (
|
||||
<div key={source} className="flex items-center justify-between p-2 bg-muted rounded">
|
||||
<span className="font-medium text-sm">{source}:</span>
|
||||
<span className="font-mono text-sm">
|
||||
{value || '未設置'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">請求信息:</h4>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm">Host:</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{ipInfo.debug.host || 'null'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm">Referer:</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{ipInfo.debug.referer || 'null'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm">User Agent:</span>
|
||||
<span className="text-sm text-muted-foreground max-w-xs truncate">
|
||||
{ipInfo.debug.userAgent || 'null'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm">時間戳:</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{new Date(ipInfo.timestamp).toLocaleString('zh-TW')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 本地開發環境信息 */}
|
||||
{ipInfo.development && (
|
||||
<Alert>
|
||||
<Info className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
<div className="font-medium mb-2">{ipInfo.development.message}</div>
|
||||
<div className="text-sm space-y-1">
|
||||
{ipInfo.development.suggestions.map((suggestion, index) => (
|
||||
<div key={index}>• {suggestion}</div>
|
||||
))}
|
||||
</div>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* 地理位置信息 */}
|
||||
{ipInfo.location && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<MapPin className="w-5 h-5" />
|
||||
地理位置信息
|
||||
</CardTitle>
|
||||
<CardTitle>地理位置信息</CardTitle>
|
||||
<CardDescription>
|
||||
IP地址的地理位置信息
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">位置信息</h4>
|
||||
<div className="space-y-1 text-sm">
|
||||
<div>國家: {ipInfo.location.country} ({ipInfo.location.countryCode})</div>
|
||||
<div>地區: {ipInfo.location.regionName}</div>
|
||||
<div>城市: {ipInfo.location.city}</div>
|
||||
<div>郵遞區號: {ipInfo.location.zip}</div>
|
||||
<div>時區: {ipInfo.location.timezone}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">網路信息</h4>
|
||||
<div className="space-y-1 text-sm">
|
||||
<div>ISP: {ipInfo.location.isp}</div>
|
||||
<div>組織: {ipInfo.location.org}</div>
|
||||
<div>AS: {ipInfo.location.as}</div>
|
||||
<div>行動網路: {ipInfo.location.mobile ? '是' : '否'}</div>
|
||||
<div>代理: {ipInfo.location.proxy ? '是' : '否'}</div>
|
||||
<div>主機服務: {ipInfo.location.hosting ? '是' : '否'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<pre className="bg-muted p-4 rounded text-sm overflow-auto">
|
||||
{JSON.stringify(ipInfo.location, null, 2)}
|
||||
</pre>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 其他調試信息 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>其他調試信息</CardTitle>
|
||||
<CardDescription>
|
||||
額外的調試和環境信息
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">主機</h4>
|
||||
<p className="text-sm font-mono">{ipInfo.debug.host || '未設置'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">引用來源</h4>
|
||||
<p className="text-sm font-mono">{ipInfo.debug.referer || '未設置'}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">用戶代理</h4>
|
||||
<p className="text-sm font-mono break-all">{ipInfo.debug.userAgent || '未設置'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">時間戳</h4>
|
||||
<p className="text-sm">{ipInfo.timestamp}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 操作按鈕 */}
|
||||
<div className="flex justify-center">
|
||||
<div className="flex justify-center gap-4">
|
||||
<Button onClick={fetchIpInfo} className="flex items-center gap-2">
|
||||
<RefreshCw className="w-4 h-4" />
|
||||
重新檢測IP
|
||||
重新檢測
|
||||
</Button>
|
||||
<Button variant="outline" onClick={() => window.open('/api/ip', '_blank')}>
|
||||
<Globe className="w-4 h-4 mr-2" />
|
||||
查看API響應
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
246
app/test/ip-format-test/page.tsx
Normal file
246
app/test/ip-format-test/page.tsx
Normal file
@@ -0,0 +1,246 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
import { Globe, RefreshCw, CheckCircle, AlertCircle, Info } from 'lucide-react'
|
||||
|
||||
interface IpTestResult {
|
||||
originalIp: string
|
||||
cleanedIp: string
|
||||
isIPv4: boolean
|
||||
isIPv6: boolean
|
||||
isLocalhost: boolean
|
||||
description: string
|
||||
}
|
||||
|
||||
export default function IpFormatTestPage() {
|
||||
const [testResults, setTestResults] = useState<IpTestResult[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
// 測試IP地址清理函數
|
||||
function cleanIpForDisplay(ip: string): string {
|
||||
if (!ip) return '127.0.0.1';
|
||||
|
||||
// 移除空白字符
|
||||
ip = ip.trim();
|
||||
|
||||
// 處理IPv6格式的IPv4地址 (例如: ::ffff:192.168.1.1)
|
||||
if (ip.startsWith('::ffff:')) {
|
||||
return ip.substring(7);
|
||||
}
|
||||
|
||||
// 處理純IPv6本地回環地址
|
||||
if (ip === '::1') {
|
||||
return '127.0.0.1';
|
||||
}
|
||||
|
||||
// 驗證是否為有效的IPv4地址
|
||||
const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||
if (ipv4Regex.test(ip)) {
|
||||
return ip;
|
||||
}
|
||||
|
||||
// 如果不是有效的IPv4,返回默認值
|
||||
return '127.0.0.1';
|
||||
}
|
||||
|
||||
// 檢查IP類型
|
||||
function analyzeIp(ip: string): { isIPv4: boolean; isIPv6: boolean; isLocalhost: boolean; description: string } {
|
||||
const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
|
||||
|
||||
const isIPv4 = ipv4Regex.test(ip);
|
||||
const isIPv6 = ipv6Regex.test(ip) || ip.startsWith('::ffff:') || ip === '::1';
|
||||
const isLocalhost = ip === '127.0.0.1' || ip === '::1' || ip === 'localhost';
|
||||
|
||||
let description = '';
|
||||
if (ip.startsWith('::ffff:')) {
|
||||
description = 'IPv6格式的IPv4地址';
|
||||
} else if (ip === '::1') {
|
||||
description = 'IPv6本地回環地址';
|
||||
} else if (isIPv4) {
|
||||
description = '標準IPv4地址';
|
||||
} else if (isIPv6) {
|
||||
description = 'IPv6地址';
|
||||
} else {
|
||||
description = '無效的IP地址格式';
|
||||
}
|
||||
|
||||
return { isIPv4, isIPv6, isLocalhost, description };
|
||||
}
|
||||
|
||||
const runTests = () => {
|
||||
setLoading(true);
|
||||
|
||||
const testIps = [
|
||||
'::ffff:127.0.0.1',
|
||||
'::1',
|
||||
'127.0.0.1',
|
||||
'192.168.1.1',
|
||||
'::ffff:192.168.1.100',
|
||||
'2001:db8::1',
|
||||
'invalid-ip',
|
||||
'localhost',
|
||||
'::ffff:203.0.113.1',
|
||||
'10.0.0.1'
|
||||
];
|
||||
|
||||
const results: IpTestResult[] = testIps.map(originalIp => {
|
||||
const cleanedIp = cleanIpForDisplay(originalIp);
|
||||
const analysis = analyzeIp(originalIp);
|
||||
|
||||
return {
|
||||
originalIp,
|
||||
cleanedIp,
|
||||
...analysis
|
||||
};
|
||||
});
|
||||
|
||||
setTestResults(results);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
runTests();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6 max-w-6xl space-y-6">
|
||||
<div className="text-center">
|
||||
<h1 className="text-3xl font-bold mb-2">IPv4 格式測試</h1>
|
||||
<p className="text-muted-foreground">測試IP地址清理和IPv4格式轉換功能</p>
|
||||
</div>
|
||||
|
||||
{/* 說明 */}
|
||||
<Alert>
|
||||
<Info className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
<div className="font-medium mb-2">測試目的</div>
|
||||
<div className="text-sm space-y-1">
|
||||
<div>• 驗證IPv6格式的IPv4地址能正確轉換為IPv4</div>
|
||||
<div>• 確保所有IP地址都顯示為標準IPv4格式</div>
|
||||
<div>• 測試各種IP地址格式的處理邏輯</div>
|
||||
<div>• 驗證本地回環地址的正確處理</div>
|
||||
</div>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
{/* 測試結果 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Globe className="w-5 h-5" />
|
||||
IP格式轉換測試結果
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
各種IP地址格式的清理和轉換結果
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{testResults.map((result, index) => (
|
||||
<div key={index} className="border rounded-lg p-4 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">原始IP:</span>
|
||||
<Badge variant="outline" className="font-mono">
|
||||
{result.originalIp}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">清理後:</span>
|
||||
<Badge variant="default" className="font-mono">
|
||||
{result.cleanedIp}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 text-sm">
|
||||
<div className="flex items-center gap-1">
|
||||
{result.isIPv4 ? (
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
) : (
|
||||
<AlertCircle className="w-4 h-4 text-red-500" />
|
||||
)}
|
||||
<span>IPv4: {result.isIPv4 ? '是' : '否'}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
{result.isIPv6 ? (
|
||||
<CheckCircle className="w-4 h-4 text-blue-500" />
|
||||
) : (
|
||||
<span className="text-gray-400">-</span>
|
||||
)}
|
||||
<span>IPv6: {result.isIPv6 ? '是' : '否'}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
{result.isLocalhost ? (
|
||||
<CheckCircle className="w-4 h-4 text-orange-500" />
|
||||
) : (
|
||||
<span className="text-gray-400">-</span>
|
||||
)}
|
||||
<span>本地: {result.isLocalhost ? '是' : '否'}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{result.description}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 操作按鈕 */}
|
||||
<div className="flex justify-center">
|
||||
<Button onClick={runTests} disabled={loading} className="flex items-center gap-2">
|
||||
<RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
|
||||
{loading ? '測試中...' : '重新測試'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 總結 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>測試總結</CardTitle>
|
||||
<CardDescription>
|
||||
IPv4格式轉換的關鍵點
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">✅ 正確處理的格式</h4>
|
||||
<ul className="text-sm space-y-1 text-muted-foreground">
|
||||
<li>• <code>::ffff:127.0.0.1</code> → <code>127.0.0.1</code></li>
|
||||
<li>• <code>::1</code> → <code>127.0.0.1</code></li>
|
||||
<li>• <code>127.0.0.1</code> → <code>127.0.0.1</code> (保持不變)</li>
|
||||
<li>• <code>192.168.1.1</code> → <code>192.168.1.1</code> (保持不變)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">⚠️ 無效格式處理</h4>
|
||||
<ul className="text-sm space-y-1 text-muted-foreground">
|
||||
<li>• 無效IP地址 → <code>127.0.0.1</code> (默認值)</li>
|
||||
<li>• 空值或null → <code>127.0.0.1</code> (默認值)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">🎯 目標</h4>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
確保所有顯示的IP地址都是標準的IPv4格式,提供一致且易於理解的用戶體驗。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
@@ -186,6 +186,10 @@ ALLOWED_IPS=你的真實IP地址,其他允許的IP`}
|
||||
<Globe className="w-4 h-4 mr-2" />
|
||||
詳細IP調試
|
||||
</Button>
|
||||
<Button variant="outline" onClick={() => window.open('/test/ip-format-test', '_blank')}>
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
IPv4格式測試
|
||||
</Button>
|
||||
<Button variant="outline" onClick={() => window.open('/api/ip', '_blank')}>
|
||||
<ExternalLink className="w-4 h-4 mr-2" />
|
||||
IP API 端點
|
||||
|
Reference in New Issue
Block a user