160 lines
5.9 KiB
TypeScript
160 lines
5.9 KiB
TypeScript
'use client'
|
|
import { useEffect, useState, Suspense } from 'react'
|
|
import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table'
|
|
import { getGatewayInfo, getGatewayConfig, type GatewayConfig, type GatewayInfo } from '@/actions/stats'
|
|
import { toast } from 'sonner'
|
|
import { Page } from '@/components/page'
|
|
|
|
export default function GatewayConfigs() {
|
|
const [gateways, setGateways] = useState<Map<string, GatewayInfo>>(new Map())
|
|
const [data, setData] = useState<Map<string, Map<string, GatewayConfig | undefined>>>(new Map())
|
|
|
|
// 初始化数据
|
|
const initData = async () => {
|
|
const now = Date.now()
|
|
try {
|
|
// 固定端口信息
|
|
const slots = new Set<string>()
|
|
for (let i = 2; i <= 251; i++) {
|
|
slots.add(`172.30.168.${i}`)
|
|
}
|
|
|
|
// 获取网关信息
|
|
const resp = await getGatewayInfo()
|
|
if (!resp.success) {
|
|
throw new Error(`查询网关信息失败:${resp.error}`)
|
|
}
|
|
|
|
const gateways = resp.data
|
|
const findGateways = gateways.reduce((map, gateway) => {
|
|
map.set(gateway.macaddr, gateway)
|
|
return map
|
|
}, new Map<string, GatewayInfo>())
|
|
setGateways(findGateways)
|
|
|
|
// 获取网关配置
|
|
const data = new Map<string, Map<string, GatewayConfig | undefined>>()
|
|
for (const slot of slots) {
|
|
data.set(slot, new Map<string, GatewayConfig>())
|
|
}
|
|
|
|
await Promise.all(gateways.map((gateway, index) => {
|
|
return new Promise<void>(async (resolve) => {
|
|
const resp = await getGatewayConfig(1, { mac: gateway.macaddr })
|
|
if (!resp.success) {
|
|
throw new Error(`查询网关 ${gateway.inner_ip} 配置失败:${resp.error}`)
|
|
}
|
|
|
|
const configs = resp.data.items
|
|
const findConfig = configs.reduce((map, config) => {
|
|
map.set(config.inner_ip, config)
|
|
return map
|
|
}, new Map<string, GatewayConfig>())
|
|
|
|
for (const slot of slots) {
|
|
data.get(slot)!.set(gateway.macaddr, findConfig.get(slot))
|
|
}
|
|
|
|
resolve()
|
|
})
|
|
}))
|
|
|
|
setData(data)
|
|
}
|
|
catch (error) {
|
|
toast.error(`初始化页面数据失败:${(error as Error).message}`)
|
|
}
|
|
console.log('初始化数据耗时', Date.now() - now, 'ms')
|
|
}
|
|
|
|
useEffect(() => {
|
|
initData()
|
|
}, [])
|
|
|
|
return (
|
|
<Page className="gap-3">
|
|
<div className="flex gap-2">
|
|
<div className="flex items-center">
|
|
<div className="w-3 h-3 bg-green-500 rounded-full mr-2"></div>
|
|
<span className="text-sm font-medium">在线 {Array.from(gateways.values()).filter(item => item.enable).length}</span>
|
|
</div>
|
|
<div className="flex items-center">
|
|
<div className="w-3 h-3 bg-red-500 rounded-full mr-2"></div>
|
|
<span className="text-sm font-medium">离线 {Array.from(gateways.values()).filter(item => !item.enable).length}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead className="border-r sticky left-0 bg-gray-50">端口</TableHead>
|
|
{gateways.values().map((pair, index) => (
|
|
<TableHead key={index} className="border-r h-auto">
|
|
<div className="flex flex-col items-center">
|
|
<div className="font-medium">{pair.inner_ip}</div>
|
|
<div className="text-xs text-gray-500 mt-1">{pair.macaddr}</div>
|
|
</div>
|
|
</TableHead>
|
|
)).toArray()}
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{data.entries().map(([slot, configs], rowIndex) => (
|
|
<TableRow key={rowIndex}>
|
|
<TableCell className="border-r sticky left-0 bg-background">{slot}</TableCell>
|
|
{configs.entries().map(([_, config], colIndex) => {
|
|
if (!config) {
|
|
return (
|
|
<TableCell key={colIndex} className="not-last:border-r">
|
|
-
|
|
</TableCell>
|
|
)
|
|
}
|
|
|
|
const statusConfig = {
|
|
ischange: config.ischange === 0
|
|
? { bg: 'bg-green-100', text: 'text-green-800', label: '正常' }
|
|
: { bg: 'bg-yellow-100', text: 'text-yellow-800', label: '更新' },
|
|
isonline: config.isonline === 0
|
|
? { bg: 'bg-green-100', text: 'text-green-800', label: '空闲' }
|
|
: { bg: 'bg-blue-100', text: 'text-blue-800', label: '在用' },
|
|
}
|
|
return (
|
|
<TableCell key={`${colIndex}`} className="not-last:border-r">
|
|
<div key={colIndex} className="flex flex-col gap-1">
|
|
<div className="text-sm font-medium">{config.public}</div>
|
|
<div className="text-xs font-medium flex justify-between">
|
|
<span>{config.user}</span>
|
|
<span>{shrinkCity(config.city || '?')}</span>
|
|
</div>
|
|
<div className="flex justify-between items-center">
|
|
<span className={`px-2 py-0.5 rounded text-xs font-medium ${statusConfig.ischange.bg} ${statusConfig.ischange.text}`}>
|
|
{statusConfig.ischange.label}
|
|
</span>
|
|
<span className={`px-2 py-0.5 rounded text-xs font-medium ${statusConfig.isonline.bg} ${statusConfig.isonline.text}`}>
|
|
{statusConfig.isonline.label}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</TableCell>
|
|
)
|
|
}).toArray()}
|
|
</TableRow>
|
|
)).toArray()}
|
|
</TableBody>
|
|
</Table>
|
|
</Page>
|
|
)
|
|
}
|
|
|
|
function shrinkCity(city: string) {
|
|
switch (city) {
|
|
case '黔东南苗族侗族自治州':
|
|
return '黔东南'
|
|
case '延边朝鲜族自治州':
|
|
return '延边'
|
|
default:
|
|
return city
|
|
}
|
|
}
|