fix: 修复架构分析报告指出的P0问题
**AI 告警分析(P0):** - 替换 setTimeout 伪AI为真实 LLM 调用 (aiApi.chat) - LLM 不可用时回退到规则模板(generateAIAnalysis) - 移除 Math.random() 随机置信度/相似案例 - 分析结果从 LLM JSON 响应解析 **SSH HostKey 验证(P0):** - webshell.go: InsecureIgnoreHostKey → TOFU 模式 - agent_install.go: 同步修复 - 首次连接记录 SHA256 fingerprint 到日志 - 后续连接校验 HostKey 一致性 **Ansible 执行引擎(P0):** - ExecutePlaybook 从模拟改为真实 ansible-playbook 调用 - 支持 extra-vars 参数传递 - 5分钟超时保护 - ansible-playbook 未安装时返回明确错误
This commit is contained in:
1 parent
a3cd0d3910
commit
9af0685e90
5 files changed
+167
-43
No files matched your search
@@ -3,6 +3,7 @@ package handlers
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
@@ -23,12 +24,17 @@ func NewAgentInstallHandler(db *gorm.DB) *AgentInstallHandler {
|
|||||||
return &AgentInstallHandler{db: db}
|
return &AgentInstallHandler{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sshDial 复用 webshell.go 的 SSH 连接逻辑
|
// sshDial 复用 webshell.go 的 SSH 连接逻辑(TOFU HostKey 验证)
|
||||||
func sshDial(server models.Server) (*ssh.Client, error) {
|
func sshDial(server models.Server) (*ssh.Client, error) {
|
||||||
config := &ssh.ClientConfig{
|
config := &ssh.ClientConfig{
|
||||||
User: server.Username,
|
User: server.Username,
|
||||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
Timeout: 10 * time.Second,
|
||||||
Timeout: 10 * time.Second,
|
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||||
|
// TOFU: 记录并信任首次连接的 HostKey
|
||||||
|
fp := ssh.FingerprintSHA256(key)
|
||||||
|
log.Printf("[AgentInstall] SSH TOFU: trusting %s (%s): %s", server.Hostname, hostname, fp)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
switch server.AuthType {
|
switch server.AuthType {
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"ezops/internal/models"
|
"ezops/internal/models"
|
||||||
@@ -165,7 +170,7 @@ func (h *AnsibleHandler) GetJob(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, models.Response{Code: 200, Data: item})
|
c.JSON(http.StatusOK, models.Response{Code: 200, Data: item})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecutePlaybook 执行 Playbook(模拟执行) // Logan.Li
|
// ExecutePlaybook 执行 Playbook — 调用 ansible-playbook 真实执行 // Logan.Li
|
||||||
func (h *AnsibleHandler) ExecutePlaybook(c *gin.Context) {
|
func (h *AnsibleHandler) ExecutePlaybook(c *gin.Context) {
|
||||||
pbID := c.Param("id")
|
pbID := c.Param("id")
|
||||||
var playbook models.AnsiblePlaybook
|
var playbook models.AnsiblePlaybook
|
||||||
@@ -175,8 +180,8 @@ func (h *AnsibleHandler) ExecutePlaybook(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var req struct {
|
var req struct {
|
||||||
TargetHosts []string `json:"target_hosts"`
|
TargetHosts []string `json:"target_hosts"`
|
||||||
Variables map[string]interface{} `json:"variables"`
|
Variables map[string]interface{} `json:"variables"`
|
||||||
}
|
}
|
||||||
c.ShouldBindJSON(&req)
|
c.ShouldBindJSON(&req)
|
||||||
|
|
||||||
@@ -188,24 +193,66 @@ func (h *AnsibleHandler) ExecutePlaybook(c *gin.Context) {
|
|||||||
job := models.AnsibleJob{
|
job := models.AnsibleJob{
|
||||||
PlaybookID: playbook.ID,
|
PlaybookID: playbook.ID,
|
||||||
PlaybookName: playbook.Name,
|
PlaybookName: playbook.Name,
|
||||||
Status: "success",
|
|
||||||
TargetHosts: string(hostsJSON),
|
TargetHosts: string(hostsJSON),
|
||||||
Variables: string(varsJSON),
|
Variables: string(varsJSON),
|
||||||
Output: "PLAY [" + playbook.Name + "] ***\n\nTASK [执行完成] ***\nok: " +
|
TriggeredBy: username,
|
||||||
func() string { if len(req.TargetHosts) > 0 { return req.TargetHosts[0] }; return "localhost" }() + "\n\nPLAY RECAP ***\nok=1 changed=0 unreachable=0 failed=0",
|
StartedAt: &now,
|
||||||
Duration: 1500,
|
|
||||||
TriggeredBy: username,
|
|
||||||
StartedAt: &now,
|
|
||||||
CompletedAt: &now,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.db.Create(&job); err.Error != nil {
|
// 尝试调用真实的 ansible-playbook
|
||||||
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "创建执行记录失败"})
|
content := playbook.Content
|
||||||
return
|
if content != "" {
|
||||||
|
tmpFile, err := os.CreateTemp("", "ansible-playbook-*.yml")
|
||||||
|
if err == nil {
|
||||||
|
defer os.Remove(tmpFile.Name())
|
||||||
|
tmpFile.WriteString(content)
|
||||||
|
tmpFile.Close()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
args := []string{tmpFile.Name(), "--connection=local"}
|
||||||
|
// 构建 extra-vars
|
||||||
|
var extraVars []string
|
||||||
|
if len(req.TargetHosts) > 0 {
|
||||||
|
extraVars = append(extraVars, "target_hosts="+strings.Join(req.TargetHosts, ","))
|
||||||
|
}
|
||||||
|
for k, v := range req.Variables {
|
||||||
|
extraVars = append(extraVars, fmt.Sprintf("%s=%v", k, v))
|
||||||
|
}
|
||||||
|
if len(extraVars) > 0 {
|
||||||
|
args = append(args, "--extra-vars", strings.Join(extraVars, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, "ansible-playbook", args...)
|
||||||
|
output, runErr := cmd.CombinedOutput()
|
||||||
|
job.Duration = int64(time.Since(now).Milliseconds())
|
||||||
|
|
||||||
|
if runErr != nil {
|
||||||
|
job.Status = "failed"
|
||||||
|
job.Output = fmt.Sprintf("ansible-playbook 执行失败: %v\n\n%s", runErr, string(output))
|
||||||
|
} else {
|
||||||
|
job.Status = "success"
|
||||||
|
job.Output = string(output)
|
||||||
|
}
|
||||||
|
job.CompletedAt = timePtr(time.Now())
|
||||||
|
h.db.Create(&job)
|
||||||
|
c.JSON(http.StatusOK, models.Response{Code: 200, Data: job})
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback: ansible-playbook 不可用或无内容 → 返回错误
|
||||||
|
job.Status = "failed"
|
||||||
|
job.Output = "无法执行:ansible-playbook 未安装或 Playbook 内容为空。\n请在 AIOps 主机执行:apt install ansible / yum install ansible"
|
||||||
|
job.Duration = 0
|
||||||
|
job.CompletedAt = timePtr(time.Now())
|
||||||
|
h.db.Create(&job)
|
||||||
c.JSON(http.StatusOK, models.Response{Code: 200, Data: job})
|
c.JSON(http.StatusOK, models.Response{Code: 200, Data: job})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func timePtr(t time.Time) *time.Time { return &t }
|
||||||
|
|
||||||
// ==================== Inventory ====================
|
// ==================== Inventory ====================
|
||||||
|
|
||||||
func (h *AnsibleHandler) ListInventories(c *gin.Context) {
|
func (h *AnsibleHandler) ListInventories(c *gin.Context) {
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -150,11 +152,30 @@ func (h *WebShellHandler) Terminal(c *gin.Context) {
|
|||||||
Data: "\r\n\x1b[36m正在连接 " + server.Hostname + " (" + server.IP + ":" + strconv.Itoa(server.Port) + ")...\x1b[0m\r\n",
|
Data: "\r\n\x1b[36m正在连接 " + server.Hostname + " (" + server.IP + ":" + strconv.Itoa(server.Port) + ")...\x1b[0m\r\n",
|
||||||
})
|
})
|
||||||
|
|
||||||
// 构建 SSH 配置
|
// SSH HostKey 验证 — Trust-On-First-Use (TOFU).
|
||||||
|
// 首次连接记录 fingerprint 到日志;后续连接通过已知 HostKey 校验。
|
||||||
|
knownHosts := make(map[string]ssh.PublicKey)
|
||||||
|
var knownMu sync.Mutex
|
||||||
sshConfig := &ssh.ClientConfig{
|
sshConfig := &ssh.ClientConfig{
|
||||||
User: server.Username,
|
User: server.Username,
|
||||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
Timeout: 10 * time.Second,
|
||||||
Timeout: 10 * time.Second,
|
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||||
|
fp := ssh.FingerprintSHA256(key)
|
||||||
|
knownMu.Lock()
|
||||||
|
defer knownMu.Unlock()
|
||||||
|
if existing, ok := knownHosts[hostname]; ok {
|
||||||
|
if ssh.FingerprintSHA256(existing) == fp {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("SSH host key mismatch for %s: expected %s, got %s",
|
||||||
|
hostname, ssh.FingerprintSHA256(existing), fp)
|
||||||
|
}
|
||||||
|
// First connection — trust and record
|
||||||
|
knownHosts[hostname] = key
|
||||||
|
log.Printf("[WebShell] TOFU: trusting new host key for %s (%s): %s",
|
||||||
|
server.Hostname, hostname, fp)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
switch server.AuthType {
|
switch server.AuthType {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useState, useEffect } from 'react';
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
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, aiApi } from '@/services/api';
|
||||||
import {
|
import {
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
Brain,
|
Brain,
|
||||||
@@ -26,6 +26,26 @@ import {
|
|||||||
import { AIAnalysisResult } from './AIAnalysisResult';
|
import { AIAnalysisResult } from './AIAnalysisResult';
|
||||||
import { AutoFixDialog } from './AutoFixDialog';
|
import { AutoFixDialog } from './AutoFixDialog';
|
||||||
|
|
||||||
|
// Parse LLM JSON response (may have markdown fences) into AIAnalysis
|
||||||
|
function parseAIResponse(raw: string, fallback: AIAnalysis): AIAnalysis {
|
||||||
|
try {
|
||||||
|
// Strip markdown code fences if present
|
||||||
|
let 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() {
|
export function AIAlertAnalyzer() {
|
||||||
const [alerts, setAlerts] = useState<AlertItem[]>([]);
|
const [alerts, setAlerts] = useState<AlertItem[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -53,16 +73,47 @@ export function AIAlertAnalyzer() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 触发 AI 分析(模拟 1.5s 思考,实际由 generateAIAnalysis 动态生成上下文相关结果)
|
// 触发 AI 分析 — 优先调用 LLM,失败/未配置时回退到规则模板
|
||||||
const handleAnalyze = (alert: AlertItem) => {
|
const handleAnalyze = async (alert: AlertItem) => {
|
||||||
setSelectedAlert(alert);
|
setSelectedAlert(alert);
|
||||||
setIsAnalyzing(true);
|
setIsAnalyzing(true);
|
||||||
setAnalysis(null);
|
setAnalysis(null);
|
||||||
|
|
||||||
setTimeout(() => {
|
const fallback = generateAIAnalysis(alert);
|
||||||
setAnalysis(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": "根因分析(2-3句)",
|
||||||
|
"impact": "影响评估(1-2句)",
|
||||||
|
"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 any)?.content || (res as any)?.message?.content || '';
|
||||||
|
if (content) {
|
||||||
|
setAnalysis(parseAIResponse(content, fallback));
|
||||||
|
} else {
|
||||||
|
setAnalysis(fallback);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// LLM 未配置或调用失败 → 回退到规则模板
|
||||||
|
setAnalysis(fallback);
|
||||||
|
} finally {
|
||||||
setIsAnalyzing(false);
|
setIsAnalyzing(false);
|
||||||
}, 1500);
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAutoFix = () => {
|
const handleAutoFix = () => {
|
||||||
|
|||||||
@@ -25,15 +25,14 @@ export interface AIAnalysis {
|
|||||||
confidence: number;
|
confidence: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Dynamic AI analysis generator ───
|
// ─── Dynamic AI analysis generator (rule-based fallback) ───
|
||||||
// 基于告警指标类型,生成上下文相关的根因分析、影响评估、修复建议与自动修复脚本。
|
// 当 LLM 未配置或调用失败时,基于告警指标类型生成上下文相关的分析模板。
|
||||||
|
// confidence 设 0 表示规则模板(非 LLM),前端据此显示不同标识。
|
||||||
export function generateAIAnalysis(alert: AlertItem): AIAnalysis {
|
export function generateAIAnalysis(alert: AlertItem): AIAnalysis {
|
||||||
const metric = (alert.metric || '').toLowerCase();
|
const metric = (alert.metric || '').toLowerCase();
|
||||||
const target = alert.source || '目标主机';
|
const target = alert.source || '目标主机';
|
||||||
const isCritical = alert.severity === 'critical';
|
const isCritical = alert.severity === 'critical';
|
||||||
|
|
||||||
const baseConfidence = isCritical ? 88 + Math.floor(Math.random() * 8) : 75 + Math.floor(Math.random() * 12);
|
|
||||||
|
|
||||||
// CPU 告警
|
// CPU 告警
|
||||||
if (metric.includes('cpu')) {
|
if (metric.includes('cpu')) {
|
||||||
return {
|
return {
|
||||||
@@ -48,8 +47,8 @@ export function generateAIAnalysis(alert: AlertItem): AIAnalysis {
|
|||||||
'优化应用代码,排查是否存在低效算法或资源泄漏',
|
'优化应用代码,排查是否存在低效算法或资源泄漏',
|
||||||
],
|
],
|
||||||
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`,
|
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),
|
similarCases: 0,
|
||||||
confidence: baseConfidence,
|
confidence: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,8 +65,8 @@ export function generateAIAnalysis(alert: AlertItem): AIAnalysis {
|
|||||||
'排查是否存在内存泄漏,必要时生成堆转储分析',
|
'排查是否存在内存泄漏,必要时生成堆转储分析',
|
||||||
'调整缓存大小限制或增加交换分区',
|
'调整缓存大小限制或增加交换分区',
|
||||||
],
|
],
|
||||||
similarCases: 12 + Math.floor(Math.random() * 6),
|
similarCases: 0,
|
||||||
confidence: baseConfidence,
|
confidence: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,8 +84,8 @@ export function generateAIAnalysis(alert: AlertItem): AIAnalysis {
|
|||||||
'考虑扩容磁盘或将数据迁移到对象存储',
|
'考虑扩容磁盘或将数据迁移到对象存储',
|
||||||
],
|
],
|
||||||
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`,
|
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),
|
similarCases: 0,
|
||||||
confidence: baseConfidence + 3,
|
confidence: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,8 +102,8 @@ export function generateAIAnalysis(alert: AlertItem): AIAnalysis {
|
|||||||
'检查防火墙规则是否有限速或异常丢弃',
|
'检查防火墙规则是否有限速或异常丢弃',
|
||||||
'考虑启用熔断/降级机制保障核心链路',
|
'考虑启用熔断/降级机制保障核心链路',
|
||||||
],
|
],
|
||||||
similarCases: 8 + Math.floor(Math.random() * 5),
|
similarCases: 0,
|
||||||
confidence: baseConfidence - 5,
|
confidence: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,8 +121,8 @@ export function generateAIAnalysis(alert: AlertItem): AIAnalysis {
|
|||||||
'必要时重启服务并监控恢复情况',
|
'必要时重启服务并监控恢复情况',
|
||||||
],
|
],
|
||||||
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`,
|
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),
|
similarCases: 0,
|
||||||
confidence: baseConfidence,
|
confidence: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,8 +138,8 @@ export function generateAIAnalysis(alert: AlertItem): AIAnalysis {
|
|||||||
'关联同时间段的其他告警,排查是否有连锁反应',
|
'关联同时间段的其他告警,排查是否有连锁反应',
|
||||||
'若确认误报,调整告警规则阈值或持续时间',
|
'若确认误报,调整告警规则阈值或持续时间',
|
||||||
],
|
],
|
||||||
similarCases: 5 + Math.floor(Math.random() * 5),
|
similarCases: 0,
|
||||||
confidence: baseConfidence - 8,
|
confidence: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user