正確顯示 ip 結果 ( cloudflare )

This commit is contained in:
2025-10-07 17:57:17 +08:00
parent 0e517dc694
commit 707820697a
8 changed files with 935 additions and 46 deletions

View File

@@ -0,0 +1,193 @@
import { NextRequest, NextResponse } from 'next/server'
export async function GET(request: NextRequest) {
try {
// 收集所有可能的IP相关头部
const allHeaders = Object.fromEntries(request.headers.entries())
// 专门收集IP相关的头部
const ipHeaders = {
'x-forwarded-for': request.headers.get('x-forwarded-for'),
'x-real-ip': request.headers.get('x-real-ip'),
'x-client-ip': request.headers.get('x-client-ip'),
'cf-connecting-ip': request.headers.get('cf-connecting-ip'), // Cloudflare
'cf-ray': request.headers.get('cf-ray'), // Cloudflare Ray ID
'cf-visitor': request.headers.get('cf-visitor'), // Cloudflare visitor info
'cf-ipcountry': request.headers.get('cf-ipcountry'), // Cloudflare country
'x-forwarded': request.headers.get('x-forwarded'),
'forwarded-for': request.headers.get('forwarded-for'),
'forwarded': request.headers.get('forwarded'),
'x-original-forwarded-for': request.headers.get('x-original-forwarded-for'),
'x-cluster-client-ip': request.headers.get('x-cluster-client-ip'),
'x-1panel-client-ip': request.headers.get('x-1panel-client-ip'),
'x-nginx-proxy-real-ip': request.headers.get('x-nginx-proxy-real-ip'),
'x-original-remote-addr': request.headers.get('x-original-remote-addr'),
'x-remote-addr': request.headers.get('x-remote-addr'),
'x-client-real-ip': request.headers.get('x-client-real-ip'),
'true-client-ip': request.headers.get('true-client-ip'),
}
// 获取其他有用的头部信息
const otherHeaders = {
'user-agent': request.headers.get('user-agent'),
'host': request.headers.get('host'),
'referer': request.headers.get('referer'),
'origin': request.headers.get('origin'),
'x-forwarded-proto': request.headers.get('x-forwarded-proto'),
'x-forwarded-host': request.headers.get('x-forwarded-host'),
'x-forwarded-port': request.headers.get('x-forwarded-port'),
'via': request.headers.get('via'),
'connection': request.headers.get('connection'),
'upgrade-insecure-requests': request.headers.get('upgrade-insecure-requests'),
}
// 尝试从不同来源获取IP
const ipAnalysis = {
// 标准代理头部
xForwardedFor: request.headers.get('x-forwarded-for'),
xRealIp: request.headers.get('x-real-ip'),
xClientIp: request.headers.get('x-client-ip'),
// Cloudflare 特定头部
cfConnectingIp: request.headers.get('cf-connecting-ip'),
// 其他可能的头部
forwarded: request.headers.get('forwarded'),
xOriginalForwardedFor: request.headers.get('x-original-forwarded-for'),
// 分析结果
analysis: {
hasCloudflare: !!request.headers.get('cf-connecting-ip') || !!request.headers.get('cf-ray'),
hasXForwardedFor: !!request.headers.get('x-forwarded-for'),
hasXRealIp: !!request.headers.get('x-real-ip'),
hasForwarded: !!request.headers.get('forwarded'),
recommendedIpSource: '',
recommendedIp: '',
}
}
// 分析并推荐最佳IP来源
if (ipAnalysis.cfConnectingIp) {
ipAnalysis.analysis.recommendedIpSource = 'cf-connecting-ip (Cloudflare)'
ipAnalysis.analysis.recommendedIp = ipAnalysis.cfConnectingIp
} else if (ipAnalysis.xForwardedFor) {
// 解析 x-forwarded-for通常格式为 "client-ip, proxy1-ip, proxy2-ip"
const forwardedIps = ipAnalysis.xForwardedFor.split(',').map(ip => ip.trim())
const clientIp = forwardedIps[0] // 第一个IP通常是客户端IP
ipAnalysis.analysis.recommendedIpSource = 'x-forwarded-for (first IP)'
ipAnalysis.analysis.recommendedIp = clientIp
} else if (ipAnalysis.xRealIp) {
ipAnalysis.analysis.recommendedIpSource = 'x-real-ip'
ipAnalysis.analysis.recommendedIp = ipAnalysis.xRealIp
} else if (ipAnalysis.forwarded) {
ipAnalysis.analysis.recommendedIpSource = 'forwarded header'
ipAnalysis.analysis.recommendedIp = ipAnalysis.forwarded
} else {
ipAnalysis.analysis.recommendedIpSource = 'no reliable IP source found'
ipAnalysis.analysis.recommendedIp = 'unknown'
}
// 检查是否是Cloudflare
const isCloudflare = {
hasCfConnectingIp: !!request.headers.get('cf-connecting-ip'),
hasCfRay: !!request.headers.get('cf-ray'),
hasCfVisitor: !!request.headers.get('cf-visitor'),
hasCfIpCountry: !!request.headers.get('cf-ipcountry'),
cfRay: request.headers.get('cf-ray'),
cfCountry: request.headers.get('cf-ipcountry'),
cfVisitor: request.headers.get('cf-visitor'),
}
// 检查是否是1Panel或Nginx代理
const proxyInfo = {
hasNginxProxy: !!request.headers.get('x-nginx-proxy-real-ip'),
has1Panel: !!request.headers.get('x-1panel-client-ip'),
nginxProxyIp: request.headers.get('x-nginx-proxy-real-ip'),
panelClientIp: request.headers.get('x-1panel-client-ip'),
}
// 解析 x-forwarded-for 中的所有IP
const parseXForwardedFor = (xff: string | null) => {
if (!xff) return []
return xff.split(',').map(ip => ip.trim()).filter(ip => ip)
}
// 分析IP来源链
const ipChain = {
xForwardedForChain: parseXForwardedFor(ipAnalysis.xForwardedFor),
recommendedClientIp: ipAnalysis.analysis.recommendedIp,
isPublicIp: (ip: string) => {
// 简单的公网IP检查
return !ip.match(/^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|127\.|169\.254\.)/)
}
}
return NextResponse.json({
timestamp: new Date().toISOString(),
environment: process.env.NODE_ENV,
url: request.url,
// 主要分析结果
ipAnalysis,
isCloudflare,
proxyInfo,
ipChain,
// 所有IP相关头部
ipHeaders,
// 其他有用头部
otherHeaders,
// 完整头部列表(用于调试)
allHeaders,
// 建议
recommendations: {
primaryIpSource: ipAnalysis.analysis.recommendedIpSource,
primaryIp: ipAnalysis.analysis.recommendedIp,
isCloudflareSetup: isCloudflare.hasCfConnectingIp,
isProxySetup: proxyInfo.hasNginxProxy || proxyInfo.has1Panel,
suggestedConfig: generateSuggestedConfig(ipAnalysis, isCloudflare, proxyInfo)
}
})
} catch (error) {
console.error('IP诊断错误:', error)
return NextResponse.json(
{ error: 'IP诊断失败', details: error.message },
{ status: 500 }
)
}
}
function generateSuggestedConfig(ipAnalysis: any, isCloudflare: any, proxyInfo: any): string[] {
const suggestions = []
if (isCloudflare.hasCfConnectingIp) {
suggestions.push('检测到Cloudflare代理建议优先使用 cf-connecting-ip 头部')
suggestions.push('确保1Panel/反向代理正确转发 cf-connecting-ip 头部')
}
if (ipAnalysis.xForwardedFor) {
suggestions.push('检测到 x-forwarded-for 头部建议解析第一个IP作为客户端IP')
const ips = ipAnalysis.xForwardedFor.split(',').map((ip: string) => ip.trim())
if (ips.length > 1) {
suggestions.push(`IP链: ${ips.join(' -> ')}`)
}
}
if (proxyInfo.hasNginxProxy) {
suggestions.push('检测到Nginx代理建议配置 nginx.conf 正确转发客户端IP')
}
if (proxyInfo.has1Panel) {
suggestions.push('检测到1Panel建议检查1Panel的反向代理配置')
}
if (!isCloudflare.hasCfConnectingIp && !ipAnalysis.xForwardedFor && !ipAnalysis.xRealIp) {
suggestions.push('警告: 未检测到可靠的IP来源头部请检查代理配置')
}
return suggestions
}

