refactor: restructure navigation and module layout

Reorganize navigation and module structure based on new requirements.

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
v0
2026-01-31 04:32:36 +00:00
parent 22e725887a
commit b17b488f8e
105 changed files with 20530 additions and 3622 deletions

View File

@@ -0,0 +1,3 @@
export default function Loading() {
return null
}

View File

@@ -0,0 +1,383 @@
"use client"
import { useState } from "react"
import { Card, CardContent } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Input } from "@/components/ui/input"
import {
Bell,
ArrowLeft,
Plus,
Search,
Filter,
AlertTriangle,
CheckCircle2,
Clock,
Settings,
Trash2,
XCircle,
} from "lucide-react"
import Link from "next/link"
import { CreateAlertRuleDialog } from "@/components/dialogs/create-alert-rule-dialog"
import { AlertRuleSettingsDialog } from "@/components/dialogs/alert-rule-settings-dialog"
const alertRules = [
{
id: 1,
name: "CPU使用率告警",
condition: "CPU > 80%",
duration: "5分钟",
severity: "warning",
status: "enabled",
triggers: 3,
},
{
id: 2,
name: "内存使用率告警",
condition: "Memory > 85%",
duration: "5分钟",
severity: "warning",
status: "enabled",
triggers: 1,
},
{
id: 3,
name: "API延迟告警",
condition: "P99 > 200ms",
duration: "3分钟",
severity: "critical",
status: "enabled",
triggers: 5,
},
{
id: 4,
name: "磁盘空间告警",
condition: "Disk > 90%",
duration: "10分钟",
severity: "critical",
status: "enabled",
triggers: 0,
},
{
id: 5,
name: "服务宕机告警",
condition: "Service Down",
duration: "1分钟",
severity: "critical",
status: "enabled",
triggers: 0,
},
]
const alertHistory = [
{
id: 1,
rule: "CPU使用率告警",
message: "node-04 CPU使用率达到 85%",
severity: "warning",
status: "active",
time: "10分钟前",
},
{
id: 2,
rule: "磁盘空间告警",
message: "磁盘使用率达到 80%,接近阈值",
severity: "warning",
status: "active",
time: "2小时前",
},
{
id: 3,
rule: "API延迟告警",
message: "API网关P99延迟达到 250ms",
severity: "critical",
status: "resolved",
time: "1小时前",
resolvedAt: "45分钟前",
},
{
id: 4,
rule: "内存使用率告警",
message: "node-02 内存使用率达到 88%",
severity: "warning",
status: "resolved",
time: "3小时前",
resolvedAt: "2小时前",
},
{
id: 5,
rule: "服务宕机告警",
message: "Redis服务短暂不可用",
severity: "critical",
status: "resolved",
time: "1天前",
resolvedAt: "1天前",
},
]
export default function AlertsPage() {
const [searchTerm, setSearchTerm] = useState("")
const [activeTab, setActiveTab] = useState<"rules" | "history">("history")
const [showCreateDialog, setShowCreateDialog] = useState(false)
const [showSettingsDialog, setShowSettingsDialog] = useState(false)
const [selectedRule, setSelectedRule] = useState<(typeof alertRules)[0] | null>(null)
const activeAlerts = alertHistory.filter((a) => a.status === "active")
const handleRuleSettings = (rule: (typeof alertRules)[0]) => {
setSelectedRule(rule)
setShowSettingsDialog(true)
}
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-red-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
className="bg-gradient-to-r from-red-500 to-orange-600 hover:from-red-600 hover:to-orange-700"
onClick={() => setShowCreateDialog(true)}
>
<Plus className="w-4 h-4 mr-2" />
</Button>
</div>
{/* 告警统计 */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card 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-xs text-slate-500"></p>
<p className="text-2xl font-bold text-red-600">{activeAlerts.length}</p>
</div>
<div className="w-10 h-10 rounded-lg bg-red-100 flex items-center justify-center">
<AlertTriangle className="w-5 h-5 text-red-600" />
</div>
</div>
</CardContent>
</Card>
<Card 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-xs text-slate-500"></p>
<p className="text-2xl font-bold text-emerald-600">8</p>
</div>
<div className="w-10 h-10 rounded-lg bg-emerald-100 flex items-center justify-center">
<CheckCircle2 className="w-5 h-5 text-emerald-600" />
</div>
</div>
</CardContent>
</Card>
<Card 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-xs text-slate-500"></p>
<p className="text-2xl font-bold text-slate-800">{alertRules.length}</p>
</div>
<div className="w-10 h-10 rounded-lg bg-blue-100 flex items-center justify-center">
<Bell className="w-5 h-5 text-blue-600" />
</div>
</div>
</CardContent>
</Card>
<Card 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-xs text-slate-500"></p>
<p className="text-2xl font-bold text-slate-800">15</p>
</div>
<div className="w-10 h-10 rounded-lg bg-amber-100 flex items-center justify-center">
<Clock className="w-5 h-5 text-amber-600" />
</div>
</div>
</CardContent>
</Card>
</div>
{/* 标签页切换 */}
<div className="flex items-center gap-2 border-b border-slate-200">
<Button
variant="ghost"
className={`rounded-none border-b-2 ${activeTab === "history" ? "border-slate-800 text-slate-800" : "border-transparent text-slate-500"}`}
onClick={() => setActiveTab("history")}
>
</Button>
<Button
variant="ghost"
className={`rounded-none border-b-2 ${activeTab === "rules" ? "border-slate-800 text-slate-800" : "border-transparent text-slate-500"}`}
onClick={() => setActiveTab("rules")}
>
</Button>
</div>
{activeTab === "history" ? (
<>
{/* 筛选工具栏 */}
<div className="flex items-center gap-4">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
<Input
placeholder="搜索告警..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-9 bg-white/70"
/>
</div>
<Button variant="outline">
<Filter className="w-4 h-4 mr-2" />
</Button>
</div>
{/* 告警历史列表 */}
<div className="space-y-3">
{alertHistory.map((alert) => (
<Card
key={alert.id}
className={`bg-white/70 backdrop-blur border-slate-200/60 ${alert.status === "active" ? "border-l-4 border-l-red-500" : ""}`}
>
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
{alert.status === "active" ? (
<div className="w-10 h-10 rounded-lg bg-red-100 flex items-center justify-center">
<AlertTriangle className="w-5 h-5 text-red-600" />
</div>
) : (
<div className="w-10 h-10 rounded-lg bg-slate-100 flex items-center justify-center">
<CheckCircle2 className="w-5 h-5 text-slate-400" />
</div>
)}
<div>
<div className="flex items-center gap-2">
<h4 className="font-medium text-slate-800">{alert.rule}</h4>
<Badge
className={
alert.severity === "critical"
? "bg-red-100 text-red-700"
: "bg-amber-100 text-amber-700"
}
>
{alert.severity === "critical" ? "严重" : "警告"}
</Badge>
{alert.status === "active" ? (
<Badge className="bg-red-100 text-red-700"></Badge>
) : (
<Badge className="bg-emerald-100 text-emerald-700"></Badge>
)}
</div>
<p className="text-sm text-slate-500 mt-1">{alert.message}</p>
</div>
</div>
<div className="flex items-center gap-4">
<div className="text-right">
<p className="text-sm text-slate-600">{alert.time}</p>
{alert.resolvedAt && <p className="text-xs text-slate-400"> {alert.resolvedAt}</p>}
</div>
{alert.status === "active" && (
<Button variant="outline" size="sm">
<XCircle className="w-4 h-4 mr-1" />
</Button>
)}
</div>
</div>
</CardContent>
</Card>
))}
</div>
</>
) : (
/* 告警规则列表 */
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardContent className="p-0">
<table className="w-full">
<thead>
<tr className="border-b border-slate-200">
<th className="text-left py-4 px-6 text-xs font-medium text-slate-500"></th>
<th className="text-left py-4 px-6 text-xs font-medium text-slate-500"></th>
<th className="text-left py-4 px-6 text-xs font-medium text-slate-500"></th>
<th className="text-left py-4 px-6 text-xs font-medium text-slate-500"></th>
<th className="text-left py-4 px-6 text-xs font-medium text-slate-500"></th>
<th className="text-left py-4 px-6 text-xs font-medium text-slate-500"></th>
<th className="text-left py-4 px-6 text-xs font-medium text-slate-500"></th>
</tr>
</thead>
<tbody>
{alertRules.map((rule) => (
<tr key={rule.id} className="border-b border-slate-100 hover:bg-slate-50/50">
<td className="py-4 px-6 text-sm font-medium text-slate-800">{rule.name}</td>
<td className="py-4 px-6">
<code className="text-xs bg-slate-100 px-2 py-1 rounded">{rule.condition}</code>
</td>
<td className="py-4 px-6 text-sm text-slate-600">{rule.duration}</td>
<td className="py-4 px-6">
<Badge
className={
rule.severity === "critical" ? "bg-red-100 text-red-700" : "bg-amber-100 text-amber-700"
}
>
{rule.severity === "critical" ? "严重" : "警告"}
</Badge>
</td>
<td className="py-4 px-6 text-sm text-slate-600">{rule.triggers}</td>
<td className="py-4 px-6">
<Badge className="bg-emerald-100 text-emerald-700"></Badge>
</td>
<td className="py-4 px-6">
<div className="flex items-center gap-2">
<Button variant="ghost" size="sm" onClick={() => handleRuleSettings(rule)}>
<Settings className="w-4 h-4" />
</Button>
<Button variant="ghost" size="sm">
<Trash2 className="w-4 h-4 text-red-500" />
</Button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</CardContent>
</Card>
)}
</div>
<CreateAlertRuleDialog open={showCreateDialog} onOpenChange={setShowCreateDialog} />
<AlertRuleSettingsDialog
open={showSettingsDialog}
onOpenChange={setShowSettingsDialog}
rule={
selectedRule
? {
name: selectedRule.name,
condition: selectedRule.condition,
severity: selectedRule.severity,
}
: undefined
}
/>
</div>
)
}

View File

@@ -0,0 +1,3 @@
export default function Loading() {
return null
}

View File

@@ -0,0 +1,299 @@
"use client"
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 {
TrendingUp,
ArrowLeft,
Users,
Target,
DollarSign,
Activity,
RefreshCw,
ArrowUpRight,
ArrowDownRight,
} from "lucide-react"
import Link from "next/link"
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
PieChart,
Pie,
Cell,
} from "recharts"
const kpiData = [
{ name: "数据接入成功率", value: 99.2, target: 99, unit: "%", trend: "up", change: 0.3 },
{ name: "标签覆盖率", value: 87.5, target: 85, unit: "%", trend: "up", change: 2.1 },
{ name: "价值预测准确率", value: 91.8, target: 90, unit: "%", trend: "up", change: 1.2 },
{ name: "API调用增长", value: 15.2, target: 10, unit: "%", trend: "up", change: 3.5 },
]
const userTrend = [
{ date: "12-06", total: 4020000, active: 1250000, new: 12500 },
{ date: "12-07", total: 4025000, active: 1280000, new: 13200 },
{ date: "12-08", total: 4030000, active: 1260000, new: 11800 },
{ date: "12-09", total: 4038000, active: 1320000, new: 14500 },
{ date: "12-10", total: 4045000, active: 1350000, new: 15200 },
{ date: "12-11", total: 4052000, active: 1380000, new: 16800 },
{ date: "12-12", total: 4062000, active: 1420000, new: 18500 },
]
const valueDistribution = [
{ name: "S级", value: 328000, percent: 8.1, color: "#f59e0b" },
{ name: "A级", value: 650000, percent: 16.0, color: "#3b82f6" },
{ name: "B级", value: 1220000, percent: 30.0, color: "#10b981" },
{ name: "C级", value: 1180000, percent: 29.1, color: "#8b5cf6" },
{ name: "D级", value: 684000, percent: 16.8, color: "#6b7280" },
]
const projectRanking = [
{ rank: 1, name: "美妆护肤项目", users: 580000, value: 28.5, growth: 12.3 },
{ rank: 2, name: "母婴亲子项目", users: 420000, value: 22.1, growth: 8.5 },
{ rank: 3, name: "运动健康项目", users: 380000, value: 18.6, growth: 15.2 },
{ rank: 4, name: "家居生活项目", users: 320000, value: 15.2, growth: 6.8 },
{ rank: 5, name: "数码科技项目", users: 280000, value: 12.8, growth: 10.5 },
]
export default function BusinessPage() {
const [totalValue, setTotalValue] = useState(125.8)
useEffect(() => {
const interval = setInterval(() => {
setTotalValue((prev) => prev + Math.random() * 0.01)
}, 5000)
return () => clearInterval(interval)
}, [])
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-blue-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">KPI监控与分析</p>
</div>
</div>
<Button variant="outline">
<RefreshCw className="w-4 h-4 mr-2" />
</Button>
</div>
{/* 核心KPI */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
{kpiData.map((kpi, 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 mb-2">
<p className="text-sm text-slate-500">{kpi.name}</p>
<Badge
className={
kpi.value >= kpi.target ? "bg-emerald-100 text-emerald-700" : "bg-amber-100 text-amber-700"
}
>
: {kpi.target}
{kpi.unit}
</Badge>
</div>
<div className="flex items-end justify-between">
<p className="text-3xl font-bold text-slate-800">
{kpi.value}
<span className="text-lg text-slate-500">{kpi.unit}</span>
</p>
<div
className={`flex items-center gap-1 ${kpi.trend === "up" ? "text-emerald-600" : "text-red-600"}`}
>
{kpi.trend === "up" ? <ArrowUpRight className="w-4 h-4" /> : <ArrowDownRight className="w-4 h-4" />}
<span className="text-sm font-medium">{kpi.change}%</span>
</div>
</div>
</CardContent>
</Card>
))}
</div>
{/* 资产总价值 */}
<Card className="bg-gradient-to-r from-blue-500 to-indigo-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">{totalValue.toFixed(2)} 亿</p>
<div className="flex items-center gap-4 mt-4">
<div>
<p className="text-xs opacity-60"></p>
<p className="text-lg font-semibold">40.62 亿</p>
</div>
<div className="w-px h-10 bg-white/20" />
<div>
<p className="text-xs opacity-60"></p>
<p className="text-lg font-semibold">156 </p>
</div>
<div className="w-px h-10 bg-white/20" />
<div>
<p className="text-xs opacity-60"></p>
<p className="text-lg font-semibold">89 </p>
</div>
</div>
</div>
<div className="w-32 h-32 rounded-full bg-white/20 flex items-center justify-center">
<DollarSign className="w-16 h-16 text-white" />
</div>
</div>
</CardContent>
</Card>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* 用户增长趋势 */}
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardHeader className="pb-2">
<CardTitle className="text-base flex items-center gap-2">
<Users className="w-4 h-4 text-slate-500" />
(7)
</CardTitle>
</CardHeader>
<CardContent>
<div className="h-64">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={userTrend}>
<CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" />
<XAxis dataKey="date" tick={{ fontSize: 12 }} stroke="#94a3b8" />
<YAxis
tick={{ fontSize: 12 }}
stroke="#94a3b8"
tickFormatter={(v) => `${(v / 10000).toFixed(0)}`}
/>
<Tooltip
contentStyle={{
backgroundColor: "white",
border: "1px solid #e2e8f0",
borderRadius: "8px",
}}
formatter={(value: number) => [`${(value / 10000).toFixed(1)}`]}
/>
<Line type="monotone" dataKey="active" stroke="#3b82f6" strokeWidth={2} name="活跃用户" />
<Line type="monotone" dataKey="new" stroke="#10b981" strokeWidth={2} name="新增用户" />
</LineChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
{/* 价值等级分布 */}
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardHeader className="pb-2">
<CardTitle className="text-base flex items-center gap-2">
<Target className="w-4 h-4 text-slate-500" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center justify-between">
<div className="h-64 flex-1">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={valueDistribution}
cx="50%"
cy="50%"
innerRadius={60}
outerRadius={90}
dataKey="value"
label={({ name, percent }) => `${name} ${percent}%`}
>
{valueDistribution.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Tooltip formatter={(value: number) => [`${(value / 10000).toFixed(1)}`, "用户数"]} />
</PieChart>
</ResponsiveContainer>
</div>
<div className="space-y-2">
{valueDistribution.map((item, i) => (
<div key={i} className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full" style={{ backgroundColor: item.color }} />
<span className="text-sm text-slate-600">
{item.name}: {(item.value / 10000).toFixed(1)}
</span>
</div>
))}
</div>
</div>
</CardContent>
</Card>
</div>
{/* 项目价值排名 */}
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardHeader className="pb-3">
<CardTitle className="text-base flex items-center gap-2">
<Activity className="w-4 h-4 text-slate-500" />
Top 5
</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"></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>
{projectRanking.map((project) => (
<tr key={project.rank} className="border-b border-slate-100 hover:bg-slate-50/50">
<td className="py-3 px-4">
<div
className={`w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold ${
project.rank === 1
? "bg-amber-100 text-amber-700"
: project.rank === 2
? "bg-slate-200 text-slate-700"
: project.rank === 3
? "bg-orange-100 text-orange-700"
: "bg-slate-100 text-slate-600"
}`}
>
{project.rank}
</div>
</td>
<td className="py-3 px-4 text-sm font-medium text-slate-800">{project.name}</td>
<td className="py-3 px-4 text-sm text-slate-600">{(project.users / 10000).toFixed(1)}</td>
<td className="py-3 px-4 text-sm font-medium text-slate-800">{project.value}亿</td>
<td className="py-3 px-4">
<div className="flex items-center gap-1 text-emerald-600">
<TrendingUp className="w-4 h-4" />
<span className="text-sm font-medium">{project.growth}%</span>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</CardContent>
</Card>
</div>
</div>
)
}

View File

@@ -0,0 +1,3 @@
export default function Loading() {
return null
}

View File

@@ -0,0 +1,220 @@
"use client"
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 { 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"
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 },
]
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: "已使用" },
]
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 HealthPage() {
const [overallHealth, setOverallHealth] = useState(98.5)
useEffect(() => {
const interval = setInterval(() => {
setOverallHealth((prev) => Math.min(100, Math.max(95, prev + (Math.random() - 0.5) * 1)))
}, 5000)
return () => clearInterval(interval)
}, [])
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" />
</Button>
</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>
</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" />
) : (
<AlertTriangle className="w-8 h-8 text-amber-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>
</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>
</div>
</CardContent>
</Card>
</div>
</div>
)
}

View File

@@ -0,0 +1,17 @@
import { Skeleton } from "@/components/ui/skeleton"
export default function Loading() {
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">
<Skeleton className="h-10 w-64" />
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
{[...Array(4)].map((_, i) => (
<Skeleton key={i} className="h-28 rounded-xl" />
))}
</div>
<Skeleton className="h-64 rounded-xl" />
</div>
</div>
)
}

