From fd8fede3016e4b99b8eed84693a2c40d25402c4d Mon Sep 17 00:00:00 2001 From: wmp <17516219072@163.com> Date: Mon, 22 Sep 2025 15:11:09 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=AD=97=E6=AE=B5=E6=9E=9A?= =?UTF-8?q?=E4=B8=BE=E5=80=BC=E5=B1=95=E7=A4=BA&=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E5=88=86=E9=A1=B5=E6=BB=91=E8=BD=AE=E6=BB=9A?= =?UTF-8?q?=E5=8A=A8=E5=92=8C=E6=95=B0=E6=8D=AE=E6=80=BB=E6=95=B0=E5=B1=95?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/stats/route.ts | 51 +++++-- src/app/dashboard/components/edge.tsx | 130 ++++++++++++------ .../dashboard/components/gatewayConfig.tsx | 17 +-- src/app/dashboard/components/gatewayinfo.tsx | 28 ++-- 4 files changed, 150 insertions(+), 76 deletions(-) diff --git a/src/app/api/stats/route.ts b/src/app/api/stats/route.ts index 7db30ab..1218774 100644 --- a/src/app/api/stats/route.ts +++ b/src/app/api/stats/route.ts @@ -91,11 +91,23 @@ async function getCityConfigCount() { async function getCityNodeCount() { try { const result = await prisma.$queryRaw` - SELECT c.city, c.hash, c.label, COUNT(e.id) as count, c.offset - FROM cityhash c - LEFT JOIN edge e ON c.id = e.city_id - GROUP BY c.hash, c.city, c.label, c.offset - ORDER BY count DESC + select c.city, c.hash, c.label, e.count, c.\`offset\` + from + cityhash c + left join ( + select city_id, count(*) as count + from + edge + where + edge.active is true + group by + city_id + ) e + on c.id = e.city_id + group by + c.hash + order by + count desc ` return NextResponse.json(safeSerialize(result)) } catch (error) { @@ -158,21 +170,34 @@ async function getAllocationStatus(request: NextRequest) { // 获取节点信息 async function getEdgeNodes(request: NextRequest) { - try { const { searchParams } = new URL(request.url) - const threshold = searchParams.get('threshold') || '0' - const limit = searchParams.get('limit') || '100' + const offset = parseInt(searchParams.get('offset') || '0') + const limit = parseInt(searchParams.get('limit') || '100') + // 获取总数 - 使用类型断言 + const totalCountResult = await prisma.$queryRaw<[{ total: bigint }]>` + SELECT COUNT(*) as total + FROM edge + WHERE active = true + ` + const totalCount = Number(totalCountResult[0]?.total || 0) - // 使用参数化查询防止SQL注入 + // 获取分页数据 const result = await prisma.$queryRaw` SELECT edge.id, edge.macaddr, city, public, isp, single, sole, arch, online FROM edge LEFT JOIN cityhash ON cityhash.id = edge.city_id - WHERE edge.id > ${threshold} AND active = true - LIMIT ${limit} - ` - return NextResponse.json(safeSerialize(result)) + WHERE edge.active = true + ORDER BY edge.id + LIMIT ${limit} OFFSET ${offset} + ` + return NextResponse.json({ + data: safeSerialize(result), + totalCount: totalCount, + currentPage: Math.floor(offset / limit) + 1, + totalPages: Math.ceil(totalCount / limit) + }) + } catch (error) { console.error('Edge nodes query error:', error) return NextResponse.json({ error: '查询边缘节点失败' }, { status: 500 }) diff --git a/src/app/dashboard/components/edge.tsx b/src/app/dashboard/components/edge.tsx index 2edb6ae..58cc8cc 100644 --- a/src/app/dashboard/components/edge.tsx +++ b/src/app/dashboard/components/edge.tsx @@ -21,27 +21,28 @@ export default function Edge() { const [data, setData] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) - const [idThreshold,] = useState(0) - const [limit,] = useState(100) // 分页状态 const [currentPage, setCurrentPage] = useState(1) - const [itemsPerPage, setItemsPerPage] = useState(10) + const [itemsPerPage, setItemsPerPage] = useState(100) // 默认100条 const [totalItems, setTotalItems] = useState(0) useEffect(() => { fetchData() - }, []) + }, [currentPage, itemsPerPage]) // 监听页码和每页数量的变化 - const fetchData = async (threshold: number = idThreshold, resultLimit: number = limit) => { + const fetchData = async () => { try { setError(null) setLoading(true) - const response = await fetch(`/api/stats?type=edge_nodes&threshold=${threshold}&limit=${resultLimit}`) + + // 计算偏移量 + const offset = (currentPage - 1) * itemsPerPage + + const response = await fetch(`/api/stats?type=edge_nodes&offset=${offset}&limit=${itemsPerPage}`) if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`) const result = await response.json() - type ResultEdge = { id: number macaddr: string @@ -54,21 +55,20 @@ export default function Edge() { online: number } - const validatedData = (result as ResultEdge[]).map((item) => ({ + const validatedData = (result.data as ResultEdge[]).map((item) => ({ id: validateNumber(item.id), macaddr: item.macaddr || '', city: item.city || '', public: item.public || '', isp: item.isp || '', - single: item.single === 1 || item.single === true, - sole: item.sole === 1 || item.sole === true, + single: item.single, + sole: item.sole, arch: validateNumber(item.arch), online: validateNumber(item.online) })) setData(validatedData) - setTotalItems(validatedData.length) - setCurrentPage(1) // 重置到第一页 + setTotalItems(result.totalCount || 0) } catch (error) { console.error('Failed to fetch edge nodes:', error) setError(error instanceof Error ? error.message : '获取边缘节点数据失败') @@ -77,8 +77,66 @@ export default function Edge() { } } - const formatBoolean = (value: boolean | number): string => { - return value ? '是' : '否' +// 多IP节点格式化 +const formatMultiIP = (value: number | boolean): string => { + if (typeof value === 'number') { + switch (value) { + case 1: return '是' + case 0: return '否' + case -1: return '未知' + default: return `未知 (${value})` + } + } + return value ? '是' : '否' +} + +// 独享IP节点格式化 +const formatExclusiveIP = (value: number | boolean): string => { + if (typeof value === 'number') { + return value === 1 ? '是' : '否' + } + return value ? '是' : '否' +} + +// 多IP节点颜色 +const getMultiIPColor = (value: number | boolean): string => { + if (typeof value === 'number') { + switch (value) { + case 1: return 'bg-red-100 text-red-800' + case 0: return 'bg-green-100 text-green-800' + case -1: return 'bg-gray-100 text-gray-800' + default: return 'bg-gray-100 text-gray-800' + } + } + return value ? 'bg-red-100 text-red-800' : 'bg-green-100 text-green-800' +} + +// 独享IP节点颜色 +const getExclusiveIPColor = (value: number | boolean): string => { + if (typeof value === 'number') { + return value === 1 ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800' + } + return value ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800' +} + + const formatArchType = (arch: number): string => { + switch (arch) { + case 0: return '一代' + case 1: return '二代' + case 2: return 'AMD64' + case 3: return 'x86' + default: return `未知 (${arch})` + } + } + + const getArchColor = (arch: number): string => { + switch (arch) { + case 0: return 'bg-blue-100 text-blue-800' + case 1: return 'bg-green-100 text-green-800' + case 2: return 'bg-purple-100 text-purple-800' + case 3: return 'bg-orange-100 text-orange-800' + default: return 'bg-gray-100 text-gray-800' + } } const formatOnlineTime = (seconds: number): string => { @@ -88,11 +146,6 @@ export default function Edge() { return `${Math.floor(seconds / 86400)}天` } - // 计算分页数据 - const indexOfLastItem = currentPage * itemsPerPage - const indexOfFirstItem = indexOfLastItem - itemsPerPage - const currentItems = data.slice(indexOfFirstItem, indexOfLastItem) - // 处理页码变化 const handlePageChange = (page: number) => { setCurrentPage(page) @@ -125,7 +178,7 @@ export default function Edge() { ) return ( -
+
{data.length === 0 ? (
📋
@@ -138,7 +191,6 @@ export default function Edge() { - ID MAC地址 城市 公网IP @@ -150,9 +202,8 @@ export default function Edge() { - {currentItems.map((item, index) => ( - - {item.id} + {data.map((item, index) => ( + {item.macaddr} {item.city} {item.public} @@ -164,23 +215,23 @@ export default function Edge() { 'bg-gray-100 text-gray-800' }`}> {item.isp} - + + - - {formatBoolean(item.single)} - + + {formatMultiIP(item.single)} + + - - {formatBoolean(item.sole)} - + + {formatExclusiveIP(item.sole)} + + - - {item.arch} - + + {formatArchType(item.arch)} + + {formatOnlineTime(item.online)} ))} @@ -188,7 +239,8 @@ export default function Edge() {
- {/* 使用 Pagination 组件 */} + + {/* 分页 */} { + // 0是正常1是更新,正常(绿)+ 更新(红) return ( + value === 0 ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}`}> {value === 0 ? trueText : falseText} ) } const getOnlineStatus = (isonline: number) => { + // 0是空闲1是在用,在用(红)+ 空闲(绿) return (
-
- {getStatusBadge(isonline, '在线', '离线')} +
+ {getStatusBadge(isonline, '空闲', '在用')}
) } @@ -189,7 +186,7 @@ function GatewayConfigContent() { IP地址 内网入口 配置更新 - 在线状态 + 在用状态 @@ -213,7 +210,7 @@ function GatewayConfigContent() {
{item.inner_ip}
-
{getStatusBadge(item.ischange, '正常', '需更新')}
+
{getStatusBadge(item.ischange, '正常', '更新')}
{getOnlineStatus(item.isonline)}
diff --git a/src/app/dashboard/components/gatewayinfo.tsx b/src/app/dashboard/components/gatewayinfo.tsx index 6abca3c..0059229 100644 --- a/src/app/dashboard/components/gatewayinfo.tsx +++ b/src/app/dashboard/components/gatewayinfo.tsx @@ -42,7 +42,7 @@ export default function Gatewayinfo() { const form = useForm({ resolver: zodResolver(filterSchema), defaultValues: { - status: 'all', + status: '1', }, }) @@ -53,21 +53,21 @@ export default function Gatewayinfo() { fetchData() }, []) - useEffect(() => { - if (!data.length) return - - if (statusFilter === 'all') { +useEffect(() => { + if (!data.length) return + + if (statusFilter === 'all') { + setFilteredData(data) + } else { + const enableValue = parseInt(statusFilter) + // 添加 NaN 检查 + if (isNaN(enableValue)) { setFilteredData(data) } else { - const filterValue = statusFilter || 'all' - if (filterValue === 'all') { - setFilteredData(data) - } else { - const enableValue = parseInt(filterValue) - setFilteredData(data.filter(item => item.enable === enableValue)) - } + setFilteredData(data.filter(item => item.enable === enableValue)) } - }, [data, statusFilter]) + } +}, [data, statusFilter]) const fetchData = async () => { try { @@ -136,7 +136,7 @@ export default function Gatewayinfo() {