chore: 以本地为准,上传全部并替换 GitHub
This commit is contained in:
@@ -2,219 +2,216 @@
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Progress } from "@/components/ui/progress"
|
||||
import { ArrowLeft, Server, CheckCircle2, AlertTriangle, RefreshCw, Clock } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts"
|
||||
import { Activity, Database, Server, Cpu, HardDrive, Clock, RefreshCw, CheckCircle, XCircle, AlertTriangle } from "lucide-react"
|
||||
|
||||
const performanceData = [
|
||||
{ time: "5m", p50: 32, p95: 85, p99: 120 },
|
||||
{ time: "10m", p50: 35, p95: 88, p99: 125 },
|
||||
{ time: "15m", p50: 30, p95: 82, p99: 115 },
|
||||
{ time: "20m", p50: 38, p95: 92, p99: 135 },
|
||||
{ time: "25m", p50: 33, p95: 86, p99: 122 },
|
||||
{ time: "30m", p50: 31, p95: 84, p99: 118 },
|
||||
]
|
||||
interface DatabaseStatus {
|
||||
name: string
|
||||
connected: boolean
|
||||
latency: number
|
||||
documents: number
|
||||
size: string
|
||||
}
|
||||
|
||||
const healthChecks = [
|
||||
{ name: "数据库连接池", status: "healthy", value: "48/50", description: "活跃连接数" },
|
||||
{ name: "Redis缓存", status: "healthy", value: "99.9%", description: "命中率" },
|
||||
{ name: "消息队列", status: "healthy", value: "0", description: "积压消息" },
|
||||
{ name: "定时任务", status: "healthy", value: "12/12", description: "运行中任务" },
|
||||
{ name: "外部API", status: "degraded", value: "95.2%", description: "成功率" },
|
||||
{ name: "文件存储", status: "healthy", value: "1.6TB", description: "已使用" },
|
||||
]
|
||||
interface SystemHealth {
|
||||
mongodb: {
|
||||
connected: boolean
|
||||
totalUsers: number
|
||||
latency: number
|
||||
databases: string[]
|
||||
}
|
||||
api: {
|
||||
status: string
|
||||
uptime: number
|
||||
requests24h: number
|
||||
}
|
||||
}
|
||||
|
||||
const nodeStatus = [
|
||||
{ id: 1, name: "node-01", role: "Master", cpu: 62, memory: 68, status: "healthy" },
|
||||
{ id: 2, name: "node-02", role: "Worker", cpu: 55, memory: 72, status: "healthy" },
|
||||
{ id: 3, name: "node-03", role: "Worker", cpu: 48, memory: 65, status: "healthy" },
|
||||
{ id: 4, name: "node-04", role: "Worker", cpu: 71, memory: 78, status: "degraded" },
|
||||
]
|
||||
export default function SystemHealthPage() {
|
||||
const [isRefreshing, setIsRefreshing] = useState(false)
|
||||
const [lastUpdate, setLastUpdate] = useState(new Date())
|
||||
const [health, setHealth] = useState<SystemHealth | null>(null)
|
||||
|
||||
export default function HealthPage() {
|
||||
const [overallHealth, setOverallHealth] = useState(98.5)
|
||||
const [databases] = useState<DatabaseStatus[]>([
|
||||
{ name: "KR", connected: true, latency: 15, documents: 14360000, size: "16.3 GB" },
|
||||
{ name: "KR_腾讯", connected: true, latency: 18, documents: 705000000, size: "117.1 GB" },
|
||||
{ name: "KR_京东", connected: true, latency: 22, documents: 142000000, size: "31.0 GB" },
|
||||
{ name: "KR_微博", connected: true, latency: 20, documents: 217000000, size: "12.8 GB" },
|
||||
{ name: "KR_酒店", connected: true, latency: 25, documents: 96670000, size: "114.5 GB" },
|
||||
{ name: "KR_顺丰", connected: true, latency: 19, documents: 39200000, size: "12.8 GB" },
|
||||
{ name: "KR_存客宝", connected: true, latency: 12, documents: 216426, size: "0.5 GB" },
|
||||
{ name: "KR_户口", connected: true, latency: 16, documents: 9550000, size: "2.1 GB" },
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setOverallHealth((prev) => Math.min(100, Math.max(95, prev + (Math.random() - 0.5) * 1)))
|
||||
}, 5000)
|
||||
return () => clearInterval(interval)
|
||||
fetchHealth()
|
||||
}, [])
|
||||
|
||||
const fetchHealth = async () => {
|
||||
try {
|
||||
const res = await fetch("/api/ai-chat")
|
||||
const data = await res.json()
|
||||
setHealth({
|
||||
mongodb: {
|
||||
connected: data.database?.connected || false,
|
||||
totalUsers: data.database?.totalUsers || 0,
|
||||
latency: data.database?.latency || 0,
|
||||
databases: ["KR", "KR_腾讯", "KR_京东", "KR_微博", "KR_存客宝"],
|
||||
},
|
||||
api: {
|
||||
status: data.status || "unknown",
|
||||
uptime: 99.9,
|
||||
requests24h: 125680,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch health:", error)
|
||||
}
|
||||
}
|
||||
|
||||
const refresh = async () => {
|
||||
setIsRefreshing(true)
|
||||
await fetchHealth()
|
||||
setLastUpdate(new Date())
|
||||
setIsRefreshing(false)
|
||||
}
|
||||
|
||||
const formatNumber = (num: number): string => {
|
||||
if (num >= 1000000000) return `${(num / 1000000000).toFixed(2)}B`
|
||||
if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`
|
||||
if (num >= 1000) return `${(num / 1000).toFixed(1)}K`
|
||||
return num.toString()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-emerald-50/30 p-6">
|
||||
<div className="max-w-7xl mx-auto space-y-6">
|
||||
{/* 页面标题 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<Link href="/monitoring">
|
||||
<Button variant="ghost" size="icon" className="rounded-full">
|
||||
<ArrowLeft className="w-5 h-5" />
|
||||
</Button>
|
||||
</Link>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-800">系统健康度</h1>
|
||||
<p className="text-slate-500 mt-1">服务状态、性能指标与集群监控</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline">
|
||||
<RefreshCw className="w-4 h-4 mr-2" />
|
||||
<div className="space-y-6 p-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">系统健康</h1>
|
||||
<p className="text-gray-500 mt-1">MongoDB数据库与API服务状态监控</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-xs text-gray-400">更新于 {lastUpdate.toLocaleTimeString()}</span>
|
||||
<Button variant="outline" size="sm" onClick={refresh} disabled={isRefreshing}>
|
||||
<RefreshCw className={`w-4 h-4 mr-2 ${isRefreshing ? "animate-spin" : ""}`} />
|
||||
刷新
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 整体健康度 */}
|
||||
<Card className="bg-gradient-to-r from-emerald-500 to-teal-600 border-0">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-white">
|
||||
<p className="text-sm opacity-80">系统整体健康度</p>
|
||||
<p className="text-5xl font-bold mt-2">{overallHealth.toFixed(1)}%</p>
|
||||
<p className="text-sm opacity-80 mt-2">所有核心服务运行正常</p>
|
||||
{/* Overall Status */}
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<Card className="border-none shadow-sm bg-white/60">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-10 h-10 rounded-lg ${health?.mongodb.connected ? 'bg-green-100' : 'bg-red-100'} flex items-center justify-center`}>
|
||||
<Database className={`w-5 h-5 ${health?.mongodb.connected ? 'text-green-600' : 'text-red-600'}`} />
|
||||
</div>
|
||||
<div className="w-32 h-32 rounded-full bg-white/20 flex items-center justify-center">
|
||||
<CheckCircle2 className="w-16 h-16 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 健康检查项 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{healthChecks.map((check, i) => (
|
||||
<Card key={i} className="bg-white/70 backdrop-blur border-slate-200/60">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-700">{check.name}</p>
|
||||
<p className="text-2xl font-bold text-slate-800 mt-1">{check.value}</p>
|
||||
<p className="text-xs text-slate-500">{check.description}</p>
|
||||
</div>
|
||||
{check.status === "healthy" ? (
|
||||
<CheckCircle2 className="w-8 h-8 text-emerald-500" />
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-lg font-bold text-gray-900">MongoDB</span>
|
||||
{health?.mongodb.connected ? (
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
) : (
|
||||
<AlertTriangle className="w-8 h-8 text-amber-500" />
|
||||
<XCircle className="w-4 h-4 text-red-500" />
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 响应时间分布 */}
|
||||
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<Clock className="w-4 h-4 text-slate-500" />
|
||||
API响应时间分布 (最近30分钟)
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="h-64">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={performanceData}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" />
|
||||
<XAxis dataKey="time" tick={{ fontSize: 12 }} stroke="#94a3b8" />
|
||||
<YAxis tick={{ fontSize: 12 }} stroke="#94a3b8" tickFormatter={(v) => `${v}ms`} />
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: "white",
|
||||
border: "1px solid #e2e8f0",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
formatter={(value: number) => [`${value}ms`]}
|
||||
/>
|
||||
<Line type="monotone" dataKey="p50" stroke="#10b981" strokeWidth={2} name="P50" />
|
||||
<Line type="monotone" dataKey="p95" stroke="#f59e0b" strokeWidth={2} name="P95" />
|
||||
<Line type="monotone" dataKey="p99" stroke="#ef4444" strokeWidth={2} name="P99" />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-6 mt-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-emerald-500" />
|
||||
<span className="text-xs text-slate-600">P50: 32ms</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-amber-500" />
|
||||
<span className="text-xs text-slate-600">P95: 85ms</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-red-500" />
|
||||
<span className="text-xs text-slate-600">P99: 120ms</span>
|
||||
<div className="text-xs text-gray-500">{health?.mongodb.latency || '--'}ms 延迟</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 集群节点状态 */}
|
||||
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<Server className="w-4 h-4 text-slate-500" />
|
||||
集群节点状态
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-slate-200">
|
||||
<th className="text-left py-3 px-4 text-xs font-medium text-slate-500">节点</th>
|
||||
<th className="text-left py-3 px-4 text-xs font-medium text-slate-500">角色</th>
|
||||
<th className="text-left py-3 px-4 text-xs font-medium text-slate-500">CPU</th>
|
||||
<th className="text-left py-3 px-4 text-xs font-medium text-slate-500">内存</th>
|
||||
<th className="text-left py-3 px-4 text-xs font-medium text-slate-500">状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{nodeStatus.map((node) => (
|
||||
<tr key={node.id} className="border-b border-slate-100 hover:bg-slate-50/50">
|
||||
<td className="py-3 px-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Server className="w-4 h-4 text-slate-400" />
|
||||
<span className="text-sm font-medium text-slate-800">{node.name}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
<Badge variant="outline">{node.role}</Badge>
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Progress value={node.cpu} className="w-20 h-2" />
|
||||
<span className="text-sm text-slate-600">{node.cpu}%</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Progress value={node.memory} className="w-20 h-2" />
|
||||
<span className="text-sm text-slate-600">{node.memory}%</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
{node.status === "healthy" ? (
|
||||
<Badge className="bg-emerald-100 text-emerald-700">
|
||||
<CheckCircle2 className="w-3 h-3 mr-1" />
|
||||
正常
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge className="bg-amber-100 text-amber-700">
|
||||
<AlertTriangle className="w-3 h-3 mr-1" />
|
||||
降级
|
||||
</Badge>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Card className="border-none shadow-sm bg-white/60">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-10 h-10 rounded-lg ${health?.api.status === 'online' ? 'bg-green-100' : 'bg-yellow-100'} flex items-center justify-center`}>
|
||||
<Server className={`w-5 h-5 ${health?.api.status === 'online' ? 'text-green-600' : 'text-yellow-600'}`} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-lg font-bold text-gray-900">API服务</span>
|
||||
{health?.api.status === 'online' ? (
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
) : (
|
||||
<AlertTriangle className="w-4 h-4 text-yellow-500" />
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">{health?.api.uptime || '--'}% 可用</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-none shadow-sm bg-white/60">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-blue-100 flex items-center justify-center">
|
||||
<HardDrive className="w-5 h-5 text-blue-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-lg font-bold text-gray-900">{formatNumber(health?.mongodb.totalUsers || 0)}</div>
|
||||
<div className="text-xs text-gray-500">总文档数</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-none shadow-sm bg-white/60">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-purple-100 flex items-center justify-center">
|
||||
<Activity className="w-5 h-5 text-purple-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-lg font-bold text-gray-900">{formatNumber(health?.api.requests24h || 0)}</div>
|
||||
<div className="text-xs text-gray-500">24h请求</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Database Details */}
|
||||
<Card className="border-none shadow-md bg-white/60">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-gray-700 flex items-center gap-2">
|
||||
<Database className="w-5 h-5" />
|
||||
数据库详情
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{databases.map((db, i) => (
|
||||
<div key={i} className="flex items-center justify-between p-3 rounded-lg bg-gray-50">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-2 h-2 rounded-full ${db.connected ? 'bg-green-500' : 'bg-red-500'}`} />
|
||||
<div>
|
||||
<div className="font-medium text-gray-900">{db.name}</div>
|
||||
<div className="text-xs text-gray-500">{db.size}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="text-right">
|
||||
<div className="text-sm font-medium text-gray-900">{formatNumber(db.documents)}</div>
|
||||
<div className="text-xs text-gray-500">文档</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-sm font-medium text-gray-900">{db.latency}ms</div>
|
||||
<div className="text-xs text-gray-500">延迟</div>
|
||||
</div>
|
||||
<Badge className={db.connected ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}>
|
||||
{db.connected ? '正常' : '异常'}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -47,19 +47,95 @@ const serviceStatus = [
|
||||
{ name: "缓存服务", status: "healthy", uptime: "99.99%", latency: "2ms" },
|
||||
]
|
||||
|
||||
// 服务状态接口
|
||||
interface ServiceStatus {
|
||||
name: string
|
||||
status: 'healthy' | 'degraded' | 'unhealthy'
|
||||
latency: string
|
||||
message: string
|
||||
}
|
||||
|
||||
// 告警接口
|
||||
interface Alert {
|
||||
id: string
|
||||
type: 'info' | 'warning' | 'error'
|
||||
message: string
|
||||
time: string
|
||||
status: 'active' | 'resolved'
|
||||
}
|
||||
|
||||
export default function MonitoringPage() {
|
||||
const [cpuUsage, setCpuUsage] = useState(65)
|
||||
const [memoryUsage, setMemoryUsage] = useState(68)
|
||||
const [diskUsage, setDiskUsage] = useState(80)
|
||||
const [networkIO, setNetworkIO] = useState(256)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [services, setServices] = useState<ServiceStatus[]>(serviceStatus.map(s => ({
|
||||
...s,
|
||||
status: s.status as 'healthy' | 'degraded' | 'unhealthy',
|
||||
message: ''
|
||||
})))
|
||||
const [alerts, setAlerts] = useState<Alert[]>(recentAlerts.map(a => ({
|
||||
...a,
|
||||
type: a.type as 'info' | 'warning' | 'error',
|
||||
status: a.status as 'active' | 'resolved'
|
||||
})))
|
||||
const [dbInfo, setDbInfo] = useState<any>(null)
|
||||
|
||||
// 获取真实监控数据
|
||||
const fetchMonitoringData = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/monitoring')
|
||||
const data = await res.json()
|
||||
|
||||
if (data.success) {
|
||||
// 更新服务状态
|
||||
if (data.health?.services) {
|
||||
setServices(data.health.services.map((s: any) => ({
|
||||
name: s.name,
|
||||
status: s.status,
|
||||
latency: s.latency,
|
||||
uptime: s.status === 'healthy' ? '99.99%' : '99.5%',
|
||||
message: s.message
|
||||
})))
|
||||
}
|
||||
|
||||
// 更新告警
|
||||
if (data.alerts?.alerts) {
|
||||
setAlerts(data.alerts.alerts)
|
||||
}
|
||||
|
||||
// 更新数据库信息
|
||||
if (data.database?.server) {
|
||||
setDbInfo(data.database)
|
||||
// 根据MongoDB内存使用更新内存指标
|
||||
const memMB = data.database.server.memory?.resident || 0
|
||||
setMemoryUsage(Math.min(90, (memMB / 8000) * 100))
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取监控数据失败:', e)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchMonitoringData()
|
||||
|
||||
// 模拟CPU和网络波动
|
||||
const interval = setInterval(() => {
|
||||
setCpuUsage((prev) => Math.min(95, Math.max(30, prev + (Math.random() - 0.5) * 10)))
|
||||
setMemoryUsage((prev) => Math.min(90, Math.max(50, prev + (Math.random() - 0.5) * 5)))
|
||||
setNetworkIO((prev) => Math.min(500, Math.max(100, prev + (Math.random() - 0.5) * 50)))
|
||||
}, 3000)
|
||||
return () => clearInterval(interval)
|
||||
|
||||
// 每30秒刷新真实数据
|
||||
const refreshInterval = setInterval(fetchMonitoringData, 30000)
|
||||
|
||||
return () => {
|
||||
clearInterval(interval)
|
||||
clearInterval(refreshInterval)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user