Reorganize navigation and module structure based on new requirements. Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
384 lines
14 KiB
TypeScript
384 lines
14 KiB
TypeScript
"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>
|
|
)
|
|
}
|