View File

@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
import { getClientIp, isIpAllowed, getIpLocation, getDetailedIpInfo } from '@/lib/ip-utils'
import { getClientIp, isIpAllowed, getIpLocation, getDetailedIpInfo, isValidIp, isValidIPv6 } from '@/lib/ip-utils'
// 強制動態渲染
export const dynamic = 'force-dynamic'
@@ -10,6 +10,17 @@ export async function GET(request: NextRequest) {
const detailedInfo = getDetailedIpInfo(request);
let clientIp = detailedInfo.detectedIp;
// 根據你的環境,優先使用 cf-connecting-ip (支持IPv4和IPv6)
const cfConnectingIp = request.headers.get('cf-connecting-ip');
if (cfConnectingIp && cfConnectingIp.trim() !== '') {
const cleanCfIp = cfConnectingIp.trim();
// 检查是否是有效的IPv4或IPv6地址
if (isValidIp(cleanCfIp) || isValidIPv6(cleanCfIp)) {
clientIp = cleanCfIp;
console.log('使用 cf-connecting-ip:', clientIp, isValidIPv6(clientIp) ? '(IPv6)' : '(IPv4)');
}
}
// 確保返回IPv4格式的地址
function ensureIPv4Format(ip: string): string {
if (!ip) return '127.0.0.1';

View File

@@ -0,0 +1,224 @@
"use client"
import { useEffect, useState } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { RefreshCw, Shield, AlertTriangle, CheckCircle, Lock } from "lucide-react"
interface IpInfo {
ip: string
isAllowed: boolean
enableIpWhitelist: boolean
allowedIps: string[]
timestamp: string
}
export default function IpBlockingTestPage() {
const [ipInfo, setIpInfo] = useState<IpInfo | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const fetchIpInfo = async () => {
setLoading(true)
setError(null)
try {
const response = await fetch('/api/ip')
if (!response.ok) {
throw new Error('無法獲取IP信息')
}
const data = await response.json()
setIpInfo(data)
} catch (error) {
console.error("無法獲取IP信息:", error)
setError("無法獲取IP信息")
} finally {
setLoading(false)
}
}
useEffect(() => {
fetchIpInfo()
}, [])
if (loading) {
return (
<div className="container mx-auto p-6">
<div className="flex items-center justify-center min-h-[400px]">
<div className="flex items-center gap-2">
<RefreshCw className="w-4 h-4 animate-spin" />
<span>...</span>
</div>
</div>
</div>
)
}
if (error || !ipInfo) {
return (
<div className="container mx-auto p-6">
<Card className="border-red-200 bg-red-50">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-red-600">
<AlertTriangle className="w-5 h-5" />
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-red-600">{error}</p>
<Button onClick={fetchIpInfo} className="mt-4">
<RefreshCw className="w-4 h-4 mr-2" />
</Button>
</CardContent>
</Card>
</div>
)
}
return (
<div className="container mx-auto p-6 space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-3xl font-bold">IP </h1>
<Button onClick={fetchIpInfo} variant="outline">
<RefreshCw className="w-4 h-4 mr-2" />
</Button>
</div>
{/* IP狀態卡片 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
{ipInfo.enableIpWhitelist && !ipInfo.isAllowed ? (
<>
<Lock className="w-5 h-5 text-red-500" />
IP
</>
) : (
<>
<CheckCircle className="w-5 h-5 text-green-500" />
IP
</>
)}
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium text-gray-500">IP</label>
<p className="text-lg font-mono bg-gray-100 p-2 rounded">
{ipInfo.ip}
</p>
</div>
<div>
<label className="text-sm font-medium text-gray-500"></label>
<div className="mt-1">
<Badge variant={ipInfo.isAllowed ? "default" : "destructive"}>
{ipInfo.isAllowed ? "允許" : "阻擋"}
</Badge>
</div>
</div>
</div>
<div>
<label className="text-sm font-medium text-gray-500"></label>
<div className="flex items-center gap-2 mt-1">
<Shield className="w-4 h-4 text-blue-500" />
<span className="text-sm">
{ipInfo.enableIpWhitelist ? "已啟用" : "未啟用"}
</span>
</div>
</div>
</CardContent>
</Card>
{/* 允許的IP列表 */}
{ipInfo.enableIpWhitelist && ipInfo.allowedIps.length > 0 && (
<Card>
<CardHeader>
<CardTitle>IP列表</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
{ipInfo.allowedIps.map((ip, index) => (
<div key={index} className="flex items-center gap-2">
<Badge variant={ip === ipInfo.ip ? "default" : "outline"}>
{ip === ipInfo.ip ? "當前" : ""}
</Badge>
<span className="text-sm font-mono">{ip}</span>
</div>
))}
</div>
</CardContent>
</Card>
)}
{/* 阻擋測試說明 */}
<Card>
<CardHeader>
<CardTitle>IP </CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="p-4 bg-blue-50 border border-blue-200 rounded-lg">
<h3 className="font-medium text-blue-900 mb-2"> </h3>
<p className="text-sm text-blue-800">
IP ({ipInfo.ip})
</p>
</div>
<div className="p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
<h3 className="font-medium text-yellow-900 mb-2"> </h3>
<p className="text-sm text-yellow-800">
IP阻擋功能
</p>
<ul className="text-sm text-yellow-800 mt-2 ml-4 list-disc">
<li> .env.local ALLOWED_IPS IP</li>
<li></li>
<li>403</li>
<li>IP加回白名單</li>
</ul>
</div>
<div className="p-4 bg-red-50 border border-red-200 rounded-lg">
<h3 className="font-medium text-red-900 mb-2">🚫 </h3>
<p className="text-sm text-red-800">
IP不在白名單中
</p>
<ul className="text-sm text-red-800 mt-2 ml-4 list-disc">
<li>403 Forbidden </li>
<li>IP地址</li>
<li></li>
<li>IP檢測API可以訪問</li>
</ul>
</div>
</CardContent>
</Card>
{/* 當前配置 */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<div className="flex justify-between">
<span className="text-sm font-medium text-gray-500">:</span>
<Badge variant={ipInfo.enableIpWhitelist ? "default" : "secondary"}>
{ipInfo.enableIpWhitelist ? "已啟用" : "未啟用"}
</Badge>
</div>
<div className="flex justify-between">
<span className="text-sm font-medium text-gray-500">IP:</span>
<span className="text-sm font-mono">{ipInfo.ip}</span>
</div>
<div className="flex justify-between">
<span className="text-sm font-medium text-gray-500">:</span>
<Badge variant={ipInfo.isAllowed ? "default" : "destructive"}>
{ipInfo.isAllowed ? "允許" : "阻擋"}
</Badge>
</div>
</CardContent>
</Card>
</div>
)
}

View File

@@ -100,10 +100,18 @@ export default function IpDebugPage() {
<div className="container mx-auto p-6 space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-3xl font-bold">IP 調</h1>
<Button onClick={fetchIpInfo} variant="outline">
<RefreshCw className="w-4 h-4 mr-2" />
</Button>
<div className="flex gap-2">
<Button onClick={fetchIpInfo} variant="outline">
<RefreshCw className="w-4 h-4 mr-2" />
</Button>
<Button
onClick={() => window.open('/api/ip-diagnostic', '_blank')}
variant="secondary"
>
</Button>
</div>
</div>
{/* 主要IP信息 */}

View File

@@ -0,0 +1,339 @@
"use client"
import { useEffect, useState } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { RefreshCw, Shield, AlertTriangle, CheckCircle, Info, Cloud, Server } from "lucide-react"
interface DiagnosticData {
timestamp: string
environment: string
url: string
ipAnalysis: {
xForwardedFor: string | null
xRealIp: string | null
xClientIp: string | null
cfConnectingIp: string | null
forwarded: string | null
xOriginalForwardedFor: string | null
analysis: {
hasCloudflare: boolean
hasXForwardedFor: boolean
hasXRealIp: boolean
hasForwarded: boolean
recommendedIpSource: string
recommendedIp: string
}
}
isCloudflare: {
hasCfConnectingIp: boolean
hasCfRay: boolean
hasCfVisitor: boolean
hasCfIpCountry: boolean
cfRay: string | null
cfCountry: string | null
cfVisitor: string | null
}
proxyInfo: {
hasNginxProxy: boolean
has1Panel: boolean
nginxProxyIp: string | null
panelClientIp: string | null
}
ipChain: {
xForwardedForChain: string[]
recommendedClientIp: string
}
ipHeaders: Record<string, string | null>
otherHeaders: Record<string, string | null>
allHeaders: Record<string, string>
recommendations: {
primaryIpSource: string
primaryIp: string
isCloudflareSetup: boolean
isProxySetup: boolean
suggestedConfig: string[]
}
}
export default function IpDiagnosticPage() {
const [diagnosticData, setDiagnosticData] = useState<DiagnosticData | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const fetchDiagnosticData = async () => {
setLoading(true)
setError(null)
try {
const response = await fetch('/api/ip-diagnostic')
if (!response.ok) {
throw new Error('無法獲取診斷數據')
}
const data = await response.json()
setDiagnosticData(data)
} catch (error) {
console.error("無法獲取診斷數據:", error)
setError("無法獲取診斷數據")
} finally {
setLoading(false)
}
}
useEffect(() => {
fetchDiagnosticData()
}, [])
if (loading) {
return (
<div className="container mx-auto p-6">
<div className="flex items-center justify-center min-h-[400px]">
<div className="flex items-center gap-2">
<RefreshCw className="w-4 h-4 animate-spin" />
<span>...</span>
</div>
</div>
</div>
)
}
if (error || !diagnosticData) {
return (
<div className="container mx-auto p-6">
<Card className="border-red-200 bg-red-50">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-red-600">
<AlertTriangle className="w-5 h-5" />
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-red-600">{error}</p>
<Button onClick={fetchDiagnosticData} className="mt-4">
<RefreshCw className="w-4 h-4 mr-2" />
</Button>
</CardContent>
</Card>
</div>
)
}
return (
<div className="container mx-auto p-6 space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-3xl font-bold">IP </h1>
<Button onClick={fetchDiagnosticData} variant="outline">
<RefreshCw className="w-4 h-4 mr-2" />
</Button>
</div>
{/* 環境檢測結果 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Info className="w-5 h-5" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="flex items-center gap-2">
<Cloud className="w-4 h-4" />
<span className="text-sm font-medium">Cloudflare:</span>
<Badge variant={diagnosticData.isCloudflare.hasCfConnectingIp ? "default" : "secondary"}>
{diagnosticData.isCloudflare.hasCfConnectingIp ? "檢測到" : "未檢測到"}
</Badge>
</div>
<div className="flex items-center gap-2">
<Server className="w-4 h-4" />
<span className="text-sm font-medium">:</span>
<Badge variant={diagnosticData.proxyInfo.hasNginxProxy || diagnosticData.proxyInfo.has1Panel ? "default" : "secondary"}>
{diagnosticData.proxyInfo.hasNginxProxy || diagnosticData.proxyInfo.has1Panel ? "檢測到" : "未檢測到"}
</Badge>
</div>
<div className="flex items-center gap-2">
<Shield className="w-4 h-4" />
<span className="text-sm font-medium">IP來源:</span>
<Badge variant="default">
{diagnosticData.recommendations.primaryIpSource}
</Badge>
</div>
</div>
{/* 推薦的IP */}
<div className="p-4 bg-blue-50 border border-blue-200 rounded-lg">
<h3 className="font-medium text-blue-900 mb-2">使IP</h3>
<p className="text-lg font-mono text-blue-800">
{diagnosticData.recommendations.primaryIp}
</p>
<p className="text-sm text-blue-600 mt-1">
: {diagnosticData.recommendations.primaryIpSource}
</p>
</div>
</CardContent>
</Card>
{/* Cloudflare 信息 */}
{diagnosticData.isCloudflare.hasCfConnectingIp && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Cloud className="w-5 h-5 text-blue-500" />
Cloudflare
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium text-gray-500">CF-Connecting-IP</label>
<p className="text-sm bg-gray-100 p-2 rounded font-mono">
{diagnosticData.ipAnalysis.cfConnectingIp || 'null'}
</p>
</div>
<div>
<label className="text-sm font-medium text-gray-500">CF-Ray</label>
<p className="text-sm bg-gray-100 p-2 rounded">
{diagnosticData.isCloudflare.cfRay || 'null'}
</p>
</div>
<div>
<label className="text-sm font-medium text-gray-500">CF-Country</label>
<p className="text-sm bg-gray-100 p-2 rounded">
{diagnosticData.isCloudflare.cfCountry || 'null'}
</p>
</div>
<div>
<label className="text-sm font-medium text-gray-500">CF-Visitor</label>
<p className="text-sm bg-gray-100 p-2 rounded">
{diagnosticData.isCloudflare.cfVisitor || 'null'}
</p>
</div>
</div>
</CardContent>
</Card>
)}
{/* 代理信息 */}
{(diagnosticData.proxyInfo.hasNginxProxy || diagnosticData.proxyInfo.has1Panel) && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Server className="w-5 h-5 text-green-500" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{diagnosticData.proxyInfo.hasNginxProxy && (
<div>
<label className="text-sm font-medium text-gray-500">Nginx Proxy Real IP</label>
<p className="text-sm bg-gray-100 p-2 rounded font-mono">
{diagnosticData.proxyInfo.nginxProxyIp || 'null'}
</p>
</div>
)}
{diagnosticData.proxyInfo.has1Panel && (
<div>
<label className="text-sm font-medium text-gray-500">1Panel Client IP</label>
<p className="text-sm bg-gray-100 p-2 rounded font-mono">
{diagnosticData.proxyInfo.panelClientIp || 'null'}
</p>
</div>
)}
</CardContent>
</Card>
)}
{/* IP鏈分析 */}
<Card>
<CardHeader>
<CardTitle>IP </CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<label className="text-sm font-medium text-gray-500">X-Forwarded-For </label>
{diagnosticData.ipChain.xForwardedForChain.length > 0 ? (
<div className="mt-1 space-y-1">
{diagnosticData.ipChain.xForwardedForChain.map((ip, index) => (
<div key={index} className="flex items-center gap-2 text-sm bg-gray-100 p-2 rounded">
<Badge variant={index === 0 ? "default" : "outline"}>
{index === 0 ? "客戶端" : `代理${index}`}
</Badge>
<span className="font-mono">{ip}</span>
</div>
))}
</div>
) : (
<p className="text-sm text-gray-500 mt-1"> X-Forwarded-For </p>
)}
</div>
</CardContent>
</Card>
{/* 所有IP相關頭部 */}
<Card>
<CardHeader>
<CardTitle> IP </CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2 max-h-80 overflow-y-auto">
{Object.entries(diagnosticData.ipHeaders).map(([key, value]) => (
<div key={key} className="flex items-start gap-2 text-sm bg-gray-100 p-2 rounded">
<span className="font-medium text-blue-600 min-w-32">{key}:</span>
<span className="font-mono text-gray-800 flex-1">{value || 'null'}</span>
</div>
))}
</div>
</CardContent>
</Card>
{/* 配置建議 */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3">
{diagnosticData.recommendations.suggestedConfig.map((suggestion, index) => (
<div key={index} className="flex items-start gap-2 p-3 bg-yellow-50 border border-yellow-200 rounded">
<Info className="w-4 h-4 text-yellow-600 mt-0.5 flex-shrink-0" />
<p className="text-sm text-yellow-800">{suggestion}</p>
</div>
))}
</div>
</CardContent>
</Card>
{/* 完整頭部列表 */}
<Card>
<CardHeader>
<CardTitle> HTTP </CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2 max-h-96 overflow-y-auto">
{Object.entries(diagnosticData.allHeaders).map(([key, value]) => (
<div key={key} className="flex items-start gap-2 text-sm bg-gray-100 p-2 rounded">
<span className="font-medium text-blue-600 min-w-40">{key}:</span>
<span className="text-gray-800 flex-1 break-all">{value}</span>
</div>
))}
</div>
</CardContent>
</Card>
{/* 使用說明 */}
<Card>
<CardHeader>
<CardTitle>使</CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm text-gray-600">
<p>1. Cloudflare</p>
<p>2. 使IPIP (114.33.18.13)</p>
<p>3. 調IP檢測邏輯</p>
<p>4. X-Forwarded-For Cloudflare </p>
</CardContent>
</Card>
</div>
)
}