435 lines
17 KiB
TypeScript
435 lines
17 KiB
TypeScript
|
|
import Link from 'next/link'
|
|||
|
|
|
|||
|
|
export type DashboardPageProps = {}
|
|||
|
|
|
|||
|
|
export default function DashboardPage(props: DashboardPageProps) {
|
|||
|
|
return (
|
|||
|
|
<div className="space-y-5">
|
|||
|
|
{/* 欢迎区域 - 全宽 */}
|
|||
|
|
<div className="bg-white border border-gray-200 rounded-md">
|
|||
|
|
<div className="flex items-center justify-between p-5">
|
|||
|
|
<div>
|
|||
|
|
<h1 className="text-xl font-bold text-gray-800">IP代理管理控制台</h1>
|
|||
|
|
<p className="text-gray-500 mt-1">上次更新: {new Date().toLocaleString('zh-CN')}</p>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex space-x-3">
|
|||
|
|
<button
|
|||
|
|
className="px-4 py-2 bg-gray-100 text-gray-700 border border-gray-200 rounded-md hover:bg-gray-200 transition-colors text-sm font-medium">
|
|||
|
|
查看使用报告
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
className="px-4 py-2 bg-blue-600 text-white border border-blue-700 rounded-md hover:bg-blue-700 transition-colors text-sm font-medium">
|
|||
|
|
添加新代理
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 主体内容 - 双栏布局 */}
|
|||
|
|
<div className="flex flex-col lg:flex-row space-y-5 lg:space-y-0 lg:space-x-5">
|
|||
|
|
{/* 左侧栏 - 占比较大 */}
|
|||
|
|
<div className="w-full lg:w-8/12 space-y-5">
|
|||
|
|
{/* 代理资源统计卡片组 */}
|
|||
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-5">
|
|||
|
|
<DataCard
|
|||
|
|
title="在线代理"
|
|||
|
|
value="14,283"
|
|||
|
|
change="+12.5%"
|
|||
|
|
isIncrease={true}
|
|||
|
|
icon={
|
|||
|
|
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|||
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 12h14M12 5l7 7-7 7"/>
|
|||
|
|
</svg>
|
|||
|
|
}
|
|||
|
|
/>
|
|||
|
|
<DataCard
|
|||
|
|
title="总请求数"
|
|||
|
|
value="851,492"
|
|||
|
|
change="+8.2%"
|
|||
|
|
isIncrease={true}
|
|||
|
|
icon={
|
|||
|
|
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|||
|
|
<path
|
|||
|
|
strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"/>
|
|||
|
|
</svg>
|
|||
|
|
}
|
|||
|
|
/>
|
|||
|
|
<DataCard
|
|||
|
|
title="成功率"
|
|||
|
|
value="98.5%"
|
|||
|
|
change="+2.4%"
|
|||
|
|
isIncrease={true}
|
|||
|
|
icon={
|
|||
|
|
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|||
|
|
<path
|
|||
|
|
strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
|||
|
|
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|||
|
|
</svg>
|
|||
|
|
}
|
|||
|
|
/>
|
|||
|
|
<DataCard
|
|||
|
|
title="平均响应时间"
|
|||
|
|
value="0.82s"
|
|||
|
|
change="-12.3%"
|
|||
|
|
isIncrease={true}
|
|||
|
|
icon={
|
|||
|
|
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|||
|
|
<path
|
|||
|
|
strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
|||
|
|
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|||
|
|
</svg>
|
|||
|
|
}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 代理使用图表 */}
|
|||
|
|
<div className="bg-white border border-gray-200 rounded-md">
|
|||
|
|
<div className="flex justify-between items-center p-5 border-b border-gray-200">
|
|||
|
|
<h2 className="font-bold text-gray-800">代理使用趋势</h2>
|
|||
|
|
<div className="flex items-center space-x-2">
|
|||
|
|
<select className="text-sm border border-gray-200 rounded-md px-3 py-1 bg-white">
|
|||
|
|
<option>今日</option>
|
|||
|
|
<option>本周</option>
|
|||
|
|
<option>本月</option>
|
|||
|
|
<option>全年</option>
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div className="h-80 bg-white p-5 flex items-center justify-center text-gray-400 border-b border-gray-200">
|
|||
|
|
请求量与响应时间统计图表
|
|||
|
|
</div>
|
|||
|
|
<div className="flex justify-between p-5 text-sm">
|
|||
|
|
<div className="text-gray-500">
|
|||
|
|
<span className="inline-block w-3 h-3 rounded-full bg-blue-500 mr-1"></span>
|
|||
|
|
请求数量
|
|||
|
|
</div>
|
|||
|
|
<div className="text-gray-500">
|
|||
|
|
<span className="inline-block w-3 h-3 rounded-full bg-green-500 mr-1"></span>
|
|||
|
|
成功率
|
|||
|
|
</div>
|
|||
|
|
<div className="text-gray-500">
|
|||
|
|
<span className="inline-block w-3 h-3 rounded-full bg-orange-500 mr-1"></span>
|
|||
|
|
响应时间
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* IP代理列表 */}
|
|||
|
|
<div className="bg-white border border-gray-200 rounded-md">
|
|||
|
|
<div className="p-5 border-b border-gray-200">
|
|||
|
|
<div className="flex justify-between items-center">
|
|||
|
|
<h2 className="font-bold text-gray-800">活跃代理IP</h2>
|
|||
|
|
<Link href="/proxies" className="text-blue-600 text-sm hover:underline">查看全部</Link>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div className="overflow-x-auto">
|
|||
|
|
<table className="w-full">
|
|||
|
|
<thead>
|
|||
|
|
<tr
|
|||
|
|
className="bg-gray-50 text-left text-xs text-gray-500 uppercase tracking-wider border-b border-gray-200">
|
|||
|
|
<th className="px-5 py-3">IP地址</th>
|
|||
|
|
<th className="px-5 py-3">位置</th>
|
|||
|
|
<th className="px-5 py-3">状态</th>
|
|||
|
|
<th className="px-5 py-3">请求次数</th>
|
|||
|
|
<th className="px-5 py-3">成功率</th>
|
|||
|
|
<th className="px-5 py-3">操作</th>
|
|||
|
|
</tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody className="divide-y divide-gray-200">
|
|||
|
|
{[1, 2, 3, 4, 5].map((item) => (
|
|||
|
|
<ContentRow
|
|||
|
|
key={item}
|
|||
|
|
ip={`192.168.${item}.${item * 10}`}
|
|||
|
|
location={item % 2 === 0 ? '中国' : '美国'}
|
|||
|
|
status={item % 3 === 0 ? 'error' : item % 2 === 0 ? 'warning' : 'active'}
|
|||
|
|
requests={Math.floor(Math.random() * 10000)}
|
|||
|
|
successRate={`${95 + Math.floor(Math.random() * 5)}%`}
|
|||
|
|
/>
|
|||
|
|
))}
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
</div>
|
|||
|
|
<div className="p-3 bg-gray-50 border-t border-gray-200 flex justify-end">
|
|||
|
|
<button className="px-3 py-1 bg-white border border-gray-200 rounded-md text-sm mr-2">上一页</button>
|
|||
|
|
<button className="px-3 py-1 bg-blue-600 text-white rounded-md text-sm">下一页</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 右侧栏 - 占比较小 */}
|
|||
|
|
<div className="w-full lg:w-4/12 space-y-5">
|
|||
|
|
{/* 代理资源分布 */}
|
|||
|
|
<div className="bg-white border border-gray-200 rounded-md">
|
|||
|
|
<div className="p-5 border-b border-gray-200">
|
|||
|
|
<h2 className="font-bold text-gray-800">资源分布</h2>
|
|||
|
|
</div>
|
|||
|
|
<div className="h-64 bg-white p-5 flex items-center justify-center text-gray-400">
|
|||
|
|
IP地区分布饼图
|
|||
|
|
</div>
|
|||
|
|
<div className="grid grid-cols-2 gap-3 p-5 border-t border-gray-200">
|
|||
|
|
<div className="flex justify-between p-2 bg-gray-50 rounded-md border border-gray-200">
|
|||
|
|
<span className="text-xs text-gray-600">中国</span>
|
|||
|
|
<span className="text-xs font-medium">42%</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex justify-between p-2 bg-gray-50 rounded-md border border-gray-200">
|
|||
|
|
<span className="text-xs text-gray-600">美国</span>
|
|||
|
|
<span className="text-xs font-medium">28%</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex justify-between p-2 bg-gray-50 rounded-md border border-gray-200">
|
|||
|
|
<span className="text-xs text-gray-600">欧洲</span>
|
|||
|
|
<span className="text-xs font-medium">16%</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex justify-between p-2 bg-gray-50 rounded-md border border-gray-200">
|
|||
|
|
<span className="text-xs text-gray-600">其他</span>
|
|||
|
|
<span className="text-xs font-medium">14%</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 系统告警 */}
|
|||
|
|
<div className="bg-white border border-gray-200 rounded-md">
|
|||
|
|
<div className="p-5 border-b border-gray-200">
|
|||
|
|
<div className="flex justify-between items-center">
|
|||
|
|
<h2 className="font-bold text-gray-800">告警通知</h2>
|
|||
|
|
<span className="px-2 py-1 bg-red-100 text-red-800 text-xs rounded-full">3 个新告警</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div className="p-3 space-y-3">
|
|||
|
|
<AlertItem
|
|||
|
|
title="IP不足告警"
|
|||
|
|
severity="high"
|
|||
|
|
time="10分钟前"
|
|||
|
|
message="特定地区(美国加州)代理IP资源不足,影响用户请求。"
|
|||
|
|
/>
|
|||
|
|
<AlertItem
|
|||
|
|
title="响应延迟"
|
|||
|
|
severity="medium"
|
|||
|
|
time="30分钟前"
|
|||
|
|
message="欧洲区域代理响应时间超过阈值(1.5s),请检查网络状况。"
|
|||
|
|
/>
|
|||
|
|
<AlertItem
|
|||
|
|
title="异常请求"
|
|||
|
|
severity="low"
|
|||
|
|
time="2小时前"
|
|||
|
|
message="检测到异常请求模式,可能存在爬虫攻击行为。"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="p-4 border-t border-gray-200">
|
|||
|
|
<button
|
|||
|
|
className="w-full py-2 bg-gray-100 text-gray-600 border border-gray-200 rounded-md text-sm hover:bg-gray-200 transition-colors">
|
|||
|
|
查看全部告警
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 系统状态 */}
|
|||
|
|
<div className="bg-white border border-gray-200 rounded-md">
|
|||
|
|
<div className="p-5 border-b border-gray-200">
|
|||
|
|
<div className="flex justify-between items-center">
|
|||
|
|
<h2 className="font-bold text-gray-800">系统状态</h2>
|
|||
|
|
<span className="px-2 py-1 bg-green-100 text-green-800 text-xs rounded-full font-medium">运行正常</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div className="p-3 space-y-3">
|
|||
|
|
<StatusBar
|
|||
|
|
title="代理服务器负载"
|
|||
|
|
value={28}
|
|||
|
|
status="normal"
|
|||
|
|
/>
|
|||
|
|
<StatusBar
|
|||
|
|
title="带宽使用率"
|
|||
|
|
value={65}
|
|||
|
|
status="normal"
|
|||
|
|
/>
|
|||
|
|
<StatusBar
|
|||
|
|
title="存储空间"
|
|||
|
|
value={82}
|
|||
|
|
status="warning"
|
|||
|
|
/>
|
|||
|
|
<StatusBar
|
|||
|
|
title="API请求队列"
|
|||
|
|
value={45}
|
|||
|
|
status="normal"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="p-4 border-t border-gray-200">
|
|||
|
|
<Link
|
|||
|
|
href="/system/status"
|
|||
|
|
className="block w-full py-2 text-center bg-gray-100 text-gray-600 border border-gray-200 rounded-md text-sm hover:bg-gray-200 transition-colors">
|
|||
|
|
查看详细状态
|
|||
|
|
</Link>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 数据卡片组件 - 显示关键指标
|
|||
|
|
function DataCard({
|
|||
|
|
title,
|
|||
|
|
value,
|
|||
|
|
change,
|
|||
|
|
isIncrease,
|
|||
|
|
icon,
|
|||
|
|
}: {
|
|||
|
|
title: string;
|
|||
|
|
value: string;
|
|||
|
|
change: string;
|
|||
|
|
isIncrease: boolean;
|
|||
|
|
icon: React.ReactNode;
|
|||
|
|
}) {
|
|||
|
|
return (
|
|||
|
|
<div className="bg-white border border-gray-200 rounded-md p-5">
|
|||
|
|
<div className="flex justify-between items-center">
|
|||
|
|
<div>
|
|||
|
|
<h3 className="text-sm text-gray-500">{title}</h3>
|
|||
|
|
<p className="text-xl font-bold mt-1 text-gray-800">{value}</p>
|
|||
|
|
<div className="flex items-center mt-2">
|
|||
|
|
<span className={`text-xs font-medium ${isIncrease ? 'text-green-600' : 'text-red-600'}`}>
|
|||
|
|
{change}
|
|||
|
|
</span>
|
|||
|
|
<span className="text-xs text-gray-400 ml-1">相比上周期</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div
|
|||
|
|
className={`p-3 rounded-md ${isIncrease ? 'bg-blue-50 text-blue-600' : 'bg-orange-50 text-orange-500'} border ${isIncrease ? 'border-blue-100' : 'border-orange-100'}`}>
|
|||
|
|
{icon}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 代理IP内容行组件
|
|||
|
|
function ContentRow({
|
|||
|
|
ip,
|
|||
|
|
location,
|
|||
|
|
status,
|
|||
|
|
requests,
|
|||
|
|
successRate,
|
|||
|
|
}: {
|
|||
|
|
ip: string;
|
|||
|
|
location: string;
|
|||
|
|
status: 'active' | 'warning' | 'error';
|
|||
|
|
requests: number;
|
|||
|
|
successRate: string;
|
|||
|
|
}) {
|
|||
|
|
const statusConfig = {
|
|||
|
|
active: {color: 'bg-green-100 text-green-800 border-green-200', label: '在线'},
|
|||
|
|
warning: {color: 'bg-yellow-100 text-yellow-800 border-yellow-200', label: '不稳定'},
|
|||
|
|
error: {color: 'bg-red-100 text-red-800 border-red-200', label: '离线'},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<tr className="hover:bg-gray-50">
|
|||
|
|
<td className="px-5 py-4">
|
|||
|
|
<div className="font-mono text-sm">{ip}</div>
|
|||
|
|
</td>
|
|||
|
|
<td className="px-5 py-4">
|
|||
|
|
<div className="text-sm text-gray-700">{location}</div>
|
|||
|
|
</td>
|
|||
|
|
<td className="px-5 py-4">
|
|||
|
|
<span className={`px-2 py-1 text-xs rounded-md ${statusConfig[status].color} border`}>
|
|||
|
|
{statusConfig[status].label}
|
|||
|
|
</span>
|
|||
|
|
</td>
|
|||
|
|
<td className="px-5 py-4 text-sm text-gray-700">
|
|||
|
|
{requests.toLocaleString()}
|
|||
|
|
</td>
|
|||
|
|
<td className="px-5 py-4 text-sm text-gray-700">
|
|||
|
|
{successRate}
|
|||
|
|
</td>
|
|||
|
|
<td className="px-5 py-4">
|
|||
|
|
<div className="flex space-x-2">
|
|||
|
|
<button className="p-1 border border-gray-200 rounded-md hover:bg-gray-50">
|
|||
|
|
<svg className="h-4 w-4 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|||
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|||
|
|
<path
|
|||
|
|
strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
|||
|
|
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
|||
|
|
</svg>
|
|||
|
|
</button>
|
|||
|
|
<button className="p-1 border border-gray-200 rounded-md hover:bg-gray-50">
|
|||
|
|
<svg className="h-4 w-4 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|||
|
|
<path
|
|||
|
|
strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
|||
|
|
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
|||
|
|
</svg>
|
|||
|
|
</button>
|
|||
|
|
<button className="p-1 border border-red-200 rounded-md hover:bg-red-50">
|
|||
|
|
<svg className="h-4 w-4 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|||
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12"/>
|
|||
|
|
</svg>
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</td>
|
|||
|
|
</tr>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 告警通知组件
|
|||
|
|
function AlertItem({
|
|||
|
|
title,
|
|||
|
|
severity,
|
|||
|
|
time,
|
|||
|
|
message,
|
|||
|
|
}: {
|
|||
|
|
title: string;
|
|||
|
|
severity: 'high' | 'medium' | 'low';
|
|||
|
|
time: string;
|
|||
|
|
message: string;
|
|||
|
|
}) {
|
|||
|
|
const severityConfig = {
|
|||
|
|
high: {color: 'bg-red-50 border-red-200', dot: 'bg-red-500'},
|
|||
|
|
medium: {color: 'bg-yellow-50 border-yellow-200', dot: 'bg-yellow-500'},
|
|||
|
|
low: {color: 'bg-blue-50 border-blue-200', dot: 'bg-blue-500'},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className={`p-3 rounded-md ${severityConfig[severity].color} border`}>
|
|||
|
|
<div className="flex justify-between items-start">
|
|||
|
|
<div className="flex items-center">
|
|||
|
|
<span className={`h-2 w-2 rounded-full ${severityConfig[severity].dot} mr-2`}></span>
|
|||
|
|
<span className="font-medium text-gray-800 text-sm">{title}</span>
|
|||
|
|
</div>
|
|||
|
|
<span className="text-xs text-gray-500">{time}</span>
|
|||
|
|
</div>
|
|||
|
|
<p className="mt-2 text-xs text-gray-600">{message}</p>
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 系统状态条组件
|
|||
|
|
function StatusBar({
|
|||
|
|
title,
|
|||
|
|
value,
|
|||
|
|
status,
|
|||
|
|
}: {
|
|||
|
|
title: string;
|
|||
|
|
value: number;
|
|||
|
|
status: 'normal' | 'warning' | 'error';
|
|||
|
|
}) {
|
|||
|
|
const statusConfig = {
|
|||
|
|
normal: {color: 'bg-green-500', bgColor: 'bg-green-100'},
|
|||
|
|
warning: {color: 'bg-yellow-500', bgColor: 'bg-yellow-100'},
|
|||
|
|
error: {color: 'bg-red-500', bgColor: 'bg-red-100'},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="p-3 border border-gray-200 rounded-md">
|
|||
|
|
<div className="flex justify-between mb-2">
|
|||
|
|
<span className="text-sm text-gray-700">{title}</span>
|
|||
|
|
<span className="text-sm font-medium">{value}%</span>
|
|||
|
|
</div>
|
|||
|
|
<div className={`w-full h-2 ${statusConfig[status].bgColor} rounded-full`}>
|
|||
|
|
<div
|
|||
|
|
className={`h-2 ${statusConfig[status].color} rounded-full`}
|
|||
|
|
style={{width: `${value}%`}}
|
|||
|
|
></div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|