From 4f3671c8a6e09295bad8fb7f99cad6200f54da17 Mon Sep 17 00:00:00 2001 From: wmp <17516219072@163.com> Date: Sat, 20 Sep 2025 17:03:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=B8=83=E5=B1=80=E5=92=8C?= =?UTF-8?q?=E5=BE=AE=E8=B0=83=E6=95=B4=E9=A1=B5=E9=9D=A2=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/stats/route.ts | 3 +- .../dashboard/components/allocationStatus.tsx | 89 +++--- .../dashboard/components/cityNodeStats.tsx | 65 ++-- src/app/dashboard/components/edge.tsx | 300 +++++------------- .../dashboard/components/gatewayConfig.tsx | 200 ++++++------ src/app/dashboard/components/gatewayinfo.tsx | 220 +++++++++---- src/app/dashboard/components/settings.tsx | 59 ++-- src/app/dashboard/page.tsx | 14 +- src/components/ui/pagination.tsx | 222 +++++++++++-- src/components/ui/table.tsx | 8 +- 10 files changed, 636 insertions(+), 544 deletions(-) diff --git a/src/app/api/stats/route.ts b/src/app/api/stats/route.ts index 1cc20ef..4953426 100644 --- a/src/app/api/stats/route.ts +++ b/src/app/api/stats/route.ts @@ -153,9 +153,10 @@ async function getAllocationStatus() { // 获取节点信息 async function getEdgeNodes(request: NextRequest) { + try { const { searchParams } = new URL(request.url) - const threshold = searchParams.get('threshold') || '20' + const threshold = searchParams.get('threshold') || '0' const limit = searchParams.get('limit') || '100' // 使用参数化查询防止SQL注入 diff --git a/src/app/dashboard/components/allocationStatus.tsx b/src/app/dashboard/components/allocationStatus.tsx index 8df8df8..cd463ea 100644 --- a/src/app/dashboard/components/allocationStatus.tsx +++ b/src/app/dashboard/components/allocationStatus.tsx @@ -4,6 +4,7 @@ import { useEffect, useState, useCallback } from 'react' import { formatNumber, validateNumber } from '@/lib/formatters' import LoadingCard from '@/components/ui/loadingCard' import ErrorCard from '@/components/ui/errorCard' +import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table' interface AllocationStatus { city: string @@ -66,8 +67,13 @@ export default function AllocationStatus({ detailed = false }: { detailed?: bool return filterDate.toISOString().slice(0, 19).replace('T', ' ') }, [timeFilter, customTime]) + // 计算超额量 + const calculateOverage = (assigned: number, count: number) => { + const overage = assigned - count; + return Math.max(0, overage); + } - const fetchData = useCallback(async () => { + const fetchData = useCallback(async () => { try { setError(null) setLoading(true) @@ -86,7 +92,9 @@ export default function AllocationStatus({ detailed = false }: { detailed?: bool assigned: validateNumber(item.assigned), })) - setData(validatedData) + const sortedData = validatedData.sort((a, b) => b.count - a.count) + + setData(sortedData) } catch (error) { console.error('Failed to fetch allocation status:', error) setError(error instanceof Error ? error.message : 'Unknown error') @@ -102,10 +110,10 @@ export default function AllocationStatus({ detailed = false }: { detailed?: bool if (loading) return if (error) return - const problematicCities = data.filter(item => item.count < item.count) + const problematicCities = data.filter(item => item.assigned > item.count) return ( -
+

节点分配状态

{/* 时间筛选器 */} @@ -140,45 +148,54 @@ export default function AllocationStatus({ detailed = false }: { detailed?: bool onClick={fetchData} className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600" > - 刷新 + 查询
- -
-
-
{formatNumber(data.length)}
-
监控城市数量
-
-
-
{formatNumber(problematicCities.length)}
-
需关注城市
-
-
- - {detailed && ( -
- - - - - - - - - +
+
+
城市可用IP量分配IP量
+ + + 城市 + 可用IP量 + 分配IP量 + 超额量 + + + {data.map((item, index) => { + const overage = calculateOverage(item.assigned, item.count) return ( - - - - - + + {item.city} + {formatNumber(item.count)} + {formatNumber(item.assigned)} + + 0 ? 'text-red-600 font-medium' : ''}> + {formatNumber(overage)} + + + ) })} - -
{item.city}{formatNumber(item.count)}{formatNumber(item.assigned)}
+ +
- )} + +
+
+
{formatNumber(data.length)}
+
监控城市数量
+
+
+
{formatNumber(problematicCities.length)}
+
需关注城市
+
+
+
) } \ No newline at end of file diff --git a/src/app/dashboard/components/cityNodeStats.tsx b/src/app/dashboard/components/cityNodeStats.tsx index 4428834..6d7d705 100644 --- a/src/app/dashboard/components/cityNodeStats.tsx +++ b/src/app/dashboard/components/cityNodeStats.tsx @@ -1,6 +1,7 @@ 'use client' import { useEffect, useState } from 'react' +import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table' interface CityNode { city: string @@ -32,7 +33,7 @@ export default function CityNodeStats() { if (loading) { return ( -
+

