From 61e755d2b9b964a616be5bbd791a4b3b4bc44aab Mon Sep 17 00:00:00 2001 From: UUBulb <35923940+uubulb@users.noreply.github.com> Date: Wed, 23 Oct 2024 21:55:12 +0800 Subject: [PATCH] implement notification group (#450) * implement notification group * some fixes * fix sql * add listNotification * retrieve notification from map * create notification_group_notification if non-exist * NotificationIDToGroup -> NotificationIDToGroups * clean --- cmd/dashboard/controller/controller.go | 10 + cmd/dashboard/controller/ddns.go | 8 +- cmd/dashboard/controller/member_api.go | 55 ++--- cmd/dashboard/controller/notification.go | 170 +++++++++++++++ .../controller/notification_group.go | 205 ++++++++++++++++++ cmd/dashboard/controller/server_group.go | 23 +- model/alertrule.go | 2 +- model/cron.go | 20 +- model/ddns.go | 4 +- model/monitor.go | 16 +- model/notification.go | 27 ++- model/notification_group_api.go | 11 + model/notification_group_notification.go | 4 +- service/singleton/alertsentinel.go | 24 +- service/singleton/crontask.go | 29 +-- service/singleton/notification.go | 205 +++++++++++++----- service/singleton/servicesentinel.go | 31 ++- service/singleton/singleton.go | 2 +- 18 files changed, 666 insertions(+), 180 deletions(-) create mode 100644 cmd/dashboard/controller/notification.go create mode 100644 cmd/dashboard/controller/notification_group.go create mode 100644 model/notification_group_api.go diff --git a/cmd/dashboard/controller/controller.go b/cmd/dashboard/controller/controller.go index 1195a02..bc2d3ba 100644 --- a/cmd/dashboard/controller/controller.go +++ b/cmd/dashboard/controller/controller.go @@ -66,10 +66,20 @@ func routers(r *gin.Engine) { auth.PATCH("/server-group/:id", commonHandler(updateServerGroup)) auth.POST("/batch-delete/server-group", commonHandler(batchDeleteServerGroup)) + auth.GET("/notification-group", commonHandler(listNotificationGroup)) + auth.POST("/notification-group", commonHandler(createNotificationGroup)) + auth.PATCH("/notification-group/:id", commonHandler(updateNotificationGroup)) + auth.POST("/batch-delete/notification-group", commonHandler(batchDeleteNotificationGroup)) + auth.GET("/server", commonHandler(listServer)) auth.PATCH("/server/:id", commonHandler(updateServer)) auth.POST("/batch-delete/server", commonHandler(batchDeleteServer)) + auth.GET("/notification", commonHandler(listNotification)) + auth.POST("/notification", commonHandler(createNotification)) + auth.PATCH("/notification/:id", commonHandler(updateNotification)) + auth.POST("/batch-delete/notification", commonHandler(batchDeleteNotification)) + auth.GET("/ddns", commonHandler(listDDNS)) auth.GET("/ddns/providers", commonHandler(listProviders)) auth.POST("/ddns", commonHandler(createDDNS)) diff --git a/cmd/dashboard/controller/ddns.go b/cmd/dashboard/controller/ddns.go index 8bae96d..4f5dbbd 100644 --- a/cmd/dashboard/controller/ddns.go +++ b/cmd/dashboard/controller/ddns.go @@ -37,8 +37,8 @@ func createDDNS(c *gin.Context) (uint64, error) { } p.Name = df.Name - enableIPv4 := df.EnableIPv4 == "on" - enableIPv6 := df.EnableIPv6 == "on" + enableIPv4 := df.EnableIPv4 + enableIPv6 := df.EnableIPv6 p.EnableIPv4 = &enableIPv4 p.EnableIPv6 = &enableIPv6 p.MaxRetries = df.MaxRetries @@ -107,8 +107,8 @@ func updateDDNS(c *gin.Context) (any, error) { p.Name = df.Name p.ID = id - enableIPv4 := df.EnableIPv4 == "on" - enableIPv6 := df.EnableIPv6 == "on" + enableIPv4 := df.EnableIPv4 + enableIPv6 := df.EnableIPv6 p.EnableIPv4 = &enableIPv4 p.EnableIPv6 = &enableIPv6 p.MaxRetries = df.MaxRetries diff --git a/cmd/dashboard/controller/member_api.go b/cmd/dashboard/controller/member_api.go index 411718d..dfc66c7 100644 --- a/cmd/dashboard/controller/member_api.go +++ b/cmd/dashboard/controller/member_api.go @@ -173,16 +173,7 @@ func (ma *memberAPI) delete(c *gin.Context) { var err error switch c.Param("model") { - case "notification": - err = singleton.DB.Unscoped().Delete(&model.Notification{}, "id = ?", id).Error - if err == nil { - singleton.OnDeleteNotification(id) - } - case "ddns": - err = singleton.DB.Unscoped().Delete(&model.DDNSProfile{}, "id = ?", id).Error - if err == nil { - singleton.OnDDNSUpdate() - } + case "nat": err = singleton.DB.Unscoped().Delete(&model.NAT{}, "id = ?", id).Error if err == nil { @@ -224,13 +215,13 @@ func (ma *memberAPI) delete(c *gin.Context) { } type monitorForm struct { - ID uint64 - Name string - Target string - Type uint8 - Cover uint8 - Notify string - NotificationTag string + ID uint64 + Name string + Target string + Type uint8 + Cover uint8 + Notify string + //NotificationTag string SkipServersRaw string Duration uint64 MinLatency float32 @@ -254,7 +245,7 @@ func (ma *memberAPI) addOrEditMonitor(c *gin.Context) { m.SkipServersRaw = mf.SkipServersRaw m.Cover = mf.Cover m.Notify = mf.Notify == "on" - m.NotificationTag = mf.NotificationTag + //m.NotificationTag = mf.NotificationTag m.Duration = mf.Duration m.LatencyNotify = mf.LatencyNotify == "on" m.MinLatency = mf.MinLatency @@ -267,9 +258,9 @@ func (ma *memberAPI) addOrEditMonitor(c *gin.Context) { } if err == nil { // 保证NotificationTag不为空 - if m.NotificationTag == "" { - m.NotificationTag = "default" - } + //if m.NotificationTag == "" { + // m.NotificationTag = "default" + //} err = utils.Json.Unmarshal([]byte(mf.FailTriggerTasksRaw), &m.FailTriggerTasks) } if err == nil { @@ -327,7 +318,7 @@ func (ma *memberAPI) addOrEditCron(c *gin.Context) { cr.Command = cf.Command cr.ServersRaw = cf.ServersRaw cr.PushSuccessful = cf.PushSuccessful == "on" - cr.NotificationTag = cf.NotificationTag + //cr.NotificationTag = cf.NotificationTag cr.ID = cf.ID cr.Cover = cf.Cover err = utils.Json.Unmarshal([]byte(cf.ServersRaw), &cr.Servers) @@ -346,9 +337,9 @@ func (ma *memberAPI) addOrEditCron(c *gin.Context) { tx := singleton.DB.Begin() if err == nil { // 保证NotificationTag不为空 - if cr.NotificationTag == "" { - cr.NotificationTag = "default" - } + //if cr.NotificationTag == "" { + // cr.NotificationTag = "default" + //} if cf.ID == 0 { err = tx.Create(&cr).Error } else { @@ -507,7 +498,6 @@ func (ma *memberAPI) forceUpdate(c *gin.Context) { type notificationForm struct { ID uint64 Name string - Tag string // 分组名 URL string RequestMethod int RequestType int @@ -523,7 +513,6 @@ func (ma *memberAPI) addOrEditNotification(c *gin.Context) { err := c.ShouldBindJSON(&nf) if err == nil { n.Name = nf.Name - n.Tag = nf.Tag n.RequestMethod = nf.RequestMethod n.RequestType = nf.RequestType n.RequestHeader = nf.RequestHeader @@ -543,10 +532,6 @@ func (ma *memberAPI) addOrEditNotification(c *gin.Context) { } } if err == nil { - // 保证Tag不为空 - if n.Tag == "" { - n.Tag = "default" - } if n.ID == 0 { err = singleton.DB.Create(&n).Error } else { @@ -730,7 +715,7 @@ func (ma *memberAPI) addOrEditAlertRule(c *gin.Context) { r.RulesRaw = arf.RulesRaw r.FailTriggerTasksRaw = arf.FailTriggerTasksRaw r.RecoverTriggerTasksRaw = arf.RecoverTriggerTasksRaw - r.NotificationTag = arf.NotificationTag + //r.NotificationTag = arf.NotificationTag enable := arf.Enable == "on" r.TriggerMode = arf.TriggerMode r.Enable = &enable @@ -744,9 +729,9 @@ func (ma *memberAPI) addOrEditAlertRule(c *gin.Context) { } //保证NotificationTag不为空 if err == nil { - if r.NotificationTag == "" { - r.NotificationTag = "default" - } + //if r.NotificationTag == "" { + // r.NotificationTag = "default" + //} if r.ID == 0 { err = singleton.DB.Create(&r).Error } else { diff --git a/cmd/dashboard/controller/notification.go b/cmd/dashboard/controller/notification.go new file mode 100644 index 0000000..b0db063 --- /dev/null +++ b/cmd/dashboard/controller/notification.go @@ -0,0 +1,170 @@ +package controller + +import ( + "fmt" + "strconv" + + "github.com/gin-gonic/gin" + "github.com/naiba/nezha/model" + "github.com/naiba/nezha/service/singleton" + "gorm.io/gorm" +) + +// List notification +// @Summary List notification +// @Security BearerAuth +// @Schemes +// @Description List notification +// @Tags auth required +// @Produce json +// @Success 200 {object} model.CommonResponse[any] +// @Router /notification [get] +func listNotification(c *gin.Context) ([]model.Notification, error) { + singleton.NotificationsLock.RLock() + defer singleton.NotificationsLock.RUnlock() + notifications := make([]model.Notification, 0, len(singleton.NotificationMap)) + for _, n := range singleton.NotificationMap { + notifications = append(notifications, *n) + } + return notifications, nil +} + +// Add notification +// @Summary Add notification +// @Security BearerAuth +// @Schemes +// @Description Add notification +// @Tags auth required +// @Accept json +// @param request body model.NotificationForm true "NotificationForm" +// @Produce json +// @Success 200 {object} model.CommonResponse[any] +// @Router /notification [post] +func createNotification(c *gin.Context) (uint64, error) { + var nf model.NotificationForm + if err := c.ShouldBindJSON(&nf); err != nil { + return 0, err + } + + var n model.Notification + n.Name = nf.Name + n.RequestMethod = nf.RequestMethod + n.RequestType = nf.RequestType + n.RequestHeader = nf.RequestHeader + n.RequestBody = nf.RequestBody + n.URL = nf.URL + verifySSL := nf.VerifySSL + n.VerifySSL = &verifySSL + n.ID = nf.ID + ns := model.NotificationServerBundle{ + Notification: &n, + Server: nil, + Loc: singleton.Loc, + } + // 未勾选跳过检查 + if !nf.SkipCheck { + if err := ns.Send("这是测试消息"); err != nil { + return 0, err + } + } + + if err := singleton.DB.Create(&n).Error; err != nil { + return 0, newGormError("%v", err) + } + + singleton.OnRefreshOrAddNotification(&n) + return n.ID, nil +} + +// Edit notification +// @Summary Edit notification +// @Security BearerAuth +// @Schemes +// @Description Edit notification +// @Tags auth required +// @Accept json +// @Param id path uint true "Notification ID" +// @Param body body model.NotificationForm true "NotificationForm" +// @Produce json +// @Success 200 {object} model.CommonResponse[any] +// @Router /notification/{id} [patch] +func updateNotification(c *gin.Context) (any, error) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + return nil, err + } + var nf model.NotificationForm + if err := c.ShouldBindJSON(&nf); err != nil { + return nil, err + } + + var n model.Notification + if err := singleton.DB.First(&n, id).Error; err != nil { + return nil, fmt.Errorf("notification id %d does not exist", id) + } + + n.Name = nf.Name + n.RequestMethod = nf.RequestMethod + n.RequestType = nf.RequestType + n.RequestHeader = nf.RequestHeader + n.RequestBody = nf.RequestBody + n.URL = nf.URL + verifySSL := nf.VerifySSL + n.VerifySSL = &verifySSL + n.ID = nf.ID + ns := model.NotificationServerBundle{ + Notification: &n, + Server: nil, + Loc: singleton.Loc, + } + // 未勾选跳过检查 + if !nf.SkipCheck { + if err := ns.Send("这是测试消息"); err != nil { + return nil, err + } + } + + if err := singleton.DB.Save(&n).Error; err != nil { + return nil, newGormError("%v", err) + } + + singleton.OnRefreshOrAddNotification(&n) + return nil, nil +} + +// Batch delete notifications +// @Summary Batch delete notifications +// @Security BearerAuth +// @Schemes +// @Description Batch delete notifications +// @Tags auth required +// @Accept json +// @param request body []uint64 true "id list" +// @Produce json +// @Success 200 {object} model.CommonResponse[any] +// @Router /batch-delete/notification [post] +func batchDeleteNotification(c *gin.Context) (any, error) { + var n []uint64 + + if err := c.ShouldBindJSON(&n); err != nil { + return nil, err + } + + err := singleton.DB.Transaction(func(tx *gorm.DB) error { + if err := tx.Unscoped().Delete(&model.Notification{}, "id in (?)", n).Error; err != nil { + return err + } + if err := tx.Unscoped().Delete(&model.NotificationGroupNotification{}, "notification_id in (?)", n).Error; err != nil { + return err + } + return nil + }) + + if err != nil { + return nil, newGormError("%v", err) + } + + singleton.OnDeleteNotification(n) + return nil, nil +} diff --git a/cmd/dashboard/controller/notification_group.go b/cmd/dashboard/controller/notification_group.go new file mode 100644 index 0000000..f750869 --- /dev/null +++ b/cmd/dashboard/controller/notification_group.go @@ -0,0 +1,205 @@ +package controller + +import ( + "fmt" + "slices" + "strconv" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" + + "github.com/naiba/nezha/model" + "github.com/naiba/nezha/service/singleton" +) + +// List notification group +// @Summary List notification group +// @Schemes +// @Description List notification group +// @Security BearerAuth +// @Tags common +// @Produce json +// @Success 200 {object} model.CommonResponse[[]model.NotificationGroupResponseItem] +// @Router /notification-group [get] +func listNotificationGroup(c *gin.Context) ([]model.NotificationGroupResponseItem, error) { + var ng []model.NotificationGroup + if err := singleton.DB.Find(&ng).Error; err != nil { + return nil, err + } + + var ngn []model.NotificationGroupNotification + if err := singleton.DB.Find(&ngn).Error; err != nil { + return nil, err + } + + groupNotifications := make(map[uint64][]uint64, len(ng)) + for _, n := range ngn { + if _, ok := groupNotifications[n.NotificationGroupID]; !ok { + groupNotifications[n.NotificationGroupID] = make([]uint64, 0) + } + groupNotifications[n.NotificationGroupID] = append(groupNotifications[n.NotificationGroupID], n.NotificationID) + } + + ngRes := make([]model.NotificationGroupResponseItem, 0, len(ng)) + for _, n := range ng { + ngRes = append(ngRes, model.NotificationGroupResponseItem{ + Group: n, + Notifications: groupNotifications[n.ID], + }) + } + + return ngRes, nil +} + +// New notification group +// @Summary New notification group +// @Schemes +// @Description New notification group +// @Security BearerAuth +// @Tags auth required +// @Accept json +// @Param body body model.NotificationGroupForm true "NotificationGroupForm" +// @Produce json +// @Success 200 {object} model.CommonResponse[any] +// @Router /notification-group [post] +func createNotificationGroup(c *gin.Context) (uint64, error) { + var ngf model.NotificationGroupForm + if err := c.ShouldBindJSON(&ngf); err != nil { + return 0, err + } + ngf.Notifications = slices.Compact(ngf.Notifications) + + var ng model.NotificationGroup + ng.Name = ngf.Name + + var count int64 + if err := singleton.DB.Model(&model.Notification{}).Where("id in (?)", ngf.Notifications).Count(&count).Error; err != nil { + return 0, newGormError("%v", err) + } + + if count != int64(len(ngf.Notifications)) { + return 0, fmt.Errorf("have invalid notification id") + } + + err := singleton.DB.Transaction(func(tx *gorm.DB) error { + if err := tx.Create(&ng).Error; err != nil { + return err + } + for _, n := range ngf.Notifications { + if err := tx.Create(&model.NotificationGroupNotification{ + NotificationGroupID: ng.ID, + NotificationID: n, + }).Error; err != nil { + return err + } + } + return nil + }) + if err != nil { + return 0, newGormError("%v", err) + } + + singleton.OnRefreshOrAddNotificationGroup(&ng, ngf.Notifications) + return ng.ID, nil +} + +// Edit notification group +// @Summary Edit notification group +// @Schemes +// @Description Edit notification group +// @Security BearerAuth +// @Tags auth required +// @Accept json +// @Param id path string true "ID" +// @Param body body model.NotificationGroupForm true "NotificationGroupForm" +// @Produce json +// @Success 200 {object} model.CommonResponse[any] +// @Router /notification-group/{id} [patch] +func updateNotificationGroup(c *gin.Context) (any, error) { + idStr := c.Param("id") + + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + return nil, err + } + + var ngf model.NotificationGroupForm + if err := c.ShouldBindJSON(&ngf); err != nil { + return nil, err + } + var ngDB model.NotificationGroup + if err := singleton.DB.First(&ngDB, id).Error; err != nil { + return nil, fmt.Errorf("group id %d does not exist", id) + } + + ngDB.Name = ngf.Name + ngf.Notifications = slices.Compact(ngf.Notifications) + + var count int64 + if err := singleton.DB.Model(&model.Server{}).Where("id in (?)", ngf.Notifications).Count(&count).Error; err != nil { + return nil, newGormError("%v", err) + } + if count != int64(len(ngf.Notifications)) { + return nil, fmt.Errorf("have invalid notification id") + } + + err = singleton.DB.Transaction(func(tx *gorm.DB) error { + if err := tx.Save(&ngDB).Error; err != nil { + return err + } + if err := tx.Delete(&model.NotificationGroupNotification{}, "notification_group_id = ?", id).Error; err != nil { + return err + } + + for _, n := range ngf.Notifications { + if err := tx.Create(&model.NotificationGroupNotification{ + NotificationGroupID: ngDB.ID, + NotificationID: n, + }).Error; err != nil { + return err + } + } + return nil + }) + if err != nil { + return nil, newGormError("%v", err) + } + + singleton.OnRefreshOrAddNotificationGroup(&ngDB, ngf.Notifications) + return nil, nil +} + +// Batch delete notification group +// @Summary Batch delete notification group +// @Security BearerAuth +// @Schemes +// @Description Batch delete notification group +// @Tags auth required +// @Accept json +// @param request body []uint64 true "id list" +// @Produce json +// @Success 200 {object} model.CommonResponse[any] +// @Router /batch-delete/notification-group [post] +func batchDeleteNotificationGroup(c *gin.Context) (any, error) { + var ngn []uint64 + if err := c.ShouldBindJSON(&ngn); err != nil { + return nil, err + } + + err := singleton.DB.Transaction(func(tx *gorm.DB) error { + if err := tx.Unscoped().Delete(&model.NotificationGroup{}, "id in (?)", ngn).Error; err != nil { + return err + } + if err := tx.Unscoped().Delete(&model.NotificationGroupNotification{}, "notification_group_id in (?)", ngn).Error; err != nil { + return err + } + return nil + }) + + if err != nil { + return nil, newGormError("%v", err) + } + + singleton.OnDeleteNotificationGroup(ngn) + return nil, nil +} diff --git a/cmd/dashboard/controller/server_group.go b/cmd/dashboard/controller/server_group.go index cbbc084..9a50ba7 100644 --- a/cmd/dashboard/controller/server_group.go +++ b/cmd/dashboard/controller/server_group.go @@ -2,6 +2,8 @@ package controller import ( "fmt" + "slices" + "strconv" "github.com/gin-gonic/gin" "gorm.io/gorm" @@ -64,13 +66,14 @@ func createServerGroup(c *gin.Context) (uint64, error) { if err := c.ShouldBindJSON(&sgf); err != nil { return 0, err } + sgf.Servers = slices.Compact(sgf.Servers) var sg model.ServerGroup sg.Name = sgf.Name var count int64 - if err := singleton.DB.Model(&model.Server{}).Where("id = ?", sgf.Servers).Count(&count).Error; err != nil { - return 0, err + if err := singleton.DB.Model(&model.Server{}).Where("id in (?)", sgf.Servers).Count(&count).Error; err != nil { + return 0, newGormError("%v", err) } if count != int64(len(sgf.Servers)) { return 0, fmt.Errorf("have invalid server id") @@ -110,26 +113,34 @@ func createServerGroup(c *gin.Context) (uint64, error) { // @Success 200 {object} model.CommonResponse[any] // @Router /server-group/{id} [patch] func updateServerGroup(c *gin.Context) (any, error) { - id := c.Param("id") + idStr := c.Param("id") + + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + return nil, err + } + var sg model.ServerGroupForm if err := c.ShouldBindJSON(&sg); err != nil { return nil, err } + sg.Servers = slices.Compact(sg.Servers) + var sgDB model.ServerGroup if err := singleton.DB.First(&sgDB, id).Error; err != nil { - return nil, fmt.Errorf("group id %s does not exist", id) + return nil, fmt.Errorf("group id %d does not exist", id) } sgDB.Name = sg.Name var count int64 - if err := singleton.DB.Model(&model.Server{}).Where("id = ?", sg.Servers).Count(&count).Error; err != nil { + if err := singleton.DB.Model(&model.Server{}).Where("id in (?)", sg.Servers).Count(&count).Error; err != nil { return nil, err } if count != int64(len(sg.Servers)) { return nil, fmt.Errorf("have invalid server id") } - err := singleton.DB.Transaction(func(tx *gorm.DB) error { + err = singleton.DB.Transaction(func(tx *gorm.DB) error { if err := tx.Save(&sgDB).Error; err != nil { return err } diff --git a/model/alertrule.go b/model/alertrule.go index 9d9e643..23e651f 100644 --- a/model/alertrule.go +++ b/model/alertrule.go @@ -29,7 +29,7 @@ type AlertRule struct { RulesRaw string Enable *bool TriggerMode int `gorm:"default:0"` // 触发模式: 0-始终触发(默认) 1-单次触发 - NotificationTag string // 该报警规则所在的通知组 + NotificationGroupID uint64 // 该报警规则所在的通知组 FailTriggerTasksRaw string `gorm:"default:'[]'"` RecoverTriggerTasksRaw string `gorm:"default:'[]'"` Rules []Rule `gorm:"-" json:"-"` diff --git a/model/cron.go b/model/cron.go index 253b440..6db2602 100644 --- a/model/cron.go +++ b/model/cron.go @@ -18,16 +18,16 @@ const ( type Cron struct { Common - Name string - TaskType uint8 `gorm:"default:0"` // 0:计划任务 1:触发任务 - Scheduler string //分钟 小时 天 月 星期 - Command string - Servers []uint64 `gorm:"-"` - PushSuccessful bool // 推送成功的通知 - NotificationTag string // 指定通知方式的分组 - LastExecutedAt time.Time // 最后一次执行时间 - LastResult bool // 最后一次执行结果 - Cover uint8 // 计划任务覆盖范围 (0:仅覆盖特定服务器 1:仅忽略特定服务器 2:由触发该计划任务的服务器执行) + Name string + TaskType uint8 `gorm:"default:0"` // 0:计划任务 1:触发任务 + Scheduler string //分钟 小时 天 月 星期 + Command string + Servers []uint64 `gorm:"-"` + PushSuccessful bool // 推送成功的通知 + NotificationGroupID uint64 // 指定通知方式的分组 + LastExecutedAt time.Time // 最后一次执行时间 + LastResult bool // 最后一次执行结果 + Cover uint8 // 计划任务覆盖范围 (0:仅覆盖特定服务器 1:仅忽略特定服务器 2:由触发该计划任务的服务器执行) CronJobID cron.EntryID `gorm:"-"` ServersRaw string diff --git a/model/ddns.go b/model/ddns.go index ae93b15..f63dfcb 100644 --- a/model/ddns.go +++ b/model/ddns.go @@ -49,8 +49,8 @@ func (d *DDNSProfile) AfterFind(tx *gorm.DB) error { type DDNSForm struct { ID uint64 `json:"id,omitempty"` MaxRetries uint64 `json:"max_retries,omitempty"` - EnableIPv4 string `json:"enable_ipv4,omitempty"` - EnableIPv6 string `json:"enable_ipv6,omitempty"` + EnableIPv4 bool `json:"enable_ipv4,omitempty"` + EnableIPv6 bool `json:"enable_ipv6,omitempty"` Name string `json:"name,omitempty"` Provider string `json:"provider,omitempty"` DomainsRaw string `json:"domains_raw,omitempty"` diff --git a/model/monitor.go b/model/monitor.go index 9bfc57e..df33735 100644 --- a/model/monitor.go +++ b/model/monitor.go @@ -46,14 +46,14 @@ const ( type Monitor struct { Common - Name string - Type uint8 - Target string - SkipServersRaw string - Duration uint64 - Notify bool - NotificationTag string // 当前服务监控所属的通知组 - Cover uint8 + Name string + Type uint8 + Target string + SkipServersRaw string + Duration uint64 + Notify bool + NotificationGroupID uint64 // 当前服务监控所属的通知组 ID + Cover uint8 EnableTriggerTask bool `gorm:"default: false"` EnableShowInService bool `gorm:"default: false"` diff --git a/model/notification.go b/model/notification.go index 4207367..4b4a9ca 100644 --- a/model/notification.go +++ b/model/notification.go @@ -32,14 +32,25 @@ type NotificationServerBundle struct { type Notification struct { Common - Name string - Tag string // 分组名 - URL string - RequestMethod int - RequestType int - RequestHeader string `gorm:"type:longtext" ` - RequestBody string `gorm:"type:longtext" ` - VerifySSL *bool + Name string `json:"name,omitempty"` + URL string `json:"url,omitempty"` + RequestMethod int `json:"request_method,omitempty"` + RequestType int `json:"request_type,omitempty"` + RequestHeader string `json:"request_header,omitempty" gorm:"type:longtext"` + RequestBody string `json:"request_body,omitempty" gorm:"type:longtext"` + VerifySSL *bool `json:"verify_ssl,omitempty"` +} + +type NotificationForm struct { + ID uint64 `json:"id,omitempty"` + Name string `json:"name,omitempty"` + URL string `json:"url,omitempty"` + RequestMethod int `json:"request_method,omitempty"` + RequestType int `json:"request_type,omitempty"` + RequestHeader string `json:"request_header,omitempty"` + RequestBody string `json:"request_body,omitempty"` + VerifySSL bool `json:"verify_ssl,omitempty"` + SkipCheck bool `json:"skip_check,omitempty"` } func (ns *NotificationServerBundle) reqURL(message string) string { diff --git a/model/notification_group_api.go b/model/notification_group_api.go new file mode 100644 index 0000000..bac006e --- /dev/null +++ b/model/notification_group_api.go @@ -0,0 +1,11 @@ +package model + +type NotificationGroupForm struct { + Name string `json:"name"` + Notifications []uint64 `json:"notifications"` +} + +type NotificationGroupResponseItem struct { + Group NotificationGroup `json:"group"` + Notifications []uint64 `json:"notifications"` +} diff --git a/model/notification_group_notification.go b/model/notification_group_notification.go index 63e20c2..1771ab7 100644 --- a/model/notification_group_notification.go +++ b/model/notification_group_notification.go @@ -2,6 +2,6 @@ package model type NotificationGroupNotification struct { Common - NotificationGroupID uint64 `json:"notification_group_id"` - NotificationID uint64 `json:"notification_id"` + NotificationGroupID uint64 `json:"notification_group_id" gorm:"uniqueIndex:idx_notification_group_notification"` + NotificationID uint64 `json:"notification_id" gorm:"uniqueIndex:idx_notification_group_notification"` } diff --git a/service/singleton/alertsentinel.go b/service/singleton/alertsentinel.go index ec70a92..a15f17b 100644 --- a/service/singleton/alertsentinel.go +++ b/service/singleton/alertsentinel.go @@ -1,6 +1,7 @@ package singleton import ( + "fmt" "log" "sync" "time" @@ -66,11 +67,6 @@ func AlertSentinelStart() { panic(err) } for _, alert := range Alerts { - // 旧版本可能不存在通知组 为其添加默认值 - if alert.NotificationTag == "" { - alert.NotificationTag = "default" - DB.Save(alert) - } alertsStore[alert.ID] = make(map[uint64][][]interface{}) alertsPrevState[alert.ID] = make(map[uint64]uint) addCycleTransferStatsInfo(alert) @@ -157,24 +153,22 @@ func checkStatus() { // 始终触发模式或上次检查不为失败时触发报警(跳过单次触发+上次失败的情况) if alert.TriggerMode == model.ModeAlwaysTrigger || alertsPrevState[alert.ID][server.ID] != _RuleCheckFail { alertsPrevState[alert.ID][server.ID] = _RuleCheckFail - // message := fmt.Sprintf("[%s] %s(%s) %s", Localizer.MustLocalize(&i18n.LocalizeConfig{ - // MessageID: "Incident", - // }), server.Name, IPDesensitize(server.Host.IP), alert.Name) + message := fmt.Sprintf("[%s] %s(%s) %s", "Incident", + server.Name, IPDesensitize(server.Host.IP), alert.Name) go SendTriggerTasks(alert.FailTriggerTasks, curServer.ID) - // go SendNotification(alert.NotificationTag, message, NotificationMuteLabel.ServerIncident(server.ID, alert.ID), &curServer) + go SendNotification(alert.NotificationGroupID, message, NotificationMuteLabel.ServerIncident(server.ID, alert.ID), &curServer) // 清除恢复通知的静音缓存 - UnMuteNotification(alert.NotificationTag, NotificationMuteLabel.ServerIncidentResolved(server.ID, alert.ID)) + UnMuteNotification(alert.NotificationGroupID, NotificationMuteLabel.ServerIncidentResolved(server.ID, alert.ID)) } } else { // 本次通过检查但上一次的状态为失败,则发送恢复通知 if alertsPrevState[alert.ID][server.ID] == _RuleCheckFail { - // message := fmt.Sprintf("[%s] %s(%s) %s", Localizer.MustLocalize(&i18n.LocalizeConfig{ - // MessageID: "Resolved", - // }), server.Name, IPDesensitize(server.Host.IP), alert.Name) + message := fmt.Sprintf("[%s] %s(%s) %s", "Resolved", + server.Name, IPDesensitize(server.Host.IP), alert.Name) go SendTriggerTasks(alert.RecoverTriggerTasks, curServer.ID) - // go SendNotification(alert.NotificationTag, message, NotificationMuteLabel.ServerIncidentResolved(server.ID, alert.ID), &curServer) + go SendNotification(alert.NotificationGroupID, message, NotificationMuteLabel.ServerIncidentResolved(server.ID, alert.ID), &curServer) // 清除失败通知的静音缓存 - UnMuteNotification(alert.NotificationTag, NotificationMuteLabel.ServerIncident(server.ID, alert.ID)) + UnMuteNotification(alert.NotificationGroupID, NotificationMuteLabel.ServerIncident(server.ID, alert.ID)) } alertsPrevState[alert.ID][server.ID] = _RuleCheckPass } diff --git a/service/singleton/crontask.go b/service/singleton/crontask.go index 8ab2289..09104b9 100644 --- a/service/singleton/crontask.go +++ b/service/singleton/crontask.go @@ -30,37 +30,32 @@ func loadCronTasks() { var crons []model.Cron DB.Find(&crons) var err error - var notificationTagList []string - notificationMsgMap := make(map[string]*bytes.Buffer) + var notificationGroupList []uint64 + notificationMsgMap := make(map[uint64]*bytes.Buffer) for i := 0; i < len(crons); i++ { // 触发任务类型无需注册 if crons[i].TaskType == model.CronTypeTriggerTask { Crons[crons[i].ID] = &crons[i] continue } - // 旧版本计划任务可能不存在通知组 为其添加默认通知组 - if crons[i].NotificationTag == "" { - crons[i].NotificationTag = "default" - DB.Save(crons[i]) - } // 注册计划任务 crons[i].CronJobID, err = Cron.AddFunc(crons[i].Scheduler, CronTrigger(crons[i])) if err == nil { Crons[crons[i].ID] = &crons[i] } else { // 当前通知组首次出现 将其加入通知组列表并初始化通知组消息缓存 - if _, ok := notificationMsgMap[crons[i].NotificationTag]; !ok { - notificationTagList = append(notificationTagList, crons[i].NotificationTag) - notificationMsgMap[crons[i].NotificationTag] = bytes.NewBufferString("") - notificationMsgMap[crons[i].NotificationTag].WriteString("调度失败的计划任务:[") + if _, ok := notificationMsgMap[crons[i].NotificationGroupID]; !ok { + notificationGroupList = append(notificationGroupList, crons[i].NotificationGroupID) + notificationMsgMap[crons[i].NotificationGroupID] = bytes.NewBufferString("") + notificationMsgMap[crons[i].NotificationGroupID].WriteString("调度失败的计划任务:[") } - notificationMsgMap[crons[i].NotificationTag].WriteString(fmt.Sprintf("%d,", crons[i].ID)) + notificationMsgMap[crons[i].NotificationGroupID].WriteString(fmt.Sprintf("%d,", crons[i].ID)) } } // 向注册错误的计划任务所在通知组发送通知 - for _, tag := range notificationTagList { - notificationMsgMap[tag].WriteString("] 这些任务将无法正常执行,请进入后点重新修改保存。") - SendNotification(tag, notificationMsgMap[tag].String(), nil) + for _, gid := range notificationGroupList { + notificationMsgMap[gid].WriteString("] 这些任务将无法正常执行,请进入后点重新修改保存。") + SendNotification(gid, notificationMsgMap[gid].String(), nil) } Cron.Start() } @@ -108,7 +103,7 @@ func CronTrigger(cr model.Cron, triggerServer ...uint64) func() { // 保存当前服务器状态信息 curServer := model.Server{} copier.Copy(&curServer, s) - SendNotification(cr.NotificationTag, fmt.Sprintf("[任务失败] %s,服务器 %s 离线,无法执行。", cr.Name, s.Name), nil, &curServer) + SendNotification(cr.NotificationGroupID, fmt.Sprintf("[任务失败] %s,服务器 %s 离线,无法执行。", cr.Name, s.Name), nil, &curServer) } } return @@ -133,7 +128,7 @@ func CronTrigger(cr model.Cron, triggerServer ...uint64) func() { // 保存当前服务器状态信息 curServer := model.Server{} copier.Copy(&curServer, s) - SendNotification(cr.NotificationTag, fmt.Sprintf("[任务失败] %s,服务器 %s 离线,无法执行。", cr.Name, s.Name), nil, &curServer) + SendNotification(cr.NotificationGroupID, fmt.Sprintf("[任务失败] %s,服务器 %s 离线,无法执行。", cr.Name, s.Name), nil, &curServer) } } } diff --git a/service/singleton/notification.go b/service/singleton/notification.go index a14cfa6..19782d9 100644 --- a/service/singleton/notification.go +++ b/service/singleton/notification.go @@ -9,55 +9,152 @@ import ( "github.com/naiba/nezha/model" ) -const firstNotificationDelay = time.Minute * 15 +const ( + firstNotificationDelay = time.Minute * 15 +) // 通知方式 var ( - NotificationList map[string]map[uint64]*model.Notification // [NotificationMethodTag][NotificationID] -> model.Notification - NotificationIDToTag map[uint64]string // [NotificationID] -> NotificationTag - notificationsLock sync.RWMutex + NotificationList map[uint64]map[uint64]*model.Notification // [NotificationGroupID][NotificationID] -> model.Notification + NotificationIDToGroups map[uint64]map[uint64]struct{} // [NotificationID] -> NotificationGroupID + + NotificationMap map[uint64]*model.Notification + NotificationGroup map[uint64]string // [NotificationGroupID] -> [NotificationGroupName] + + NotificationsLock sync.RWMutex + NotificationGroupLock sync.RWMutex ) -// InitNotification 初始化 Tag <-> ID <-> Notification 的映射 +// InitNotification 初始化 GroupID <-> ID <-> Notification 的映射 func InitNotification() { - NotificationList = make(map[string]map[uint64]*model.Notification) - NotificationIDToTag = make(map[uint64]string) + NotificationList = make(map[uint64]map[uint64]*model.Notification) + NotificationIDToGroups = make(map[uint64]map[uint64]struct{}) + NotificationGroup = make(map[uint64]string) } // loadNotifications 从 DB 初始化通知方式相关参数 func loadNotifications() { InitNotification() - notificationsLock.Lock() - defer notificationsLock.Unlock() + NotificationsLock.Lock() + defer NotificationsLock.Unlock() + + groupNotifications := make(map[uint64][]uint64) + var ngn []model.NotificationGroupNotification + if err := DB.Find(&ngn).Error; err != nil { + panic(err) + } + + for _, n := range ngn { + groupNotifications[n.NotificationGroupID] = append(groupNotifications[n.NotificationGroupID], n.NotificationID) + } var notifications []model.Notification if err := DB.Find(¬ifications).Error; err != nil { panic(err) } - for i := 0; i < len(notifications); i++ { - // 旧版本的Tag可能不存在 自动设置为默认值 - if notifications[i].Tag == "" { - SetDefaultNotificationTagInDB(¬ifications[i]) + + NotificationMap = make(map[uint64]*model.Notification, len(notifications)) + for i := range notifications { + NotificationMap[notifications[i].ID] = ¬ifications[i] + } + + for gid, nids := range groupNotifications { + NotificationList[gid] = make(map[uint64]*model.Notification) + for _, nid := range nids { + if n, ok := NotificationMap[nid]; ok { + NotificationList[gid][n.ID] = n + + if NotificationIDToGroups[n.ID] == nil { + NotificationIDToGroups[n.ID] = make(map[uint64]struct{}) + } + + NotificationIDToGroups[n.ID][gid] = struct{}{} + } } - AddNotificationToList(¬ifications[i]) } } -// SetDefaultNotificationTagInDB 设置默认通知方式的 Tag -func SetDefaultNotificationTagInDB(n *model.Notification) { - n.Tag = "default" - if err := DB.Save(n).Error; err != nil { - log.Println("NEZHA>> SetDefaultNotificationTagInDB 错误: ", err) +// OnRefreshOrAddNotificationGroup 刷新通知方式组相关参数 +func OnRefreshOrAddNotificationGroup(ng *model.NotificationGroup, ngn []uint64) { + NotificationsLock.Lock() + defer NotificationsLock.Unlock() + + NotificationGroupLock.Lock() + defer NotificationGroupLock.Unlock() + var isEdit bool + if _, ok := NotificationGroup[ng.ID]; ok { + isEdit = true + } + + if !isEdit { + AddNotificationGroupToList(ng, ngn) + } else { + UpdateNotificationGroupInList(ng, ngn) + } +} + +// AddNotificationGroupToList 添加通知方式组到map中 +func AddNotificationGroupToList(ng *model.NotificationGroup, ngn []uint64) { + NotificationGroup[ng.ID] = ng.Name + + NotificationList[ng.ID] = make(map[uint64]*model.Notification, len(ngn)) + + for _, n := range ngn { + if NotificationIDToGroups[n] == nil { + NotificationIDToGroups[n] = make(map[uint64]struct{}) + } + NotificationIDToGroups[n][ng.ID] = struct{}{} + NotificationList[ng.ID][n] = NotificationMap[n] + } +} + +// UpdateNotificationGroupInList 在 map 中更新通知方式组 +func UpdateNotificationGroupInList(ng *model.NotificationGroup, ngn []uint64) { + NotificationGroup[ng.ID] = ng.Name + + oldList := make(map[uint64]struct{}) + for nid := range NotificationList[ng.ID] { + oldList[nid] = struct{}{} + } + + NotificationList[ng.ID] = make(map[uint64]*model.Notification) + for _, nid := range ngn { + NotificationList[ng.ID][nid] = NotificationMap[nid] + if NotificationIDToGroups[nid] == nil { + NotificationIDToGroups[nid] = make(map[uint64]struct{}) + } + NotificationIDToGroups[nid][ng.ID] = struct{}{} + } + + for oldID := range oldList { + if _, ok := NotificationList[ng.ID][oldID]; !ok { + delete(NotificationIDToGroups[oldID], ng.ID) + if len(NotificationIDToGroups[oldID]) == 0 { + delete(NotificationIDToGroups, oldID) + } + } + } +} + +// UpdateNotificationGroupInList 删除通知方式组 +func OnDeleteNotificationGroup(gids []uint64) { + NotificationsLock.Lock() + defer NotificationsLock.Unlock() + + for _, gid := range gids { + delete(NotificationGroup, gid) + delete(NotificationList, gid) } } // OnRefreshOrAddNotification 刷新通知方式相关参数 func OnRefreshOrAddNotification(n *model.Notification) { - notificationsLock.Lock() - defer notificationsLock.Unlock() + NotificationsLock.Lock() + defer NotificationsLock.Unlock() var isEdit bool - if _, ok := NotificationIDToTag[n.ID]; ok { + _, ok := NotificationMap[n.ID] + if ok { isEdit = true } if !isEdit { @@ -69,47 +166,47 @@ func OnRefreshOrAddNotification(n *model.Notification) { // AddNotificationToList 添加通知方式到map中 func AddNotificationToList(n *model.Notification) { - // 当前 Tag 不存在,创建对应该 Tag 的 子 map 后再添加 - if _, ok := NotificationList[n.Tag]; !ok { - NotificationList[n.Tag] = make(map[uint64]*model.Notification) - } - NotificationList[n.Tag][n.ID] = n - NotificationIDToTag[n.ID] = n.Tag + NotificationMap[n.ID] = n } // UpdateNotificationInList 在 map 中更新通知方式 func UpdateNotificationInList(n *model.Notification) { - if n.Tag != NotificationIDToTag[n.ID] { - // 如果 Tag 不一致,则需要先移除原有的映射关系 - delete(NotificationList[NotificationIDToTag[n.ID]], n.ID) - delete(NotificationIDToTag, n.ID) - // 将新的 Tag 中的通知方式添加到 map 中 - AddNotificationToList(n) - } else { - // 如果 Tag 一致,则直接更新 - NotificationList[n.Tag][n.ID] = n + NotificationMap[n.ID] = n + // 如果已经与通知组有绑定关系,更新 + if gids, ok := NotificationIDToGroups[n.ID]; ok { + for gid := range gids { + NotificationList[gid][n.ID] = n + } } } -// OnDeleteNotification 在map中删除通知方式 -func OnDeleteNotification(id uint64) { - notificationsLock.Lock() - defer notificationsLock.Unlock() +// OnDeleteNotification 在map和表中删除通知方式 +func OnDeleteNotification(id []uint64) { + NotificationsLock.Lock() + defer NotificationsLock.Unlock() - delete(NotificationList[NotificationIDToTag[id]], id) - delete(NotificationIDToTag, id) + for _, i := range id { + delete(NotificationMap, i) + // 如果绑定了通知组才删除 + if gids, ok := NotificationIDToGroups[i]; ok { + for gid := range gids { + delete(NotificationList[gid], i) + delete(NotificationIDToGroups, i) + } + } + } } -func UnMuteNotification(notificationTag string, muteLabel *string) { - fullMuteLabel := *NotificationMuteLabel.AppendNotificationTag(muteLabel, notificationTag) +func UnMuteNotification(notificationGroupID uint64, muteLabel *string) { + fullMuteLabel := *NotificationMuteLabel.AppendNotificationGroupName(muteLabel, notificationGroupID) Cache.Delete(fullMuteLabel) } // SendNotification 向指定的通知方式组的所有通知方式发送通知 -func SendNotification(notificationTag string, desc string, muteLabel *string, ext ...*model.Server) { +func SendNotification(notificationGroupID uint64, desc string, muteLabel *string, ext ...*model.Server) { if muteLabel != nil { // 将通知方式组名称加入静音标志 - muteLabel := *NotificationMuteLabel.AppendNotificationTag(muteLabel, notificationTag) + muteLabel := *NotificationMuteLabel.AppendNotificationGroupName(muteLabel, notificationGroupID) // 通知防骚扰策略 var flag bool if cacheN, has := Cache.Get(muteLabel); has { @@ -142,12 +239,12 @@ func SendNotification(notificationTag string, desc string, muteLabel *string, ex } } // 向该通知方式组的所有通知方式发出通知 - notificationsLock.RLock() - defer notificationsLock.RUnlock() - for _, n := range NotificationList[notificationTag] { + NotificationsLock.RLock() + defer NotificationsLock.RUnlock() + for _, n := range NotificationList[notificationGroupID] { log.Println("NEZHA>> 尝试通知", n.Name) } - for _, n := range NotificationList[notificationTag] { + for _, n := range NotificationList[notificationGroupID] { ns := model.NotificationServerBundle{ Notification: n, Server: nil, @@ -183,8 +280,10 @@ func (_NotificationMuteLabel) ServerIncidentResolved(alertId uint64, serverId ui return &label } -func (_NotificationMuteLabel) AppendNotificationTag(label *string, notificationTag string) *string { - newLabel := fmt.Sprintf("%s:%s", *label, notificationTag) +func (_NotificationMuteLabel) AppendNotificationGroupName(label *string, notificationGroupID uint64) *string { + NotificationGroupLock.RLock() + defer NotificationGroupLock.RUnlock() + newLabel := fmt.Sprintf("%s:%s", *label, NotificationGroup[notificationGroupID]) return &newLabel } diff --git a/service/singleton/servicesentinel.go b/service/singleton/servicesentinel.go index 0934c12..193a99e 100644 --- a/service/singleton/servicesentinel.go +++ b/service/singleton/servicesentinel.go @@ -186,11 +186,6 @@ func (ss *ServiceSentinel) loadMonitorHistory() { defer ss.monitorsLock.Unlock() for i := 0; i < len(monitors); i++ { - // 旧版本可能不存在通知组 为其设置默认组 - if monitors[i].NotificationTag == "" { - monitors[i].NotificationTag = "default" - DB.Save(monitors[i]) - } task := *monitors[i] // 通过cron定时将服务监控任务传递给任务调度管道 monitors[i].CronJobID, err = Cron.AddFunc(task.CronSpec(), func() { @@ -432,7 +427,7 @@ func (ss *ServiceSentinel) worker() { if mh.Delay > 0 { ss.monitorsLock.RLock() if ss.monitors[mh.GetId()].LatencyNotify { - notificationTag := ss.monitors[mh.GetId()].NotificationTag + notificationGroupID := ss.monitors[mh.GetId()].NotificationGroupID minMuteLabel := NotificationMuteLabel.ServiceLatencyMin(mh.GetId()) maxMuteLabel := NotificationMuteLabel.ServiceLatencyMax(mh.GetId()) if mh.Delay > ss.monitors[mh.GetId()].MaxLatency { @@ -440,19 +435,19 @@ func (ss *ServiceSentinel) worker() { ServerLock.RLock() reporterServer := ServerList[r.Reporter] msg := fmt.Sprintf("[Latency] %s %2f > %2f, Reporter: %s", ss.monitors[mh.GetId()].Name, mh.Delay, ss.monitors[mh.GetId()].MaxLatency, reporterServer.Name) - go SendNotification(notificationTag, msg, minMuteLabel) + go SendNotification(notificationGroupID, msg, minMuteLabel) ServerLock.RUnlock() } else if mh.Delay < ss.monitors[mh.GetId()].MinLatency { // 延迟低于最小值 ServerLock.RLock() reporterServer := ServerList[r.Reporter] msg := fmt.Sprintf("[Latency] %s %2f < %2f, Reporter: %s", ss.monitors[mh.GetId()].Name, mh.Delay, ss.monitors[mh.GetId()].MinLatency, reporterServer.Name) - go SendNotification(notificationTag, msg, maxMuteLabel) + go SendNotification(notificationGroupID, msg, maxMuteLabel) ServerLock.RUnlock() } else { // 正常延迟, 清除静音缓存 - UnMuteNotification(notificationTag, minMuteLabel) - UnMuteNotification(notificationTag, maxMuteLabel) + UnMuteNotification(notificationGroupID, minMuteLabel) + UnMuteNotification(notificationGroupID, maxMuteLabel) } } ss.monitorsLock.RUnlock() @@ -471,16 +466,16 @@ func (ss *ServiceSentinel) worker() { ServerLock.RLock() reporterServer := ServerList[r.Reporter] - notificationTag := ss.monitors[mh.GetId()].NotificationTag + notificationGroupID := ss.monitors[mh.GetId()].NotificationGroupID notificationMsg := fmt.Sprintf("[%s] %s Reporter: %s, Error: %s", StatusCodeToString(stateCode), ss.monitors[mh.GetId()].Name, reporterServer.Name, mh.Data) muteLabel := NotificationMuteLabel.ServiceStateChanged(mh.GetId()) // 状态变更时,清除静音缓存 if stateCode != lastStatus { - UnMuteNotification(notificationTag, muteLabel) + UnMuteNotification(notificationGroupID, muteLabel) } - go SendNotification(notificationTag, notificationMsg, muteLabel) + go SendNotification(notificationGroupID, notificationMsg, muteLabel) ServerLock.RUnlock() } @@ -515,14 +510,14 @@ func (ss *ServiceSentinel) worker() { ss.monitorsLock.RLock() if ss.monitors[mh.GetId()].Notify { muteLabel := NotificationMuteLabel.ServiceSSL(mh.GetId(), "network") - go SendNotification(ss.monitors[mh.GetId()].NotificationTag, fmt.Sprintf("[SSL] Fetch cert info failed, %s %s", ss.monitors[mh.GetId()].Name, errMsg), muteLabel) + go SendNotification(ss.monitors[mh.GetId()].NotificationGroupID, fmt.Sprintf("[SSL] Fetch cert info failed, %s %s", ss.monitors[mh.GetId()].Name, errMsg), muteLabel) } ss.monitorsLock.RUnlock() } } else { // 清除网络错误静音缓存 - UnMuteNotification(ss.monitors[mh.GetId()].NotificationTag, NotificationMuteLabel.ServiceSSL(mh.GetId(), "network")) + UnMuteNotification(ss.monitors[mh.GetId()].NotificationGroupID, NotificationMuteLabel.ServiceSSL(mh.GetId(), "network")) var newCert = strings.Split(mh.Data, "|") if len(newCert) > 1 { @@ -545,7 +540,7 @@ func (ss *ServiceSentinel) worker() { ss.sslCertCache[mh.GetId()] = mh.Data } - notificationTag := ss.monitors[mh.GetId()].NotificationTag + notificationGroupID := ss.monitors[mh.GetId()].NotificationGroupID serviceName := ss.monitors[mh.GetId()].Name ss.monitorsLock.Unlock() @@ -562,7 +557,7 @@ func (ss *ServiceSentinel) worker() { // 静音规则: 服务id+证书过期时间 // 用于避免多个监测点对相同证书同时报警 muteLabel := NotificationMuteLabel.ServiceSSL(mh.GetId(), fmt.Sprintf("expire_%s", expiresTimeStr)) - go SendNotification(notificationTag, fmt.Sprintf("[SSL] %s %s", serviceName, errMsg), muteLabel) + go SendNotification(notificationGroupID, fmt.Sprintf("[SSL] %s %s", serviceName, errMsg), muteLabel) } // 证书变更提醒 @@ -572,7 +567,7 @@ func (ss *ServiceSentinel) worker() { oldCert[0], expiresOld.Format("2006-01-02 15:04:05"), newCert[0], expiresNew.Format("2006-01-02 15:04:05")) // 证书变更后会自动更新缓存,所以不需要静音 - go SendNotification(notificationTag, fmt.Sprintf("[SSL] %s %s", serviceName, errMsg), nil) + go SendNotification(notificationGroupID, fmt.Sprintf("[SSL] %s %s", serviceName, errMsg), nil) } } } diff --git a/service/singleton/singleton.go b/service/singleton/singleton.go index 82a88f9..46c2717 100644 --- a/service/singleton/singleton.go +++ b/service/singleton/singleton.go @@ -65,7 +65,7 @@ func InitDBFromPath(path string) { err = DB.AutoMigrate(model.Server{}, model.User{}, model.Notification{}, model.AlertRule{}, model.Monitor{}, model.MonitorHistory{}, model.Cron{}, model.Transfer{}, - model.ApiToken{}, model.NAT{}, model.DDNSProfile{}) + model.ApiToken{}, model.NAT{}, model.DDNSProfile{}, model.NotificationGroupNotification{}) if err != nil { panic(err) }