✨ [dashboard v0.3.9] 手动即时触发计划任务
This commit is contained in:
		
							parent
							
								
									175eb996d3
								
							
						
					
					
						commit
						a957b21265
					
				@ -1,6 +1,6 @@
 | 
				
			|||||||
# 哪吒面板
 | 
					# 哪吒面板
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
					 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
系统状态监控报警、API(SSL证书变更、即将到期、到期)/TCP端口存活/PING 监控、计划任务(可以定时在Agent上执行命令,备份、重启、What ever you want)、极省资源,64M 服务器也能装 agent。
 | 
					系统状态监控报警、API(SSL证书变更、即将到期、到期)/TCP端口存活/PING 监控、计划任务(可以定时在Agent上执行命令,备份、重启、What ever you want)、极省资源,64M 服务器也能装 agent。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,6 @@ import (
 | 
				
			|||||||
	"github.com/naiba/nezha/pkg/mygin"
 | 
						"github.com/naiba/nezha/pkg/mygin"
 | 
				
			||||||
	"github.com/naiba/nezha/pkg/utils"
 | 
						"github.com/naiba/nezha/pkg/utils"
 | 
				
			||||||
	pb "github.com/naiba/nezha/proto"
 | 
						pb "github.com/naiba/nezha/proto"
 | 
				
			||||||
	"github.com/naiba/nezha/service/alertmanager"
 | 
					 | 
				
			||||||
	"github.com/naiba/nezha/service/dao"
 | 
						"github.com/naiba/nezha/service/dao"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -37,6 +36,7 @@ func (ma *memberAPI) serve() {
 | 
				
			|||||||
	mr.POST("/server", ma.addOrEditServer)
 | 
						mr.POST("/server", ma.addOrEditServer)
 | 
				
			||||||
	mr.POST("/monitor", ma.addOrEditMonitor)
 | 
						mr.POST("/monitor", ma.addOrEditMonitor)
 | 
				
			||||||
	mr.POST("/cron", ma.addOrEditCron)
 | 
						mr.POST("/cron", ma.addOrEditCron)
 | 
				
			||||||
 | 
						mr.GET("/cron/:id/manual", ma.manualTrigger)
 | 
				
			||||||
	mr.POST("/notification", ma.addOrEditNotification)
 | 
						mr.POST("/notification", ma.addOrEditNotification)
 | 
				
			||||||
	mr.POST("/alert-rule", ma.addOrEditAlertRule)
 | 
						mr.POST("/alert-rule", ma.addOrEditAlertRule)
 | 
				
			||||||
	mr.POST("/setting", ma.updateSetting)
 | 
						mr.POST("/setting", ma.updateSetting)
 | 
				
			||||||
@ -67,7 +67,7 @@ func (ma *memberAPI) delete(c *gin.Context) {
 | 
				
			|||||||
	case "notification":
 | 
						case "notification":
 | 
				
			||||||
		err = dao.DB.Delete(&model.Notification{}, "id = ?", id).Error
 | 
							err = dao.DB.Delete(&model.Notification{}, "id = ?", id).Error
 | 
				
			||||||
		if err == nil {
 | 
							if err == nil {
 | 
				
			||||||
			alertmanager.OnDeleteNotification(id)
 | 
								dao.OnDeleteNotification(id)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	case "monitor":
 | 
						case "monitor":
 | 
				
			||||||
		err = dao.DB.Delete(&model.Monitor{}, "id = ?", id).Error
 | 
							err = dao.DB.Delete(&model.Monitor{}, "id = ?", id).Error
 | 
				
			||||||
@ -88,7 +88,7 @@ func (ma *memberAPI) delete(c *gin.Context) {
 | 
				
			|||||||
	case "alert-rule":
 | 
						case "alert-rule":
 | 
				
			||||||
		err = dao.DB.Delete(&model.AlertRule{}, "id = ?", id).Error
 | 
							err = dao.DB.Delete(&model.AlertRule{}, "id = ?", id).Error
 | 
				
			||||||
		if err == nil {
 | 
							if err == nil {
 | 
				
			||||||
			alertmanager.OnDeleteAlert(id)
 | 
								dao.OnDeleteAlert(id)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@ -281,7 +281,7 @@ func (ma *memberAPI) addOrEditCron(c *gin.Context) {
 | 
				
			|||||||
					Type: model.TaskTypeCommand,
 | 
										Type: model.TaskTypeCommand,
 | 
				
			||||||
				})
 | 
									})
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				alertmanager.SendNotification(fmt.Sprintf("计划任务:%s,服务器:%d 离线,无法执行。", cr.Name, cr.Servers[j]), false)
 | 
									dao.SendNotification(fmt.Sprintf("计划任务:%s,服务器:%d 离线,无法执行。", cr.Name, cr.Servers[j]), false)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
@ -294,6 +294,23 @@ func (ma *memberAPI) addOrEditCron(c *gin.Context) {
 | 
				
			|||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ma *memberAPI) manualTrigger(c *gin.Context) {
 | 
				
			||||||
 | 
						var cr model.Cron
 | 
				
			||||||
 | 
						if err := dao.DB.First(&cr, "id = ?", c.Param("id")).Error; err != nil {
 | 
				
			||||||
 | 
							c.JSON(http.StatusOK, model.Response{
 | 
				
			||||||
 | 
								Code:    http.StatusBadRequest,
 | 
				
			||||||
 | 
								Message: err.Error(),
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dao.CronTrigger(&cr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.JSON(http.StatusOK, model.Response{
 | 
				
			||||||
 | 
							Code: http.StatusOK,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type notificationForm struct {
 | 
					type notificationForm struct {
 | 
				
			||||||
	ID            uint64
 | 
						ID            uint64
 | 
				
			||||||
	Name          string
 | 
						Name          string
 | 
				
			||||||
@ -333,7 +350,7 @@ func (ma *memberAPI) addOrEditNotification(c *gin.Context) {
 | 
				
			|||||||
		})
 | 
							})
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	alertmanager.OnRefreshOrAddNotification(n)
 | 
						dao.OnRefreshOrAddNotification(n)
 | 
				
			||||||
	c.JSON(http.StatusOK, model.Response{
 | 
						c.JSON(http.StatusOK, model.Response{
 | 
				
			||||||
		Code: http.StatusOK,
 | 
							Code: http.StatusOK,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
@ -384,7 +401,7 @@ func (ma *memberAPI) addOrEditAlertRule(c *gin.Context) {
 | 
				
			|||||||
		})
 | 
							})
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	alertmanager.OnRefreshOrAddAlert(r)
 | 
						dao.OnRefreshOrAddAlert(r)
 | 
				
			||||||
	c.JSON(http.StatusOK, model.Response{
 | 
						c.JSON(http.StatusOK, model.Response{
 | 
				
			||||||
		Code: http.StatusOK,
 | 
							Code: http.StatusOK,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,6 @@ import (
 | 
				
			|||||||
	"github.com/naiba/nezha/cmd/dashboard/rpc"
 | 
						"github.com/naiba/nezha/cmd/dashboard/rpc"
 | 
				
			||||||
	"github.com/naiba/nezha/model"
 | 
						"github.com/naiba/nezha/model"
 | 
				
			||||||
	pb "github.com/naiba/nezha/proto"
 | 
						pb "github.com/naiba/nezha/proto"
 | 
				
			||||||
	"github.com/naiba/nezha/service/alertmanager"
 | 
					 | 
				
			||||||
	"github.com/naiba/nezha/service/dao"
 | 
						"github.com/naiba/nezha/service/dao"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -90,7 +89,7 @@ func loadCrons() {
 | 
				
			|||||||
						Type: model.TaskTypeCommand,
 | 
											Type: model.TaskTypeCommand,
 | 
				
			||||||
					})
 | 
										})
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
					alertmanager.SendNotification(fmt.Sprintf("计划任务:%s,服务器:%d 离线,无法执行。", cr.Name, cr.Servers[j]), false)
 | 
										dao.SendNotification(fmt.Sprintf("计划任务:%s,服务器:%d 离线,无法执行。", cr.Name, cr.Servers[j]), false)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
@ -106,5 +105,5 @@ func main() {
 | 
				
			|||||||
	go controller.ServeWeb(dao.Conf.HTTPPort)
 | 
						go controller.ServeWeb(dao.Conf.HTTPPort)
 | 
				
			||||||
	go rpc.ServeRPC(5555)
 | 
						go rpc.ServeRPC(5555)
 | 
				
			||||||
	go rpc.DispatchTask(time.Minute * 3)
 | 
						go rpc.DispatchTask(time.Minute * 3)
 | 
				
			||||||
	alertmanager.Start()
 | 
						dao.AlertSentinelStart()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -149,7 +149,7 @@ function addOrEditCron(cron) {
 | 
				
			|||||||
    modal.find('input[name=ID]').val(cron ? cron.ID : null)
 | 
					    modal.find('input[name=ID]').val(cron ? cron.ID : null)
 | 
				
			||||||
    modal.find('input[name=Name]').val(cron ? cron.Name : null)
 | 
					    modal.find('input[name=Name]').val(cron ? cron.Name : null)
 | 
				
			||||||
    modal.find('input[name=Scheduler]').val(cron ? cron.Scheduler : null)
 | 
					    modal.find('input[name=Scheduler]').val(cron ? cron.Scheduler : null)
 | 
				
			||||||
    modal.find('a.ui.label.visible').each((i,el) => {
 | 
					    modal.find('a.ui.label.visible').each((i, el) => {
 | 
				
			||||||
        el.remove()
 | 
					        el.remove()
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    var servers
 | 
					    var servers
 | 
				
			||||||
@ -192,6 +192,42 @@ function deleteRequest(api) {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function manualTrigger(btn, cronId) {
 | 
				
			||||||
 | 
					    $(btn).toggleClass('loading')
 | 
				
			||||||
 | 
					    $.ajax({
 | 
				
			||||||
 | 
					        url: '/api/cron/' + cronId + '/manual',
 | 
				
			||||||
 | 
					        type: 'GET',
 | 
				
			||||||
 | 
					    }).done(resp => {
 | 
				
			||||||
 | 
					        $(btn).toggleClass('loading')
 | 
				
			||||||
 | 
					        if (resp.code == 200) {
 | 
				
			||||||
 | 
					            $.suiAlert({
 | 
				
			||||||
 | 
					                title: '触发成功,等待执行结果',
 | 
				
			||||||
 | 
					                type: 'success',
 | 
				
			||||||
 | 
					                description: resp.message,
 | 
				
			||||||
 | 
					                time: '3',
 | 
				
			||||||
 | 
					                position: 'top-center',
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            $.suiAlert({
 | 
				
			||||||
 | 
					                title: '触发失败 ',
 | 
				
			||||||
 | 
					                type: 'error',
 | 
				
			||||||
 | 
					                description: resp.code + ':' + resp.message,
 | 
				
			||||||
 | 
					                time: '3',
 | 
				
			||||||
 | 
					                position: 'top-center',
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }).fail(err => {
 | 
				
			||||||
 | 
					        $(btn).toggleClass('loading')
 | 
				
			||||||
 | 
					        $.suiAlert({
 | 
				
			||||||
 | 
					            title: '触发失败 ',
 | 
				
			||||||
 | 
					            type: 'error',
 | 
				
			||||||
 | 
					            description: '网络错误:' + err.responseText,
 | 
				
			||||||
 | 
					            time: '3',
 | 
				
			||||||
 | 
					            position: 'top-center',
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function logout(id) {
 | 
					function logout(id) {
 | 
				
			||||||
    $.post('/api/logout', JSON.stringify({ id: id })).done(function (resp) {
 | 
					    $.post('/api/logout', JSON.stringify({ id: id })).done(function (resp) {
 | 
				
			||||||
        if (resp.code == 200) {
 | 
					        if (resp.code == 200) {
 | 
				
			||||||
 | 
				
			|||||||
@ -8,7 +8,7 @@
 | 
				
			|||||||
<script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.1/dist/semantic.min.js"></script>
 | 
					<script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.1/dist/semantic.min.js"></script>
 | 
				
			||||||
<script src="/static/semantic-ui-alerts.min.js"></script>
 | 
					<script src="/static/semantic-ui-alerts.min.js"></script>
 | 
				
			||||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.min.js"></script>
 | 
					<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.min.js"></script>
 | 
				
			||||||
<script src="/static/main.js?v202101220904"></script>
 | 
					<script src="/static/main.js?v202101240939"></script>
 | 
				
			||||||
</body>
 | 
					</body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
 | 
				
			|||||||
@ -37,6 +37,9 @@
 | 
				
			|||||||
                    <td>{{$cron.LastResult}}</td>
 | 
					                    <td>{{$cron.LastResult}}</td>
 | 
				
			||||||
                    <td>
 | 
					                    <td>
 | 
				
			||||||
                        <div class="ui mini icon buttons">
 | 
					                        <div class="ui mini icon buttons">
 | 
				
			||||||
 | 
					                            <button class="ui button" onclick="manualTrigger(this, {{$cron.ID}})">
 | 
				
			||||||
 | 
					                                <i class="play icon"></i>
 | 
				
			||||||
 | 
					                            </button>
 | 
				
			||||||
                            <button class="ui button" onclick="addOrEditCron({{$cron}})">
 | 
					                            <button class="ui button" onclick="addOrEditCron({{$cron}})">
 | 
				
			||||||
                                <i class="edit icon"></i>
 | 
					                                <i class="edit icon"></i>
 | 
				
			||||||
                            </button>
 | 
					                            </button>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,23 +1,14 @@
 | 
				
			|||||||
package alertmanager
 | 
					package dao
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"crypto/md5"
 | 
					 | 
				
			||||||
	"encoding/hex"
 | 
					 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/naiba/nezha/model"
 | 
						"github.com/naiba/nezha/model"
 | 
				
			||||||
	"github.com/naiba/nezha/service/dao"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const firstNotificationDelay = time.Minute * 15
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 通知方式
 | 
					 | 
				
			||||||
var notifications []model.Notification
 | 
					 | 
				
			||||||
var notificationsLock sync.RWMutex
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 报警规则
 | 
					// 报警规则
 | 
				
			||||||
var alertsLock sync.RWMutex
 | 
					var alertsLock sync.RWMutex
 | 
				
			||||||
var alerts []model.AlertRule
 | 
					var alerts []model.AlertRule
 | 
				
			||||||
@ -28,15 +19,15 @@ type NotificationHistory struct {
 | 
				
			|||||||
	Until    time.Time
 | 
						Until    time.Time
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func Start() {
 | 
					func AlertSentinelStart() {
 | 
				
			||||||
	alertsStore = make(map[uint64]map[uint64][][]interface{})
 | 
						alertsStore = make(map[uint64]map[uint64][][]interface{})
 | 
				
			||||||
	notificationsLock.Lock()
 | 
						notificationsLock.Lock()
 | 
				
			||||||
	if err := dao.DB.Find(¬ifications).Error; err != nil {
 | 
						if err := DB.Find(¬ifications).Error; err != nil {
 | 
				
			||||||
		panic(err)
 | 
							panic(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	notificationsLock.Unlock()
 | 
						notificationsLock.Unlock()
 | 
				
			||||||
	alertsLock.Lock()
 | 
						alertsLock.Lock()
 | 
				
			||||||
	if err := dao.DB.Find(&alerts).Error; err != nil {
 | 
						if err := DB.Find(&alerts).Error; err != nil {
 | 
				
			||||||
		panic(err)
 | 
							panic(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for i := 0; i < len(alerts); i++ {
 | 
						for i := 0; i < len(alerts); i++ {
 | 
				
			||||||
@ -56,7 +47,7 @@ func Start() {
 | 
				
			|||||||
			checkCount = 0
 | 
								checkCount = 0
 | 
				
			||||||
			lastPrint = startedAt
 | 
								lastPrint = startedAt
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		time.Sleep(time.Until(startedAt.Add(time.Second * dao.SnapshotDelay)))
 | 
							time.Sleep(time.Until(startedAt.Add(time.Second * SnapshotDelay)))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -89,44 +80,18 @@ func OnDeleteAlert(id uint64) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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 !isEdit {
 | 
					 | 
				
			||||||
		notifications = append(notifications, n)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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--
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func checkStatus() {
 | 
					func checkStatus() {
 | 
				
			||||||
	alertsLock.RLock()
 | 
						alertsLock.RLock()
 | 
				
			||||||
	defer alertsLock.RUnlock()
 | 
						defer alertsLock.RUnlock()
 | 
				
			||||||
	dao.ServerLock.RLock()
 | 
						ServerLock.RLock()
 | 
				
			||||||
	defer dao.ServerLock.RUnlock()
 | 
						defer ServerLock.RUnlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, alert := range alerts {
 | 
						for _, alert := range alerts {
 | 
				
			||||||
		// 跳过未启用
 | 
							// 跳过未启用
 | 
				
			||||||
		if alert.Enable == nil || !*alert.Enable {
 | 
							if alert.Enable == nil || !*alert.Enable {
 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		for _, server := range dao.ServerList {
 | 
							for _, server := range ServerList {
 | 
				
			||||||
			// 监测点
 | 
								// 监测点
 | 
				
			||||||
			alertsStore[alert.ID][server.ID] = append(alertsStore[alert.
 | 
								alertsStore[alert.ID][server.ID] = append(alertsStore[alert.
 | 
				
			||||||
				ID][server.ID], alert.Snapshot(server))
 | 
									ID][server.ID], alert.Snapshot(server))
 | 
				
			||||||
@ -143,42 +108,3 @@ func checkStatus() {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
func SendNotification(desc string, muteable bool) {
 | 
					 | 
				
			||||||
	if muteable {
 | 
					 | 
				
			||||||
		// 通知防骚扰策略
 | 
					 | 
				
			||||||
		nID := hex.EncodeToString(md5.New().Sum([]byte(desc)))
 | 
					 | 
				
			||||||
		var flag bool
 | 
					 | 
				
			||||||
		if cacheN, has := dao.Cache.Get(nID); has {
 | 
					 | 
				
			||||||
			nHistory := cacheN.(NotificationHistory)
 | 
					 | 
				
			||||||
			// 每次提醒都增加一倍等待时间,最后每天最多提醒一次
 | 
					 | 
				
			||||||
			if time.Now().After(nHistory.Until) {
 | 
					 | 
				
			||||||
				flag = true
 | 
					 | 
				
			||||||
				nHistory.Duration *= 2
 | 
					 | 
				
			||||||
				if nHistory.Duration > time.Hour*24 {
 | 
					 | 
				
			||||||
					nHistory.Duration = time.Hour * 24
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				nHistory.Until = time.Now().Add(nHistory.Duration)
 | 
					 | 
				
			||||||
				// 缓存有效期加 10 分钟
 | 
					 | 
				
			||||||
				dao.Cache.Set(nID, nHistory, nHistory.Duration+time.Minute*10)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			// 新提醒直接通知
 | 
					 | 
				
			||||||
			flag = true
 | 
					 | 
				
			||||||
			dao.Cache.Set(nID, NotificationHistory{
 | 
					 | 
				
			||||||
				Duration: firstNotificationDelay,
 | 
					 | 
				
			||||||
				Until:    time.Now().Add(firstNotificationDelay),
 | 
					 | 
				
			||||||
			}, firstNotificationDelay+time.Minute*10)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if !flag {
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	// 发出通知
 | 
					 | 
				
			||||||
	notificationsLock.RLock()
 | 
					 | 
				
			||||||
	defer notificationsLock.RUnlock()
 | 
					 | 
				
			||||||
	for i := 0; i < len(notifications); i++ {
 | 
					 | 
				
			||||||
		notifications[i].Send(desc)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
package dao
 | 
					package dao
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -9,32 +10,27 @@ import (
 | 
				
			|||||||
	"gorm.io/gorm"
 | 
						"gorm.io/gorm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/naiba/nezha/model"
 | 
						"github.com/naiba/nezha/model"
 | 
				
			||||||
 | 
						pb "github.com/naiba/nezha/proto"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var Version = "v0.3.9"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	SnapshotDelay = 3
 | 
						SnapshotDelay = 3
 | 
				
			||||||
	ReportDelay   = 2
 | 
						ReportDelay   = 2
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var Conf *model.Config
 | 
					var (
 | 
				
			||||||
 | 
						Conf  *model.Config
 | 
				
			||||||
 | 
						Cache *cache.Cache
 | 
				
			||||||
 | 
						DB    *gorm.DB
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var Cache *cache.Cache
 | 
						ServerList map[uint64]*model.Server
 | 
				
			||||||
 | 
						ServerLock sync.RWMutex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var DB *gorm.DB
 | 
						SortedServerList []*model.Server
 | 
				
			||||||
 | 
						SortedServerLock sync.RWMutex
 | 
				
			||||||
// 服务器监控、状态相关
 | 
					)
 | 
				
			||||||
var ServerList map[uint64]*model.Server
 | 
					 | 
				
			||||||
var ServerLock sync.RWMutex
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var SortedServerList []*model.Server
 | 
					 | 
				
			||||||
var SortedServerLock sync.RWMutex
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 计划任务相关
 | 
					 | 
				
			||||||
var CronLock sync.RWMutex
 | 
					 | 
				
			||||||
var Crons map[uint64]*model.Cron
 | 
					 | 
				
			||||||
var Cron *cron.Cron
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var Version = "v0.3.8"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ReSortServer() {
 | 
					func ReSortServer() {
 | 
				
			||||||
	ServerLock.RLock()
 | 
						ServerLock.RLock()
 | 
				
			||||||
@ -54,3 +50,25 @@ func ReSortServer() {
 | 
				
			|||||||
		return SortedServerList[i].DisplayIndex > SortedServerList[j].DisplayIndex
 | 
							return SortedServerList[i].DisplayIndex > SortedServerList[j].DisplayIndex
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// =============== Cron Mixin ===============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var CronLock sync.RWMutex
 | 
				
			||||||
 | 
					var Crons map[uint64]*model.Cron
 | 
				
			||||||
 | 
					var Cron *cron.Cron
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func CronTrigger(c *model.Cron) {
 | 
				
			||||||
 | 
						ServerLock.RLock()
 | 
				
			||||||
 | 
						defer ServerLock.RUnlock()
 | 
				
			||||||
 | 
						for j := 0; j < len(c.Servers); j++ {
 | 
				
			||||||
 | 
							if ServerList[c.Servers[j]].TaskStream != nil {
 | 
				
			||||||
 | 
								ServerList[c.Servers[j]].TaskStream.Send(&pb.Task{
 | 
				
			||||||
 | 
									Id:   c.ID,
 | 
				
			||||||
 | 
									Data: c.Command,
 | 
				
			||||||
 | 
									Type: model.TaskTypeCommand,
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								SendNotification(fmt.Sprintf("计划任务:%s,服务器:%d 离线,无法执行。", c.Name, c.Servers[j]), false)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										81
									
								
								service/dao/notification.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								service/dao/notification.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					package dao
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/md5"
 | 
				
			||||||
 | 
						"encoding/hex"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/naiba/nezha/model"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const firstNotificationDelay = time.Minute * 15
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 通知方式
 | 
				
			||||||
 | 
					var notifications []model.Notification
 | 
				
			||||||
 | 
					var notificationsLock sync.RWMutex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 !isEdit {
 | 
				
			||||||
 | 
							notifications = append(notifications, n)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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--
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SendNotification(desc string, muteable bool) {
 | 
				
			||||||
 | 
						if muteable {
 | 
				
			||||||
 | 
							// 通知防骚扰策略
 | 
				
			||||||
 | 
							nID := hex.EncodeToString(md5.New().Sum([]byte(desc)))
 | 
				
			||||||
 | 
							var flag bool
 | 
				
			||||||
 | 
							if cacheN, has := Cache.Get(nID); has {
 | 
				
			||||||
 | 
								nHistory := cacheN.(NotificationHistory)
 | 
				
			||||||
 | 
								// 每次提醒都增加一倍等待时间,最后每天最多提醒一次
 | 
				
			||||||
 | 
								if time.Now().After(nHistory.Until) {
 | 
				
			||||||
 | 
									flag = true
 | 
				
			||||||
 | 
									nHistory.Duration *= 2
 | 
				
			||||||
 | 
									if nHistory.Duration > time.Hour*24 {
 | 
				
			||||||
 | 
										nHistory.Duration = time.Hour * 24
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									nHistory.Until = time.Now().Add(nHistory.Duration)
 | 
				
			||||||
 | 
									// 缓存有效期加 10 分钟
 | 
				
			||||||
 | 
									Cache.Set(nID, nHistory, nHistory.Duration+time.Minute*10)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								// 新提醒直接通知
 | 
				
			||||||
 | 
								flag = true
 | 
				
			||||||
 | 
								Cache.Set(nID, NotificationHistory{
 | 
				
			||||||
 | 
									Duration: firstNotificationDelay,
 | 
				
			||||||
 | 
									Until:    time.Now().Add(firstNotificationDelay),
 | 
				
			||||||
 | 
								}, firstNotificationDelay+time.Minute*10)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if !flag {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// 发出通知
 | 
				
			||||||
 | 
						notificationsLock.RLock()
 | 
				
			||||||
 | 
						defer notificationsLock.RUnlock()
 | 
				
			||||||
 | 
						for i := 0; i < len(notifications); i++ {
 | 
				
			||||||
 | 
							notifications[i].Send(desc)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -8,7 +8,6 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"github.com/naiba/nezha/model"
 | 
						"github.com/naiba/nezha/model"
 | 
				
			||||||
	pb "github.com/naiba/nezha/proto"
 | 
						pb "github.com/naiba/nezha/proto"
 | 
				
			||||||
	"github.com/naiba/nezha/service/alertmanager"
 | 
					 | 
				
			||||||
	"github.com/naiba/nezha/service/dao"
 | 
						"github.com/naiba/nezha/service/dao"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -52,7 +51,7 @@ func (s *NezhaHandler) ReportTask(c context.Context, r *pb.TaskResult) (*pb.Rece
 | 
				
			|||||||
		if errMsg != "" {
 | 
							if errMsg != "" {
 | 
				
			||||||
			var monitor model.Monitor
 | 
								var monitor model.Monitor
 | 
				
			||||||
			dao.DB.First(&monitor, "id = ?", r.GetId())
 | 
								dao.DB.First(&monitor, "id = ?", r.GetId())
 | 
				
			||||||
			alertmanager.SendNotification(fmt.Sprintf("服务监控:%s %s", monitor.Name, errMsg), true)
 | 
								dao.SendNotification(fmt.Sprintf("服务监控:%s %s", monitor.Name, errMsg), true)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if r.GetType() == model.TaskTypeCommand {
 | 
						if r.GetType() == model.TaskTypeCommand {
 | 
				
			||||||
@ -62,10 +61,10 @@ func (s *NezhaHandler) ReportTask(c context.Context, r *pb.TaskResult) (*pb.Rece
 | 
				
			|||||||
		cr := dao.Crons[r.GetId()]
 | 
							cr := dao.Crons[r.GetId()]
 | 
				
			||||||
		if cr != nil {
 | 
							if cr != nil {
 | 
				
			||||||
			if cr.PushSuccessful && r.GetSuccessful() {
 | 
								if cr.PushSuccessful && r.GetSuccessful() {
 | 
				
			||||||
				alertmanager.SendNotification(fmt.Sprintf("成功计划任务:%s ,服务器:%d,日志:\n%s", cr.Name, clientID, r.GetData()), false)
 | 
									dao.SendNotification(fmt.Sprintf("成功计划任务:%s ,服务器:%d,日志:\n%s", cr.Name, clientID, r.GetData()), false)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if !r.GetSuccessful() {
 | 
								if !r.GetSuccessful() {
 | 
				
			||||||
				alertmanager.SendNotification(fmt.Sprintf("失败计划任务:%s ,服务器:%d,日志:\n%s", cr.Name, clientID, r.GetData()), false)
 | 
									dao.SendNotification(fmt.Sprintf("失败计划任务:%s ,服务器:%d,日志:\n%s", cr.Name, clientID, r.GetData()), false)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			dao.DB.Model(cr).Updates(model.Cron{
 | 
								dao.DB.Model(cr).Updates(model.Cron{
 | 
				
			||||||
				LastExecutedAt: time.Now().Add(time.Second * -1 * time.Duration(r.GetDelay())),
 | 
									LastExecutedAt: time.Now().Add(time.Second * -1 * time.Duration(r.GetDelay())),
 | 
				
			||||||
@ -127,7 +126,7 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece
 | 
				
			|||||||
		dao.ServerList[clientID].Host.IP != "" &&
 | 
							dao.ServerList[clientID].Host.IP != "" &&
 | 
				
			||||||
		host.IP != "" &&
 | 
							host.IP != "" &&
 | 
				
			||||||
		dao.ServerList[clientID].Host.IP != host.IP {
 | 
							dao.ServerList[clientID].Host.IP != host.IP {
 | 
				
			||||||
		alertmanager.SendNotification(fmt.Sprintf(
 | 
							dao.SendNotification(fmt.Sprintf(
 | 
				
			||||||
			"IP变更提醒 服务器:%s ,旧IP:%s,新IP:%s。",
 | 
								"IP变更提醒 服务器:%s ,旧IP:%s,新IP:%s。",
 | 
				
			||||||
			dao.ServerList[clientID].Name, dao.ServerList[clientID].Host.IP, host.IP), true)
 | 
								dao.ServerList[clientID].Name, dao.ServerList[clientID].Host.IP, host.IP), true)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user