feat: 监控告警体系统一重构 + 自动化任务调度引擎 + 监控大屏拆分
告警中心 - 新增统一告警(alert_unified)、监控告警规则(monitor_alert_rule)、指标目录(alert_metric_catalog) - 通知策略与消息分发(alert_notify_policy/dispatch/message)、静默规则 - 告警引擎按阈值/SNMP 拆分(alert_engine_monitor/snmp) 监控采集 - SNMP 常量/模板与历史指标(snmp templates/constants、snmp_metrics_history、snmp_collect) - 中间件监控 Redis 采集与种子(middleware_monitor_redis/seed) - 站点探测 pkg(siteprobe) + 站点视图(site_view) 自动化任务 - cron 调度器(cronscheduler)、脚本运行器(scriptrunner)、系统任务(systask)及运行记录(cron_job_run、system_task) 审计与凭证 - 审计设置(audit_setting) + 历史清理(auditcleaner) 前端 - 监控页面(host/middleware/site/snmp)拆分为子模块并大屏化 - 告警中心多 Tab(规则/指标目录/通知策略/静默)、任务管理组件重构、Agent CLI Markdown 渲染 - 移除旧脚本卡片/详情/日志组件,统一为 ScriptTable/ScriptCodeEditor/TerminalOutput Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
092d0e4b9e
commit
471353e3d7
190 files changed
+16640
-3484
No files matched your search
@@ -0,0 +1,211 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"ezops/model"
|
||||
"ezops/pkg/siteprobe"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// SiteTaskView 前端拨测任务视图(字段与前端对齐)// Logan.Li
|
||||
type SiteTaskView struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Type string `json:"type"`
|
||||
FrequencySec int `json:"frequency_sec"`
|
||||
TimeoutSec int `json:"timeout_sec"`
|
||||
ExpectedStatusCode int `json:"expected_status_code"`
|
||||
Enabled bool `json:"enabled"`
|
||||
LastStatus string `json:"last_status,omitempty"`
|
||||
LastResponseTimeMs float64 `json:"last_response_time_ms,omitempty"`
|
||||
LastCheckedAt *string `json:"last_checked_at,omitempty"`
|
||||
SuccessRate float64 `json:"success_rate"`
|
||||
AlertStatus string `json:"alert_status"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
SSLDaysRemaining *int `json:"ssl_days_remaining,omitempty"`
|
||||
SSLCertExpiry *string `json:"ssl_cert_expiry,omitempty"`
|
||||
}
|
||||
|
||||
func toSiteTaskView(task model.SiteTask) SiteTaskView {
|
||||
taskType := normalizeTaskType(task)
|
||||
enabled := task.Status == model.SiteTaskStatusRunning
|
||||
lastStatus := "unknown"
|
||||
switch task.LastResult {
|
||||
case "success":
|
||||
lastStatus = "up"
|
||||
case "fail", "timeout":
|
||||
lastStatus = "down"
|
||||
}
|
||||
var lastChecked *string
|
||||
if task.LastCheckAt != nil {
|
||||
s := task.LastCheckAt.Format("2006-01-02T15:04:05Z07:00")
|
||||
lastChecked = &s
|
||||
}
|
||||
return SiteTaskView{
|
||||
ID: task.ID,
|
||||
Name: task.Name,
|
||||
URL: task.URL,
|
||||
Type: taskType,
|
||||
FrequencySec: task.Frequency,
|
||||
TimeoutSec: task.Timeout,
|
||||
ExpectedStatusCode: task.ExpectedStatus,
|
||||
Enabled: enabled,
|
||||
LastStatus: lastStatus,
|
||||
LastResponseTimeMs: task.AvgResponse,
|
||||
LastCheckedAt: lastChecked,
|
||||
SuccessRate: task.SuccessRate,
|
||||
AlertStatus: "normal",
|
||||
Status: string(task.Status),
|
||||
CreatedAt: task.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
}
|
||||
}
|
||||
|
||||
// enrichSiteTaskViewsAlert 批量附加活跃告警状态 // Logan.Li
|
||||
func enrichSiteTaskViewsAlert(db *gorm.DB, views []SiteTaskView) {
|
||||
if len(views) == 0 {
|
||||
return
|
||||
}
|
||||
ids := make([]uint, 0, len(views))
|
||||
for _, v := range views {
|
||||
ids = append(ids, v.ID)
|
||||
}
|
||||
type row struct {
|
||||
TaskID uint
|
||||
}
|
||||
var rows []row
|
||||
db.Model(&model.SiteAlert{}).
|
||||
Select("DISTINCT task_id").
|
||||
Where("task_id IN ? AND status = ?", ids, model.SiteAlertStatusActive).
|
||||
Find(&rows)
|
||||
alerting := make(map[uint]bool, len(rows))
|
||||
for _, r := range rows {
|
||||
alerting[r.TaskID] = true
|
||||
}
|
||||
for i := range views {
|
||||
if alerting[views[i].ID] {
|
||||
views[i].AlertStatus = "alerting"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeTaskType(task model.SiteTask) string {
|
||||
t := strings.ToLower(string(task.TaskType))
|
||||
if t == "https" {
|
||||
return "HTTP"
|
||||
}
|
||||
if t != "" {
|
||||
return strings.ToUpper(t)
|
||||
}
|
||||
return strings.ToUpper(siteprobe.InferTaskTypePublic(task))
|
||||
}
|
||||
|
||||
type siteSSLInfo struct {
|
||||
DaysRemaining int
|
||||
CertExpiry *time.Time
|
||||
}
|
||||
|
||||
// enrichSiteTaskViewsSSL 批量附加 SSL 拨测任务的证书到期信息 // Logan.Li
|
||||
func enrichSiteTaskViewsSSL(db *gorm.DB, views []SiteTaskView) {
|
||||
if len(views) == 0 {
|
||||
return
|
||||
}
|
||||
ids := make([]uint, 0, len(views))
|
||||
for _, v := range views {
|
||||
if strings.ToUpper(v.Type) != "SSL" {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, v.ID)
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return
|
||||
}
|
||||
type row struct {
|
||||
TaskID uint
|
||||
SSLDaysRemaining int
|
||||
SSLCertExpiry *time.Time
|
||||
}
|
||||
var rows []row
|
||||
db.Model(&model.SiteResult{}).
|
||||
Select("task_id, ssl_days_remaining, ssl_cert_expiry").
|
||||
Where("task_id IN ? AND ssl_cert_expiry IS NOT NULL", ids).
|
||||
Order("checked_at DESC").
|
||||
Find(&rows)
|
||||
seen := map[uint]bool{}
|
||||
sslMap := make(map[uint]siteSSLInfo, len(rows))
|
||||
for _, r := range rows {
|
||||
if seen[r.TaskID] {
|
||||
continue
|
||||
}
|
||||
seen[r.TaskID] = true
|
||||
sslMap[r.TaskID] = siteSSLInfo{DaysRemaining: r.SSLDaysRemaining, CertExpiry: r.SSLCertExpiry}
|
||||
}
|
||||
for i := range views {
|
||||
if ssl, ok := sslMap[views[i].ID]; ok {
|
||||
d := ssl.DaysRemaining
|
||||
views[i].SSLDaysRemaining = &d
|
||||
if ssl.CertExpiry != nil {
|
||||
s := ssl.CertExpiry.Format("2006-01-02T15:04:05Z07:00")
|
||||
views[i].SSLCertExpiry = &s
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseCreateTaskRequest(raw map[string]interface{}) model.SiteTask {
|
||||
getStr := func(keys ...string) string {
|
||||
for _, k := range keys {
|
||||
if v, ok := raw[k]; ok {
|
||||
if s, ok := v.(string); ok {
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
getInt := func(def int, keys ...string) int {
|
||||
for _, k := range keys {
|
||||
if v, ok := raw[k]; ok {
|
||||
switch n := v.(type) {
|
||||
case float64:
|
||||
return int(n)
|
||||
case int:
|
||||
return n
|
||||
}
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
taskType := strings.ToLower(getStr("type", "task_type"))
|
||||
switch taskType {
|
||||
case "https":
|
||||
taskType = "http"
|
||||
case "":
|
||||
taskType = "http"
|
||||
}
|
||||
|
||||
task := model.SiteTask{
|
||||
Name: getStr("name"),
|
||||
URL: getStr("url"),
|
||||
TaskType: model.SiteTaskType(taskType),
|
||||
Method: model.SiteTaskMethodGET,
|
||||
Timeout: getInt(10, "timeout_sec", "timeout"),
|
||||
Frequency: getInt(60, "frequency_sec", "frequency"),
|
||||
ExpectedStatus: getInt(200, "expected_status_code", "expected_status"),
|
||||
FollowRedirect: true,
|
||||
VerifySSL: true,
|
||||
Status: model.SiteTaskStatusRunning,
|
||||
}
|
||||
if v, ok := raw["enabled"].(bool); ok && !v {
|
||||
task.Status = model.SiteTaskStatusPaused
|
||||
}
|
||||
if v, ok := raw["dns_resolver"].(string); ok {
|
||||
task.DNSResolver = strings.TrimSpace(v)
|
||||
}
|
||||
return task
|
||||
}
|
||||
Reference in new issue
Block a user