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:
2025-08-01 15:35:15 +08:00
parent ad8676cac3
commit 2808852e9f
11 changed files with 1198 additions and 386 deletions

View File

@@ -1,7 +1,7 @@
"use client"
import { useEffect, useState } from "react"
import { Globe, Shield, ShieldAlert } from "lucide-react"
import { Globe, Shield, ShieldAlert, Info } from "lucide-react"
interface IpDisplayProps {
mobileSimplified?: boolean
@@ -13,12 +13,63 @@ interface IpInfo {
enableIpWhitelist: boolean
allowedIps: string[]
timestamp: string
ipv6Info?: {
isIPv6Mapped: boolean
originalFormat: string
ipv6Format: string
hasIPv6Support: boolean
}
debug?: {
originalDetectedIp?: string
finalDetectedIp?: string
rawDetectedIp?: string
allFoundIps?: string[]
ipDetectionMethod?: string
}
}
// 清理IP地址確保顯示IPv4格式
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';
}
// 檢查是否為IPv6格式的IPv4地址
function isIPv6MappedIPv4(ip: string): boolean {
return ip.startsWith('::ffff:');
}
// 獲取IPv6格式的IPv4地址
function getIPv6MappedFormat(ipv4: string): string {
return `::ffff:${ipv4}`;
}
export default function IpDisplay({ mobileSimplified = false }: IpDisplayProps) {
const [ipInfo, setIpInfo] = useState<IpInfo | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [showIPv6Format, setShowIPv6Format] = useState(false)
useEffect(() => {
const fetchIpInfo = async () => {
@@ -58,6 +109,14 @@ export default function IpDisplay({ mobileSimplified = false }: IpDisplayProps)
)
}
// 清理IP地址確保顯示IPv4格式
const displayIp = cleanIpForDisplay(ipInfo.ip);
// 使用API返回的IPv6信息如果沒有則回退到本地檢測
const isIPv6Mapped = ipInfo.ipv6Info?.isIPv6Mapped || isIPv6MappedIPv4(ipInfo.debug?.originalDetectedIp || ipInfo.ip);
const ipv6Format = ipInfo.ipv6Info?.ipv6Format || getIPv6MappedFormat(displayIp);
const originalFormat = ipInfo.ipv6Info?.originalFormat || ipInfo.debug?.originalDetectedIp || ipInfo.ip;
// 手機版簡化顯示
if (mobileSimplified) {
return (
@@ -72,32 +131,99 @@ export default function IpDisplay({ mobileSimplified = false }: IpDisplayProps)
<Shield className="w-2.5 h-2.5 text-green-300" />
)}
<span className="text-xs font-mono text-blue-200">
{ipInfo.ip.split('.').slice(0, 2).join('.')}...
{isIPv6Mapped ? 'IPv6' : 'IPv4'}: {displayIp.split('.').slice(0, 2).join('.')}...
</span>
</div>
)
}
return (
<div className={`flex items-center gap-1.5 px-2 py-1 rounded-md border backdrop-blur-sm ${
ipInfo.enableIpWhitelist && !ipInfo.isAllowed
? 'bg-red-900/50 border-red-800/30'
: 'bg-slate-800/50 border-blue-800/30'
}`}>
{ipInfo.enableIpWhitelist && !ipInfo.isAllowed ? (
<ShieldAlert className="w-3 h-3 text-red-300" />
) : (
<Shield className="w-3 h-3 text-green-300" />
)}
<span className="text-xs text-blue-200 font-mono">{ipInfo.ip}</span>
{ipInfo.enableIpWhitelist && (
<span className={`text-xs px-1 py-0.5 rounded ${
ipInfo.isAllowed
? 'bg-green-900/50 text-green-200'
: 'bg-red-900/50 text-red-200'
}`}>
{ipInfo.isAllowed ? '允許' : '拒絕'}
</span>
<div className="relative group">
<div className={`flex items-center gap-1.5 px-2 py-1 rounded-md border backdrop-blur-sm ${
ipInfo.enableIpWhitelist && !ipInfo.isAllowed
? 'bg-red-900/50 border-red-800/30'
: 'bg-slate-800/50 border-blue-800/30'
}`}>
{ipInfo.enableIpWhitelist && !ipInfo.isAllowed ? (
<ShieldAlert className="w-3 h-3 text-red-300" />
) : (
<Shield className="w-3 h-3 text-green-300" />
)}
<div className="flex flex-col">
<span className="text-xs text-blue-200 font-mono">
{isIPv6Mapped ? 'IPv6' : 'IPv4'}: {displayIp}
</span>
{isIPv6Mapped && (
<span className="text-xs text-cyan-300 font-mono">
{ipv6Format}
</span>
)}
</div>
{ipInfo.enableIpWhitelist && (
<span className={`text-xs px-1 py-0.5 rounded ${
ipInfo.isAllowed
? 'bg-green-900/50 text-green-200'
: 'bg-red-900/50 text-red-200'
}`}>
{ipInfo.isAllowed ? '允許' : '拒絕'}
</span>
)}
{/* IPv6格式切換按鈕 */}
<button
onClick={() => setShowIPv6Format(!showIPv6Format)}
className="ml-1 p-0.5 rounded hover:bg-blue-800/30 transition-colors"
title="切換IPv6格式顯示"
>
<Info className="w-3 h-3 text-blue-300" />
</button>
</div>
{/* 詳細信息彈出框 */}
{showIPv6Format && (
<div className="absolute top-full left-0 mt-1 p-2 bg-slate-800/95 border border-blue-800/50 rounded-md shadow-lg backdrop-blur-sm z-50 min-w-64">
<div className="text-xs space-y-1">
<div className="flex justify-between">
<span className="text-blue-200">IPv4格式:</span>
<span className="text-white font-mono">{displayIp}</span>
</div>
<div className="flex justify-between">
<span className="text-blue-200">IPv6格式:</span>
<span className="text-cyan-300 font-mono">{ipv6Format}</span>
</div>
{originalFormat && originalFormat !== displayIp && (
<div className="flex justify-between">
<span className="text-blue-200">:</span>
<span className="text-yellow-300 font-mono">{originalFormat}</span>
</div>
)}
{ipInfo.debug?.ipDetectionMethod && (
<div className="flex justify-between">
<span className="text-blue-200">:</span>
<span className="text-green-300 font-mono">{ipInfo.debug.ipDetectionMethod}</span>
</div>
)}
{ipInfo.debug?.allFoundIps && ipInfo.debug.allFoundIps.length > 0 && (
<div className="mt-2 pt-1 border-t border-blue-800/30">
<span className="text-blue-200">IP:</span>
<div className="mt-1 space-y-0.5">
{ipInfo.debug.allFoundIps.map((ip, index) => (
<div key={index} className="text-yellow-300 font-mono text-xs">
{ip}
</div>
))}
</div>
</div>
)}
{ipInfo.ipv6Info?.hasIPv6Support && (
<div className="mt-2 pt-1 border-t border-blue-800/30">
<span className="text-green-300"> IPv6支援已啟用</span>
</div>
)}
</div>
</div>
)}
</div>
)