城市节点数量分布

加载中...
@@ -40,7 +41,7 @@ export default function CityNodeStats() { } return ( -
+

城市节点数量分布

@@ -48,35 +49,37 @@ export default function CityNodeStats() {
-
- - - - - - - - - - - - {data.map((item, index) => ( - - - - - - - - ))} - -
城市节点数量Hash标签轮换顺位
{item.city} - {item.count} - {item.hash} - - {item.label} - - {item.offset}
+
+
+ + + + 城市 + 节点数量 + Hash + 标签 + 轮换顺位 + + + + {data.map((item, index) => ( + + {item.city} + {item.count} + {item.hash} + + + {item.label} + + {item.offset} + + ))} + +
+
) diff --git a/src/app/dashboard/components/edge.tsx b/src/app/dashboard/components/edge.tsx index c5dbc3b..2edb6ae 100644 --- a/src/app/dashboard/components/edge.tsx +++ b/src/app/dashboard/components/edge.tsx @@ -2,14 +2,8 @@ import { useEffect, useState } from 'react' import { validateNumber } from '@/lib/formatters' -import { - Pagination, - PaginationContent, - PaginationItem, - PaginationLink, - PaginationNext, - PaginationPrevious, -} from "@/components/ui/pagination" +import { Pagination } from '@/components/ui/pagination' +import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table' interface Edge { id: number @@ -27,8 +21,8 @@ export default function Edge() { const [data, setData] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) - const [idThreshold, setIdThreshold] = useState(20) - const [limit, setLimit] = useState(100) + const [idThreshold,] = useState(0) + const [limit,] = useState(100) // 分页状态 const [currentPage, setCurrentPage] = useState(1) @@ -43,7 +37,6 @@ export default function Edge() { try { setError(null) setLoading(true) - const response = await fetch(`/api/stats?type=edge_nodes&threshold=${threshold}&limit=${resultLimit}`) if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`) @@ -84,11 +77,6 @@ export default function Edge() { } } - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault() - fetchData(idThreshold, limit) - } - const formatBoolean = (value: boolean | number): string => { return value ? '是' : '否' } @@ -104,38 +92,16 @@ export default function Edge() { const indexOfLastItem = currentPage * itemsPerPage const indexOfFirstItem = indexOfLastItem - itemsPerPage const currentItems = data.slice(indexOfFirstItem, indexOfLastItem) - const totalPages = Math.ceil(totalItems / itemsPerPage) - // 生成页码按钮 - const renderPageNumbers = () => { - const pageNumbers = [] - const maxVisiblePages = 5 - - let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2)) - const endPage = Math.min(totalPages, startPage + maxVisiblePages - 1) - - if (endPage - startPage + 1 < maxVisiblePages) { - startPage = Math.max(1, endPage - maxVisiblePages + 1) - } - - for (let i = startPage; i <= endPage; i++) { - pageNumbers.push( - - { - e.preventDefault() - setCurrentPage(i) - }} - > - {i} - - - ) - } - - return pageNumbers + // 处理页码变化 + const handlePageChange = (page: number) => { + setCurrentPage(page) + } + + // 处理每页显示数量变化 + const handleSizeChange = (size: number) => { + setItemsPerPage(size) + setCurrentPage(1) // 重置到第一页 } if (loading) return ( @@ -160,57 +126,6 @@ export default function Edge() { return (
-
-

节点列表

- -
- - {/* 查询表单 */} -
-
-
- - setIdThreshold(Number(e.target.value))} - min="0" - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500" - /> -
-
- - setLimit(Number(e.target.value))} - min="1" - max="1000" - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500" - /> -
-
- -
-
-
- {data.length === 0 ? (
📋
@@ -218,135 +133,70 @@ export default function Edge() {
) : ( <> -
-

- 共找到 {totalItems} 个节点 - {idThreshold > 0 && ` (ID大于${idThreshold})`} -

-
- - {/* 每页显示数量选择器 */} -
-
- 每页显示 - - -
-
- -
- - - - - - - - - - - - - - - - {currentItems.map((item, index) => ( - - - - - - - - - - - - ))} - -
IDMAC地址城市公网IP运营商多IP节点独享IP设备类型在线时长
{item.id}{item.macaddr}{item.city}{item.public} - - {item.isp} - - - - {formatBoolean(item.single)} - - - - {formatBoolean(item.sole)} - - - - {item.arch} - - - {formatOnlineTime(item.online)} -
-
- - {/* 分页控件 */} -
-
- 显示 {indexOfFirstItem + 1} 到 {Math.min(indexOfLastItem, totalItems)} 条,共 {totalItems} 条记录 -
- - - - - { - e.preventDefault() - if (currentPage > 1) setCurrentPage(currentPage - 1) - }} - className={currentPage === 1 ? "pointer-events-none opacity-50" : ""} - /> - - - {renderPageNumbers()} - - - { - e.preventDefault() - if (currentPage < totalPages) setCurrentPage(currentPage + 1) - }} - className={currentPage === totalPages ? "pointer-events-none opacity-50" : ""} - /> - - - - -
- 更新时间: {new Date().toLocaleTimeString()} +
+
+ + + + ID + MAC地址 + 城市 + 公网IP + 运营商 + 多IP节点 + 独享IP + 设备类型 + 在线时长 + + + + {currentItems.map((item, index) => ( + + {item.id} + {item.macaddr} + {item.city} + {item.public} + + + {item.isp} + + + + {formatBoolean(item.single)} + + + + {formatBoolean(item.sole)} + + + + {item.arch} + + {formatOnlineTime(item.online)} + + ))} + +
+ {/* 使用 Pagination 组件 */} + )}
diff --git a/src/app/dashboard/components/gatewayConfig.tsx b/src/app/dashboard/components/gatewayConfig.tsx index 9829a4a..889d675 100644 --- a/src/app/dashboard/components/gatewayConfig.tsx +++ b/src/app/dashboard/components/gatewayConfig.tsx @@ -1,6 +1,7 @@ '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 @@ -28,25 +29,24 @@ function GatewayConfigContent() { setMacAddress(urlMac) fetchData(urlMac) } else { - // 如果没有mac参数,显示空状态或默认查询 - setData([]) - setSuccess('请输入MAC地址查询网关配置信息') + // 如果没有mac参数,显示所有网关配置 + setMacAddress('') + fetchData('') } }, [searchParams]) const fetchData = async (mac: string) => { - if (!mac.trim()) { - setError('请输入MAC地址') - setSuccess('') - setData([]) - return - } - setLoading(true) setError('') setSuccess('') + try { - const response = await fetch(`/api/stats?type=gateway_config&mac=${encodeURIComponent(mac)}`) + // 构建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) { @@ -55,7 +55,11 @@ function GatewayConfigContent() { // 检查返回的数据是否有效 if (!result || result.length === 0) { - setError(`未找到MAC地址为 ${mac} 的网关配置信息`) + if (mac.trim()) { + setError(`未找到MAC地址为 ${mac} 的网关配置信息`) + } else { + setError('未找到任何网关配置信息') + } setData([]) return } @@ -79,7 +83,6 @@ function GatewayConfigContent() { })) setData(validatedData) - setSuccess(`成功查询到 ${validatedData.length} 条网关配置信息`) } catch (error) { console.error('Failed to fetch gateway config:', error) setError(error instanceof Error ? error.message : '获取网关配置失败') @@ -91,19 +94,17 @@ function GatewayConfigContent() { const handleSubmit = (e: React.FormEvent) => { e.preventDefault() - if (macAddress.trim()) { - fetchData(macAddress) - } + fetchData(macAddress) } const getStatusBadge = (value: number, trueText: string = '是', falseText: string = '否') => { return ( - {value === 1 ? trueText : falseText} + {value === 0 ? trueText : falseText} ) } @@ -120,7 +121,7 @@ function GatewayConfigContent() { } return ( -
+

