Files
admin/src/app/(root)/proxy/nodes/page.tsx
2026-04-02 13:13:59 +08:00

626 lines
24 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 Link from "next/link"
import { useEffect, useState } from "react"
// 定义节点数据接口
interface ProxyNode {
id: string
ipAddress: string
location: {
country: string
region: string
}
type: string
status: "online" | "offline" | "warning"
responseTime: number
lastCheckTime: string
pool: string
isStatic: boolean
}
export type ProxyNodesPageProps = {}
export default function ProxyNodesPage(props: ProxyNodesPageProps) {
const [loading, setLoading] = useState<boolean>(true)
const [nodes, setNodes] = useState<ProxyNode[]>([])
const [searchTerm, setSearchTerm] = useState<string>("")
const [filterStatus, setFilterStatus] = useState<string>("all")
const [filterType, setFilterType] = useState<string>("all")
const [filterPool, setFilterPool] = useState<string>("all")
// 模拟数据加载
useEffect(() => {
setTimeout(() => {
setNodes([
{
id: "ip-1",
ipAddress: "203.45.167.82",
location: { country: "美国", region: "纽约" },
type: "数据中心",
status: "online",
responseTime: 126,
lastCheckTime: "2024-05-10 15:30:22",
pool: "北美专用池",
isStatic: true,
},
{
id: "ip-2",
ipAddress: "185.72.193.54",
location: { country: "德国", region: "法兰克福" },
type: "住宅",
status: "online",
responseTime: 158,
lastCheckTime: "2024-05-10 15:28:45",
pool: "欧洲高速池",
isStatic: false,
},
{
id: "ip-3",
ipAddress: "118.96.244.105",
location: { country: "新加坡", region: "中心区" },
type: "移动",
status: "warning",
responseTime: 312,
lastCheckTime: "2024-05-10 15:25:12",
pool: "亚太地区池",
isStatic: false,
},
{
id: "ip-4",
ipAddress: "45.178.29.6",
location: { country: "加拿大", region: "多伦多" },
type: "数据中心",
status: "online",
responseTime: 143,
lastCheckTime: "2024-05-10 15:23:08",
pool: "北美专用池",
isStatic: false,
},
{
id: "ip-5",
ipAddress: "79.114.83.201",
location: { country: "英国", region: "伦敦" },
type: "住宅",
status: "offline",
responseTime: 0,
lastCheckTime: "2024-05-10 15:18:33",
pool: "欧洲高速池",
isStatic: false,
},
{
id: "ip-6",
ipAddress: "164.83.219.47",
location: { country: "日本", region: "东京" },
type: "住宅",
status: "online",
responseTime: 87,
lastCheckTime: "2024-05-10 15:15:21",
pool: "亚太地区池",
isStatic: true,
},
{
id: "ip-7",
ipAddress: "221.67.93.143",
location: { country: "中国", region: "上海" },
type: "移动",
status: "online",
responseTime: 104,
lastCheckTime: "2024-05-10 15:10:46",
pool: "亚太地区池",
isStatic: false,
},
{
id: "ip-8",
ipAddress: "37.209.148.72",
location: { country: "法国", region: "巴黎" },
type: "数据中心",
status: "warning",
responseTime: 276,
lastCheckTime: "2024-05-10 15:05:19",
pool: "欧洲高速池",
isStatic: false,
},
])
setLoading(false)
}, 800)
}, [])
// 过滤节点数据
const filteredNodes = nodes.filter(node => {
return (
(searchTerm === "" ||
node.ipAddress.includes(searchTerm) ||
node.location.country.includes(searchTerm) ||
node.pool.includes(searchTerm)) &&
(filterStatus === "all" ||
(filterStatus === "online" && node.status === "online") ||
(filterStatus === "offline" && node.status === "offline") ||
(filterStatus === "warning" && node.status === "warning")) &&
(filterType === "all" || node.type === filterType) &&
(filterPool === "all" || node.pool === filterPool)
)
})
return (
<div className="space-y-5">
{/* 概览区域 - 使用色块和留白风格 */}
<div className="border border-gray-200 rounded-lg overflow-hidden">
{/* 标题区域 - 简洁风格 */}
<div className="bg-white px-5 py-4">
<div className="flex justify-between items-center">
<h1 className="text-lg font-bold text-gray-900"></h1>
<div className="flex gap-2">
<button className="bg-gray-50 border border-gray-200 text-gray-700 px-3 py-1.5 rounded-md text-sm font-medium flex items-center hover:bg-gray-100">
<svg
className="w-4 h-4 mr-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"
/>
</svg>
</button>
<button className="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1.5 rounded-md text-sm font-medium flex items-center">
<svg
className="w-4 h-4 mr-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
/>
</svg>
</button>
</div>
</div>
<p className="mt-1 text-sm text-gray-500">
IP资源
</p>
</div>
{/* 统计信息区域 - 色块风格 */}
<div className="grid grid-cols-4 gap-px bg-gray-100">
{/* 总IP数量 */}
<div className="bg-white p-4">
<div className="flex items-center">
<div className="w-10 h-10 flex items-center justify-center bg-blue-50 rounded-md">
<svg
className="h-5 w-5 text-blue-600"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9"
/>
</svg>
</div>
<div className="ml-3">
<p className="text-xs font-medium text-gray-500">IP数量</p>
<div className="text-lg font-semibold text-gray-900">
152,487
</div>
</div>
</div>
</div>
{/* 在线IP */}
<div className="bg-white p-4">
<div className="flex items-center">
<div className="w-10 h-10 flex items-center justify-center bg-green-50 rounded-md">
<svg
className="h-5 w-5 text-green-600"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</div>
<div className="ml-3">
<div className="flex items-center">
<p className="text-xs font-medium text-gray-500">线IP</p>
<span className="ml-2 text-xs font-medium text-green-600">
91%
</span>
</div>
<div className="text-lg font-semibold text-gray-900">
138,954
</div>
</div>
</div>
</div>
{/* IP池分布 */}
<div className="bg-white p-4">
<div className="flex items-center">
<div className="w-10 h-10 flex items-center justify-center bg-indigo-50 rounded-md">
<svg
className="h-5 w-5 text-indigo-600"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
/>
</svg>
</div>
<div className="ml-3">
<div className="flex items-center">
<p className="text-xs font-medium text-gray-500">IP池分布</p>
<span className="ml-2 text-xs font-medium text-gray-500">
5
</span>
</div>
<div className="text-lg font-semibold text-gray-900">12</div>
</div>
</div>
</div>
{/* 异常IP */}
<div className="bg-white p-4">
<div className="flex items-center">
<div className="w-10 h-10 flex items-center justify-center bg-red-50 rounded-md">
<svg
className="h-5 w-5 text-red-600"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
</div>
<div className="ml-3">
<div className="flex items-center">
<p className="text-xs font-medium text-gray-500">IP</p>
<span className="ml-2 text-xs font-medium text-red-600">
</span>
</div>
<div className="text-lg font-semibold text-gray-900">1,205</div>
</div>
</div>
</div>
</div>
</div>
{/* 数据展示 */}
<div className="border border-gray-200 rounded-lg overflow-hidden">
{/* 筛选搜索区域 */}
<div className="bg-white p-4 border-b border-gray-200">
<div className="grid grid-cols-1 gap-4 md:grid-cols-12">
{/* 搜索框 */}
<div className="relative md:col-span-5">
<input
type="text"
placeholder="搜索IP地址、地区或标签..."
className="w-full px-3 py-2 bg-gray-50 border border-gray-300 rounded-md focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 text-sm"
/>
<svg
className="absolute right-3 top-2.5 h-4 w-4 text-gray-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
</div>
{/* 筛选区域 */}
<div className="flex space-x-3 md:col-span-7">
<select
className="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 text-sm bg-white text-gray-700 flex-1"
value={filterStatus}
onChange={e => setFilterStatus(e.target.value)}
>
<option value="all"></option>
<option value="online">线</option>
<option value="offline">线</option>
<option value="warning"></option>
</select>
<select
className="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 text-sm bg-white text-gray-700 flex-1"
value={filterType}
onChange={e => setFilterType(e.target.value)}
>
<option value="all"></option>
<option value="数据中心"></option>
<option value="住宅"></option>
<option value="移动"></option>
</select>
<select
className="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 text-sm bg-white text-gray-700 flex-1"
value={filterPool}
onChange={e => setFilterPool(e.target.value)}
>
<option value="all"></option>
<option value="北美专用池"></option>
<option value="欧洲高速池"></option>
<option value="亚太地区池"></option>
</select>
</div>
</div>
</div>
{/* IP表格区域 */}
{loading ? (
<div className="p-12 flex justify-center items-center bg-white">
<svg
className="animate-spin h-6 w-6 text-blue-600"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
</div>
) : (
<div className="overflow-x-auto bg-white">
<table className="min-w-full">
<thead>
<tr>
<th
scope="col"
className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-b border-gray-200 bg-gray-50"
>
IP地址
</th>
<th
scope="col"
className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-b border-gray-200 bg-gray-50"
>
</th>
<th
scope="col"
className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-b border-gray-200 bg-gray-50"
>
</th>
<th
scope="col"
className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-b border-gray-200 bg-gray-50"
>
</th>
<th
scope="col"
className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-b border-gray-200 bg-gray-50"
>
</th>
<th
scope="col"
className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-b border-gray-200 bg-gray-50"
>
</th>
<th
scope="col"
className="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider border-b border-gray-200 bg-gray-50"
>
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{filteredNodes.map((node, index) => (
<tr
key={node.id}
className={index % 2 === 0 ? "bg-white" : "bg-gray-50"}
>
<td className="px-4 py-3 whitespace-nowrap text-sm">
<div className="flex items-center">
<span
className={`w-2 h-2 rounded-full mr-2 ${
node.status === "online"
? "bg-green-500"
: node.status === "offline"
? "bg-red-500"
: "bg-yellow-500"
}`}
></span>
<span className="font-medium text-gray-900">
{node.ipAddress}
</span>
{node.isStatic && (
<span className="ml-2 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
</span>
)}
</div>
</td>
<td className="px-4 py-3 whitespace-nowrap text-sm">
<div className="text-gray-900">
{node.location.country}
</div>
<div className="text-xs text-gray-500">
{node.location.region}
</div>
</td>
<td className="px-4 py-3 whitespace-nowrap">
<span
className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${
node.type === "数据中心"
? "bg-purple-100 text-purple-800"
: node.type === "住宅"
? "bg-blue-100 text-blue-800"
: "bg-indigo-100 text-indigo-800"
}`}
>
{node.type}
</span>
</td>
<td className="px-4 py-3 whitespace-nowrap text-sm">
<span
className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${
node.status === "online"
? "bg-green-100 text-green-800"
: node.status === "offline"
? "bg-red-100 text-red-800"
: "bg-yellow-100 text-yellow-800"
}`}
>
{node.status === "online"
? "在线"
: node.status === "offline"
? "离线"
: "异常"}
</span>
</td>
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
{node.responseTime > 0 ? (
<div className="flex items-center">
<span
className={`font-medium ${
node.responseTime < 150
? "text-green-600"
: node.responseTime < 250
? "text-yellow-600"
: "text-red-600"
}`}
>
{node.responseTime} ms
</span>
</div>
) : (
<span className="text-gray-400">-</span>
)}
</td>
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
{node.pool}
</td>
<td className="px-4 py-3 whitespace-nowrap text-right text-sm font-medium space-x-2">
<Link
href={`/proxy/nodes/${node.id}`}
className="text-blue-600 hover:text-blue-900"
>
</Link>
<button className="text-gray-600 hover:text-gray-900">
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{/* 分页控制 */}
<div className="bg-gray-50 px-5 py-3 border-t border-gray-200">
<div className="flex items-center justify-between">
<div className="text-sm text-gray-700">
<span className="font-medium">1</span> {" "}
<span className="font-medium">8</span> {" "}
<span className="font-medium">152,487</span>
</div>
<div>
<nav className="relative z-0 inline-flex -space-x-px">
<button className="relative inline-flex items-center px-2 py-1.5 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
<svg
className="h-4 w-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
clipRule="evenodd"
/>
</svg>
</button>
<button className="relative inline-flex items-center px-3 py-1.5 border border-gray-300 bg-white text-sm font-medium hover:bg-gray-50 text-blue-600 bg-blue-50 border-blue-300">
1
</button>
<button className="relative inline-flex items-center px-3 py-1.5 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50">
2
</button>
<button className="relative inline-flex items-center px-3 py-1.5 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50">
3
</button>
<span className="relative inline-flex items-center px-3 py-1.5 border border-gray-300 bg-white text-sm font-medium text-gray-700">
...
</span>
<button className="relative inline-flex items-center px-3 py-1.5 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50">
152
</button>
<button className="relative inline-flex items-center px-2 py-1.5 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
<svg
className="h-4 w-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clipRule="evenodd"
/>
</svg>
</button>
</nav>
</div>
</div>
</div>
</div>
</div>
)
}