Reorganize navigation and module structure based on new requirements. Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
169 lines
6.8 KiB
TypeScript
169 lines
6.8 KiB
TypeScript
"use client"
|
|
|
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
|
import { Card, CardContent } from "@/components/ui/card"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { Activity, Clock, Zap, AlertTriangle, CheckCircle } from "lucide-react"
|
|
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, AreaChart, Area } from "recharts"
|
|
|
|
interface APIMonitorDialogProps {
|
|
open: boolean
|
|
onOpenChange: (open: boolean) => void
|
|
api?: {
|
|
name: string
|
|
endpoint: string
|
|
}
|
|
}
|
|
|
|
const LATENCY_DATA = [
|
|
{ time: "00:00", p50: 28, p95: 45, p99: 68 },
|
|
{ time: "04:00", p50: 25, p95: 42, p99: 62 },
|
|
{ time: "08:00", p50: 32, p95: 55, p99: 85 },
|
|
{ time: "12:00", p50: 45, p95: 78, p99: 120 },
|
|
{ time: "16:00", p50: 42, p95: 72, p99: 110 },
|
|
{ time: "20:00", p50: 35, p95: 58, p99: 88 },
|
|
]
|
|
|
|
const QPS_DATA = [
|
|
{ time: "00:00", qps: 450 },
|
|
{ time: "04:00", qps: 280 },
|
|
{ time: "08:00", qps: 850 },
|
|
{ time: "12:00", qps: 1200 },
|
|
{ time: "16:00", qps: 1100 },
|
|
{ time: "20:00", qps: 780 },
|
|
]
|
|
|
|
const ERROR_DATA = [
|
|
{ time: "00:00", rate: 0.1 },
|
|
{ time: "04:00", rate: 0.05 },
|
|
{ time: "08:00", rate: 0.15 },
|
|
{ time: "12:00", rate: 0.3 },
|
|
{ time: "16:00", rate: 0.2 },
|
|
{ time: "20:00", rate: 0.12 },
|
|
]
|
|
|
|
export function APIMonitorDialog({ open, onOpenChange, api }: APIMonitorDialogProps) {
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="max-w-4xl max-h-[85vh] overflow-y-auto">
|
|
<DialogHeader>
|
|
<DialogTitle className="flex items-center gap-2">
|
|
<Activity className="w-5 h-5" />
|
|
API监控 - {api?.name || "API服务"}
|
|
</DialogTitle>
|
|
<DialogDescription>{api?.endpoint}</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
{/* 实时指标 */}
|
|
<div className="grid grid-cols-4 gap-4">
|
|
<Card className="bg-slate-50">
|
|
<CardContent className="p-4">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-xs text-slate-500">当前QPS</p>
|
|
<p className="text-2xl font-bold text-slate-800">1,156</p>
|
|
</div>
|
|
<Zap className="w-8 h-8 text-blue-500" />
|
|
</div>
|
|
<p className="text-xs text-emerald-600 mt-1">峰值: 1,200</p>
|
|
</CardContent>
|
|
</Card>
|
|
<Card className="bg-slate-50">
|
|
<CardContent className="p-4">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-xs text-slate-500">P99延迟</p>
|
|
<p className="text-2xl font-bold text-slate-800">95ms</p>
|
|
</div>
|
|
<Clock className="w-8 h-8 text-amber-500" />
|
|
</div>
|
|
<p className="text-xs text-slate-500 mt-1">P50: 32ms</p>
|
|
</CardContent>
|
|
</Card>
|
|
<Card className="bg-slate-50">
|
|
<CardContent className="p-4">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-xs text-slate-500">成功率</p>
|
|
<p className="text-2xl font-bold text-emerald-600">99.85%</p>
|
|
</div>
|
|
<CheckCircle className="w-8 h-8 text-emerald-500" />
|
|
</div>
|
|
<p className="text-xs text-emerald-600 mt-1">SLA: 99.9%</p>
|
|
</CardContent>
|
|
</Card>
|
|
<Card className="bg-slate-50">
|
|
<CardContent className="p-4">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-xs text-slate-500">今日错误</p>
|
|
<p className="text-2xl font-bold text-red-600">156</p>
|
|
</div>
|
|
<AlertTriangle className="w-8 h-8 text-red-500" />
|
|
</div>
|
|
<p className="text-xs text-slate-500 mt-1">较昨日 -23%</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* QPS趋势 */}
|
|
<div className="space-y-2">
|
|
<h4 className="text-sm font-medium">QPS趋势</h4>
|
|
<div className="h-48 bg-slate-50 rounded-lg p-4">
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<AreaChart data={QPS_DATA}>
|
|
<CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" />
|
|
<XAxis dataKey="time" tick={{ fontSize: 12 }} stroke="#94a3b8" />
|
|
<YAxis tick={{ fontSize: 12 }} stroke="#94a3b8" />
|
|
<Tooltip />
|
|
<Area type="monotone" dataKey="qps" stroke="#3b82f6" fill="#93c5fd" fillOpacity={0.5} />
|
|
</AreaChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 延迟分布 */}
|
|
<div className="space-y-2">
|
|
<h4 className="text-sm font-medium">延迟分布 (ms)</h4>
|
|
<div className="h-48 bg-slate-50 rounded-lg p-4">
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<LineChart data={LATENCY_DATA}>
|
|
<CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" />
|
|
<XAxis dataKey="time" tick={{ fontSize: 12 }} stroke="#94a3b8" />
|
|
<YAxis tick={{ fontSize: 12 }} stroke="#94a3b8" />
|
|
<Tooltip />
|
|
<Line type="monotone" dataKey="p50" stroke="#22c55e" 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>
|
|
|
|
{/* 最近错误 */}
|
|
<div className="space-y-2">
|
|
<h4 className="text-sm font-medium">最近错误</h4>
|
|
<div className="space-y-2">
|
|
{[
|
|
{ time: "14:35:12", code: 429, message: "Rate limit exceeded", count: 23 },
|
|
{ time: "14:32:08", code: 500, message: "Internal server error", count: 5 },
|
|
{ time: "14:28:45", code: 404, message: "User not found", count: 12 },
|
|
].map((error, i) => (
|
|
<div key={i} className="flex items-center justify-between p-3 bg-slate-50 rounded-lg">
|
|
<div className="flex items-center gap-3">
|
|
<Badge className="bg-red-100 text-red-700">{error.code}</Badge>
|
|
<span className="text-sm text-slate-700">{error.message}</span>
|
|
</div>
|
|
<div className="flex items-center gap-4 text-sm text-slate-500">
|
|
<span>{error.count} 次</span>
|
|
<span>{error.time}</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|