Files
jh-monitor/src/app/dashboard/components/gatewayConfig.tsx

269 lines
11 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 { useEffect, useState, Suspense } from 'react'
import { useSearchParams } from 'next/navigation'
import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table'
interface GatewayConfig {
id: number
city: string
edge: string
user: string
public: string
inner_ip: string
ischange: number
isonline: number
}
function GatewayConfigContent() {
const [data, setData] = useState<GatewayConfig[]>([])
const [loading, setLoading] = useState(false)
const [macAddress, setMacAddress] = useState('')
const [error, setError] = useState('')
const [success, setSuccess] = useState('')
const searchParams = useSearchParams()
// 监听URL的mac参数变化同步到输入框并触发查询
useEffect(() => {
const urlMac = searchParams.get('mac')
if (urlMac) {
setMacAddress(urlMac)
fetchData(urlMac)
} else {
// 如果没有mac参数显示所有网关配置
setMacAddress('')
fetchData('')
}
}, [searchParams])
const fetchData = async (mac: string) => {
setLoading(true)
setError('')
setSuccess('')
try {
// 构建API URL - 如果有MAC地址则添加参数否则获取全部
const apiUrl = mac.trim()
? `/api/stats?type=gateway_config&mac=${encodeURIComponent(mac)}`
: `/api/stats?type=gateway_config`
const response = await fetch(apiUrl)
const result = await response.json()
if (!response.ok) {
throw new Error(result.error || '查询失败')
}
// 检查返回的数据是否有效
if (!result || result.length === 0) {
if (mac.trim()) {
setError(`未找到MAC地址为 ${mac} 的网关配置信息`)
} else {
setError('未找到任何网关配置信息')
}
setData([])
return
}
const validatedData = result.map((item: {
city: string
edge: string
user: string
public: string
inner_ip: string
ischange: number
isonline: number
}) => ({
city: item.city,
edge: item.edge,
user: item.user,
public: item.public,
inner_ip: item.inner_ip,
ischange: item.ischange,
isonline: item.isonline,
}))
setData(validatedData)
} catch (error) {
console.error('Failed to fetch gateway config:', error)
setError(error instanceof Error ? error.message : '获取网关配置失败')
setData([])
} finally {
setLoading(false)
}
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
fetchData(macAddress)
}
const getStatusBadge = (value: number, trueText: string = '是', falseText: string = '否') => {
// 0是正常1是更新正常绿+ 更新(红)
return (
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
value === 0 ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}`}>
{value === 0 ? trueText : falseText}
</span>
)
}
const getOnlineStatus = (isonline: number) => {
// 0是空闲1是在用在用+ 空闲(绿)
return (
<div className="flex items-center">
<div className={`${isonline === 0 ? 'bg-green-500' : 'bg-red-500'}`} />
{getStatusBadge(isonline, '空闲', '在用')}
</div>
)
}
return (
<div className="flex flex-col bg-white shadow rounded-lg p-6 overflow-hidden">
<div className="flex justify-between items-start mb-6">
<div>
<h2 className="text-xl font-semibold text-gray-800"></h2>
<p className="text-sm text-gray-600 mt-1"></p>
</div>
</div>
{/* 查询表单 */}
<form onSubmit={handleSubmit} className="mb-6">
<div className="flex items-center">
<input
type="text"
value={macAddress}
onChange={(e) => setMacAddress(e.target.value)}
placeholder="输入MAC地址查询"
className="px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
<button
type="submit"
className="ml-2 px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
</button>
</div>
{error && (
<div className="mt-4 p-3 bg-red-50 border border-red-200 rounded-md">
<div className="flex items-center text-red-800">
<svg className="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
</svg>
{error}
</div>
</div>
)}
{success && !error && (
<div className="mt-4 p-3 bg-green-50 border border-green-200 rounded-md">
<div className="flex items-center text-green-800">
<svg className="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
</svg>
{success}
</div>
</div>
)}
</form>
{loading ? (
<div className="text-center py-12">
<div className="text-gray-400 text-4xl mb-4"></div>
<p className="text-gray-600">...</p>
</div>
) : data.length > 0 ? (
<>
{/* 详细表格 */}
<div className='flex gap-6 overflow-hidden'>
<div className="flex-3 w-full flex">
<Table>
<TableHeader>
<TableRow className="bg-gray-50 TableRow ">
<TableHead className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">MAC地址</TableHead>
<TableHead className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"></TableHead>
<TableHead className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"></TableHead>
<TableHead className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">IP地址</TableHead>
<TableHead className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"></TableHead>
<TableHead className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"></TableHead>
<TableHead className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((item, index) => (
<TableRow
key={index}
className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50 '}
>
<TableCell className="hover:bg-gray-50 transition-colors">
<div className="font-mono text-sm text-blue-600 font-medium">{item.edge}</div>
</TableCell>
<TableCell className="px-6 whitespace-nowrap">
<span className="px-2 py-1 bg-gray-100 text-gray-700 rounded-full text-xs">
{item.city}
</span></TableCell>
<TableCell className="px-6 whitespace-nowrap text-sm text-gray-900">{item.user}</TableCell>
<TableCell className="px-6 whitespace-nowrap">
<div className="font-mono text-sm text-green-600">{item.public}</div>
</TableCell>
<TableCell className="px-6 whitespace-nowrap">
<div className="font-mono text-sm text-purple-600">{item.inner_ip}</div>
</TableCell>
<TableCell className="px-6 whitespace-nowrap">
<div className="font-mono text-sm text-green-600">{getStatusBadge(item.ischange, '正常', '更新')}</div>
</TableCell>
<TableCell className="px-6 whitespace-nowrap">
<div className="font-mono text-sm text-purple-600">{getOnlineStatus(item.isonline)}</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<div className="flex flex-1 flex-col gap-4 mb-6">
<div className="bg-blue-50 p-4 rounded-lg border border-blue-100">
<div className="text-2xl font-bold text-blue-600">{data.length}</div>
<div className="text-sm text-blue-800"></div>
</div>
<div className="bg-green-50 p-4 rounded-lg border border-green-100">
<div className="text-2xl font-bold text-green-600">
{data.filter(item => item.isonline === 1).length}
</div>
<div className="text-sm text-green-800">线</div>
</div>
<div className="bg-orange-50 p-4 rounded-lg border border-orange-100">
<div className="text-2xl font-bold text-orange-600">
{data.filter(item => item.ischange === 1).length}
</div>
<div className="text-sm text-orange-800"></div>
</div>
<div className="bg-purple-50 p-4 rounded-lg border border-purple-100">
<div className="text-2xl font-bold text-purple-600">
{new Set(data.map(item => item.city)).size}
</div>
<div className="text-sm text-purple-800"></div>
</div>
</div>
</div>
</>
) : (
<></>
)}
</div>
)
}
export default function GatewayConfig() {
return (
<Suspense fallback={
<div className="bg-white shadow rounded-lg p-6">
<div className="text-center py-12">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto"></div>
<p className="mt-4 text-gray-600">...</p>
</div>
</div>
}>
<GatewayConfigContent />
</Suspense>
)
}