216 lines
5.9 KiB
Go
216 lines
5.9 KiB
Go
package controller
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"ezops/model"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// UnifiedAlertItem 统一告警事件视图 // Logan.Li
|
|
type UnifiedAlertItem struct {
|
|
ID string `json:"id"`
|
|
SourceType string `json:"source_type"`
|
|
RuleName string `json:"rule_name"`
|
|
Title string `json:"title"`
|
|
Message string `json:"message"`
|
|
Severity string `json:"severity"`
|
|
Status string `json:"status"`
|
|
Target string `json:"target"`
|
|
Metric string `json:"metric"`
|
|
Value float64 `json:"value"`
|
|
Threshold float64 `json:"threshold"`
|
|
StartedAt string `json:"started_at"`
|
|
ResolvedAt *string `json:"resolved_at,omitempty"`
|
|
}
|
|
|
|
// ListUnifiedAlerts 统一告警列表(主机 + 中间件 + 站点 + 日志)
|
|
// GET /api/v1/alerts/unified
|
|
func (h *AlertHandler) ListUnifiedAlerts(c *gin.Context) {
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
|
if pageSize > 100 {
|
|
pageSize = 100
|
|
}
|
|
statusFilter := c.Query("status")
|
|
severityFilter := c.Query("severity")
|
|
sourceFilter := c.Query("source_type")
|
|
|
|
items := make([]UnifiedAlertItem, 0)
|
|
|
|
// 1. Agent 主机/中间件/SNMP 告警(中间件与 SNMP 复用 AgentAlert 存储)
|
|
if sourceFilter == "" || sourceFilter == "host" || sourceFilter == "middleware" || sourceFilter == "snmp" {
|
|
var alerts []model.AgentAlert
|
|
q := h.db.Model(&model.AgentAlert{}).Order("created_at DESC").Limit(200)
|
|
if statusFilter == "firing" || statusFilter == "" {
|
|
if statusFilter == "firing" {
|
|
q = q.Where("resolved = ?", false)
|
|
}
|
|
} else if statusFilter == "resolved" {
|
|
q = q.Where("resolved = ?", true)
|
|
}
|
|
if severityFilter != "" {
|
|
q = q.Where("severity = ?", severityFilter)
|
|
}
|
|
q.Find(&alerts)
|
|
for _, a := range alerts {
|
|
st := "firing"
|
|
if a.Resolved {
|
|
st = "resolved"
|
|
}
|
|
srcType := "host"
|
|
if isSNMPMetric(a.Metric) {
|
|
srcType = "snmp"
|
|
} else if isMiddlewareMetric(a.Metric) {
|
|
srcType = "middleware"
|
|
}
|
|
if sourceFilter == "host" && srcType != "host" {
|
|
continue
|
|
}
|
|
if sourceFilter == "middleware" && srcType != "middleware" {
|
|
continue
|
|
}
|
|
if sourceFilter == "snmp" && srcType != "snmp" {
|
|
continue
|
|
}
|
|
var resolved *string
|
|
if a.ResolvedAt != nil {
|
|
s := a.ResolvedAt.Format(time.RFC3339)
|
|
resolved = &s
|
|
}
|
|
items = append(items, UnifiedAlertItem{
|
|
ID: "agent-" + strconv.Itoa(int(a.ID)), SourceType: srcType,
|
|
RuleName: a.RuleName, Title: a.RuleName, Message: a.Message,
|
|
Severity: a.Severity, Status: st, Target: a.Hostname,
|
|
Metric: a.Metric, Value: a.Value, Threshold: a.Threshold,
|
|
StartedAt: a.CreatedAt.Format(time.RFC3339), ResolvedAt: resolved,
|
|
})
|
|
}
|
|
}
|
|
|
|
// 2. 站点拨测告警
|
|
if sourceFilter == "" || sourceFilter == "site" {
|
|
var alerts []model.SiteAlert
|
|
q := h.db.Model(&model.SiteAlert{}).Preload("Task").Order("started_at DESC").Limit(200)
|
|
if statusFilter == "firing" {
|
|
q = q.Where("status = ?", model.SiteAlertStatusActive)
|
|
} else if statusFilter == "resolved" {
|
|
q = q.Where("status = ?", model.SiteAlertStatusResolved)
|
|
}
|
|
if severityFilter != "" {
|
|
q = q.Where("severity = ?", severityFilter)
|
|
}
|
|
q.Find(&alerts)
|
|
for _, a := range alerts {
|
|
st := "firing"
|
|
if a.Status == model.SiteAlertStatusResolved {
|
|
st = "resolved"
|
|
}
|
|
target := fmt.Sprintf("task-%d", a.TaskID)
|
|
if a.Task != nil {
|
|
target = a.Task.Name
|
|
}
|
|
var resolved *string
|
|
if a.ResolvedAt != nil {
|
|
s := a.ResolvedAt.Format(time.RFC3339)
|
|
resolved = &s
|
|
}
|
|
items = append(items, UnifiedAlertItem{
|
|
ID: "site-" + strconv.Itoa(int(a.ID)), SourceType: "site",
|
|
RuleName: a.Title, Title: a.Title, Message: a.Message,
|
|
Severity: string(a.Severity), Status: st, Target: target,
|
|
Metric: a.TriggerType, Value: a.ResponseTime, Threshold: a.Threshold,
|
|
StartedAt: a.StartedAt.Format(time.RFC3339), ResolvedAt: resolved,
|
|
})
|
|
}
|
|
}
|
|
|
|
// 3. 日志/通用 AlertEvent
|
|
if sourceFilter == "" || sourceFilter == "log" {
|
|
var events []model.AlertEvent
|
|
q := h.db.Model(&model.AlertEvent{}).Order("started_at DESC").Limit(200)
|
|
if statusFilter != "" && statusFilter != "all" {
|
|
q = q.Where("status = ?", statusFilter)
|
|
}
|
|
if severityFilter != "" {
|
|
q = q.Where("severity = ?", severityFilter)
|
|
}
|
|
q.Find(&events)
|
|
for _, e := range events {
|
|
var resolved *string
|
|
if e.ResolvedAt != nil {
|
|
s := e.ResolvedAt.Format(time.RFC3339)
|
|
resolved = &s
|
|
}
|
|
items = append(items, UnifiedAlertItem{
|
|
ID: "event-" + strconv.Itoa(int(e.ID)), SourceType: "log",
|
|
RuleName: e.RuleName, Title: e.RuleName, Message: e.Message,
|
|
Severity: e.Severity, Status: e.Status, Target: e.Target,
|
|
Metric: "log", Value: e.Value, Threshold: e.Threshold,
|
|
StartedAt: e.StartedAt.Format(time.RFC3339), ResolvedAt: resolved,
|
|
})
|
|
}
|
|
}
|
|
|
|
// 按时间排序
|
|
sortUnifiedAlerts(items)
|
|
|
|
total := len(items)
|
|
start := (page - 1) * pageSize
|
|
if start > total {
|
|
start = total
|
|
}
|
|
end := start + pageSize
|
|
if end > total {
|
|
end = total
|
|
}
|
|
pageItems := items[start:end]
|
|
|
|
// 统计
|
|
stats := gin.H{
|
|
"firing": countStatus(items, "firing"),
|
|
"critical": countSeverityStatus(items, "critical", "firing"),
|
|
"warning": countSeverityStatus(items, "warning", "firing"),
|
|
"resolved": countStatus(items, "resolved"),
|
|
}
|
|
|
|
c.JSON(http.StatusOK, model.Response{
|
|
Code: 200,
|
|
Data: gin.H{"list": pageItems, "total": total, "stats": stats},
|
|
})
|
|
}
|
|
|
|
func sortUnifiedAlerts(items []UnifiedAlertItem) {
|
|
for i := 0; i < len(items); i++ {
|
|
for j := i + 1; j < len(items); j++ {
|
|
if items[j].StartedAt > items[i].StartedAt {
|
|
items[i], items[j] = items[j], items[i]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func countStatus(items []UnifiedAlertItem, status string) int {
|
|
n := 0
|
|
for _, it := range items {
|
|
if it.Status == status {
|
|
n++
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
func countSeverityStatus(items []UnifiedAlertItem, sev, status string) int {
|
|
n := 0
|
|
for _, it := range items {
|
|
if it.Severity == sev && it.Status == status {
|
|
n++
|
|
}
|
|
}
|
|
return n
|
|
}
|