325 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useState, useEffect } from 'react'
import { initializeNetwork, clearNetworkCache } from '@/lib/network'
interface NetworkDebugInfo {
success: boolean
isIntranet: boolean
isPublicDomain: boolean
clientIp: string
clientIpSource: string
hostname: string
debug: {
forwardedIpList: string[]
hasInternalIpInForwarded: boolean
realIp: string | null
cfConnectingIp: string | null
xClientIp: string | null
clientIpIsInternal: boolean | null
}
timestamp: string
}
export default function NetworkDiagnosePage() {
const [loading, setLoading] = useState(true)
const [networkInfo, setNetworkInfo] = useState<NetworkDebugInfo | null>(null)
const [frontendStatus, setFrontendStatus] = useState<{ isInternal: boolean; host: string } | null>(null)
const [error, setError] = useState<string | null>(null)
const [testResults, setTestResults] = useState<{
internalMinIO: { success: boolean; time: number; error?: string } | null
publicS3: { success: boolean; time: number; error?: string } | null
}>({ internalMinIO: null, publicS3: null })
const fetchNetworkStatus = async () => {
setLoading(true)
setError(null)
try {
// 清除前端缓存,强制重新检测
clearNetworkCache()
// 获取前端网络状态
const frontendResult = await initializeNetwork(true)
setFrontendStatus(frontendResult)
// 获取后端网络状态(带诊断信息)
const response = await fetch('/api/network-status', { cache: 'no-store' })
if (response.ok) {
const data = await response.json()
setNetworkInfo(data)
} else {
setError('获取网络状态失败')
}
} catch (err) {
setError(err instanceof Error ? err.message : '未知错误')
} finally {
setLoading(false)
}
}
// 测试内网MinIO连接
const testInternalMinIO = async () => {
setTestResults(prev => ({ ...prev, internalMinIO: null }))
const startTime = Date.now()
try {
// 尝试访问内网MinIO的一个测试图片
const testUrl = 'http://192.168.58.20:9000/gongxiangpan/由壹点AI/test-connection.txt'
const response = await fetch(testUrl, {
method: 'HEAD',
mode: 'no-cors', // 跨域请求
cache: 'no-store'
})
const time = Date.now() - startTime
// no-cors 模式下无法读取响应状态,但如果没有抛出错误,说明网络可达
setTestResults(prev => ({
...prev,
internalMinIO: { success: true, time, error: '网络可达(无法确认文件是否存在)' }
}))
} catch (err) {
const time = Date.now() - startTime
setTestResults(prev => ({
...prev,
internalMinIO: {
success: false,
time,
error: err instanceof Error ? err.message : '连接失败'
}
}))
}
}
// 测试公网S3连接
const testPublicS3 = async () => {
setTestResults(prev => ({ ...prev, publicS3: null }))
const startTime = Date.now()
try {
// 尝试访问公网S3的一个测试图片
const testUrl = 'https://s3.yydd.ai/gongxiangpan/由壹点AI/test-connection.txt'
const response = await fetch(testUrl, {
method: 'HEAD',
mode: 'no-cors',
cache: 'no-store'
})
const time = Date.now() - startTime
setTestResults(prev => ({
...prev,
publicS3: { success: true, time, error: '网络可达(无法确认文件是否存在)' }
}))
} catch (err) {
const time = Date.now() - startTime
setTestResults(prev => ({
...prev,
publicS3: {
success: false,
time,
error: err instanceof Error ? err.message : '连接失败'
}
}))
}
}
useEffect(() => {
fetchNetworkStatus()
}, [])
return (
<div className="min-h-screen bg-gray-100 py-8">
<div className="max-w-4xl mx-auto px-4">
<h1 className="text-2xl font-bold text-gray-800 mb-6">网络诊断</h1>
{/* 刷新按钮 */}
<div className="mb-6">
<button
onClick={fetchNetworkStatus}
disabled={loading}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50"
>
{loading ? '检测中...' : '重新检测'}
</button>
</div>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-6">
{error}
</div>
)}
{/* 检测结果 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
{/* 前端检测结果 */}
<div className="bg-white rounded-lg shadow p-6">
<h2 className="text-lg font-semibold mb-4">前端检测结果</h2>
{frontendStatus ? (
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-gray-600">网络类型:</span>
<span className={`font-medium ${frontendStatus.isInternal ? 'text-green-600' : 'text-blue-600'}`}>
{frontendStatus.isInternal ? '✅ 内网' : '🌐 外网'}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">当前Host:</span>
<span className="font-mono text-sm">{frontendStatus.host}</span>
</div>
</div>
) : (
<div className="text-gray-500">加载中...</div>
)}
</div>
{/* 后端检测结果 */}
<div className="bg-white rounded-lg shadow p-6">
<h2 className="text-lg font-semibold mb-4">后端检测结果</h2>
{networkInfo ? (
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-gray-600">网络类型:</span>
<span className={`font-medium ${networkInfo.isIntranet ? 'text-green-600' : 'text-blue-600'}`}>
{networkInfo.isIntranet ? '✅ 内网' : '🌐 外网'}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">客户端IP:</span>
<span className="font-mono text-sm">{networkInfo.clientIp}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">IP来源:</span>
<span className="font-mono text-sm">{networkInfo.clientIpSource}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Hostname:</span>
<span className="font-mono text-sm">{networkInfo.hostname}</span>
</div>
</div>
) : (
<div className="text-gray-500">加载中...</div>
)}
</div>
</div>
{/* 详细诊断信息 */}
{networkInfo?.debug && (
<div className="bg-white rounded-lg shadow p-6 mb-6">
<h2 className="text-lg font-semibold mb-4">详细诊断信息</h2>
<div className="space-y-3">
<div>
<span className="text-gray-600">X-Forwarded-For IP列表:</span>
<div className="font-mono text-sm bg-gray-100 p-2 rounded mt-1">
{networkInfo.debug.forwardedIpList.length > 0
? networkInfo.debug.forwardedIpList.join(' → ')
: '(空)'}
</div>
</div>
<div className="flex justify-between">
<span className="text-gray-600">X-Forwarded-For中有内网IP:</span>
<span className={networkInfo.debug.hasInternalIpInForwarded ? 'text-green-600' : 'text-gray-500'}>
{networkInfo.debug.hasInternalIpInForwarded ? '是' : '否'}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">X-Real-IP:</span>
<span className="font-mono text-sm">{networkInfo.debug.realIp || '(空)'}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">CF-Connecting-IP:</span>
<span className="font-mono text-sm">{networkInfo.debug.cfConnectingIp || '(空)'}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">X-Client-IP:</span>
<span className="font-mono text-sm">{networkInfo.debug.xClientIp || '(空)'}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">客户端IP是内网:</span>
<span className={networkInfo.debug.clientIpIsInternal ? 'text-green-600' : 'text-blue-600'}>
{networkInfo.debug.clientIpIsInternal === null ? '未知' : networkInfo.debug.clientIpIsInternal ? '是' : '否'}
</span>
</div>
</div>
</div>
)}
{/* 连接测试 */}
<div className="bg-white rounded-lg shadow p-6 mb-6">
<h2 className="text-lg font-semibold mb-4">连接测试</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* 内网MinIO测试 */}
<div className="border rounded p-4">
<h3 className="font-medium mb-2">内网MinIO (192.168.58.20:9000)</h3>
<button
onClick={testInternalMinIO}
className="px-3 py-1 bg-gray-200 rounded hover:bg-gray-300 text-sm mb-2"
>
测试连接
</button>
{testResults.internalMinIO && (
<div className={`text-sm ${testResults.internalMinIO.success ? 'text-green-600' : 'text-red-600'}`}>
{testResults.internalMinIO.success ? '✅' : '❌'} {testResults.internalMinIO.time}ms
{testResults.internalMinIO.error && (
<div className="text-gray-500 text-xs mt-1">{testResults.internalMinIO.error}</div>
)}
</div>
)}
</div>
{/* 公网S3测试 */}
<div className="border rounded p-4">
<h3 className="font-medium mb-2">公网S3 (s3.yydd.ai)</h3>
<button
onClick={testPublicS3}
className="px-3 py-1 bg-gray-200 rounded hover:bg-gray-300 text-sm mb-2"
>
测试连接
</button>
{testResults.publicS3 && (
<div className={`text-sm ${testResults.publicS3.success ? 'text-green-600' : 'text-red-600'}`}>
{testResults.publicS3.success ? '✅' : '❌'} {testResults.publicS3.time}ms
{testResults.publicS3.error && (
<div className="text-gray-500 text-xs mt-1">{testResults.publicS3.error}</div>
)}
</div>
)}
</div>
</div>
</div>
{/* 浏览器信息 */}
<div className="bg-white rounded-lg shadow p-6">
<h2 className="text-lg font-semibold mb-4">浏览器信息</h2>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-600">User Agent:</span>
<span className="font-mono text-xs max-w-md truncate" title={typeof navigator !== 'undefined' ? navigator.userAgent : ''}>
{typeof navigator !== 'undefined' ? navigator.userAgent : 'N/A'}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">当前URL:</span>
<span className="font-mono text-xs">{typeof window !== 'undefined' ? window.location.href : 'N/A'}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">检测时间:</span>
<span className="font-mono text-xs">{networkInfo?.timestamp || 'N/A'}</span>
</div>
</div>
</div>
{/* 说明 */}
<div className="mt-6 text-sm text-gray-500">
<p>💡 提示:如果在内网但检测为外网,可能是以下原因:</p>
<ul className="list-disc list-inside mt-2 space-y-1">
<li>代理/VPN 导致 IP 地址被识别为公网</li>
<li>浏览器扩展修改了请求头</li>
<li>DNS 解析到了公网地址而非内网地址</li>
<li>X-Forwarded-For 头中没有内网IP</li>
</ul>
</div>
</div>
</div>
)
}