mirror of
https://github.com/nezhahq/nezha.git
synced 2025-02-02 09:38:13 -05:00
✨ [dashboard v0.3.9] 手动即时触发计划任务
This commit is contained in:
parent
175eb996d3
commit
a957b21265
@ -1,6 +1,6 @@
|
|||||||
# 哪吒面板
|
# 哪吒面板
|
||||||
|
|
||||||
![dashboard](https://img.shields.io/badge/管理面板-v0.3.8-brightgreen?style=for-the-badge&logo=github) ![Agent release](https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&label=Agent&style=for-the-badge&logo=github)
|
![dashboard](https://img.shields.io/badge/管理面板-v0.3.9-brightgreen?style=for-the-badge&logo=github) ![Agent release](https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&label=Agent&style=for-the-badge&logo=github)
|
||||||
|
|
||||||
系统状态监控报警、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…
Reference in New Issue
Block a user