381
app/monitoring/page.tsx Normal file
View File

@@ -0,0 +1,381 @@
"use client"
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 { Progress } from "@/components/ui/progress"
import {
Activity,
Server,
Cpu,
HardDrive,
Wifi,
AlertTriangle,
CheckCircle2,
Bell,
RefreshCw,
ArrowRight,
TrendingUp,
} from "lucide-react"
import Link from "next/link"
import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, AreaChart, Area } from "recharts"
// 模拟系统资源数据
const systemMetrics = [
{ time: "00:00", cpu: 45, memory: 62, disk: 78, network: 120 },
{ time: "04:00", cpu: 32, memory: 58, disk: 78, network: 80 },
{ time: "08:00", cpu: 58, memory: 65, disk: 79, network: 180 },
{ time: "12:00", cpu: 72, memory: 71, disk: 79, network: 320 },
{ time: "16:00", cpu: 68, memory: 69, disk: 80, network: 280 },
{ time: "20:00", cpu: 55, memory: 64, disk: 80, network: 200 },
]
const recentAlerts = [
{ id: 1, type: "warning", message: "数据同步任务延迟超过阈值", time: "10分钟前", status: "active" },
{ id: 2, type: "info", message: "模型训练任务已完成", time: "30分钟前", status: "resolved" },
{ id: 3, type: "error", message: "API网关响应超时", time: "1小时前", status: "resolved" },
{ id: 4, type: "warning", message: "磁盘使用率超过80%", time: "2小时前", status: "active" },
]
const serviceStatus = [
{ name: "数据接入服务", status: "healthy", uptime: "99.99%", latency: "12ms" },
{ name: "标签计算引擎", status: "healthy", uptime: "99.95%", latency: "45ms" },
{ name: "AI推理服务", status: "healthy", uptime: "99.90%", latency: "120ms" },
{ name: "API网关", status: "degraded", uptime: "99.85%", latency: "85ms" },
{ name: "数据库集群", status: "healthy", uptime: "99.99%", latency: "8ms" },
{ name: "缓存服务", status: "healthy", uptime: "99.99%", latency: "2ms" },
]
export default function MonitoringPage() {
const [cpuUsage, setCpuUsage] = useState(65)
const [memoryUsage, setMemoryUsage] = useState(68)
const [diskUsage, setDiskUsage] = useState(80)
const [networkIO, setNetworkIO] = useState(256)
useEffect(() => {
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)
}, [])
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>
<h1 className="text-2xl font-bold text-slate-800"></h1>
<p className="text-slate-500 mt-1"></p>
</div>
<div className="flex items-center gap-2">
<Button variant="outline">
<Bell className="w-4 h-4 mr-2" />
</Button>
<Button variant="outline">
<RefreshCw className="w-4 h-4 mr-2" />
</Button>
<Link href="/monitoring/alerts">
<Button className="bg-gradient-to-r from-emerald-500 to-teal-600 hover:from-emerald-600 hover:to-teal-700">
<Activity className="w-4 h-4 mr-2" />
</Button>
</Link>
</div>
</div>
{/* 系统资源概览 */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardContent className="p-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-cyan-600 flex items-center justify-center">
<Cpu className="w-4 h-4 text-white" />
</div>
<span className="text-sm font-medium text-slate-700">CPU使用率</span>
</div>
<span
className={`text-xl font-bold ${cpuUsage > 80 ? "text-red-600" : cpuUsage > 60 ? "text-amber-600" : "text-emerald-600"}`}
>
{cpuUsage.toFixed(1)}%
</span>
</div>
<Progress value={cpuUsage} className="h-2" />
<p className="text-xs text-slate-500 mt-2">8 / 16线</p>
</CardContent>
</Card>
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardContent className="p-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-violet-500 to-purple-600 flex items-center justify-center">
<Server className="w-4 h-4 text-white" />
</div>
<span className="text-sm font-medium text-slate-700">使</span>
</div>
<span
className={`text-xl font-bold ${memoryUsage > 85 ? "text-red-600" : memoryUsage > 70 ? "text-amber-600" : "text-emerald-600"}`}
>
{memoryUsage.toFixed(1)}%
</span>
</div>
<Progress value={memoryUsage} className="h-2" />
<p className="text-xs text-slate-500 mt-2">54.4GB / 80GB</p>
</CardContent>
</Card>
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardContent className="p-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-amber-500 to-orange-600 flex items-center justify-center">
<HardDrive className="w-4 h-4 text-white" />
</div>
<span className="text-sm font-medium text-slate-700">使</span>
</div>
<span className={`text-xl font-bold ${diskUsage > 85 ? "text-red-600" : "text-amber-600"}`}>
{diskUsage}%
</span>
</div>
<Progress value={diskUsage} className="h-2" />
<p className="text-xs text-slate-500 mt-2">1.6TB / 2TB</p>
</CardContent>
</Card>
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardContent className="p-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-emerald-500 to-teal-600 flex items-center justify-center">
<Wifi className="w-4 h-4 text-white" />
</div>
<span className="text-sm font-medium text-slate-700">IO</span>
</div>
<span className="text-xl font-bold text-emerald-600">{networkIO} MB/s</span>
</div>
<Progress value={(networkIO / 500) * 100} className="h-2" />
<p className="text-xs text-slate-500 mt-2">入站: 180MB/s | 出站: 76MB/s</p>
</CardContent>
</Card>
</div>
{/* 功能入口 */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Link href="/monitoring/health">
<Card className="bg-white/70 backdrop-blur border-slate-200/60 hover:shadow-lg hover:border-slate-300 transition-all cursor-pointer group h-full">
<CardContent className="p-5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-emerald-500 to-teal-600 flex items-center justify-center">
<Activity className="w-6 h-6 text-white" />
</div>
<div>
<h3 className="font-semibold text-slate-800"></h3>
<p className="text-sm text-slate-500"></p>
</div>
</div>
<ArrowRight className="w-5 h-5 text-slate-400 group-hover:text-slate-600 group-hover:translate-x-1 transition-all" />
</div>
</CardContent>
</Card>
</Link>
<Link href="/monitoring/alerts">
<Card className="bg-white/70 backdrop-blur border-slate-200/60 hover:shadow-lg hover:border-slate-300 transition-all cursor-pointer group h-full">
<CardContent className="p-5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-red-500 to-orange-600 flex items-center justify-center">
<Bell className="w-6 h-6 text-white" />
</div>
<div>
<h3 className="font-semibold text-slate-800"></h3>
<p className="text-sm text-slate-500"></p>
</div>
</div>
<div className="flex items-center gap-2">
<Badge className="bg-red-100 text-red-700">2</Badge>
<ArrowRight className="w-5 h-5 text-slate-400 group-hover:text-slate-600 group-hover:translate-x-1 transition-all" />
</div>
</div>
</CardContent>
</Card>
</Link>
<Link href="/monitoring/business">
<Card className="bg-white/70 backdrop-blur border-slate-200/60 hover:shadow-lg hover:border-slate-300 transition-all cursor-pointer group h-full">
<CardContent className="p-5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-blue-500 to-indigo-600 flex items-center justify-center">
<TrendingUp className="w-6 h-6 text-white" />
</div>
<div>
<h3 className="font-semibold text-slate-800"></h3>
<p className="text-sm text-slate-500">KPI监控</p>
</div>
</div>
<ArrowRight className="w-5 h-5 text-slate-400 group-hover:text-slate-600 group-hover:translate-x-1 transition-all" />
</div>
</CardContent>
</Card>
</Link>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* 系统资源趋势 */}
<Card className="lg:col-span-2 bg-white/70 backdrop-blur border-slate-200/60">
<CardHeader className="pb-2">
<CardTitle className="text-base"> (24)</CardTitle>
</CardHeader>
<CardContent>
<div className="h-64">
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={systemMetrics}>
<CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" />
<XAxis dataKey="time" tick={{ fontSize: 12 }} stroke="#94a3b8" />
<YAxis tick={{ fontSize: 12 }} stroke="#94a3b8" />
<Tooltip
contentStyle={{
backgroundColor: "white",
border: "1px solid #e2e8f0",
borderRadius: "8px",
}}
/>
<defs>
<linearGradient id="colorCpu" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#3b82f6" stopOpacity={0.3} />
<stop offset="95%" stopColor="#3b82f6" stopOpacity={0} />
</linearGradient>
<linearGradient id="colorMemory" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#8b5cf6" stopOpacity={0.3} />
<stop offset="95%" stopColor="#8b5cf6" stopOpacity={0} />
</linearGradient>
</defs>
<Area type="monotone" dataKey="cpu" stroke="#3b82f6" strokeWidth={2} fill="url(#colorCpu)" />
<Area type="monotone" dataKey="memory" stroke="#8b5cf6" strokeWidth={2} fill="url(#colorMemory)" />
</AreaChart>
</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-blue-500" />
<span className="text-xs text-slate-600">CPU</span>
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-violet-500" />
<span className="text-xs text-slate-600"></span>
</div>
</div>
</CardContent>
</Card>
{/* 最近告警 */}
<Card className="bg-white/70 backdrop-blur border-slate-200/60">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="text-base flex items-center gap-2">
<Bell className="w-4 h-4 text-red-500" />
</CardTitle>
<Link href="/monitoring/alerts">
<Button variant="ghost" size="sm">
</Button>
</Link>
</div>
</CardHeader>
<CardContent>
<div className="space-y-3">
{recentAlerts.map((alert) => (
<div key={alert.id} className="flex items-start gap-3 p-3 bg-slate-50/80 rounded-lg">
{alert.type === "error" ? (
<AlertTriangle className="w-5 h-5 text-red-500 mt-0.5" />
) : alert.type === "warning" ? (
<AlertTriangle className="w-5 h-5 text-amber-500 mt-0.5" />
) : (
<CheckCircle2 className="w-5 h-5 text-blue-500 mt-0.5" />
)}
<div className="flex-1">
<p className="text-sm text-slate-700">{alert.message}</p>
<div className="flex items-center gap-2 mt-1">
<span className="text-xs text-slate-500">{alert.time}</span>
{alert.status === "active" ? (
<Badge className="bg-red-100 text-red-700 text-xs"></Badge>
) : (
<Badge className="bg-emerald-100 text-emerald-700 text-xs"></Badge>
)}
</div>
</div>
</div>
))}
</div>
</CardContent>
</Card>
</div>
{/* 服务状态 */}
<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="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{serviceStatus.map((service, i) => (
<div
key={i}
className={`flex items-center justify-between p-4 rounded-lg border ${
service.status === "healthy"
? "bg-emerald-50/50 border-emerald-200"
: service.status === "degraded"
? "bg-amber-50/50 border-amber-200"
: "bg-red-50/50 border-red-200"
}`}
>
<div className="flex items-center gap-3">
<div
className={`w-3 h-3 rounded-full ${
service.status === "healthy"
? "bg-emerald-500"
: service.status === "degraded"
? "bg-amber-500 animate-pulse"
: "bg-red-500 animate-pulse"
}`}
/>
<div>
<p className="text-sm font-medium text-slate-800">{service.name}</p>
<p className="text-xs text-slate-500">
: {service.uptime} | : {service.latency}
</p>
</div>
</div>
<Badge
className={
service.status === "healthy"
? "bg-emerald-100 text-emerald-700"
: service.status === "degraded"
? "bg-amber-100 text-amber-700"
: "bg-red-100 text-red-700"
}
>
{service.status === "healthy" ? "正常" : service.status === "degraded" ? "降级" : "故障"}
</Badge>
</div>
))}
</div>
</CardContent>
</Card>
</div>
</div>
)
}