refactor: 拆分AIAlertAnalyzer.tsx(541→190行)提取analyzerHelpers+AIAnalysisResult+AutoFixDialog子组件

This commit is contained in:
attacker committed 2026-06-18 00:10:39 +08:00
1 parent 35b167a0e2
commit da5f9a3a07
4 files changed
+427 -377

No files matched your search

+26 -377
View File
@@ -1,16 +1,9 @@
// AI 告警智能分析主容器:左侧告警列表 + 右侧 AI 分析结果(已拆分子组件) // Logan.Li
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { ScrollArea } from '@/components/ui/scroll-area'; import { ScrollArea } from '@/components/ui/scroll-area';
import { alertApi } from '@/services/api'; import { alertApi } from '@/services/api';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { import {
AlertTriangle, AlertTriangle,
Brain, Brain,
@@ -18,202 +11,20 @@ import {
XCircle, XCircle,
Clock, Clock,
Server, Server,
Cpu,
MemoryStick,
HardDrive,
Network,
Zap,
FileText,
Terminal,
TrendingUp, TrendingUp,
AlertCircle, AlertCircle,
Sparkles, Sparkles,
Play,
Copy,
Check,
Loader2, Loader2,
} from 'lucide-react'; } from 'lucide-react';
import {
interface AlertItem { type AlertItem,
id: string; type AIAnalysis,
name: string; generateAIAnalysis,
severity: 'critical' | 'warning' | 'info'; mapAlertEvent,
status: 'active' | 'resolved'; getSeverityColor,
source: string; } from './analyzerHelpers';
metric: string; import { AIAnalysisResult } from './AIAnalysisResult';
threshold: string; import { AutoFixDialog } from './AutoFixDialog';
currentValue: string;
duration: string;
startedAt: string;
description: string;
}
interface AIAnalysis {
rootCause: string;
impact: string;
recommendations: string[];
autoFixScript?: string;
similarCases: number;
confidence: number;
}
// ─── Dynamic AI analysis generator ───
// Based on alert metric type, generates contextually relevant root cause analysis,
// impact assessment, recommendations, and auto-fix scripts.
function generateAIAnalysis(alert: AlertItem): AIAnalysis {
const metric = (alert.metric || '').toLowerCase();
const target = alert.source || '目标主机';
const isCritical = alert.severity === 'critical';
const baseConfidence = isCritical ? 88 + Math.floor(Math.random() * 8) : 75 + Math.floor(Math.random() * 12);
// CPU alerts
if (metric.includes('cpu')) {
return {
rootCause: `检测到 ${target} 上 CPU 使用率达到 ${alert.currentValue},持续超过阈值 ${alert.threshold}。可能原因:1) 某进程存在死循环或高计算负载;2) GC 频繁触发(Java/Go 应用);3) 业务流量突增导致处理能力不足。`,
impact: isCritical
? `CPU 持续高负载将导致服务响应延迟显著增加,严重时可能触发雪崩效应,影响全部依赖该节点的业务。`
: `CPU 使用率偏高,可能导致部分请求处理变慢,建议及时排查避免进一步恶化。`,
recommendations: [
`登录 ${target} 执行 top/htop 定位高 CPU 进程`,
'检查是否有异常定时任务或批处理任务在运行',
`考虑临时扩容或将部分流量调度到其他节点`,
'优化应用代码,排查是否存在低效算法或资源泄漏',
],
autoFixScript: `#!/bin/bash\n# CPU 高负载诊断脚本 - ${target}\necho "===== CPU TOP 10 进程 ====="\nps aux --sort=-%cpu | head -11\necho ""\necho "===== 系统负载 ====="\nuptime\necho ""\necho "===== 高 CPU 进程详情 ====="\nPID=$(ps aux --sort=-%cpu | sed -n '2p' | awk '{print $2}')\nif [ -n "$PID" ]; then\n echo "最高 CPU 进程: PID=$PID"\n cat /proc/$PID/cmdline | tr '\\0' ' '\n echo ""\nfi\n`,
similarCases: 18 + Math.floor(Math.random() * 8),
confidence: baseConfidence,
};
}
// Memory alerts
if (metric.includes('mem') || metric.includes('ram')) {
return {
rootCause: `检测到 ${target} 内存使用率达到 ${alert.currentValue},接近或超过阈值 ${alert.threshold}。常见原因:1) 应用内存泄漏,对象未被正确释放;2) 缓存配置过大;3) 数据库连接池未及时释放。`,
impact: isCritical
? `内存即将耗尽可能触发 OOM Killer,导致关键进程被强制终止,服务中断风险极高。`
: `内存持续增长,长期可能导致 OOM,建议尽快排查内存使用情况。`,
recommendations: [
`检查 ${target} 上的内存占用最高的进程`,
'如果是 Java 应用,检查 JVM 堆内存配置和 GC 日志',
'排查是否存在内存泄漏,必要时生成堆转储分析',
'调整缓存大小限制或增加交换分区',
],
similarCases: 12 + Math.floor(Math.random() * 6),
confidence: baseConfidence,
};
}
// Disk alerts
if (metric.includes('disk') || metric.includes('space')) {
return {
rootCause: `检测到 ${target} 磁盘使用率达到 ${alert.currentValue},超过阈值 ${alert.threshold}。最常见原因:1) 日志文件未配置轮转/清理;2) 临时文件堆积;3) 数据库数据文件膨胀。`,
impact: isCritical
? `磁盘空间不足可能导致服务无法写入数据/日志,数据库可能崩溃,文件系统变只读,影响范围极大。`
: `磁盘空间告警,如不及时清理可能导致服务异常。`,
recommendations: [
`清理 ${target} 上 30 天前的日志文件`,
'检查并清理临时目录 /tmp、/var/tmp',
'配置 logrotate 日志轮转策略',
'考虑扩容磁盘或将数据迁移到对象存储',
],
autoFixScript: `#!/bin/bash\n# 磁盘清理脚本 - ${target}\necho "===== 清理前磁盘使用 ====="\ndf -h /\necho ""\necho "===== 清理 30 天前日志 ====="\nfind /var/log -name "*.log" -mtime +30 -delete 2>/dev/null\nfind /var/log -name "*.gz" -mtime +30 -delete 2>/dev/null\necho ""\necho "===== 清理系统缓存 ====="\njournalctl --vacuum-time=7d 2>/dev/null\necho ""\necho "===== 清理后磁盘使用 ====="\ndf -h /\n`,
similarCases: 23 + Math.floor(Math.random() * 10),
confidence: baseConfidence + 3,
};
}
// Network alerts
if (metric.includes('net') || metric.includes('latency') || metric.includes('bandwidth')) {
return {
rootCause: `检测到 ${target} 网络指标异常(当前值 ${alert.currentValue}),可能原因:1) 上游依赖服务响应变慢;2) 网络带宽打满;3) DNS 解析延迟;4) 连接数达到上限。`,
impact: isCritical
? `网络异常将导致所有经过该节点的请求超时,影响所有上游业务的可用性。`
: `网络延迟偏高,可能影响用户体验,建议排查网络链路。`,
recommendations: [
`检查 ${target} 的网络连接和带宽使用情况`,
'排查上游依赖服务的健康状态和响应时间',
'检查防火墙规则是否有限速或异常丢弃',
'考虑启用熔断/降级机制保障核心链路',
],
similarCases: 8 + Math.floor(Math.random() * 5),
confidence: baseConfidence - 5,
};
}
// Service availability alerts
if (metric.includes('service') || metric.includes('up') || metric.includes('avail')) {
return {
rootCause: `检测到 ${target} 服务不可用(指标 ${alert.metric} = ${alert.currentValue})。可能原因:1) 进程崩溃或被 OOM Killer 终止;2) 端口被占用或监听异常;3) 健康检查端点返回错误;4) 配置文件错误导致启动失败。`,
impact: isCritical
? `服务完全不可用,所有依赖该服务的业务功能将中断,需要立即处理。`
: `服务可用性下降,部分功能可能受影响。`,
recommendations: [
`立即检查 ${target} 的服务状态: systemctl status <service>`,
'查看服务日志排查崩溃原因',
'检查端口占用和监听状态: ss -tlnp',
'必要时重启服务并监控恢复情况',
],
autoFixScript: `#!/bin/bash\n# 服务恢复脚本 - ${target}\necho "===== 检查服务状态 ====="\nsystemctl list-units --state=failed\necho ""\necho "===== 检查端口监听 ====="\nss -tlnp | grep -E ':(80|443|8080|3306|6379|9090)'\necho ""\necho "===== 重启失败服务 ====="\nsystemctl reset-failed\n`,
similarCases: 6 + Math.floor(Math.random() * 4),
confidence: baseConfidence,
};
}
// Default / generic analysis
return {
rootCause: `检测到 ${target} 触发告警「${alert.name}」,当前指标值 ${alert.currentValue} 超过阈值 ${alert.threshold}。建议进一步排查具体指标数据和相关日志以确定根因。`,
impact: isCritical
? `该告警级别为严重,可能影响业务正常运行,建议优先处理。`
: `该告警需要关注,持续恶化可能影响系统稳定性。`,
recommendations: [
`查看 ${target} 的监控仪表盘,确认指标趋势`,
'检查对应时间段的系统日志和应用日志',
'关联同时间段的其他告警,排查是否有连锁反应',
'若确认误报,调整告警规则阈值或持续时间',
],
similarCases: 5 + Math.floor(Math.random() * 5),
confidence: baseConfidence - 8,
};
}
// ─── Backend alert event → AlertItem mapping ───
function mapAlertEvent(event: any): AlertItem {
const expr = event.rule?.expression || '';
// Extract metric name and threshold from expression like "cpu_usage > 80"
const metricMatch = expr.match(/([\w_]+)\s*[><=!]+\s*([\d.]+)/);
const metric = metricMatch ? metricMatch[1] : expr || 'unknown';
const threshold = metricMatch ? metricMatch[2] + '%' : '-';
const value = event.value ?? 0;
const isPercent = metric.includes('usage') || metric.includes('cpu') || metric.includes('disk') || metric.includes('mem');
const currentValue = isPercent ? value.toFixed(1) + '%' : value.toFixed(2);
// Compute duration from started_at to now (or resolved_at)
const endTime = event.resolved_at ? new Date(event.resolved_at) : new Date();
const startTime = new Date(event.started_at);
const diffMin = Math.max(1, Math.round((endTime.getTime() - startTime.getTime()) / 60000));
const duration = diffMin >= 60 ? `${Math.floor(diffMin / 60)}小时${diffMin % 60}分钟` : `${diffMin}分钟`;
const startedAt = startTime.toLocaleString('zh-CN', { hour12: false });
return {
id: String(event.id),
name: event.rule_name || event.rule?.name || '未知告警',
severity: event.severity === 'critical' ? 'critical' : event.severity === 'warning' ? 'warning' : 'info',
status: event.status === 'firing' ? 'active' : 'resolved',
source: event.target || '未知目标',
metric,
threshold,
currentValue,
duration,
startedAt,
description: event.message && event.message !== `告警事件 ${event.id}`
? event.message
: event.rule?.description || `${event.rule_name || '告警'}: ${event.target} 当前值 ${currentValue}`,
};
}
export function AIAlertAnalyzer() { export function AIAlertAnalyzer() {
const [alerts, setAlerts] = useState<AlertItem[]>([]); const [alerts, setAlerts] = useState<AlertItem[]>([]);
@@ -222,7 +33,6 @@ export function AIAlertAnalyzer() {
const [isAnalyzing, setIsAnalyzing] = useState(false); const [isAnalyzing, setIsAnalyzing] = useState(false);
const [analysis, setAnalysis] = useState<AIAnalysis | null>(null); const [analysis, setAnalysis] = useState<AIAnalysis | null>(null);
const [showScriptDialog, setShowScriptDialog] = useState(false); const [showScriptDialog, setShowScriptDialog] = useState(false);
const [copied, setCopied] = useState(false);
useEffect(() => { useEffect(() => {
loadAlerts(); loadAlerts();
@@ -232,6 +42,7 @@ export function AIAlertAnalyzer() {
setLoading(true); setLoading(true);
try { try {
const res: any = await alertApi.getAlerts({ page: 1, page_size: 50 }); const res: any = await alertApi.getAlerts({ page: 1, page_size: 50 });
// 列表响应格式:http.ts 已提取 .data,此处仍是 {list, total}
const list = res?.list || res || []; const list = res?.list || res || [];
setAlerts(list.map(mapAlertEvent)); setAlerts(list.map(mapAlertEvent));
} catch (e) { } catch (e) {
@@ -242,12 +53,12 @@ export function AIAlertAnalyzer() {
} }
}; };
// 触发 AI 分析(模拟 1.5s 思考,实际由 generateAIAnalysis 动态生成上下文相关结果)
const handleAnalyze = (alert: AlertItem) => { const handleAnalyze = (alert: AlertItem) => {
setSelectedAlert(alert); setSelectedAlert(alert);
setIsAnalyzing(true); setIsAnalyzing(true);
setAnalysis(null); setAnalysis(null);
// Simulate AI analysis with dynamic generation
setTimeout(() => { setTimeout(() => {
setAnalysis(generateAIAnalysis(alert)); setAnalysis(generateAIAnalysis(alert));
setIsAnalyzing(false); setIsAnalyzing(false);
@@ -258,25 +69,7 @@ export function AIAlertAnalyzer() {
setShowScriptDialog(true); setShowScriptDialog(true);
}; };
const copyScript = () => { // 严重等级图标(返回 JSX,保留在主组件中)
if (analysis?.autoFixScript) {
navigator.clipboard.writeText(analysis.autoFixScript);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
};
const getSeverityColor = (severity: string) => {
switch (severity) {
case 'critical':
return 'bg-red-500/10 text-red-400 border-red-500/30';
case 'warning':
return 'bg-amber-500/10 text-amber-400 border-amber-500/30';
default:
return 'bg-blue-500/10 text-blue-400 border-blue-500/30';
}
};
const getSeverityIcon = (severity: string) => { const getSeverityIcon = (severity: string) => {
switch (severity) { switch (severity) {
case 'critical': case 'critical':
@@ -310,7 +103,7 @@ export function AIAlertAnalyzer() {
</div> </div>
<div className="grid grid-cols-2 gap-6 h-[calc(100vh-200px)]"> <div className="grid grid-cols-2 gap-6 h-[calc(100vh-200px)]">
{/* Alert List */} {/* 告警列表(左侧) */}
<Card className="bg-slate-800 border-slate-700 flex flex-col"> <Card className="bg-slate-800 border-slate-700 flex flex-col">
<CardHeader className="border-b border-slate-700 pb-3"> <CardHeader className="border-b border-slate-700 pb-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
@@ -377,165 +170,21 @@ export function AIAlertAnalyzer() {
</ScrollArea> </ScrollArea>
</Card> </Card>
{/* AI Analysis Result */} {/* AI 分析结果(右侧,子组件) */}
<Card className="bg-slate-800 border-slate-700 flex flex-col"> <AIAnalysisResult
<CardHeader className="border-b border-slate-700 pb-3"> selectedAlert={selectedAlert}
<CardTitle className="text-slate-100 flex items-center gap-2"> isAnalyzing={isAnalyzing}
<Sparkles className="h-5 w-5 text-purple-400" /> analysis={analysis}
AI 分析结果 onAutoFix={handleAutoFix}
</CardTitle>
</CardHeader>
<ScrollArea className="flex-1">
<CardContent className="p-4">
{!selectedAlert ? (
<div className="flex flex-col items-center justify-center h-64 text-slate-500">
<Brain className="h-16 w-16 mb-4 opacity-30" />
<p>选择左侧告警进行AI智能分析</p>
</div>
) : isAnalyzing ? (
<div className="flex flex-col items-center justify-center h-64">
<Loader2 className="h-8 w-8 animate-spin text-purple-400 mb-4" />
<p className="text-slate-400">AI正在分析告警根因...</p>
<p className="text-xs text-slate-500 mt-2">正在关联日志、指标和历史案例</p>
</div>
) : analysis ? (
<div className="space-y-4">
{/* Confidence Score */}
<div className="flex items-center justify-between p-3 bg-purple-500/10 rounded-lg border border-purple-500/30">
<div className="flex items-center gap-2">
<Brain className="h-5 w-5 text-purple-400" />
<span className="text-slate-300">分析置信度</span>
</div>
<div className="flex items-center gap-2">
<div className="w-32 h-2 bg-slate-700 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-purple-500 to-pink-500"
style={{ width: `${analysis.confidence}%` }}
/> />
</div> </div>
<span className="text-purple-400 font-bold">{analysis.confidence}%</span>
</div>
</div>
{/* Root Cause */} {/* 自动修复脚本确认弹窗(子组件) */}
<div className="space-y-2"> <AutoFixDialog
<h4 className="text-sm font-medium text-slate-300 flex items-center gap-2"> open={showScriptDialog}
<AlertCircle className="h-4 w-4 text-red-400" /> onOpenChange={setShowScriptDialog}
根因分析 script={analysis?.autoFixScript}
</h4> />
<div className="p-3 bg-slate-900 rounded-lg border border-slate-700">
<p className="text-sm text-slate-300 leading-relaxed">{analysis.rootCause}</p>
</div>
</div>
{/* Impact */}
<div className="space-y-2">
<h4 className="text-sm font-medium text-slate-300 flex items-center gap-2">
<TrendingUp className="h-4 w-4 text-amber-400" />
影响范围
</h4>
<div className="p-3 bg-slate-900 rounded-lg border border-slate-700">
<p className="text-sm text-slate-300 leading-relaxed">{analysis.impact}</p>
</div>
</div>
{/* Recommendations */}
<div className="space-y-2">
<h4 className="text-sm font-medium text-slate-300 flex items-center gap-2">
<CheckCircle className="h-4 w-4 text-emerald-400" />
修复建议
</h4>
<div className="space-y-2">
{analysis.recommendations.map((rec, idx) => (
<div
key={idx}
className="flex items-start gap-2 p-2 bg-slate-900 rounded-lg border border-slate-700"
>
<span className="flex-shrink-0 w-5 h-5 rounded-full bg-emerald-500/20 text-emerald-400 flex items-center justify-center text-xs">
{idx + 1}
</span>
<span className="text-sm text-slate-300">{rec}</span>
</div>
))}
</div>
</div>
{/* Similar Cases */}
<div className="flex items-center gap-2 p-3 bg-slate-900 rounded-lg border border-slate-700">
<FileText className="h-4 w-4 text-blue-400" />
<span className="text-sm text-slate-400">
发现 <span className="text-blue-400 font-medium">{analysis.similarCases}</span> 个相似历史案例
</span>
</div>
{/* Auto Fix */}
{analysis.autoFixScript && (
<div className="space-y-2">
<h4 className="text-sm font-medium text-slate-300 flex items-center gap-2">
<Terminal className="h-4 w-4 text-purple-400" />
自动修复脚本
</h4>
<div className="p-3 bg-slate-950 rounded-lg border border-slate-700">
<pre className="text-xs text-slate-400 overflow-x-auto">{analysis.autoFixScript}</pre>
</div>
<Button
className="w-full bg-purple-600 hover:bg-purple-700"
onClick={handleAutoFix}
>
<Play className="h-4 w-4 mr-2" />
执行自动修复
</Button>
</div>
)}
</div>
) : null}
</CardContent>
</ScrollArea>
</Card>
</div>
{/* Auto Fix Dialog */}
<Dialog open={showScriptDialog} onOpenChange={setShowScriptDialog}>
<DialogContent className="max-w-2xl bg-slate-900 border-slate-700 text-slate-100">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Terminal className="h-5 w-5 text-purple-400" />
自动修复脚本
</DialogTitle>
<DialogDescription className="text-slate-500">
AI生成的自动修复脚本,请确认后执行
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="p-4 bg-slate-950 rounded-lg border border-slate-700">
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-slate-400">修复脚本</span>
<Button variant="ghost" size="sm" onClick={copyScript} className="text-slate-400">
{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
</Button>
</div>
<pre className="text-sm text-slate-300 overflow-x-auto whitespace-pre-wrap">
{analysis?.autoFixScript}
</pre>
</div>
<div className="flex items-center gap-2 p-3 bg-amber-500/10 rounded-lg border border-amber-500/30">
<AlertTriangle className="h-4 w-4 text-amber-400" />
<span className="text-sm text-amber-400">
警告:执行脚本前请确保已备份重要数据
</span>
</div>
</div>
<div className="flex gap-2">
<Button variant="outline" className="flex-1 border-slate-700 text-slate-300" onClick={() => setShowScriptDialog(false)}>
取消
</Button>
<Button className="flex-1 bg-purple-600 hover:bg-purple-700" onClick={() => setShowScriptDialog(false)}>
<Play className="h-4 w-4 mr-2" />
确认执行
</Button>
</div>
</DialogContent>
</Dialog>
</div> </div>
); );
} }
@@ -0,0 +1,133 @@
// AI 分析结果卡片(右侧面板):置信度/根因/影响/修复建议/相似案例/自动修复脚本 // Logan.Li
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Sparkles, Brain, Loader2 } from 'lucide-react';
import { AlertCircle, TrendingUp, CheckCircle, FileText, Terminal, Play } from 'lucide-react';
import type { AlertItem, AIAnalysis } from './analyzerHelpers';
interface AIAnalysisResultProps {
selectedAlert: AlertItem | null;
isAnalyzing: boolean;
analysis: AIAnalysis | null;
onAutoFix: () => void;
}
export function AIAnalysisResult({ selectedAlert, isAnalyzing, analysis, onAutoFix }: AIAnalysisResultProps) {
return (
<Card className="bg-slate-800 border-slate-700 flex flex-col">
<CardHeader className="border-b border-slate-700 pb-3">
<CardTitle className="text-slate-100 flex items-center gap-2">
<Sparkles className="h-5 w-5 text-purple-400" />
AI 分析结果
</CardTitle>
</CardHeader>
<ScrollArea className="flex-1">
<CardContent className="p-4">
{!selectedAlert ? (
<div className="flex flex-col items-center justify-center h-64 text-slate-500">
<Brain className="h-16 w-16 mb-4 opacity-30" />
<p>选择左侧告警进行AI智能分析</p>
</div>
) : isAnalyzing ? (
<div className="flex flex-col items-center justify-center h-64">
<Loader2 className="h-8 w-8 animate-spin text-purple-400 mb-4" />
<p className="text-slate-400">AI正在分析告警根因...</p>
<p className="text-xs text-slate-500 mt-2">正在关联日志、指标和历史案例</p>
</div>
) : analysis ? (
<div className="space-y-4">
{/* 置信度 */}
<div className="flex items-center justify-between p-3 bg-purple-500/10 rounded-lg border border-purple-500/30">
<div className="flex items-center gap-2">
<Brain className="h-5 w-5 text-purple-400" />
<span className="text-slate-300">分析置信度</span>
</div>
<div className="flex items-center gap-2">
<div className="w-32 h-2 bg-slate-700 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-purple-500 to-pink-500"
style={{ width: `${analysis.confidence}%` }}
/>
</div>
<span className="text-purple-400 font-bold">{analysis.confidence}%</span>
</div>
</div>
{/* 根因分析 */}
<div className="space-y-2">
<h4 className="text-sm font-medium text-slate-300 flex items-center gap-2">
<AlertCircle className="h-4 w-4 text-red-400" />
根因分析
</h4>
<div className="p-3 bg-slate-900 rounded-lg border border-slate-700">
<p className="text-sm text-slate-300 leading-relaxed">{analysis.rootCause}</p>
</div>
</div>
{/* 影响范围 */}
<div className="space-y-2">
<h4 className="text-sm font-medium text-slate-300 flex items-center gap-2">
<TrendingUp className="h-4 w-4 text-amber-400" />
影响范围
</h4>
<div className="p-3 bg-slate-900 rounded-lg border border-slate-700">
<p className="text-sm text-slate-300 leading-relaxed">{analysis.impact}</p>
</div>
</div>
{/* 修复建议 */}
<div className="space-y-2">
<h4 className="text-sm font-medium text-slate-300 flex items-center gap-2">
<CheckCircle className="h-4 w-4 text-emerald-400" />
修复建议
</h4>
<div className="space-y-2">
{analysis.recommendations.map((rec, idx) => (
<div
key={idx}
className="flex items-start gap-2 p-2 bg-slate-900 rounded-lg border border-slate-700"
>
<span className="flex-shrink-0 w-5 h-5 rounded-full bg-emerald-500/20 text-emerald-400 flex items-center justify-center text-xs">
{idx + 1}
</span>
<span className="text-sm text-slate-300">{rec}</span>
</div>
))}
</div>
</div>
{/* 相似历史案例 */}
<div className="flex items-center gap-2 p-3 bg-slate-900 rounded-lg border border-slate-700">
<FileText className="h-4 w-4 text-blue-400" />
<span className="text-sm text-slate-400">
发现 <span className="text-blue-400 font-medium">{analysis.similarCases}</span> 个相似历史案例
</span>
</div>
{/* 自动修复脚本 */}
{analysis.autoFixScript && (
<div className="space-y-2">
<h4 className="text-sm font-medium text-slate-300 flex items-center gap-2">
<Terminal className="h-4 w-4 text-purple-400" />
自动修复脚本
</h4>
<div className="p-3 bg-slate-950 rounded-lg border border-slate-700">
<pre className="text-xs text-slate-400 overflow-x-auto">{analysis.autoFixScript}</pre>
</div>
<Button
className="w-full bg-purple-600 hover:bg-purple-700"
onClick={onAutoFix}
>
<Play className="h-4 w-4 mr-2" />
执行自动修复
</Button>
</div>
)}
</div>
) : null}
</CardContent>
</ScrollArea>
</Card>
);
}
@@ -0,0 +1,73 @@
// 自动修复脚本确认弹窗:展示 AI 生成的修复脚本 + 复制按钮 + 确认执行 // Logan.Li
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { AlertTriangle, Copy, Check, Play, Terminal } from 'lucide-react';
interface AutoFixDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
script?: string;
}
export function AutoFixDialog({ open, onOpenChange, script }: AutoFixDialogProps) {
const [copied, setCopied] = useState(false);
const copyScript = () => {
if (script) {
navigator.clipboard.writeText(script);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-2xl bg-slate-900 border-slate-700 text-slate-100">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Terminal className="h-5 w-5 text-purple-400" />
自动修复脚本
</DialogTitle>
<DialogDescription className="text-slate-500">
AI生成的自动修复脚本,请确认后执行
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="p-4 bg-slate-950 rounded-lg border border-slate-700">
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-slate-400">修复脚本</span>
<Button variant="ghost" size="sm" onClick={copyScript} className="text-slate-400">
{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
</Button>
</div>
<pre className="text-sm text-slate-300 overflow-x-auto whitespace-pre-wrap">
{script}
</pre>
</div>
<div className="flex items-center gap-2 p-3 bg-amber-500/10 rounded-lg border border-amber-500/30">
<AlertTriangle className="h-4 w-4 text-amber-400" />
<span className="text-sm text-amber-400">
警告:执行脚本前请确保已备份重要数据
</span>
</div>
</div>
<div className="flex gap-2">
<Button variant="outline" className="flex-1 border-slate-700 text-slate-300" onClick={() => onOpenChange(false)}>
取消
</Button>
<Button className="flex-1 bg-purple-600 hover:bg-purple-700" onClick={() => onOpenChange(false)}>
<Play className="h-4 w-4 mr-2" />
确认执行
</Button>
</div>
</DialogContent>
</Dialog>
);
}
@@ -0,0 +1,195 @@
// AI 告警分析纯逻辑辅助函数:类型定义 + 后端告警映射 + 动态 AI 分析生成 + 严重等级配色 // Logan.Li
// 告警条目(前端统一模型,从后端 alert event 映射而来)
export interface AlertItem {
id: string;
name: string;
severity: 'critical' | 'warning' | 'info';
status: 'active' | 'resolved';
source: string;
metric: string;
threshold: string;
currentValue: string;
duration: string;
startedAt: string;
description: string;
}
// AI 分析结果
export interface AIAnalysis {
rootCause: string;
impact: string;
recommendations: string[];
autoFixScript?: string;
similarCases: number;
confidence: number;
}
// ─── Dynamic AI analysis generator ───
// 基于告警指标类型,生成上下文相关的根因分析、影响评估、修复建议与自动修复脚本。
export function generateAIAnalysis(alert: AlertItem): AIAnalysis {
const metric = (alert.metric || '').toLowerCase();
const target = alert.source || '目标主机';
const isCritical = alert.severity === 'critical';
const baseConfidence = isCritical ? 88 + Math.floor(Math.random() * 8) : 75 + Math.floor(Math.random() * 12);
// CPU 告警
if (metric.includes('cpu')) {
return {
rootCause: `检测到 ${target} 上 CPU 使用率达到 ${alert.currentValue},持续超过阈值 ${alert.threshold}。可能原因:1) 某进程存在死循环或高计算负载;2) GC 频繁触发(Java/Go 应用);3) 业务流量突增导致处理能力不足。`,
impact: isCritical
? `CPU 持续高负载将导致服务响应延迟显著增加,严重时可能触发雪崩效应,影响全部依赖该节点的业务。`
: `CPU 使用率偏高,可能导致部分请求处理变慢,建议及时排查避免进一步恶化。`,
recommendations: [
`登录 ${target} 执行 top/htop 定位高 CPU 进程`,
'检查是否有异常定时任务或批处理任务在运行',
`考虑临时扩容或将部分流量调度到其他节点`,
'优化应用代码,排查是否存在低效算法或资源泄漏',
],
autoFixScript: `#!/bin/bash\n# CPU 高负载诊断脚本 - ${target}\necho "===== CPU TOP 10 进程 ====="\nps aux --sort=-%cpu | head -11\necho ""\necho "===== 系统负载 ====="\nuptime\necho ""\necho "===== 高 CPU 进程详情 ====="\nPID=$(ps aux --sort=-%cpu | sed -n '2p' | awk '{print $2}')\nif [ -n "$PID" ]; then\n echo "最高 CPU 进程: PID=$PID"\n cat /proc/$PID/cmdline | tr '\\0' ' '\n echo ""\nfi\n`,
similarCases: 18 + Math.floor(Math.random() * 8),
confidence: baseConfidence,
};
}
// 内存告警
if (metric.includes('mem') || metric.includes('ram')) {
return {
rootCause: `检测到 ${target} 内存使用率达到 ${alert.currentValue},接近或超过阈值 ${alert.threshold}。常见原因:1) 应用内存泄漏,对象未被正确释放;2) 缓存配置过大;3) 数据库连接池未及时释放。`,
impact: isCritical
? `内存即将耗尽可能触发 OOM Killer,导致关键进程被强制终止,服务中断风险极高。`
: `内存持续增长,长期可能导致 OOM,建议尽快排查内存使用情况。`,
recommendations: [
`检查 ${target} 上的内存占用最高的进程`,
'如果是 Java 应用,检查 JVM 堆内存配置和 GC 日志',
'排查是否存在内存泄漏,必要时生成堆转储分析',
'调整缓存大小限制或增加交换分区',
],
similarCases: 12 + Math.floor(Math.random() * 6),
confidence: baseConfidence,
};
}
// 磁盘告警
if (metric.includes('disk') || metric.includes('space')) {
return {
rootCause: `检测到 ${target} 磁盘使用率达到 ${alert.currentValue},超过阈值 ${alert.threshold}。最常见原因:1) 日志文件未配置轮转/清理;2) 临时文件堆积;3) 数据库数据文件膨胀。`,
impact: isCritical
? `磁盘空间不足可能导致服务无法写入数据/日志,数据库可能崩溃,文件系统变只读,影响范围极大。`
: `磁盘空间告警,如不及时清理可能导致服务异常。`,
recommendations: [
`清理 ${target} 上 30 天前的日志文件`,
'检查并清理临时目录 /tmp、/var/tmp',
'配置 logrotate 日志轮转策略',
'考虑扩容磁盘或将数据迁移到对象存储',
],
autoFixScript: `#!/bin/bash\n# 磁盘清理脚本 - ${target}\necho "===== 清理前磁盘使用 ====="\ndf -h /\necho ""\necho "===== 清理 30 天前日志 ====="\nfind /var/log -name "*.log" -mtime +30 -delete 2>/dev/null\nfind /var/log -name "*.gz" -mtime +30 -delete 2>/dev/null\necho ""\necho "===== 清理系统缓存 ====="\njournalctl --vacuum-time=7d 2>/dev/null\necho ""\necho "===== 清理后磁盘使用 ====="\ndf -h /\n`,
similarCases: 23 + Math.floor(Math.random() * 10),
confidence: baseConfidence + 3,
};
}
// 网络告警
if (metric.includes('net') || metric.includes('latency') || metric.includes('bandwidth')) {
return {
rootCause: `检测到 ${target} 网络指标异常(当前值 ${alert.currentValue}),可能原因:1) 上游依赖服务响应变慢;2) 网络带宽打满;3) DNS 解析延迟;4) 连接数达到上限。`,
impact: isCritical
? `网络异常将导致所有经过该节点的请求超时,影响所有上游业务的可用性。`
: `网络延迟偏高,可能影响用户体验,建议排查网络链路。`,
recommendations: [
`检查 ${target} 的网络连接和带宽使用情况`,
'排查上游依赖服务的健康状态和响应时间',
'检查防火墙规则是否有限速或异常丢弃',
'考虑启用熔断/降级机制保障核心链路',
],
similarCases: 8 + Math.floor(Math.random() * 5),
confidence: baseConfidence - 5,
};
}
// 服务可用性告警
if (metric.includes('service') || metric.includes('up') || metric.includes('avail')) {
return {
rootCause: `检测到 ${target} 服务不可用(指标 ${alert.metric} = ${alert.currentValue})。可能原因:1) 进程崩溃或被 OOM Killer 终止;2) 端口被占用或监听异常;3) 健康检查端点返回错误;4) 配置文件错误导致启动失败。`,
impact: isCritical
? `服务完全不可用,所有依赖该服务的业务功能将中断,需要立即处理。`
: `服务可用性下降,部分功能可能受影响。`,
recommendations: [
`立即检查 ${target} 的服务状态: systemctl status <service>`,
'查看服务日志排查崩溃原因',
'检查端口占用和监听状态: ss -tlnp',
'必要时重启服务并监控恢复情况',
],
autoFixScript: `#!/bin/bash\n# 服务恢复脚本 - ${target}\necho "===== 检查服务状态 ====="\nsystemctl list-units --state=failed\necho ""\necho "===== 检查端口监听 ====="\nss -tlnp | grep -E ':(80|443|8080|3306|6379|9090)'\necho ""\necho "===== 重启失败服务 ====="\nsystemctl reset-failed\n`,
similarCases: 6 + Math.floor(Math.random() * 4),
confidence: baseConfidence,
};
}
// 默认/通用分析
return {
rootCause: `检测到 ${target} 触发告警「${alert.name}」,当前指标值 ${alert.currentValue} 超过阈值 ${alert.threshold}。建议进一步排查具体指标数据和相关日志以确定根因。`,
impact: isCritical
? `该告警级别为严重,可能影响业务正常运行,建议优先处理。`
: `该告警需要关注,持续恶化可能影响系统稳定性。`,
recommendations: [
`查看 ${target} 的监控仪表盘,确认指标趋势`,
'检查对应时间段的系统日志和应用日志',
'关联同时间段的其他告警,排查是否有连锁反应',
'若确认误报,调整告警规则阈值或持续时间',
],
similarCases: 5 + Math.floor(Math.random() * 5),
confidence: baseConfidence - 8,
};
}
// ─── 后端告警事件 → AlertItem 映射 ───
// 后端 alert event 字段为 snake_case(rule_name/target/started_at/value 等),此处统一映射为前端 AlertItem
export function mapAlertEvent(event: any): AlertItem {
const expr = event.rule?.expression || '';
// 从表达式(如 "cpu_usage > 80")中提取指标名与阈值
const metricMatch = expr.match(/([\w_]+)\s*[><=!]+\s*([\d.]+)/);
const metric = metricMatch ? metricMatch[1] : expr || 'unknown';
const threshold = metricMatch ? metricMatch[2] + '%' : '-';
const value = event.value ?? 0;
const isPercent = metric.includes('usage') || metric.includes('cpu') || metric.includes('disk') || metric.includes('mem');
const currentValue = isPercent ? value.toFixed(1) + '%' : value.toFixed(2);
// 从 started_at 到现在(或 resolved_at)计算持续时间
const endTime = event.resolved_at ? new Date(event.resolved_at) : new Date();
const startTime = new Date(event.started_at);
const diffMin = Math.max(1, Math.round((endTime.getTime() - startTime.getTime()) / 60000));
const duration = diffMin >= 60 ? `${Math.floor(diffMin / 60)}小时${diffMin % 60}分钟` : `${diffMin}分钟`;
const startedAt = startTime.toLocaleString('zh-CN', { hour12: false });
return {
id: String(event.id),
name: event.rule_name || event.rule?.name || '未知告警',
severity: event.severity === 'critical' ? 'critical' : event.severity === 'warning' ? 'warning' : 'info',
status: event.status === 'firing' ? 'active' : 'resolved',
source: event.target || '未知目标',
metric,
threshold,
currentValue,
duration,
startedAt,
description: event.message && event.message !== `告警事件 ${event.id}`
? event.message
: event.rule?.description || `${event.rule_name || '告警'}: ${event.target} 当前值 ${currentValue}`,
};
}
// 严重等级配色(纯字符串,无 JSX)
export function getSeverityColor(severity: string): string {
switch (severity) {
case 'critical':
return 'bg-red-500/10 text-red-400 border-red-500/30';
case 'warning':
return 'bg-amber-500/10 text-amber-400 border-amber-500/30';
default:
return 'bg-blue-500/10 text-blue-400 border-blue-500/30';
}
}