网关配置状态

@@ -138,7 +139,7 @@ function GatewayConfigContent() { type="text" value={macAddress} onChange={(e) => setMacAddress(e.target.value)} - placeholder="请输入MAC地址" + 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" />
) : data.length > 0 ? ( - <> - {/* 统计卡片 */} -
-
-
{data.length}
-
网关数量
-
-
-
- {data.filter(item => item.isonline === 1).length} -
-
在线网关
-
-
-
- {data.filter(item => item.ischange === 1).length} -
-
已更新配置
-
-
-
- {new Set(data.map(item => item.city)).size} -
-
覆盖城市
-
-
- + <> {/* 详细表格 */} -
- - - - - - - - - - - - - +
+
+
MAC地址城市内部账号IP地址内网入口配置更新在线状态
+ + + MAC地址 + 城市 + 内部账号 + IP地址 + 内网入口 + 配置更新 + 在线状态 + + + {data.map((item, index) => ( - - - - - - - - - + + +
{item.edge}
+
+ + + {item.city} + + {item.user} + +
{item.public}
+
+ +
{item.inner_ip}
+
+ +
{getStatusBadge(item.ischange, '正常', '需更新')}
+
+ +
{getOnlineStatus(item.isonline)}
+
+
))} - -
-
- {item.edge} -
-
- - {item.city} - - - {item.user} - -
- {item.public} -
-
-
- {item.inner_ip} -
-
- {getStatusBadge(item.ischange, '已更新', '未更新')} - - {getOnlineStatus(item.isonline)} -
+ +
- - {/* 分页信息 */} -
- 显示 1 到 {data.length} 条,共 {data.length} 条记录 - +
+
+
{data.length}
+
网关数量
+
+
+
+ {data.filter(item => item.isonline === 1).length} +
+
在线网关
+
+
+
+ {data.filter(item => item.ischange === 1).length} +
+
已更新配置
+
+
+
+ {new Set(data.map(item => item.city)).size} +
+
覆盖城市
+
+
) : ( -
-
🔍
-

暂无数据,请输入MAC地址查询网关配置信息

-

- 您可以通过上方的搜索框查询特定MAC地址的网关配置 -

-
+ <> )}
) diff --git a/src/app/dashboard/components/gatewayinfo.tsx b/src/app/dashboard/components/gatewayinfo.tsx index e088be8..6abca3c 100644 --- a/src/app/dashboard/components/gatewayinfo.tsx +++ b/src/app/dashboard/components/gatewayinfo.tsx @@ -2,6 +2,12 @@ import { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' +import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table' +import { Form, FormField } from '@/components/ui/form' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { z } from 'zod' +import { useForm } from 'react-hook-form' +import { zodResolver } from '@hookform/resolvers/zod' interface GatewayInfo { macaddr: string @@ -10,29 +16,74 @@ interface GatewayInfo { enable: number } +const filterSchema = z.object({ + status: z.string(), +}) + +type FilterSchema = z.infer + +// IP地址排序函数 +const sortByIpAddress = (a: string, b: string): number => { + const ipToNumber = (ip: string): number => { + const parts = ip.split('.').map(part => parseInt(part, 10)); + return (parts[0] << 24) + (parts[1] << 16) + (parts[2] << 8) + parts[3]; + }; + + return ipToNumber(a) - ipToNumber(b); +} + export default function Gatewayinfo() { const [data, setData] = useState([]) + const [filteredData, setFilteredData] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState('') - const router = useRouter() + const router = useRouter() + const form = useForm({ + resolver: zodResolver(filterSchema), + defaultValues: { + status: 'all', + }, + }) + + const { watch } = form + const statusFilter = watch('status') + useEffect(() => { fetchData() }, []) + useEffect(() => { + if (!data.length) return + + if (statusFilter === 'all') { + setFilteredData(data) + } else { + const filterValue = statusFilter || 'all' + if (filterValue === 'all') { + setFilteredData(data) + } else { + const enableValue = parseInt(filterValue) + setFilteredData(data.filter(item => item.enable === enableValue)) + } + } + }, [data, statusFilter]) + const fetchData = async () => { try { setLoading(true) setError('') const response = await fetch('/api/stats?type=gateway_info') - if (!response.ok) { throw new Error('获取网关信息失败') } - const result = await response.json() - - setData(result) + // const sortedData = result.sort(( a, b) => Number(a.inner_ip) - Number(b.inner_ip)) + const sortedData = result.sort((a: GatewayInfo, b: GatewayInfo) => + sortByIpAddress(a.inner_ip, b.inner_ip) + ) + setData(sortedData) + setFilteredData(sortedData) // 初始化时设置filteredData } catch (error) { console.error('Failed to fetch gateway info:', error) setError(error instanceof Error ? error.message : '获取网关信息失败') @@ -53,7 +104,7 @@ export default function Gatewayinfo() { if (loading) { return ( -
+

网关基本信息

加载网关信息中...
@@ -67,71 +118,108 @@ export default function Gatewayinfo() {
{error}
) - } + } return ( -
-

网关基本信息

- -
-
-
{data.length}
-
网关总数
-
-
-
- {data.filter(item => item.enable === 1).length} -
-
启用网关
-
-
-
- {data.filter(item => item.enable === 0).length} -
-
禁用网关
-
-
-
- {new Set(data.map(item => item.setid)).size} -
-
配置版本数
+
+
+
+ 网关基本信息 +
+ + ( +
+ 状态筛选: + +
+ )} + /> + +
+
+ +
+
+ + + + MAC地址 + 内网IP + 配置版本 + 状态 + + + + {filteredData.map((item, index) => ( + + + + + {item.inner_ip} + {item.setid} + + + {getStatusText(item.enable)} + + + + ))} + +
+
-
- - - - - - - - - - - {data.map((item, index) => ( - - - - - - - ))} - -
MAC地址内网IP配置版本状态
- - {item.inner_ip}{item.setid} - - {getStatusText(item.enable)} - -
+
+
+
{data.length}
+
网关总数
+
+
+
+ {data.filter(item => item.enable === 1).length} +
+
启用网关
+
+
+
+ {data.filter(item => item.enable === 0).length} +
+
禁用网关
+
+
+
+ {new Set(data.map(item => item.setid)).size} +
+
配置版本数
+
+
) diff --git a/src/app/dashboard/components/settings.tsx b/src/app/dashboard/components/settings.tsx index 2de8dc4..f32a17e 100644 --- a/src/app/dashboard/components/settings.tsx +++ b/src/app/dashboard/components/settings.tsx @@ -9,14 +9,7 @@ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/com import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form' import { User, Lock, Search, Trash2, Plus, X } from 'lucide-react' import { toast, Toaster } from 'sonner' -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" // 用户类型定义 interface UserData { @@ -152,7 +145,7 @@ const handleDeleteUser = async (userId: number) => { ) return ( -
+

用户管理

@@ -271,23 +264,24 @@ const handleDeleteUser = async (userId: number) => { )} - - - 用户列表 - - 管理系统中的所有用户账户 - -
- - setSearchTerm(e.target.value)} - /> -
-
- + {/* 用户列表直接显示在页面上 */} +
+
+

用户列表

+

管理系统中的所有用户账户

+
+ +
+ + setSearchTerm(e.target.value)} + /> +
+ +
@@ -299,7 +293,7 @@ const handleDeleteUser = async (userId: number) => { {filteredUsers.length === 0 ? ( - + 暂无用户数据 @@ -309,14 +303,13 @@ const handleDeleteUser = async (userId: number) => { {user.account} {new Date(user.createdAt).toLocaleDateString()} -
+
+ >
@@ -324,8 +317,8 @@ const handleDeleteUser = async (userId: number) => { )}
- - +
+
diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 4726ee4..89d75df 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -64,8 +64,8 @@ function DashboardContent() { } return ( -
-