Files
users/app/monitoring/alerts/page.tsx
v0 b17b488f8e 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>
2026-01-31 04:32:36 +00:00

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>
)
}