diff --git a/cmd/dashboard/controller/member_api.go b/cmd/dashboard/controller/member_api.go
index 126f0d1..cf5a03f 100644
--- a/cmd/dashboard/controller/member_api.go
+++ b/cmd/dashboard/controller/member_api.go
@@ -259,13 +259,14 @@ func (ma *memberAPI) addOrEditMonitor(c *gin.Context) {
}
type cronForm struct {
- ID uint64
- Name string
- Scheduler string
- Command string
- ServersRaw string
- Cover uint8
- PushSuccessful string
+ ID uint64
+ Name string
+ Scheduler string
+ Command string
+ ServersRaw string
+ Cover uint8
+ PushSuccessful string
+ NotificationTag string
}
func (ma *memberAPI) addOrEditCron(c *gin.Context) {
@@ -278,6 +279,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.ID = cf.ID
cr.Cover = cf.Cover
err = utils.Json.Unmarshal([]byte(cf.ServersRaw), &cr.Servers)
@@ -376,6 +378,7 @@ func (ma *memberAPI) forceUpdate(c *gin.Context) {
type notificationForm struct {
ID uint64
Name string
+ Tag string // 分组名
URL string
RequestMethod int
RequestType int
@@ -390,6 +393,7 @@ 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
@@ -401,6 +405,10 @@ func (ma *memberAPI) addOrEditNotification(c *gin.Context) {
err = n.Send("这是测试消息")
}
if err == nil {
+ // 保证Tag不为空
+ if n.Tag == "" {
+ n.Tag = "default"
+ }
if n.ID == 0 {
err = singleton.DB.Create(&n).Error
} else {
@@ -414,7 +422,7 @@ func (ma *memberAPI) addOrEditNotification(c *gin.Context) {
})
return
}
- singleton.OnRefreshOrAddNotification(n)
+ singleton.OnRefreshOrAddNotification(&n)
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
})
diff --git a/model/config.go b/model/config.go
index e7cf6b7..35cdd3d 100644
--- a/model/config.go
+++ b/model/config.go
@@ -72,6 +72,7 @@ type Config struct {
TLS bool
EnableIPChangeNotification bool
+ IPChangeNotificationTag string
EnablePlainIPInNotification bool
// IP变更提醒
@@ -102,6 +103,9 @@ func (c *Config) Read(path string) error {
if c.GRPCPort == 0 {
c.GRPCPort = 5555
}
+ if c.EnableIPChangeNotification && c.IPChangeNotificationTag == "" {
+ c.IPChangeNotificationTag = "default"
+ }
c.updateIgnoredIPNotificationID()
return nil
diff --git a/model/cron.go b/model/cron.go
index fbdf75a..a8e756c 100644
--- a/model/cron.go
+++ b/model/cron.go
@@ -15,14 +15,15 @@ const (
type Cron struct {
Common
- Name string
- Scheduler string //分钟 小时 天 月 星期
- Command string
- Servers []uint64 `gorm:"-"`
- PushSuccessful bool // 推送成功的通知
- LastExecutedAt time.Time // 最后一次执行时间
- LastResult bool // 最后一次执行结果
- Cover uint8 // 计划任务覆盖范围 (0:仅覆盖特定服务器 1:仅忽略特定服务器)
+ Name string
+ Scheduler string //分钟 小时 天 月 星期
+ Command string
+ Servers []uint64 `gorm:"-"`
+ PushSuccessful bool // 推送成功的通知
+ NotificationTag string // 指定通知方式的分组
+ LastExecutedAt time.Time // 最后一次执行时间
+ LastResult bool // 最后一次执行结果
+ Cover uint8 // 计划任务覆盖范围 (0:仅覆盖特定服务器 1:仅忽略特定服务器)
CronJobID cron.EntryID `gorm:"-"`
ServersRaw string
diff --git a/model/notification.go b/model/notification.go
index 3ea2a9e..fcbfb56 100644
--- a/model/notification.go
+++ b/model/notification.go
@@ -28,6 +28,7 @@ const (
type Notification struct {
Common
Name string
+ Tag string // 分组名
URL string
RequestMethod int
RequestType int
diff --git a/resource/template/component/cron.html b/resource/template/component/cron.html
index 3ee3da8..2d3ba9d 100644
--- a/resource/template/component/cron.html
+++ b/resource/template/component/cron.html
@@ -38,6 +38,10 @@
+
+
+
+
+
+
+
+
diff --git a/resource/template/dashboard/cron.html b/resource/template/dashboard/cron.html
index 92b862d..78ab201 100644
--- a/resource/template/dashboard/cron.html
+++ b/resource/template/dashboard/cron.html
@@ -18,6 +18,7 @@
计划 |
命令 |
成功推送 |
+ 通知方式组 |
覆盖范围 |
特定服务器 |
最后执行 |
@@ -33,6 +34,7 @@
{{$cron.Scheduler}} |
{{$cron.Command}} |
{{$cron.PushSuccessful}} |
+ {$cron.NotificationTag} |
{{if eq $cron.Cover 0}}忽略所有{{else}}覆盖所有{{end}} |
{{$cron.ServersRaw}} |
{{$cron.LastExecutedAt|tf}} |
diff --git a/resource/template/dashboard/notification.html b/resource/template/dashboard/notification.html
index 8ebcc84..c48f94d 100644
--- a/resource/template/dashboard/notification.html
+++ b/resource/template/dashboard/notification.html
@@ -15,6 +15,7 @@
ID |
名称 |
+ 分组 |
URL |
验证SSL |
管理 |
@@ -25,6 +26,7 @@
{{$notification.ID}} |
{{$notification.Name}} |
+ {{$notification.Tag}} |
{{$notification.URL}} |
{{$notification.VerifySSL}} |
diff --git a/service/rpc/nezha.go b/service/rpc/nezha.go
index e48a854..2bca2c5 100644
--- a/service/rpc/nezha.go
+++ b/service/rpc/nezha.go
@@ -29,10 +29,10 @@ func (s *NezhaHandler) ReportTask(c context.Context, r *pb.TaskResult) (*pb.Rece
singleton.ServerLock.RLock()
defer singleton.ServerLock.RUnlock()
if cr.PushSuccessful && r.GetSuccessful() {
- singleton.SendNotification(fmt.Sprintf("[任务成功] %s ,服务器:%s,日志:\n%s", cr.Name, singleton.ServerList[clientID].Name, r.GetData()), false)
+ singleton.SendNotification(cr.NotificationTag, fmt.Sprintf("[任务成功] %s ,服务器:%s,日志:\n%s", cr.Name, singleton.ServerList[clientID].Name, r.GetData()), false)
}
if !r.GetSuccessful() {
- singleton.SendNotification(fmt.Sprintf("[任务失败] %s ,服务器:%s,日志:\n%s", cr.Name, singleton.ServerList[clientID].Name, r.GetData()), false)
+ singleton.SendNotification(cr.NotificationTag, fmt.Sprintf("[任务失败] %s ,服务器:%s,日志:\n%s", cr.Name, singleton.ServerList[clientID].Name, r.GetData()), false)
}
singleton.DB.Model(cr).Updates(model.Cron{
LastExecutedAt: time.Now().Add(time.Second * -1 * time.Duration(r.GetDelay())),
@@ -103,7 +103,7 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece
singleton.ServerList[clientID].Host.IP != "" &&
host.IP != "" &&
singleton.ServerList[clientID].Host.IP != host.IP {
- singleton.SendNotification(fmt.Sprintf(
+ singleton.SendNotification(singleton.Conf.IPChangeNotificationTag, fmt.Sprintf(
"[IP变更] %s ,旧IP:%s,新IP:%s。",
singleton.ServerList[clientID].Name, singleton.IPDesensitize(singleton.ServerList[clientID].Host.IP), singleton.IPDesensitize(host.IP)), true)
}
diff --git a/service/singleton/crontask.go b/service/singleton/crontask.go
index b4aafe1..eccc940 100644
--- a/service/singleton/crontask.go
+++ b/service/singleton/crontask.go
@@ -11,7 +11,7 @@ import (
var (
Cron *cron.Cron
- Crons map[uint64]*model.Cron
+ Crons map[uint64]*model.Cron // [CrondID] -> *model.Cron
CronLock sync.RWMutex
)
@@ -27,9 +27,12 @@ func LoadCronTasks() {
DB.Find(&crons)
var err error
errMsg := new(bytes.Buffer)
- for i := 0; i < len(crons); i++ {
- cr := crons[i]
-
+ var notificationTagList []string
+ for _, cr := range crons {
+ // 旧版本计划任务可能不存在通知组 为其添加默认通知组
+ if cr.NotificationTag == "" {
+ AddDefaultCronNotificationTag(&cr)
+ }
// 注册计划任务
cr.CronJobID, err = Cron.AddFunc(cr.Scheduler, CronTrigger(cr))
if err == nil {
@@ -39,15 +42,30 @@ func LoadCronTasks() {
errMsg.WriteString("调度失败的计划任务:[")
}
errMsg.WriteString(fmt.Sprintf("%d,", cr.ID))
+ notificationTagList = append(notificationTagList, cr.NotificationTag)
}
}
if errMsg.Len() > 0 {
- msg := errMsg.String()
- SendNotification(msg[:len(msg)-1]+"] 这些任务将无法正常执行,请进入后点重新修改保存。", false)
+ msg := errMsg.String() + "] 这些任务将无法正常执行,请进入后点重新修改保存。"
+ for _, tag := range notificationTagList {
+ // 向调度错误的计划任务所包含的所有通知组发送通知
+ SendNotification(tag, msg, false)
+ }
}
Cron.Start()
}
+// AddDefaultCronNotificationTag 添加默认的计划任务通知组
+func AddDefaultCronNotificationTag(c *model.Cron) {
+ CronLock.Lock()
+ defer CronLock.Unlock()
+
+ if c.NotificationTag == "" {
+ c.NotificationTag = "default"
+ }
+ DB.Save(c)
+}
+
func ManualTrigger(c model.Cron) {
CronTrigger(c)()
}
@@ -74,7 +92,7 @@ func CronTrigger(cr model.Cron) func() {
Type: model.TaskTypeCommand,
})
} else {
- SendNotification(fmt.Sprintf("[任务失败] %s,服务器 %s 离线,无法执行。", cr.Name, s.Name), false)
+ SendNotification(cr.NotificationTag, fmt.Sprintf("[任务失败] %s,服务器 %s 离线,无法执行。", cr.Name, s.Name), false)
}
}
}
diff --git a/service/singleton/notification.go b/service/singleton/notification.go
index 476403f..caf3e6d 100644
--- a/service/singleton/notification.go
+++ b/service/singleton/notification.go
@@ -13,46 +13,95 @@ import (
const firstNotificationDelay = time.Minute * 15
// 通知方式
-var notifications []model.Notification
-var notificationsLock sync.RWMutex
+var (
+ NotificationList map[string]map[uint64]*model.Notification // [NotificationMethodTag][NotificationID] -> model.Notification
+ NotificationIDToTag map[uint64]string // [NotificationID] -> NotificationTag
+ notificationsLock sync.RWMutex
+)
-// LoadNotifications 从 DB 加载通知方式到 singleton.notifications 变量
+// InitNotification 初始化 Tag <-> ID <-> Notification 的映射
+func InitNotification() {
+ NotificationList = make(map[string]map[uint64]*model.Notification)
+ NotificationIDToTag = make(map[uint64]string)
+}
+
+// LoadNotifications 从 DB 初始化通知方式相关参数
func LoadNotifications() {
+ InitNotification()
notificationsLock.Lock()
+ defer notificationsLock.Unlock()
+
+ var notifications []model.Notification
if err := DB.Find(¬ifications).Error; err != nil {
panic(err)
}
- notificationsLock.Unlock()
+ for _, n := range notifications {
+ // 旧版本的Tag可能不存在 自动设置为默认值
+ if n.Tag == "" {
+ SetDefaultNotificationTagInDB(&n)
+ }
+ AddNotificationToList(&n)
+ }
}
-func OnRefreshOrAddNotification(n model.Notification) {
+// SetDefaultNotificationTagInDB 设置默认通知方式的 Tag
+func SetDefaultNotificationTagInDB(n *model.Notification) {
+ n.Tag = "default"
+ if err := DB.Save(n).Error; err != nil {
+ log.Println("[ERROR]", err)
+ }
+}
+
+// OnRefreshOrAddNotification 刷新通知方式相关参数
+func OnRefreshOrAddNotification(n *model.Notification) {
notificationsLock.Lock()
defer notificationsLock.Unlock()
+
var isEdit bool
- for i := 0; i < len(notifications); i++ {
- if notifications[i].ID == n.ID {
- notifications[i] = n
- isEdit = true
- }
+ if _, ok := NotificationList[n.Tag][n.ID]; ok {
+ isEdit = true
}
if !isEdit {
- notifications = append(notifications, n)
+ AddNotificationToList(n)
+ } else {
+ UpdateNotificationInList(n)
}
}
+// AddNotificationToList 添加通知方式到map中
+func AddNotificationToList(n *model.Notification) {
+ notificationsLock.Lock()
+ defer notificationsLock.Unlock()
+
+ // 当前 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
+
+}
+
+// UpdateNotificationInList 在 map 中更新通知方式
+func UpdateNotificationInList(n *model.Notification) {
+ notificationsLock.Lock()
+ defer notificationsLock.Unlock()
+
+ NotificationList[n.Tag][n.ID] = n
+}
+
+// OnDeleteNotification 在map中删除通知方式
func OnDeleteNotification(id uint64) {
notificationsLock.Lock()
defer notificationsLock.Unlock()
- for i := 0; i < len(notifications); i++ {
- if notifications[i].ID == id {
- notifications = append(notifications[:i], notifications[i+1:]...)
- i--
- }
- }
+
+ delete(NotificationList[NotificationIDToTag[id]], id)
+ delete(NotificationIDToTag, id)
}
-func SendNotification(desc string, muteable bool) {
- if muteable {
+// SendNotification 向指定的通知方式组的所有通知方式发送通知
+func SendNotification(notificationTag string, desc string, mutable bool) {
+ if mutable {
// 通知防骚扰策略
nID := hex.EncodeToString(md5.New().Sum([]byte(desc))) // #nosec
var flag bool
@@ -80,16 +129,17 @@ func SendNotification(desc string, muteable bool) {
if !flag {
if Conf.Debug {
- log.Println("NEZHA>> 静音的重复通知:", desc, muteable)
+ log.Println("NEZHA>> 静音的重复通知:", desc, mutable)
}
return
}
}
- // 发出通知
+ // 向该通知方式组的所有通知方式发出通知
notificationsLock.RLock()
defer notificationsLock.RUnlock()
- for i := 0; i < len(notifications); i++ {
- if err := notifications[i].Send(desc); err != nil {
+
+ for _, n := range NotificationList[notificationTag] {
+ if err := n.Send(desc); err != nil {
log.Println("NEZHA>> 发送通知失败:", err)
}
}
|