282 lines
11 KiB
TypeScript
282 lines
11 KiB
TypeScript
'use client'
|
||
import { useEffect, useState, Suspense } from 'react'
|
||
import { useSearchParams } from 'next/navigation'
|
||
import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table'
|
||
import { Pagination } from '@/components/ui/pagination'
|
||
|
||
interface GatewayConfig {
|
||
city: string
|
||
edge: string
|
||
user: string
|
||
public: string
|
||
inner_ip: string
|
||
ischange: number
|
||
isonline: number
|
||
}
|
||
|
||
interface ApiResponse {
|
||
data: GatewayConfig[]
|
||
totalCount: number
|
||
currentPage: number
|
||
totalPages: number
|
||
}
|
||
|
||
function GatewayConfigContent() {
|
||
const [data, setData] = useState<GatewayConfig[]>([])
|
||
const [loading, setLoading] = useState(false)
|
||
const [macAddress, setMacAddress] = useState('')
|
||
const [error, setError] = useState('')
|
||
const searchParams = useSearchParams()
|
||
|
||
// 分页状态
|
||
const [currentPage, setCurrentPage] = useState(1)
|
||
const [itemsPerPage, setItemsPerPage] = useState(100)
|
||
const [totalItems, setTotalItems] = useState(0)
|
||
|
||
// 判断是否为MAC地址查询(用于控制分页显示)
|
||
const isMacQuery = !!macAddress
|
||
|
||
// 监听URL的mac参数变化
|
||
useEffect(() => {
|
||
const urlMac = searchParams.get('mac')
|
||
if (urlMac) {
|
||
setMacAddress(urlMac)
|
||
setCurrentPage(1) // 重置到第一页
|
||
fetchData(urlMac, 1, itemsPerPage)
|
||
} else {
|
||
setMacAddress('')
|
||
setCurrentPage(1) // 重置到第一页
|
||
fetchData('', 1, itemsPerPage)
|
||
}
|
||
}, [searchParams])
|
||
|
||
const fetchData = async (mac: string, page: number = 1, limit: number = itemsPerPage) => {
|
||
setLoading(true)
|
||
setError('')
|
||
|
||
try {
|
||
// 计算偏移量
|
||
const offset = (page - 1) * limit
|
||
|
||
// 构建API URL
|
||
let apiUrl = `/api/stats?type=gateway_config&offset=${offset}&limit=${limit}`
|
||
if (mac.trim()) {
|
||
apiUrl += `&mac=${encodeURIComponent(mac)}`
|
||
}
|
||
|
||
const response = await fetch(apiUrl)
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP错误! 状态: ${response.status}`)
|
||
}
|
||
|
||
const result: ApiResponse = await response.json()
|
||
|
||
// 检查返回的数据是否有效
|
||
if (!result.data || result.data.length === 0) {
|
||
if (mac.trim()) {
|
||
setError(`未找到MAC地址为 ${mac} 的网关配置信息`)
|
||
} else {
|
||
setError('未找到任何网关配置信息')
|
||
}
|
||
setData([])
|
||
setTotalItems(0)
|
||
return
|
||
}
|
||
|
||
setData(result.data)
|
||
|
||
setTotalItems(result.totalCount || 0)
|
||
} catch (error) {
|
||
console.error('获取网关配置失败:', error)
|
||
setError(error instanceof Error ? error.message : '获取网关配置失败')
|
||
setData([])
|
||
setTotalItems(0)
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
const handleSubmit = (e: React.FormEvent) => {
|
||
e.preventDefault()
|
||
setCurrentPage(1) // 重置到第一页
|
||
fetchData(macAddress, 1, itemsPerPage)
|
||
}
|
||
|
||
// 处理页码变化
|
||
const handlePageChange = (page: number) => {
|
||
setCurrentPage(page)
|
||
fetchData(macAddress, page, itemsPerPage)
|
||
}
|
||
|
||
// 处理每页显示数量变化
|
||
const handleSizeChange = (size: number) => {
|
||
setItemsPerPage(size)
|
||
setCurrentPage(1)
|
||
fetchData(macAddress, 1, size)
|
||
}
|
||
|
||
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 gap-2">
|
||
<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>
|
||
)}
|
||
</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">
|
||
<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">MAC地址</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>
|
||
<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>
|
||
<span className="px-2 py-1 bg-gray-100 text-gray-700 rounded-full text-xs">
|
||
{item.city}
|
||
</span>
|
||
</TableCell>
|
||
<TableCell>
|
||
<div className="font-mono text-sm text-blue-600 font-medium">{item.edge}</div>
|
||
</TableCell>
|
||
<TableCell>
|
||
<div className="font-mono text-sm text-green-600">{item.public}</div>
|
||
</TableCell>
|
||
<TableCell className="text-sm text-gray-900">{item.user}</TableCell>
|
||
<TableCell>
|
||
<div className="font-mono text-sm text-purple-600">{item.inner_ip}</div>
|
||
</TableCell>
|
||
<TableCell>
|
||
{getStatusBadge(item.ischange, '正常', '更新')}
|
||
</TableCell>
|
||
<TableCell>
|
||
{getOnlineStatus(item.isonline)}
|
||
</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">{totalItems}</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>
|
||
</div>
|
||
|
||
{/* 分页组件 - 仅在非MAC查询时显示 */}
|
||
{!isMacQuery && (
|
||
<Pagination
|
||
page={currentPage}
|
||
size={itemsPerPage}
|
||
total={totalItems}
|
||
onPageChange={handlePageChange}
|
||
onSizeChange={handleSizeChange}
|
||
className="mt-4"
|
||
/>
|
||
)}
|
||
</>
|
||
) : (
|
||
<div className="text-center py-12">
|
||
<div className="text-gray-400 text-4xl mb-4">📋</div>
|
||
<p className="text-gray-600">暂无网关配置数据</p>
|
||
</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>
|
||
)
|
||
} |