mirror of
https://github.com/nezhahq/nezha.git
synced 2025-01-22 12:48:14 -05:00
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
This commit is contained in:
parent
e792215f6e
commit
61e755d2b9
@ -66,10 +66,20 @@ func routers(r *gin.Engine) {
|
|||||||
auth.PATCH("/server-group/:id", commonHandler(updateServerGroup))
|
auth.PATCH("/server-group/:id", commonHandler(updateServerGroup))
|
||||||
auth.POST("/batch-delete/server-group", commonHandler(batchDeleteServerGroup))
|
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.GET("/server", commonHandler(listServer))
|
||||||
auth.PATCH("/server/:id", commonHandler(updateServer))
|
auth.PATCH("/server/:id", commonHandler(updateServer))
|
||||||
auth.POST("/batch-delete/server", commonHandler(batchDeleteServer))
|
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", commonHandler(listDDNS))
|
||||||
auth.GET("/ddns/providers", commonHandler(listProviders))
|
auth.GET("/ddns/providers", commonHandler(listProviders))
|
||||||
auth.POST("/ddns", commonHandler(createDDNS))
|
auth.POST("/ddns", commonHandler(createDDNS))
|
||||||
|
@ -37,8 +37,8 @@ func createDDNS(c *gin.Context) (uint64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p.Name = df.Name
|
p.Name = df.Name
|
||||||
enableIPv4 := df.EnableIPv4 == "on"
|
enableIPv4 := df.EnableIPv4
|
||||||
enableIPv6 := df.EnableIPv6 == "on"
|
enableIPv6 := df.EnableIPv6
|
||||||
p.EnableIPv4 = &enableIPv4
|
p.EnableIPv4 = &enableIPv4
|
||||||
p.EnableIPv6 = &enableIPv6
|
p.EnableIPv6 = &enableIPv6
|
||||||
p.MaxRetries = df.MaxRetries
|
p.MaxRetries = df.MaxRetries
|
||||||
@ -107,8 +107,8 @@ func updateDDNS(c *gin.Context) (any, error) {
|
|||||||
|
|
||||||
p.Name = df.Name
|
p.Name = df.Name
|
||||||
p.ID = id
|
p.ID = id
|
||||||
enableIPv4 := df.EnableIPv4 == "on"
|
enableIPv4 := df.EnableIPv4
|
||||||
enableIPv6 := df.EnableIPv6 == "on"
|
enableIPv6 := df.EnableIPv6
|
||||||
p.EnableIPv4 = &enableIPv4
|
p.EnableIPv4 = &enableIPv4
|
||||||
p.EnableIPv6 = &enableIPv6
|
p.EnableIPv6 = &enableIPv6
|
||||||
p.MaxRetries = df.MaxRetries
|
p.MaxRetries = df.MaxRetries
|
||||||
|
@ -173,16 +173,7 @@ func (ma *memberAPI) delete(c *gin.Context) {
|
|||||||
|
|
||||||
var err error
|
var err error
|
||||||
switch c.Param("model") {
|
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":
|
case "nat":
|
||||||
err = singleton.DB.Unscoped().Delete(&model.NAT{}, "id = ?", id).Error
|
err = singleton.DB.Unscoped().Delete(&model.NAT{}, "id = ?", id).Error
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -224,13 +215,13 @@ func (ma *memberAPI) delete(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type monitorForm struct {
|
type monitorForm struct {
|
||||||
ID uint64
|
ID uint64
|
||||||
Name string
|
Name string
|
||||||
Target string
|
Target string
|
||||||
Type uint8
|
Type uint8
|
||||||
Cover uint8
|
Cover uint8
|
||||||
Notify string
|
Notify string
|
||||||
NotificationTag string
|
//NotificationTag string
|
||||||
SkipServersRaw string
|
SkipServersRaw string
|
||||||
Duration uint64
|
Duration uint64
|
||||||
MinLatency float32
|
MinLatency float32
|
||||||
@ -254,7 +245,7 @@ func (ma *memberAPI) addOrEditMonitor(c *gin.Context) {
|
|||||||
m.SkipServersRaw = mf.SkipServersRaw
|
m.SkipServersRaw = mf.SkipServersRaw
|
||||||
m.Cover = mf.Cover
|
m.Cover = mf.Cover
|
||||||
m.Notify = mf.Notify == "on"
|
m.Notify = mf.Notify == "on"
|
||||||
m.NotificationTag = mf.NotificationTag
|
//m.NotificationTag = mf.NotificationTag
|
||||||
m.Duration = mf.Duration
|
m.Duration = mf.Duration
|
||||||
m.LatencyNotify = mf.LatencyNotify == "on"
|
m.LatencyNotify = mf.LatencyNotify == "on"
|
||||||
m.MinLatency = mf.MinLatency
|
m.MinLatency = mf.MinLatency
|
||||||
@ -267,9 +258,9 @@ func (ma *memberAPI) addOrEditMonitor(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// 保证NotificationTag不为空
|
// 保证NotificationTag不为空
|
||||||
if m.NotificationTag == "" {
|
//if m.NotificationTag == "" {
|
||||||
m.NotificationTag = "default"
|
// m.NotificationTag = "default"
|
||||||
}
|
//}
|
||||||
err = utils.Json.Unmarshal([]byte(mf.FailTriggerTasksRaw), &m.FailTriggerTasks)
|
err = utils.Json.Unmarshal([]byte(mf.FailTriggerTasksRaw), &m.FailTriggerTasks)
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -327,7 +318,7 @@ func (ma *memberAPI) addOrEditCron(c *gin.Context) {
|
|||||||
cr.Command = cf.Command
|
cr.Command = cf.Command
|
||||||
cr.ServersRaw = cf.ServersRaw
|
cr.ServersRaw = cf.ServersRaw
|
||||||
cr.PushSuccessful = cf.PushSuccessful == "on"
|
cr.PushSuccessful = cf.PushSuccessful == "on"
|
||||||
cr.NotificationTag = cf.NotificationTag
|
//cr.NotificationTag = cf.NotificationTag
|
||||||
cr.ID = cf.ID
|
cr.ID = cf.ID
|
||||||
cr.Cover = cf.Cover
|
cr.Cover = cf.Cover
|
||||||
err = utils.Json.Unmarshal([]byte(cf.ServersRaw), &cr.Servers)
|
err = utils.Json.Unmarshal([]byte(cf.ServersRaw), &cr.Servers)
|
||||||
@ -346,9 +337,9 @@ func (ma *memberAPI) addOrEditCron(c *gin.Context) {
|
|||||||
tx := singleton.DB.Begin()
|
tx := singleton.DB.Begin()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// 保证NotificationTag不为空
|
// 保证NotificationTag不为空
|
||||||
if cr.NotificationTag == "" {
|
//if cr.NotificationTag == "" {
|
||||||
cr.NotificationTag = "default"
|
// cr.NotificationTag = "default"
|
||||||
}
|
//}
|
||||||
if cf.ID == 0 {
|
if cf.ID == 0 {
|
||||||
err = tx.Create(&cr).Error
|
err = tx.Create(&cr).Error
|
||||||
} else {
|
} else {
|
||||||
@ -507,7 +498,6 @@ func (ma *memberAPI) forceUpdate(c *gin.Context) {
|
|||||||
type notificationForm struct {
|
type notificationForm struct {
|
||||||
ID uint64
|
ID uint64
|
||||||
Name string
|
Name string
|
||||||
Tag string // 分组名
|
|
||||||
URL string
|
URL string
|
||||||
RequestMethod int
|
RequestMethod int
|
||||||
RequestType int
|
RequestType int
|
||||||
@ -523,7 +513,6 @@ func (ma *memberAPI) addOrEditNotification(c *gin.Context) {
|
|||||||
err := c.ShouldBindJSON(&nf)
|
err := c.ShouldBindJSON(&nf)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
n.Name = nf.Name
|
n.Name = nf.Name
|
||||||
n.Tag = nf.Tag
|
|
||||||
n.RequestMethod = nf.RequestMethod
|
n.RequestMethod = nf.RequestMethod
|
||||||
n.RequestType = nf.RequestType
|
n.RequestType = nf.RequestType
|
||||||
n.RequestHeader = nf.RequestHeader
|
n.RequestHeader = nf.RequestHeader
|
||||||
@ -543,10 +532,6 @@ func (ma *memberAPI) addOrEditNotification(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// 保证Tag不为空
|
|
||||||
if n.Tag == "" {
|
|
||||||
n.Tag = "default"
|
|
||||||
}
|
|
||||||
if n.ID == 0 {
|
if n.ID == 0 {
|
||||||
err = singleton.DB.Create(&n).Error
|
err = singleton.DB.Create(&n).Error
|
||||||
} else {
|
} else {
|
||||||
@ -730,7 +715,7 @@ func (ma *memberAPI) addOrEditAlertRule(c *gin.Context) {
|
|||||||
r.RulesRaw = arf.RulesRaw
|
r.RulesRaw = arf.RulesRaw
|
||||||
r.FailTriggerTasksRaw = arf.FailTriggerTasksRaw
|
r.FailTriggerTasksRaw = arf.FailTriggerTasksRaw
|
||||||
r.RecoverTriggerTasksRaw = arf.RecoverTriggerTasksRaw
|
r.RecoverTriggerTasksRaw = arf.RecoverTriggerTasksRaw
|
||||||
r.NotificationTag = arf.NotificationTag
|
//r.NotificationTag = arf.NotificationTag
|
||||||
enable := arf.Enable == "on"
|
enable := arf.Enable == "on"
|
||||||
r.TriggerMode = arf.TriggerMode
|
r.TriggerMode = arf.TriggerMode
|
||||||
r.Enable = &enable
|
r.Enable = &enable
|
||||||
@ -744,9 +729,9 @@ func (ma *memberAPI) addOrEditAlertRule(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
//保证NotificationTag不为空
|
//保证NotificationTag不为空
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if r.NotificationTag == "" {
|
//if r.NotificationTag == "" {
|
||||||
r.NotificationTag = "default"
|
// r.NotificationTag = "default"
|
||||||
}
|
//}
|
||||||
if r.ID == 0 {
|
if r.ID == 0 {
|
||||||
err = singleton.DB.Create(&r).Error
|
err = singleton.DB.Create(&r).Error
|
||||||
} else {
|
} else {
|
||||||
|
170
cmd/dashboard/controller/notification.go
Normal file
170
cmd/dashboard/controller/notification.go
Normal file
@ -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
|
||||||
|
}
|
205
cmd/dashboard/controller/notification_group.go
Normal file
205
cmd/dashboard/controller/notification_group.go
Normal file
@ -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
|
||||||
|
}
|
@ -2,6 +2,8 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@ -64,13 +66,14 @@ func createServerGroup(c *gin.Context) (uint64, error) {
|
|||||||
if err := c.ShouldBindJSON(&sgf); err != nil {
|
if err := c.ShouldBindJSON(&sgf); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
sgf.Servers = slices.Compact(sgf.Servers)
|
||||||
|
|
||||||
var sg model.ServerGroup
|
var sg model.ServerGroup
|
||||||
sg.Name = sgf.Name
|
sg.Name = sgf.Name
|
||||||
|
|
||||||
var count int64
|
var count int64
|
||||||
if err := singleton.DB.Model(&model.Server{}).Where("id = ?", sgf.Servers).Count(&count).Error; err != nil {
|
if err := singleton.DB.Model(&model.Server{}).Where("id in (?)", sgf.Servers).Count(&count).Error; err != nil {
|
||||||
return 0, err
|
return 0, newGormError("%v", err)
|
||||||
}
|
}
|
||||||
if count != int64(len(sgf.Servers)) {
|
if count != int64(len(sgf.Servers)) {
|
||||||
return 0, fmt.Errorf("have invalid server id")
|
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]
|
// @Success 200 {object} model.CommonResponse[any]
|
||||||
// @Router /server-group/{id} [patch]
|
// @Router /server-group/{id} [patch]
|
||||||
func updateServerGroup(c *gin.Context) (any, error) {
|
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
|
var sg model.ServerGroupForm
|
||||||
if err := c.ShouldBindJSON(&sg); err != nil {
|
if err := c.ShouldBindJSON(&sg); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
sg.Servers = slices.Compact(sg.Servers)
|
||||||
|
|
||||||
var sgDB model.ServerGroup
|
var sgDB model.ServerGroup
|
||||||
if err := singleton.DB.First(&sgDB, id).Error; err != nil {
|
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
|
sgDB.Name = sg.Name
|
||||||
|
|
||||||
var count int64
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
if count != int64(len(sg.Servers)) {
|
if count != int64(len(sg.Servers)) {
|
||||||
return nil, fmt.Errorf("have invalid server id")
|
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 {
|
if err := tx.Save(&sgDB).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ type AlertRule struct {
|
|||||||
RulesRaw string
|
RulesRaw string
|
||||||
Enable *bool
|
Enable *bool
|
||||||
TriggerMode int `gorm:"default:0"` // 触发模式: 0-始终触发(默认) 1-单次触发
|
TriggerMode int `gorm:"default:0"` // 触发模式: 0-始终触发(默认) 1-单次触发
|
||||||
NotificationTag string // 该报警规则所在的通知组
|
NotificationGroupID uint64 // 该报警规则所在的通知组
|
||||||
FailTriggerTasksRaw string `gorm:"default:'[]'"`
|
FailTriggerTasksRaw string `gorm:"default:'[]'"`
|
||||||
RecoverTriggerTasksRaw string `gorm:"default:'[]'"`
|
RecoverTriggerTasksRaw string `gorm:"default:'[]'"`
|
||||||
Rules []Rule `gorm:"-" json:"-"`
|
Rules []Rule `gorm:"-" json:"-"`
|
||||||
|
@ -18,16 +18,16 @@ const (
|
|||||||
|
|
||||||
type Cron struct {
|
type Cron struct {
|
||||||
Common
|
Common
|
||||||
Name string
|
Name string
|
||||||
TaskType uint8 `gorm:"default:0"` // 0:计划任务 1:触发任务
|
TaskType uint8 `gorm:"default:0"` // 0:计划任务 1:触发任务
|
||||||
Scheduler string //分钟 小时 天 月 星期
|
Scheduler string //分钟 小时 天 月 星期
|
||||||
Command string
|
Command string
|
||||||
Servers []uint64 `gorm:"-"`
|
Servers []uint64 `gorm:"-"`
|
||||||
PushSuccessful bool // 推送成功的通知
|
PushSuccessful bool // 推送成功的通知
|
||||||
NotificationTag string // 指定通知方式的分组
|
NotificationGroupID uint64 // 指定通知方式的分组
|
||||||
LastExecutedAt time.Time // 最后一次执行时间
|
LastExecutedAt time.Time // 最后一次执行时间
|
||||||
LastResult bool // 最后一次执行结果
|
LastResult bool // 最后一次执行结果
|
||||||
Cover uint8 // 计划任务覆盖范围 (0:仅覆盖特定服务器 1:仅忽略特定服务器 2:由触发该计划任务的服务器执行)
|
Cover uint8 // 计划任务覆盖范围 (0:仅覆盖特定服务器 1:仅忽略特定服务器 2:由触发该计划任务的服务器执行)
|
||||||
|
|
||||||
CronJobID cron.EntryID `gorm:"-"`
|
CronJobID cron.EntryID `gorm:"-"`
|
||||||
ServersRaw string
|
ServersRaw string
|
||||||
|
@ -49,8 +49,8 @@ func (d *DDNSProfile) AfterFind(tx *gorm.DB) error {
|
|||||||
type DDNSForm struct {
|
type DDNSForm struct {
|
||||||
ID uint64 `json:"id,omitempty"`
|
ID uint64 `json:"id,omitempty"`
|
||||||
MaxRetries uint64 `json:"max_retries,omitempty"`
|
MaxRetries uint64 `json:"max_retries,omitempty"`
|
||||||
EnableIPv4 string `json:"enable_ipv4,omitempty"`
|
EnableIPv4 bool `json:"enable_ipv4,omitempty"`
|
||||||
EnableIPv6 string `json:"enable_ipv6,omitempty"`
|
EnableIPv6 bool `json:"enable_ipv6,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Provider string `json:"provider,omitempty"`
|
Provider string `json:"provider,omitempty"`
|
||||||
DomainsRaw string `json:"domains_raw,omitempty"`
|
DomainsRaw string `json:"domains_raw,omitempty"`
|
||||||
|
@ -46,14 +46,14 @@ const (
|
|||||||
|
|
||||||
type Monitor struct {
|
type Monitor struct {
|
||||||
Common
|
Common
|
||||||
Name string
|
Name string
|
||||||
Type uint8
|
Type uint8
|
||||||
Target string
|
Target string
|
||||||
SkipServersRaw string
|
SkipServersRaw string
|
||||||
Duration uint64
|
Duration uint64
|
||||||
Notify bool
|
Notify bool
|
||||||
NotificationTag string // 当前服务监控所属的通知组
|
NotificationGroupID uint64 // 当前服务监控所属的通知组 ID
|
||||||
Cover uint8
|
Cover uint8
|
||||||
|
|
||||||
EnableTriggerTask bool `gorm:"default: false"`
|
EnableTriggerTask bool `gorm:"default: false"`
|
||||||
EnableShowInService bool `gorm:"default: false"`
|
EnableShowInService bool `gorm:"default: false"`
|
||||||
|
@ -32,14 +32,25 @@ type NotificationServerBundle struct {
|
|||||||
|
|
||||||
type Notification struct {
|
type Notification struct {
|
||||||
Common
|
Common
|
||||||
Name string
|
Name string `json:"name,omitempty"`
|
||||||
Tag string // 分组名
|
URL string `json:"url,omitempty"`
|
||||||
URL string
|
RequestMethod int `json:"request_method,omitempty"`
|
||||||
RequestMethod int
|
RequestType int `json:"request_type,omitempty"`
|
||||||
RequestType int
|
RequestHeader string `json:"request_header,omitempty" gorm:"type:longtext"`
|
||||||
RequestHeader string `gorm:"type:longtext" `
|
RequestBody string `json:"request_body,omitempty" gorm:"type:longtext"`
|
||||||
RequestBody string `gorm:"type:longtext" `
|
VerifySSL *bool `json:"verify_ssl,omitempty"`
|
||||||
VerifySSL *bool
|
}
|
||||||
|
|
||||||
|
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 {
|
func (ns *NotificationServerBundle) reqURL(message string) string {
|
||||||
|
11
model/notification_group_api.go
Normal file
11
model/notification_group_api.go
Normal file
@ -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"`
|
||||||
|
}
|
@ -2,6 +2,6 @@ package model
|
|||||||
|
|
||||||
type NotificationGroupNotification struct {
|
type NotificationGroupNotification struct {
|
||||||
Common
|
Common
|
||||||
NotificationGroupID uint64 `json:"notification_group_id"`
|
NotificationGroupID uint64 `json:"notification_group_id" gorm:"uniqueIndex:idx_notification_group_notification"`
|
||||||
NotificationID uint64 `json:"notification_id"`
|
NotificationID uint64 `json:"notification_id" gorm:"uniqueIndex:idx_notification_group_notification"`
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package singleton
|
package singleton
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -66,11 +67,6 @@ func AlertSentinelStart() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
for _, alert := range Alerts {
|
for _, alert := range Alerts {
|
||||||
// 旧版本可能不存在通知组 为其添加默认值
|
|
||||||
if alert.NotificationTag == "" {
|
|
||||||
alert.NotificationTag = "default"
|
|
||||||
DB.Save(alert)
|
|
||||||
}
|
|
||||||
alertsStore[alert.ID] = make(map[uint64][][]interface{})
|
alertsStore[alert.ID] = make(map[uint64][][]interface{})
|
||||||
alertsPrevState[alert.ID] = make(map[uint64]uint)
|
alertsPrevState[alert.ID] = make(map[uint64]uint)
|
||||||
addCycleTransferStatsInfo(alert)
|
addCycleTransferStatsInfo(alert)
|
||||||
@ -157,24 +153,22 @@ func checkStatus() {
|
|||||||
// 始终触发模式或上次检查不为失败时触发报警(跳过单次触发+上次失败的情况)
|
// 始终触发模式或上次检查不为失败时触发报警(跳过单次触发+上次失败的情况)
|
||||||
if alert.TriggerMode == model.ModeAlwaysTrigger || alertsPrevState[alert.ID][server.ID] != _RuleCheckFail {
|
if alert.TriggerMode == model.ModeAlwaysTrigger || alertsPrevState[alert.ID][server.ID] != _RuleCheckFail {
|
||||||
alertsPrevState[alert.ID][server.ID] = _RuleCheckFail
|
alertsPrevState[alert.ID][server.ID] = _RuleCheckFail
|
||||||
// message := fmt.Sprintf("[%s] %s(%s) %s", Localizer.MustLocalize(&i18n.LocalizeConfig{
|
message := fmt.Sprintf("[%s] %s(%s) %s", "Incident",
|
||||||
// MessageID: "Incident",
|
server.Name, IPDesensitize(server.Host.IP), alert.Name)
|
||||||
// }), server.Name, IPDesensitize(server.Host.IP), alert.Name)
|
|
||||||
go SendTriggerTasks(alert.FailTriggerTasks, curServer.ID)
|
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 {
|
} else {
|
||||||
// 本次通过检查但上一次的状态为失败,则发送恢复通知
|
// 本次通过检查但上一次的状态为失败,则发送恢复通知
|
||||||
if alertsPrevState[alert.ID][server.ID] == _RuleCheckFail {
|
if alertsPrevState[alert.ID][server.ID] == _RuleCheckFail {
|
||||||
// message := fmt.Sprintf("[%s] %s(%s) %s", Localizer.MustLocalize(&i18n.LocalizeConfig{
|
message := fmt.Sprintf("[%s] %s(%s) %s", "Resolved",
|
||||||
// MessageID: "Resolved",
|
server.Name, IPDesensitize(server.Host.IP), alert.Name)
|
||||||
// }), server.Name, IPDesensitize(server.Host.IP), alert.Name)
|
|
||||||
go SendTriggerTasks(alert.RecoverTriggerTasks, curServer.ID)
|
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
|
alertsPrevState[alert.ID][server.ID] = _RuleCheckPass
|
||||||
}
|
}
|
||||||
|
@ -30,37 +30,32 @@ func loadCronTasks() {
|
|||||||
var crons []model.Cron
|
var crons []model.Cron
|
||||||
DB.Find(&crons)
|
DB.Find(&crons)
|
||||||
var err error
|
var err error
|
||||||
var notificationTagList []string
|
var notificationGroupList []uint64
|
||||||
notificationMsgMap := make(map[string]*bytes.Buffer)
|
notificationMsgMap := make(map[uint64]*bytes.Buffer)
|
||||||
for i := 0; i < len(crons); i++ {
|
for i := 0; i < len(crons); i++ {
|
||||||
// 触发任务类型无需注册
|
// 触发任务类型无需注册
|
||||||
if crons[i].TaskType == model.CronTypeTriggerTask {
|
if crons[i].TaskType == model.CronTypeTriggerTask {
|
||||||
Crons[crons[i].ID] = &crons[i]
|
Crons[crons[i].ID] = &crons[i]
|
||||||
continue
|
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]))
|
crons[i].CronJobID, err = Cron.AddFunc(crons[i].Scheduler, CronTrigger(crons[i]))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
Crons[crons[i].ID] = &crons[i]
|
Crons[crons[i].ID] = &crons[i]
|
||||||
} else {
|
} else {
|
||||||
// 当前通知组首次出现 将其加入通知组列表并初始化通知组消息缓存
|
// 当前通知组首次出现 将其加入通知组列表并初始化通知组消息缓存
|
||||||
if _, ok := notificationMsgMap[crons[i].NotificationTag]; !ok {
|
if _, ok := notificationMsgMap[crons[i].NotificationGroupID]; !ok {
|
||||||
notificationTagList = append(notificationTagList, crons[i].NotificationTag)
|
notificationGroupList = append(notificationGroupList, crons[i].NotificationGroupID)
|
||||||
notificationMsgMap[crons[i].NotificationTag] = bytes.NewBufferString("")
|
notificationMsgMap[crons[i].NotificationGroupID] = bytes.NewBufferString("")
|
||||||
notificationMsgMap[crons[i].NotificationTag].WriteString("调度失败的计划任务:[")
|
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 {
|
for _, gid := range notificationGroupList {
|
||||||
notificationMsgMap[tag].WriteString("] 这些任务将无法正常执行,请进入后点重新修改保存。")
|
notificationMsgMap[gid].WriteString("] 这些任务将无法正常执行,请进入后点重新修改保存。")
|
||||||
SendNotification(tag, notificationMsgMap[tag].String(), nil)
|
SendNotification(gid, notificationMsgMap[gid].String(), nil)
|
||||||
}
|
}
|
||||||
Cron.Start()
|
Cron.Start()
|
||||||
}
|
}
|
||||||
@ -108,7 +103,7 @@ func CronTrigger(cr model.Cron, triggerServer ...uint64) func() {
|
|||||||
// 保存当前服务器状态信息
|
// 保存当前服务器状态信息
|
||||||
curServer := model.Server{}
|
curServer := model.Server{}
|
||||||
copier.Copy(&curServer, s)
|
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
|
return
|
||||||
@ -133,7 +128,7 @@ func CronTrigger(cr model.Cron, triggerServer ...uint64) func() {
|
|||||||
// 保存当前服务器状态信息
|
// 保存当前服务器状态信息
|
||||||
curServer := model.Server{}
|
curServer := model.Server{}
|
||||||
copier.Copy(&curServer, s)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,55 +9,152 @@ import (
|
|||||||
"github.com/naiba/nezha/model"
|
"github.com/naiba/nezha/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
const firstNotificationDelay = time.Minute * 15
|
const (
|
||||||
|
firstNotificationDelay = time.Minute * 15
|
||||||
|
)
|
||||||
|
|
||||||
// 通知方式
|
// 通知方式
|
||||||
var (
|
var (
|
||||||
NotificationList map[string]map[uint64]*model.Notification // [NotificationMethodTag][NotificationID] -> model.Notification
|
NotificationList map[uint64]map[uint64]*model.Notification // [NotificationGroupID][NotificationID] -> model.Notification
|
||||||
NotificationIDToTag map[uint64]string // [NotificationID] -> NotificationTag
|
NotificationIDToGroups map[uint64]map[uint64]struct{} // [NotificationID] -> NotificationGroupID
|
||||||
notificationsLock sync.RWMutex
|
|
||||||
|
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() {
|
func InitNotification() {
|
||||||
NotificationList = make(map[string]map[uint64]*model.Notification)
|
NotificationList = make(map[uint64]map[uint64]*model.Notification)
|
||||||
NotificationIDToTag = make(map[uint64]string)
|
NotificationIDToGroups = make(map[uint64]map[uint64]struct{})
|
||||||
|
NotificationGroup = make(map[uint64]string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadNotifications 从 DB 初始化通知方式相关参数
|
// loadNotifications 从 DB 初始化通知方式相关参数
|
||||||
func loadNotifications() {
|
func loadNotifications() {
|
||||||
InitNotification()
|
InitNotification()
|
||||||
notificationsLock.Lock()
|
NotificationsLock.Lock()
|
||||||
defer notificationsLock.Unlock()
|
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
|
var notifications []model.Notification
|
||||||
if err := DB.Find(¬ifications).Error; err != nil {
|
if err := DB.Find(¬ifications).Error; err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
for i := 0; i < len(notifications); i++ {
|
|
||||||
// 旧版本的Tag可能不存在 自动设置为默认值
|
NotificationMap = make(map[uint64]*model.Notification, len(notifications))
|
||||||
if notifications[i].Tag == "" {
|
for i := range notifications {
|
||||||
SetDefaultNotificationTagInDB(¬ifications[i])
|
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
|
// OnRefreshOrAddNotificationGroup 刷新通知方式组相关参数
|
||||||
func SetDefaultNotificationTagInDB(n *model.Notification) {
|
func OnRefreshOrAddNotificationGroup(ng *model.NotificationGroup, ngn []uint64) {
|
||||||
n.Tag = "default"
|
NotificationsLock.Lock()
|
||||||
if err := DB.Save(n).Error; err != nil {
|
defer NotificationsLock.Unlock()
|
||||||
log.Println("NEZHA>> SetDefaultNotificationTagInDB 错误: ", err)
|
|
||||||
|
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 刷新通知方式相关参数
|
// OnRefreshOrAddNotification 刷新通知方式相关参数
|
||||||
func OnRefreshOrAddNotification(n *model.Notification) {
|
func OnRefreshOrAddNotification(n *model.Notification) {
|
||||||
notificationsLock.Lock()
|
NotificationsLock.Lock()
|
||||||
defer notificationsLock.Unlock()
|
defer NotificationsLock.Unlock()
|
||||||
|
|
||||||
var isEdit bool
|
var isEdit bool
|
||||||
if _, ok := NotificationIDToTag[n.ID]; ok {
|
_, ok := NotificationMap[n.ID]
|
||||||
|
if ok {
|
||||||
isEdit = true
|
isEdit = true
|
||||||
}
|
}
|
||||||
if !isEdit {
|
if !isEdit {
|
||||||
@ -69,47 +166,47 @@ func OnRefreshOrAddNotification(n *model.Notification) {
|
|||||||
|
|
||||||
// AddNotificationToList 添加通知方式到map中
|
// AddNotificationToList 添加通知方式到map中
|
||||||
func AddNotificationToList(n *model.Notification) {
|
func AddNotificationToList(n *model.Notification) {
|
||||||
// 当前 Tag 不存在,创建对应该 Tag 的 子 map 后再添加
|
NotificationMap[n.ID] = n
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateNotificationInList 在 map 中更新通知方式
|
// UpdateNotificationInList 在 map 中更新通知方式
|
||||||
func UpdateNotificationInList(n *model.Notification) {
|
func UpdateNotificationInList(n *model.Notification) {
|
||||||
if n.Tag != NotificationIDToTag[n.ID] {
|
NotificationMap[n.ID] = n
|
||||||
// 如果 Tag 不一致,则需要先移除原有的映射关系
|
// 如果已经与通知组有绑定关系,更新
|
||||||
delete(NotificationList[NotificationIDToTag[n.ID]], n.ID)
|
if gids, ok := NotificationIDToGroups[n.ID]; ok {
|
||||||
delete(NotificationIDToTag, n.ID)
|
for gid := range gids {
|
||||||
// 将新的 Tag 中的通知方式添加到 map 中
|
NotificationList[gid][n.ID] = n
|
||||||
AddNotificationToList(n)
|
}
|
||||||
} else {
|
|
||||||
// 如果 Tag 一致,则直接更新
|
|
||||||
NotificationList[n.Tag][n.ID] = n
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnDeleteNotification 在map中删除通知方式
|
// OnDeleteNotification 在map和表中删除通知方式
|
||||||
func OnDeleteNotification(id uint64) {
|
func OnDeleteNotification(id []uint64) {
|
||||||
notificationsLock.Lock()
|
NotificationsLock.Lock()
|
||||||
defer notificationsLock.Unlock()
|
defer NotificationsLock.Unlock()
|
||||||
|
|
||||||
delete(NotificationList[NotificationIDToTag[id]], id)
|
for _, i := range id {
|
||||||
delete(NotificationIDToTag, 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) {
|
func UnMuteNotification(notificationGroupID uint64, muteLabel *string) {
|
||||||
fullMuteLabel := *NotificationMuteLabel.AppendNotificationTag(muteLabel, notificationTag)
|
fullMuteLabel := *NotificationMuteLabel.AppendNotificationGroupName(muteLabel, notificationGroupID)
|
||||||
Cache.Delete(fullMuteLabel)
|
Cache.Delete(fullMuteLabel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendNotification 向指定的通知方式组的所有通知方式发送通知
|
// 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 {
|
if muteLabel != nil {
|
||||||
// 将通知方式组名称加入静音标志
|
// 将通知方式组名称加入静音标志
|
||||||
muteLabel := *NotificationMuteLabel.AppendNotificationTag(muteLabel, notificationTag)
|
muteLabel := *NotificationMuteLabel.AppendNotificationGroupName(muteLabel, notificationGroupID)
|
||||||
// 通知防骚扰策略
|
// 通知防骚扰策略
|
||||||
var flag bool
|
var flag bool
|
||||||
if cacheN, has := Cache.Get(muteLabel); has {
|
if cacheN, has := Cache.Get(muteLabel); has {
|
||||||
@ -142,12 +239,12 @@ func SendNotification(notificationTag string, desc string, muteLabel *string, ex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 向该通知方式组的所有通知方式发出通知
|
// 向该通知方式组的所有通知方式发出通知
|
||||||
notificationsLock.RLock()
|
NotificationsLock.RLock()
|
||||||
defer notificationsLock.RUnlock()
|
defer NotificationsLock.RUnlock()
|
||||||
for _, n := range NotificationList[notificationTag] {
|
for _, n := range NotificationList[notificationGroupID] {
|
||||||
log.Println("NEZHA>> 尝试通知", n.Name)
|
log.Println("NEZHA>> 尝试通知", n.Name)
|
||||||
}
|
}
|
||||||
for _, n := range NotificationList[notificationTag] {
|
for _, n := range NotificationList[notificationGroupID] {
|
||||||
ns := model.NotificationServerBundle{
|
ns := model.NotificationServerBundle{
|
||||||
Notification: n,
|
Notification: n,
|
||||||
Server: nil,
|
Server: nil,
|
||||||
@ -183,8 +280,10 @@ func (_NotificationMuteLabel) ServerIncidentResolved(alertId uint64, serverId ui
|
|||||||
return &label
|
return &label
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_NotificationMuteLabel) AppendNotificationTag(label *string, notificationTag string) *string {
|
func (_NotificationMuteLabel) AppendNotificationGroupName(label *string, notificationGroupID uint64) *string {
|
||||||
newLabel := fmt.Sprintf("%s:%s", *label, notificationTag)
|
NotificationGroupLock.RLock()
|
||||||
|
defer NotificationGroupLock.RUnlock()
|
||||||
|
newLabel := fmt.Sprintf("%s:%s", *label, NotificationGroup[notificationGroupID])
|
||||||
return &newLabel
|
return &newLabel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,11 +186,6 @@ func (ss *ServiceSentinel) loadMonitorHistory() {
|
|||||||
defer ss.monitorsLock.Unlock()
|
defer ss.monitorsLock.Unlock()
|
||||||
|
|
||||||
for i := 0; i < len(monitors); i++ {
|
for i := 0; i < len(monitors); i++ {
|
||||||
// 旧版本可能不存在通知组 为其设置默认组
|
|
||||||
if monitors[i].NotificationTag == "" {
|
|
||||||
monitors[i].NotificationTag = "default"
|
|
||||||
DB.Save(monitors[i])
|
|
||||||
}
|
|
||||||
task := *monitors[i]
|
task := *monitors[i]
|
||||||
// 通过cron定时将服务监控任务传递给任务调度管道
|
// 通过cron定时将服务监控任务传递给任务调度管道
|
||||||
monitors[i].CronJobID, err = Cron.AddFunc(task.CronSpec(), func() {
|
monitors[i].CronJobID, err = Cron.AddFunc(task.CronSpec(), func() {
|
||||||
@ -432,7 +427,7 @@ func (ss *ServiceSentinel) worker() {
|
|||||||
if mh.Delay > 0 {
|
if mh.Delay > 0 {
|
||||||
ss.monitorsLock.RLock()
|
ss.monitorsLock.RLock()
|
||||||
if ss.monitors[mh.GetId()].LatencyNotify {
|
if ss.monitors[mh.GetId()].LatencyNotify {
|
||||||
notificationTag := ss.monitors[mh.GetId()].NotificationTag
|
notificationGroupID := ss.monitors[mh.GetId()].NotificationGroupID
|
||||||
minMuteLabel := NotificationMuteLabel.ServiceLatencyMin(mh.GetId())
|
minMuteLabel := NotificationMuteLabel.ServiceLatencyMin(mh.GetId())
|
||||||
maxMuteLabel := NotificationMuteLabel.ServiceLatencyMax(mh.GetId())
|
maxMuteLabel := NotificationMuteLabel.ServiceLatencyMax(mh.GetId())
|
||||||
if mh.Delay > ss.monitors[mh.GetId()].MaxLatency {
|
if mh.Delay > ss.monitors[mh.GetId()].MaxLatency {
|
||||||
@ -440,19 +435,19 @@ func (ss *ServiceSentinel) worker() {
|
|||||||
ServerLock.RLock()
|
ServerLock.RLock()
|
||||||
reporterServer := ServerList[r.Reporter]
|
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)
|
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()
|
ServerLock.RUnlock()
|
||||||
} else if mh.Delay < ss.monitors[mh.GetId()].MinLatency {
|
} else if mh.Delay < ss.monitors[mh.GetId()].MinLatency {
|
||||||
// 延迟低于最小值
|
// 延迟低于最小值
|
||||||
ServerLock.RLock()
|
ServerLock.RLock()
|
||||||
reporterServer := ServerList[r.Reporter]
|
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)
|
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()
|
ServerLock.RUnlock()
|
||||||
} else {
|
} else {
|
||||||
// 正常延迟, 清除静音缓存
|
// 正常延迟, 清除静音缓存
|
||||||
UnMuteNotification(notificationTag, minMuteLabel)
|
UnMuteNotification(notificationGroupID, minMuteLabel)
|
||||||
UnMuteNotification(notificationTag, maxMuteLabel)
|
UnMuteNotification(notificationGroupID, maxMuteLabel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ss.monitorsLock.RUnlock()
|
ss.monitorsLock.RUnlock()
|
||||||
@ -471,16 +466,16 @@ func (ss *ServiceSentinel) worker() {
|
|||||||
ServerLock.RLock()
|
ServerLock.RLock()
|
||||||
|
|
||||||
reporterServer := ServerList[r.Reporter]
|
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)
|
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())
|
muteLabel := NotificationMuteLabel.ServiceStateChanged(mh.GetId())
|
||||||
|
|
||||||
// 状态变更时,清除静音缓存
|
// 状态变更时,清除静音缓存
|
||||||
if stateCode != lastStatus {
|
if stateCode != lastStatus {
|
||||||
UnMuteNotification(notificationTag, muteLabel)
|
UnMuteNotification(notificationGroupID, muteLabel)
|
||||||
}
|
}
|
||||||
|
|
||||||
go SendNotification(notificationTag, notificationMsg, muteLabel)
|
go SendNotification(notificationGroupID, notificationMsg, muteLabel)
|
||||||
ServerLock.RUnlock()
|
ServerLock.RUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -515,14 +510,14 @@ func (ss *ServiceSentinel) worker() {
|
|||||||
ss.monitorsLock.RLock()
|
ss.monitorsLock.RLock()
|
||||||
if ss.monitors[mh.GetId()].Notify {
|
if ss.monitors[mh.GetId()].Notify {
|
||||||
muteLabel := NotificationMuteLabel.ServiceSSL(mh.GetId(), "network")
|
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()
|
ss.monitorsLock.RUnlock()
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} 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, "|")
|
var newCert = strings.Split(mh.Data, "|")
|
||||||
if len(newCert) > 1 {
|
if len(newCert) > 1 {
|
||||||
@ -545,7 +540,7 @@ func (ss *ServiceSentinel) worker() {
|
|||||||
ss.sslCertCache[mh.GetId()] = mh.Data
|
ss.sslCertCache[mh.GetId()] = mh.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
notificationTag := ss.monitors[mh.GetId()].NotificationTag
|
notificationGroupID := ss.monitors[mh.GetId()].NotificationGroupID
|
||||||
serviceName := ss.monitors[mh.GetId()].Name
|
serviceName := ss.monitors[mh.GetId()].Name
|
||||||
ss.monitorsLock.Unlock()
|
ss.monitorsLock.Unlock()
|
||||||
|
|
||||||
@ -562,7 +557,7 @@ func (ss *ServiceSentinel) worker() {
|
|||||||
// 静音规则: 服务id+证书过期时间
|
// 静音规则: 服务id+证书过期时间
|
||||||
// 用于避免多个监测点对相同证书同时报警
|
// 用于避免多个监测点对相同证书同时报警
|
||||||
muteLabel := NotificationMuteLabel.ServiceSSL(mh.GetId(), fmt.Sprintf("expire_%s", expiresTimeStr))
|
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"))
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ func InitDBFromPath(path string) {
|
|||||||
err = DB.AutoMigrate(model.Server{}, model.User{},
|
err = DB.AutoMigrate(model.Server{}, model.User{},
|
||||||
model.Notification{}, model.AlertRule{}, model.Monitor{},
|
model.Notification{}, model.AlertRule{}, model.Monitor{},
|
||||||
model.MonitorHistory{}, model.Cron{}, model.Transfer{},
|
model.MonitorHistory{}, model.Cron{}, model.Transfer{},
|
||||||
model.ApiToken{}, model.NAT{}, model.DDNSProfile{})
|
model.ApiToken{}, model.NAT{}, model.DDNSProfile{}, model.NotificationGroupNotification{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user