218 lines
8.7 KiB
TypeScript
218 lines
8.7 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect } from "react"
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Progress } from "@/components/ui/progress"
|
|
import { Activity, Database, Server, Cpu, HardDrive, Clock, RefreshCw, CheckCircle, XCircle, AlertTriangle } from "lucide-react"
|
|
|
|
interface DatabaseStatus {
|
|
name: string
|
|
connected: boolean
|
|
latency: number
|
|
documents: number
|
|
size: string
|
|
}
|
|
|
|
interface SystemHealth {
|
|
mongodb: {
|
|
connected: boolean
|
|
totalUsers: number
|
|
latency: number
|
|
databases: string[]
|
|
}
|
|
api: {
|
|
status: string
|
|
uptime: number
|
|
requests24h: number
|
|
}
|
|
}
|
|
|
|
export default function SystemHealthPage() {
|
|
const [isRefreshing, setIsRefreshing] = useState(false)
|
|
const [lastUpdate, setLastUpdate] = useState(new Date())
|
|
const [health, setHealth] = useState<SystemHealth | null>(null)
|
|
|
|
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(() => {
|
|
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="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>
|
|
|
|
{/* 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>
|
|
<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" />
|
|
) : (
|
|
<XCircle className="w-4 h-4 text-red-500" />
|
|
)}
|
|
</div>
|
|
<div className="text-xs text-gray-500">{health?.mongodb.latency || '--'}ms 延迟</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 ${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>
|
|
)
|
|
}
|