diff --git a/README.md b/README.md index ba60352..7c4b3d4 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@
LOGO designed by 熊大 .

-    +   

:trollface: 哪吒监控 一站式轻监控轻运维系统。支持系统状态、HTTP(SSL 证书变更、即将到期、到期)、TCP、Ping 监控报警,计划任务和在线终端。

diff --git a/cmd/dashboard/controller/common_page.go b/cmd/dashboard/controller/common_page.go index 6447d7e..ba78c85 100644 --- a/cmd/dashboard/controller/common_page.go +++ b/cmd/dashboard/controller/common_page.go @@ -98,17 +98,19 @@ func (p *commonPage) checkViewPassword(c *gin.Context) { } func (p *commonPage) service(c *gin.Context) { + dao.AlertsLock.RLock() + defer dao.AlertsLock.RUnlock() c.HTML(http.StatusOK, "theme-"+dao.Conf.Site.Theme+"/service", mygin.CommonEnvironment(c, gin.H{ - "Title": "服务状态", - "Services": dao.ServiceSentinelShared.LoadStats(), - "CustomCode": dao.Conf.Site.CustomCode, + "Title": "服务状态", + "Services": dao.ServiceSentinelShared.LoadStats(), + "CycleTransferStats": dao.AlertsCycleTransferStatsStore, + "CustomCode": dao.Conf.Site.CustomCode, })) } func (cp *commonPage) home(c *gin.Context) { dao.SortedServerLock.RLock() defer dao.SortedServerLock.RUnlock() - c.HTML(http.StatusOK, "theme-"+dao.Conf.Site.Theme+"/home", mygin.CommonEnvironment(c, gin.H{ "Servers": dao.SortedServerList, "CustomCode": dao.Conf.Site.CustomCode, diff --git a/cmd/dashboard/controller/member_api.go b/cmd/dashboard/controller/member_api.go index 52c546c..331f7e2 100644 --- a/cmd/dashboard/controller/member_api.go +++ b/cmd/dashboard/controller/member_api.go @@ -61,11 +61,24 @@ func (ma *memberAPI) delete(c *gin.Context) { case "server": err = dao.DB.Unscoped().Delete(&model.Server{}, "id = ?", id).Error if err == nil { + // 删除服务器 dao.ServerLock.Lock() delete(dao.SecretToID, dao.ServerList[id].Secret) delete(dao.ServerList, id) dao.ServerLock.Unlock() dao.ReSortServer() + // 删除循环流量状态中的此服务器相关的记录 + dao.AlertsLock.Lock() + for i := 0; i < len(dao.Alerts); i++ { + if dao.AlertsCycleTransferStatsStore[dao.Alerts[i].ID] != nil { + delete(dao.AlertsCycleTransferStatsStore[dao.Alerts[i].ID].ServerName, id) + delete(dao.AlertsCycleTransferStatsStore[dao.Alerts[i].ID].Transfer, id) + delete(dao.AlertsCycleTransferStatsStore[dao.Alerts[i].ID].NextUpdate, id) + } + } + dao.AlertsLock.Unlock() + // 删除服务器相关循环流量记录 + dao.DB.Unscoped().Delete(&model.Transfer{}, "server_id = ?", id) } case "notification": err = dao.DB.Unscoped().Delete(&model.Notification{}, "id = ?", id).Error diff --git a/model/alertrule.go b/model/alertrule.go index c9d50ad..a5aee7f 100644 --- a/model/alertrule.go +++ b/model/alertrule.go @@ -2,10 +2,20 @@ package model import ( "encoding/json" + "time" "gorm.io/gorm" ) +type CycleTransferStats struct { + Name string + From time.Time + To time.Time + ServerName map[uint64]string + Transfer map[uint64]uint64 + NextUpdate map[uint64]time.Time +} + type AlertRule struct { Common Name string @@ -27,10 +37,14 @@ func (r *AlertRule) AfterFind(tx *gorm.DB) error { return json.Unmarshal([]byte(r.RulesRaw), &r.Rules) } -func (r *AlertRule) Snapshot(server *Server, db *gorm.DB) []interface{} { +func (r *AlertRule) Enabled() bool { + return r.Enable != nil && *r.Enable +} + +func (r *AlertRule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server, db *gorm.DB) []interface{} { var point []interface{} for i := 0; i < len(r.Rules); i++ { - point = append(point, r.Rules[i].Snapshot(server, db)) + point = append(point, r.Rules[i].Snapshot(cycleTransferStats, server, db)) } return point } diff --git a/model/rule.go b/model/rule.go index 2fb3a72..7ed6112 100644 --- a/model/rule.go +++ b/model/rule.go @@ -42,7 +42,7 @@ func percentage(used, total uint64) float64 { } // Snapshot 未通过规则返回 struct{}{}, 通过返回 nil -func (u *Rule) Snapshot(server *Server, db *gorm.DB) interface{} { +func (u *Rule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server, db *gorm.DB) interface{} { // 监控全部但是排除了此服务器 if u.Cover == RuleCoverAll && u.Ignore[server.ID] { return nil @@ -139,6 +139,11 @@ func (u *Rule) Snapshot(server *Server, db *gorm.DB) interface{} { } else { u.LastCycleStatus[server.ID] = nil } + if cycleTransferStats.ServerName[server.ID] != server.Name { + cycleTransferStats.ServerName[server.ID] = server.Name + } + cycleTransferStats.Transfer[server.ID] = uint64(src) + cycleTransferStats.NextUpdate[server.ID] = u.NextTransferAt[server.ID] } if u.Type == "offline" && float64(time.Now().Unix())-src > 6 { diff --git a/resource/template/theme-default/service.html b/resource/template/theme-default/service.html index 5a910bd..a936771 100644 --- a/resource/template/theme-default/service.html +++ b/resource/template/theme-default/service.html @@ -7,7 +7,6 @@
- @@ -30,12 +29,48 @@ {{end}} {{end}}
- + {{statusName (divU64 $service.CurrentUp (addU64 $service.CurrentUp $service.CurrentDown))}}
+ + {{if .CycleTransferStats}} + +

循环流量统计

+ + + + + + + + + + + + + + + {{range $id, $stats := .CycleTransferStats}} + {{range $innerId, $transfer := $stats.Transfer}} + + + + + + + + + + {{end}} + {{end}} + +
ID规则服务器下次刷新流量
{{$id}}{{$stats.Name}}{{index $stats.ServerName $innerId}}{{$stats.From|tf}}{{$stats.To|tf}}{{(index $stats.NextUpdate $innerId)|tf}}{{$transfer|bf}}
+ + {{end}}
diff --git a/service/dao/alertsentinel.go b/service/dao/alertsentinel.go index ca623c1..127851a 100644 --- a/service/dao/alertsentinel.go +++ b/service/dao/alertsentinel.go @@ -16,29 +16,54 @@ const ( _RuleCheckPass ) -// 报警规则 -var alertsLock sync.RWMutex -var alerts []*model.AlertRule -var alertsStore map[uint64]map[uint64][][]interface{} -var alertsPrevState map[uint64]map[uint64]uint - type NotificationHistory struct { Duration time.Duration Until time.Time } +// 报警规则 +var AlertsLock sync.RWMutex +var Alerts []*model.AlertRule +var alertsStore map[uint64]map[uint64][][]interface{} +var alertsPrevState map[uint64]map[uint64]uint +var AlertsCycleTransferStatsStore map[uint64]*model.CycleTransferStats + +func addCycleTransferStatsInfo(alert *model.AlertRule) { + if !alert.Enabled() { + return + } + for j := 0; j < len(alert.Rules); j++ { + if !alert.Rules[j].IsTransferDurationRule() { + continue + } + if AlertsCycleTransferStatsStore[alert.ID] == nil { + from := alert.Rules[j].GetTransferDurationStart() + AlertsCycleTransferStatsStore[alert.ID] = &model.CycleTransferStats{ + Name: alert.Name, + From: from, + To: from.Add(time.Hour * time.Duration(alert.Rules[j].CycleInterval)), + ServerName: make(map[uint64]string), + Transfer: make(map[uint64]uint64), + NextUpdate: make(map[uint64]time.Time), + } + } + } +} + func AlertSentinelStart() { alertsStore = make(map[uint64]map[uint64][][]interface{}) alertsPrevState = make(map[uint64]map[uint64]uint) - alertsLock.Lock() - if err := DB.Find(&alerts).Error; err != nil { + AlertsCycleTransferStatsStore = make(map[uint64]*model.CycleTransferStats) + AlertsLock.Lock() + if err := DB.Find(&Alerts).Error; err != nil { panic(err) } - for i := 0; i < len(alerts); i++ { - alertsStore[alerts[i].ID] = make(map[uint64][][]interface{}) - alertsPrevState[alerts[i].ID] = make(map[uint64]uint) + for i := 0; i < len(Alerts); i++ { + alertsStore[Alerts[i].ID] = make(map[uint64][][]interface{}) + alertsPrevState[Alerts[i].ID] = make(map[uint64]uint) + addCycleTransferStatsInfo(Alerts[i]) } - alertsLock.Unlock() + AlertsLock.Unlock() time.Sleep(time.Second * 10) var lastPrint time.Time @@ -59,52 +84,55 @@ func AlertSentinelStart() { } func OnRefreshOrAddAlert(alert model.AlertRule) { - alertsLock.Lock() - defer alertsLock.Unlock() + AlertsLock.Lock() + defer AlertsLock.Unlock() delete(alertsStore, alert.ID) delete(alertsPrevState, alert.ID) var isEdit bool - for i := 0; i < len(alerts); i++ { - if alerts[i].ID == alert.ID { - alerts[i] = &alert + for i := 0; i < len(Alerts); i++ { + if Alerts[i].ID == alert.ID { + Alerts[i] = &alert isEdit = true } } if !isEdit { - alerts = append(alerts, &alert) + Alerts = append(Alerts, &alert) } alertsStore[alert.ID] = make(map[uint64][][]interface{}) alertsPrevState[alert.ID] = make(map[uint64]uint) + delete(AlertsCycleTransferStatsStore, alert.ID) + addCycleTransferStatsInfo(&alert) } func OnDeleteAlert(id uint64) { - alertsLock.Lock() - defer alertsLock.Unlock() + AlertsLock.Lock() + defer AlertsLock.Unlock() delete(alertsStore, id) delete(alertsPrevState, id) - for i := 0; i < len(alerts); i++ { - if alerts[i].ID == id { - alerts = append(alerts[:i], alerts[i+1:]...) + for i := 0; i < len(Alerts); i++ { + if Alerts[i].ID == id { + Alerts = append(Alerts[:i], Alerts[i+1:]...) i-- } } + delete(AlertsCycleTransferStatsStore, id) } func checkStatus() { - alertsLock.RLock() - defer alertsLock.RUnlock() + AlertsLock.RLock() + defer AlertsLock.RUnlock() ServerLock.RLock() defer ServerLock.RUnlock() - for _, alert := range alerts { + for _, alert := range Alerts { // 跳过未启用 - if alert.Enable == nil || !*alert.Enable { + if !alert.Enabled() { continue } for _, server := range ServerList { // 监测点 alertsStore[alert.ID][server.ID] = append(alertsStore[alert. - ID][server.ID], alert.Snapshot(server, DB)) + ID][server.ID], alert.Snapshot(AlertsCycleTransferStatsStore[alert.ID], server, DB)) // 发送通知,分为触发报警和恢复通知 max, passed := alert.Check(alertsStore[alert.ID][server.ID]) if !passed { diff --git a/service/dao/dao.go b/service/dao/dao.go index ef2ab00..767e2a2 100644 --- a/service/dao/dao.go +++ b/service/dao/dao.go @@ -13,7 +13,7 @@ import ( pb "github.com/naiba/nezha/proto" ) -var Version = "v0.11.2" // !!记得修改 README 中的 badge 版本!! +var Version = "v0.11.3" // !!记得修改 README 中的 badge 版本!! var ( Conf *model.Config