refactor: 拆分AIAlertAnalyzer.tsx(541→190行)提取analyzerHelpers+AIAnalysisResult+AutoFixDialog子组件
This commit is contained in:
1 parent
35b167a0e2
commit
da5f9a3a07
4 files changed
+427
-377
No files matched your search
@@ -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';
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user