Files
aiops/frontend/src/components/aiops/AIAlertAnalyzer.tsx
T
LoganandClaude 76ec9387f0 feat(rdp): 新增 RDP 远程桌面/SMB/账单同步能力 + 清理编译产物
主要变更:
- RDP 远程桌面:新增 backend/pkg/rdp(RDP 引擎 + credssp/display/rle/trace)、
  backend/controller/rdp*(连接/凭证/文件/会话/实时)、frontend RDP 组件与 API
- Windows SMB 文件传输:backend/pkg/winsmb
- 云账单同步:backend/pkg/billingsync + billing_sync/aliyun_billing,前端账单详情
- Agent CLI Kubernetes 工具:kubernetes_action/get、pod_execute/logs
- 服务器导入助手:server_import_helpers、ImportCredentialSettings
- 国际化与组件重构(cloud/deploy/monitor/server/system 等多模块)
- 清理编译产物二进制:移除 backend/server、backend/agent/bin/aiops-agent-*
- .gitignore:新增 backend/agent/ 与 .cache/(避免 334MB go-build 缓存误入库)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-04 01:50:36 +08:00

248 lines
9.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// AI 告警智能分析主容器:左侧告警列表 + 右侧 AI 分析结果(已拆分子组件) // Logan.Li
import { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { ScrollArea } from '@/components/ui/scroll-area';
import { alertApi, aiApi } from '@/api';
import {
AlertTriangle,
Brain,
CheckCircle,
XCircle,
Clock,
Server,
TrendingUp,
AlertCircle,
Sparkles,
Loader2,
} from 'lucide-react';
import {
type AlertItem,
type AIAnalysis,
generateAIAnalysis,
mapAlertEvent,
getSeverityColor,
} from './analyzerHelpers';
import { AIAnalysisResult } from './AIAnalysisResult';
import { AutoFixDialog } from './AutoFixDialog';
import { PageShell } from '@/components/layout/PageShell';
import { StatStrip } from '@/components/ui/stat-strip';
import type { JsonData } from '@/types/common';
import { useTranslation } from 'react-i18next';
// Parse LLM JSON response (may have markdown fences) into AIAnalysis
function parseAIResponse(raw: string, fallback: AIAnalysis): AIAnalysis {
try {
// Strip markdown code fences if present
const json = raw.replace(/```(?:json)?\s*\n?/g, '').replace(/```$/, '').trim();
const parsed = JSON.parse(json);
return {
rootCause: parsed.root_cause || parsed.rootCause || fallback.rootCause,
impact: parsed.impact || fallback.impact,
recommendations: Array.isArray(parsed.recommendations) ? parsed.recommendations : fallback.recommendations,
autoFixScript: parsed.auto_fix_script || parsed.autoFixScript || fallback.autoFixScript,
similarCases: typeof parsed.similar_cases === 'number' ? parsed.similar_cases : (parsed.similarCases ?? fallback.similarCases),
confidence: typeof parsed.confidence === 'number' ? parsed.confidence : fallback.confidence,
};
} catch {
// Not valid JSON — return raw text as rootCause
return { ...fallback, rootCause: raw };
}
}
export function AIAlertAnalyzer() {
const { t } = useTranslation();
const [alerts, setAlerts] = useState<AlertItem[]>([]);
const [loading, setLoading] = useState(true);
const [selectedAlert, setSelectedAlert] = useState<AlertItem | null>(null);
const [isAnalyzing, setIsAnalyzing] = useState(false);
const [analysis, setAnalysis] = useState<AIAnalysis | null>(null);
const [showScriptDialog, setShowScriptDialog] = useState(false);
useEffect(() => {
loadAlerts();
}, []);
const loadAlerts = async () => {
setLoading(true);
try {
const res: JsonData = await alertApi.getAlerts({ page: 1, page_size: 50 });
// 列表响应格式:http.ts 已提取 .data,此处仍是 {list, total}
const list = res?.list || res || [];
setAlerts(list.map(mapAlertEvent));
} catch (e) {
console.error('加载告警失败:', e);
setAlerts([]);
} finally {
setLoading(false);
}
};
// 触发 AI 分析 — 优先调用 LLM,失败/未配置时回退到规则模板
const handleAnalyze = async (alert: AlertItem) => {
setSelectedAlert(alert);
setIsAnalyzing(true);
setAnalysis(null);
const fallback = generateAIAnalysis(alert);
try {
const prompt = `你是运维专家。请分析以下告警并以 JSON 格式回复(不要 markdown 代码块)。
告警名称:${alert.name}
严重程度:${alert.severity}
目标:${alert.source}
指标:${alert.metric} = ${alert.currentValue}(阈值 ${alert.threshold})
持续:${alert.duration}
描述:${alert.description}
返回 JSON 格式(字段名用 snake_case):
{
"root_cause": t('aiops.rootCauseSent'),
"impact": t('aiops.impactSent'),
"recommendations": ["建议1", "建议2", "建议3"],
"auto_fix_script": "#!/bin/bash\\n...\\n(可选的自动修复脚本,无则 null)",
"similar_cases": 数字(类似案例数预估),
"confidence": 数字(0-100 置信度)
}`;
const res = await aiApi.chat([{ role: 'user', content: prompt }]);
const content = (res as JsonData)?.content || (res as JsonData)?.message?.content || '';
if (content) {
setAnalysis(parseAIResponse(content, fallback));
} else {
setAnalysis(fallback);
}
} catch {
// LLM 未配置或调用失败 → 回退到规则模板
setAnalysis(fallback);
} finally {
setIsAnalyzing(false);
}
};
const handleAutoFix = () => {
setShowScriptDialog(true);
};
// 严重等级图标(返回 JSX,保留在主组件中)
const getSeverityIcon = (severity: string) => {
switch (severity) {
case 'critical':
return <XCircle className="h-4 w-4 dg-text-red" />;
case 'warning':
return <AlertCircle className="h-4 w-4 dg-text-yellow" />;
default:
return <CheckCircle className="h-4 w-4 dg-text-blue" />;
}
};
const activeCount = alerts.filter(a => a.status === 'active').length;
return (
<PageShell
variant="workspace"
title={t('aiops.aiAlertAnalysis')}
subtitle={t('aiops.alertRootCauseAnalysis')}
icon={<Brain className="h-5 w-5" />}
loading={loading}
metrics={(
<StatStrip
items={[
{ label: t('aiops.alertTotal'), value: alerts.length, color: 'blue' },
{ label: t('common.active'), value: activeCount, color: 'red' },
]}
/>
)}
actions={(
<Badge variant="outline" className="border-emerald-500/30 dg-text-green bg-emerald-500/10">
<Sparkles className="h-3 w-3 mr-1" />{t('aiops.aiEngineRunning')}
</Badge>
)}
contentClassName="grid grid-cols-2 gap-6 h-[calc(100vh-200px)]"
>
{/* 告警列表(左侧) */}
<Card className="dg-card flex flex-col">
<CardHeader className="border-b border-[var(--dg-border)] pb-3">
<div className="flex items-center justify-between">
<CardTitle className="text-[var(--dg-text-primary)] flex items-center gap-2">
<AlertTriangle className="h-5 w-5 dg-text-yellow" />
{t('dashboard.ops.firingAlerts')}
</CardTitle>
<Badge className="bg-red-500/10 dg-text-red border-red-500/30">
{t('aiops.activeCount', { count: alerts.filter(a => a.status === 'active').length })}
</Badge>
</div>
</CardHeader>
<ScrollArea className="flex-1">
<CardContent className="p-4 space-y-3">
{loading ? (
<div className="flex flex-col items-center justify-center h-32">
<Loader2 className="h-6 w-6 animate-spin dg-text-muted mb-2" />
<p className="text-sm dg-text-muted">{t('aiops.loadAlertsDot')}</p>
</div>
) : alerts.length === 0 ? (
<div className="flex flex-col items-center justify-center h-32 dg-text-muted">
<CheckCircle className="h-8 w-8 mb-2 opacity-50" />
<p className="text-sm">{t('dashboard.alerts.empty')}</p>
</div>
) : (
alerts.map((alert) => (
<div
key={alert.id}
className={`p-4 rounded-lg border cursor-pointer transition-all ${
selectedAlert?.id === alert.id
? 'border-purple-500 bg-purple-500/10'
: 'border-[var(--dg-border)] bg-[var(--dg-bg-tertiary)]/50 hover:border-[var(--dg-border-hover)]'
}`}
onClick={() => handleAnalyze(alert)}
>
<div className="flex items-start justify-between mb-2">
<div className="flex items-center gap-2">
{getSeverityIcon(alert.severity)}
<span className="font-medium text-[var(--dg-text-primary)]">{alert.name}</span>
</div>
<Badge variant="outline" className={getSeverityColor(alert.severity)}>
{alert.severity === 'critical' ? t('aiops.severityCritical') : alert.severity === 'warning' ? t('aiops.severityWarning') : t('aiops.severityInfo')}
</Badge>
</div>
<div className="text-sm dg-text-secondary mb-2">{alert.description}</div>
<div className="flex items-center gap-4 text-xs dg-text-muted">
<span className="flex items-center gap-1">
<Server className="h-3 w-3" />
{alert.source}
</span>
<span className="flex items-center gap-1">
<Clock className="h-3 w-3" />
{alert.duration}
</span>
<span className="flex items-center gap-1">
<TrendingUp className="h-3 w-3" />
{alert.currentValue}
</span>
</div>
</div>
))
)}
</CardContent>
</ScrollArea>
</Card>
{/* AI 分析结果(右侧,子组件) */}
<AIAnalysisResult
selectedAlert={selectedAlert}
isAnalyzing={isAnalyzing}
analysis={analysis}
onAutoFix={handleAutoFix}
/>
{/* 自动修复脚本确认弹窗(子组件) */}
<AutoFixDialog
open={showScriptDialog}
onOpenChange={setShowScriptDialog}
script={analysis?.autoFixScript}
/>
</PageShell>
);
}