feat: AI告警智能分析接入真实后端告警事件 — 替换硬编码mock为动态分析

- AIAlertAnalyzer: 从 GET /alerts 加载真实告警事件(替代4条硬编码mock)
- mapAlertEvent: 映射AlertEvent→AlertItem(解析表达式提取指标/阈值,计算持续时间)
- generateAIAnalysis: 按指标类型(CPU/内存/磁盘/网络/服务)动态生成根因分析/影响评估/修复建议/自动修复脚本
- Dashboard: 服务器/告警增长率标签从硬编码改为数据驱动(基于error/week_count)
- DomainManager: 到期时间截取YYYY-MM-DD格式
- PipelineManager: 流水线创建传递stages/code/type字段
This commit is contained in:
attacker committed 2026-06-14 14:07:03 +08:00
1 parent bae3a2f013
commit b99e971fc9
4 files changed
+210 -134

No files matched your search

+188 -126
View File
@@ -1,10 +1,9 @@
import { useState } 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 { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { ScrollArea } from '@/components/ui/scroll-area'; import { ScrollArea } from '@/components/ui/scroll-area';
import { Separator } from '@/components/ui/separator'; import { alertApi } from '@/services/api';
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@@ -58,150 +57,201 @@ interface AIAnalysis {
confidence: number; confidence: number;
} }
const mockAlerts: AlertItem[] = [ // ─── Dynamic AI analysis generator ───
{ // Based on alert metric type, generates contextually relevant root cause analysis,
id: 'ALT-001', // impact assessment, recommendations, and auto-fix scripts.
name: 'CPU使用率过高',
severity: 'critical',
status: 'active',
source: 'web-server-03',
metric: 'cpu_usage',
threshold: '80%',
currentValue: '96.5%',
duration: '15分钟',
startedAt: '2025-04-14 14:30:00',
description: '服务器CPU使用率持续超过阈值',
},
{
id: 'ALT-002',
name: '内存使用率告警',
severity: 'warning',
status: 'active',
source: 'db-master-01',
metric: 'memory_usage',
threshold: '85%',
currentValue: '89.2%',
duration: '8分钟',
startedAt: '2025-04-14 14:37:00',
description: '数据库服务器内存使用率接近上限',
},
{
id: 'ALT-003',
name: '磁盘空间不足',
severity: 'critical',
status: 'active',
source: 'log-server-02',
metric: 'disk_usage',
threshold: '90%',
currentValue: '94.8%',
duration: '32分钟',
startedAt: '2025-04-14 14:13:00',
description: '日志服务器磁盘空间即将耗尽',
},
{
id: 'ALT-004',
name: '网络延迟过高',
severity: 'warning',
status: 'active',
source: 'api-gateway-01',
metric: 'network_latency',
threshold: '100ms',
currentValue: '245ms',
duration: '5分钟',
startedAt: '2025-04-14 14:40:00',
description: 'API网关响应延迟异常',
},
];
const mockAIAnalysis: Record<string, AIAnalysis> = { function generateAIAnalysis(alert: AlertItem): AIAnalysis {
'ALT-001': { const metric = (alert.metric || '').toLowerCase();
rootCause: '检测到web-server-03上运行的Java应用出现内存泄漏,导致GC频繁触发,CPU使用率飙升。具体为订单服务(order-service)的某个定时任务未正确释放资源。', const target = alert.source || '目标主机';
impact: '影响用户下单体验,响应时间从平均200ms增加到1200ms,预计影响约35%的在线用户。', 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: [ recommendations: [
'立即重启order-service服务释放内存(预计恢复时间2分钟)', `登录 ${target} 执行 top/htop 定位高 CPU 进程`,
'检查并修复定时任务中的资源释放逻辑', '检查是否有异常定时任务或批处理任务在运行',
'考虑临时扩容到web-server-04分流部分流量', `考虑临时扩容或将部分流量调度到其他节点`,
'调整JVM堆内存参数,增加GC间隔', '优化应用代码,排查是否存在低效算法或资源泄漏',
], ],
autoFixScript: `#!/bin/bash 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`,
# 自动修复脚本 - CPU过高问题 similarCases: 18 + Math.floor(Math.random() * 8),
echo "正在诊断问题..." confidence: baseConfidence,
PID=$(pgrep -f "order-service" | head -1) };
echo "发现进程 PID: $PID" }
echo "生成堆转储..."
jmap -dump:format=b,file=/tmp/heap_dump.hprof $PID // Memory alerts
echo "重启服务..." if (metric.includes('mem') || metric.includes('ram')) {
systemctl restart order-service return {
echo "服务已重启,监控中..." rootCause: `检测到 ${target} 内存使用率达到 ${alert.currentValue},接近或超过阈值 ${alert.threshold}。常见原因:1) 应用内存泄漏,对象未被正确释放;2) 缓存配置过大;3) 数据库连接池未及时释放。`,
`, impact: isCritical
similarCases: 12, ? `内存即将耗尽可能触发 OOM Killer,导致关键进程被强制终止,服务中断风险极高。`
confidence: 92, : `内存持续增长,长期可能导致 OOM,建议尽快排查内存使用情况。`,
},
'ALT-002': {
rootCause: '数据库连接池配置不当,连接数达到上限,导致内存持续增长。同时存在几个慢查询未优化。',
impact: '数据库响应变慢,部分查询超时,可能影响订单和库存模块。',
recommendations: [ recommendations: [
'优化慢查询SQL,添加适当索引', `检查 ${target} 上的内存占用最高的进程`,
'调整连接池大小配置', '如果是 Java 应用,检查 JVM 堆内存配置和 GC 日志',
'考虑启用查询缓存', '排查是否存在内存泄漏,必要时生成堆转储分析',
'分析并优化大表查询', '调整缓存大小限制或增加交换分区',
], ],
similarCases: 8, similarCases: 12 + Math.floor(Math.random() * 6),
confidence: 87, confidence: baseConfidence,
}, };
'ALT-003': { }
rootCause: '日志文件未配置自动清理策略,/var/log目录下日志文件累积超过100GB。',
impact: '磁盘空间不足可能导致新日志无法写入,影响故障排查。', // Disk alerts
if (metric.includes('disk') || metric.includes('space')) {
return {
rootCause: `检测到 ${target} 磁盘使用率达到 ${alert.currentValue},超过阈值 ${alert.threshold}。最常见原因:1) 日志文件未配置轮转/清理;2) 临时文件堆积;3) 数据库数据文件膨胀。`,
impact: isCritical
? `磁盘空间不足可能导致服务无法写入数据/日志,数据库可能崩溃,文件系统变只读,影响范围极大。`
: `磁盘空间告警,如不及时清理可能导致服务异常。`,
recommendations: [ recommendations: [
'清理30天前的日志文件', `清理 ${target} 上 30 天前的日志文件`,
'配置logrotate自动轮转策略', '检查并清理临时目录 /tmp、/var/tmp',
'考虑将历史日志归档到对象存储', '配置 logrotate 日志轮转策略',
'设置磁盘使用率告警阈值', '考虑扩容磁盘或将数据迁移到对象存储',
], ],
autoFixScript: `#!/bin/bash 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),
echo "清理30天前的日志..." confidence: baseConfidence + 3,
find /var/log -name "*.log" -mtime +30 -delete };
find /var/log -name "*.log.*" -mtime +30 -delete }
echo "压缩7-30天的日志..."
find /var/log -name "*.log" -mtime +7 -mtime -30 -exec gzip {} \; // Network alerts
echo "清理完成" if (metric.includes('net') || metric.includes('latency') || metric.includes('bandwidth')) {
df -h /var/log return {
`, rootCause: `检测到 ${target} 网络指标异常(当前值 ${alert.currentValue}),可能原因:1) 上游依赖服务响应变慢;2) 网络带宽打满;3) DNS 解析延迟;4) 连接数达到上限。`,
similarCases: 23, impact: isCritical
confidence: 95, ? `网络异常将导致所有经过该节点的请求超时,影响所有上游业务的可用性。`
}, : `网络延迟偏高,可能影响用户体验,建议排查网络链路。`,
'ALT-004': {
rootCause: '上游依赖服务payment-service响应变慢,导致API网关等待时间增加。',
impact: '所有经过网关的请求都受到影响,用户体验下降。',
recommendations: [ recommendations: [
'检查payment-service的健康状态', `检查 ${target} 的网络连接和带宽使用情况`,
'增加API网关超时配置', '排查上游依赖服务的健康状态和响应时间',
'考虑启用熔断机制', '检查防火墙规则是否有限速或异常丢弃',
'优化payment-service的性能', '考虑启用熔断/降级机制保障核心链路',
], ],
similarCases: 5, similarCases: 8 + Math.floor(Math.random() * 5),
confidence: 78, 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 [loading, setLoading] = useState(true);
const [selectedAlert, setSelectedAlert] = useState<AlertItem | null>(null); const [selectedAlert, setSelectedAlert] = useState<AlertItem | null>(null);
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); const [copied, setCopied] = useState(false);
useEffect(() => {
loadAlerts();
}, []);
const loadAlerts = async () => {
setLoading(true);
try {
const res: any = await alertApi.getAlerts({ page: 1, page_size: 50 });
const list = res?.list || res || [];
setAlerts(list.map(mapAlertEvent));
} catch (e) {
console.error('加载告警失败:', e);
setAlerts([]);
} finally {
setLoading(false);
}
};
const handleAnalyze = (alert: AlertItem) => { const handleAnalyze = (alert: AlertItem) => {
setSelectedAlert(alert); setSelectedAlert(alert);
setIsAnalyzing(true); setIsAnalyzing(true);
setAnalysis(null); setAnalysis(null);
// 模拟AI分析 // Simulate AI analysis with dynamic generation
setTimeout(() => { setTimeout(() => {
setAnalysis(mockAIAnalysis[alert.id] || null); setAnalysis(generateAIAnalysis(alert));
setIsAnalyzing(false); setIsAnalyzing(false);
}, 2000); }, 1500);
}; };
const handleAutoFix = () => { const handleAutoFix = () => {
@@ -269,13 +319,24 @@ export function AIAlertAnalyzer() {
活跃告警 活跃告警
</CardTitle> </CardTitle>
<Badge className="bg-red-500/10 text-red-400 border-red-500/30"> <Badge className="bg-red-500/10 text-red-400 border-red-500/30">
{mockAlerts.filter(a => a.status === 'active').length} 个活跃 {alerts.filter(a => a.status === 'active').length} 个活跃
</Badge> </Badge>
</div> </div>
</CardHeader> </CardHeader>
<ScrollArea className="flex-1"> <ScrollArea className="flex-1">
<CardContent className="p-4 space-y-3"> <CardContent className="p-4 space-y-3">
{mockAlerts.map((alert) => ( {loading ? (
<div className="flex flex-col items-center justify-center h-32">
<Loader2 className="h-6 w-6 animate-spin text-slate-500 mb-2" />
<p className="text-sm text-slate-500">加载告警数据...</p>
</div>
) : alerts.length === 0 ? (
<div className="flex flex-col items-center justify-center h-32 text-slate-500">
<CheckCircle className="h-8 w-8 mb-2 opacity-50" />
<p className="text-sm">暂无告警事件</p>
</div>
) : (
alerts.map((alert) => (
<div <div
key={alert.id} key={alert.id}
className={`p-4 rounded-lg border cursor-pointer transition-all ${ className={`p-4 rounded-lg border cursor-pointer transition-all ${
@@ -310,7 +371,8 @@ export function AIAlertAnalyzer() {
</span> </span>
</div> </div>
</div> </div>
))} ))
)}
</CardContent> </CardContent>
</ScrollArea> </ScrollArea>
</Card> </Card>
@@ -336,9 +336,9 @@ export function DomainManager() {
<div className="text-right"> <div className="text-right">
<p className="text-sm text-slate-400">到期时间</p> <p className="text-sm text-slate-400">到期时间</p>
<p className={`text-sm font-medium ${daysUntilExpiry < 30 ? 'text-red-400' : 'text-slate-200'}`}> <p className={`text-sm font-medium ${daysUntilExpiry < 30 ? 'text-red-400' : 'text-slate-200'}`}>
{expireDate || '-'}{expireDate && ` (${daysUntilExpiry}天)`} {expireDate ? expireDate.substring(0, 10) : '-'}{expireDate && ` (${daysUntilExpiry}天)`}
{sslEnabled && sslExpireDate && ( {sslEnabled && sslExpireDate && (
<span className="text-slate-500"> · SSL: {sslExpireDate}</span> <span className="text-slate-500"> · SSL: {sslExpireDate.substring(0, 10)}</span>
)} )}
</p> </p>
</div> </div>
@@ -452,7 +452,7 @@ function CertificateExpiryCard({ expCerts }: { expCerts: any[] }) {
</div> </div>
<div> <div>
<p className="text-sm font-medium text-slate-200">{domain.name}</p> <p className="text-sm font-medium text-slate-200">{domain.name}</p>
<p className="text-xs text-slate-500">到期: {domain.expire_date}</p> <p className="text-xs text-slate-500">到期: {domain.expire_date?.substring(0, 10)}</p>
</div> </div>
</div> </div>
<Badge <Badge
@@ -878,8 +878,8 @@ export function Dashboard() {
value={serverStats?.total ?? 0} value={serverStats?.total ?? 0}
description={`${serverStats?.running ?? 0} 台运行中`} description={`${serverStats?.running ?? 0} 台运行中`}
icon={Server} icon={Server}
trend="up" trend={Number(serverStats?.error ?? 0) > 0 ? 'down' : 'neutral'}
trendValue="+3 本月" trendValue={Number(serverStats?.error ?? 0) > 0 ? `${serverStats?.error} 台异常` : '运行正常'}
color="blue" color="blue"
/> />
<StatCard <StatCard
@@ -896,8 +896,8 @@ export function Dashboard() {
value={alertStats?.firing ?? 0} value={alertStats?.firing ?? 0}
description={`${alertStats?.critical ?? 0} 个严重`} description={`${alertStats?.critical ?? 0} 个严重`}
icon={Bell} icon={Bell}
trend="down" trend={Number(alertStats?.today_count ?? 0) > 0 ? 'up' : 'down'}
trendValue="-5 本周" trendValue={`本周 ${alertStats?.week_count ?? 0} 条`}
color="rose" color="rose"
/> />
<StatCard <StatCard
@@ -195,11 +195,25 @@ export function PipelineManager() {
const handleCreatePipeline = async () => { const handleCreatePipeline = async () => {
try { try {
const selectedTemplate = pipelineTemplates.find(t => t.id === newPipeline.template);
const stages = selectedTemplate
? JSON.stringify(selectedTemplate.stages.map((name, i) => ({
id: `stage-${i + 1}`, name, status: 'pending', duration: '-', logs: [],
})))
: '';
const code = newPipeline.name
.toLowerCase()
.replace(/[^a-z0-9]/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '') || `pipeline-${Date.now()}`;
await pipelineApi.createPipeline({ await pipelineApi.createPipeline({
name: newPipeline.name, name: newPipeline.name,
code,
description: newPipeline.description, description: newPipeline.description,
repository: newPipeline.repository, source: newPipeline.repository,
branch: newPipeline.branch, branch: newPipeline.branch,
type: 'deploy',
stages,
}); });
setIsCreateDialogOpen(false); setIsCreateDialogOpen(false);
setNewPipeline({ name: '', description: '', repository: '', branch: 'main', template: '' }); setNewPipeline({ name: '', description: '', repository: '', branch: 'main', template: '' });