mirror of
https://github.com/nezhahq/nezha.git
synced 2025-02-08 12:38:13 -05:00
✨ 展示月流量使用情况
This commit is contained in:
parent
611cabf369
commit
561a3c85fb
@ -4,7 +4,7 @@
|
|||||||
<br>
|
<br>
|
||||||
<small><i>LOGO designed by <a href="https://xio.ng" target="_blank">熊大</a> .</i></small>
|
<small><i>LOGO designed by <a href="https://xio.ng" target="_blank">熊大</a> .</i></small>
|
||||||
<br><br>
|
<br><br>
|
||||||
<img src="https://img.shields.io/github/workflow/status/naiba/nezha/Dashboard%20image?label=Dash%20v0.11.2&logo=github&style=for-the-badge"> <img src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&label=Agent&style=for-the-badge&logo=github"> <img src="https://img.shields.io/github/workflow/status/naiba/nezha/Agent%20release?label=Agent%20CI&logo=github&style=for-the-badge"> <img src="https://img.shields.io/badge/Installer-v0.7.1-brightgreen?style=for-the-badge&logo=linux">
|
<img src="https://img.shields.io/github/workflow/status/naiba/nezha/Dashboard%20image?label=Dash%20v0.11.3&logo=github&style=for-the-badge"> <img src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&label=Agent&style=for-the-badge&logo=github"> <img src="https://img.shields.io/github/workflow/status/naiba/nezha/Agent%20release?label=Agent%20CI&logo=github&style=for-the-badge"> <img src="https://img.shields.io/badge/Installer-v0.7.1-brightgreen?style=for-the-badge&logo=linux">
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
<p>:trollface: <b>哪吒监控</b> 一站式轻监控轻运维系统。支持系统状态、HTTP(SSL 证书变更、即将到期、到期)、TCP、Ping 监控报警,计划任务和在线终端。</p>
|
<p>:trollface: <b>哪吒监控</b> 一站式轻监控轻运维系统。支持系统状态、HTTP(SSL 证书变更、即将到期、到期)、TCP、Ping 监控报警,计划任务和在线终端。</p>
|
||||||
|
@ -98,17 +98,19 @@ func (p *commonPage) checkViewPassword(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *commonPage) service(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{
|
c.HTML(http.StatusOK, "theme-"+dao.Conf.Site.Theme+"/service", mygin.CommonEnvironment(c, gin.H{
|
||||||
"Title": "服务状态",
|
"Title": "服务状态",
|
||||||
"Services": dao.ServiceSentinelShared.LoadStats(),
|
"Services": dao.ServiceSentinelShared.LoadStats(),
|
||||||
"CustomCode": dao.Conf.Site.CustomCode,
|
"CycleTransferStats": dao.AlertsCycleTransferStatsStore,
|
||||||
|
"CustomCode": dao.Conf.Site.CustomCode,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *commonPage) home(c *gin.Context) {
|
func (cp *commonPage) home(c *gin.Context) {
|
||||||
dao.SortedServerLock.RLock()
|
dao.SortedServerLock.RLock()
|
||||||
defer dao.SortedServerLock.RUnlock()
|
defer dao.SortedServerLock.RUnlock()
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "theme-"+dao.Conf.Site.Theme+"/home", mygin.CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, "theme-"+dao.Conf.Site.Theme+"/home", mygin.CommonEnvironment(c, gin.H{
|
||||||
"Servers": dao.SortedServerList,
|
"Servers": dao.SortedServerList,
|
||||||
"CustomCode": dao.Conf.Site.CustomCode,
|
"CustomCode": dao.Conf.Site.CustomCode,
|
||||||
|
@ -61,11 +61,24 @@ func (ma *memberAPI) delete(c *gin.Context) {
|
|||||||
case "server":
|
case "server":
|
||||||
err = dao.DB.Unscoped().Delete(&model.Server{}, "id = ?", id).Error
|
err = dao.DB.Unscoped().Delete(&model.Server{}, "id = ?", id).Error
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
// 删除服务器
|
||||||
dao.ServerLock.Lock()
|
dao.ServerLock.Lock()
|
||||||
delete(dao.SecretToID, dao.ServerList[id].Secret)
|
delete(dao.SecretToID, dao.ServerList[id].Secret)
|
||||||
delete(dao.ServerList, id)
|
delete(dao.ServerList, id)
|
||||||
dao.ServerLock.Unlock()
|
dao.ServerLock.Unlock()
|
||||||
dao.ReSortServer()
|
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":
|
case "notification":
|
||||||
err = dao.DB.Unscoped().Delete(&model.Notification{}, "id = ?", id).Error
|
err = dao.DB.Unscoped().Delete(&model.Notification{}, "id = ?", id).Error
|
||||||
|
@ -2,10 +2,20 @@ package model
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"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 {
|
type AlertRule struct {
|
||||||
Common
|
Common
|
||||||
Name string
|
Name string
|
||||||
@ -27,10 +37,14 @@ func (r *AlertRule) AfterFind(tx *gorm.DB) error {
|
|||||||
return json.Unmarshal([]byte(r.RulesRaw), &r.Rules)
|
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{}
|
var point []interface{}
|
||||||
for i := 0; i < len(r.Rules); i++ {
|
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
|
return point
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ func percentage(used, total uint64) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot 未通过规则返回 struct{}{}, 通过返回 nil
|
// 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] {
|
if u.Cover == RuleCoverAll && u.Ignore[server.ID] {
|
||||||
return nil
|
return nil
|
||||||
@ -139,6 +139,11 @@ func (u *Rule) Snapshot(server *Server, db *gorm.DB) interface{} {
|
|||||||
} else {
|
} else {
|
||||||
u.LastCycleStatus[server.ID] = nil
|
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 {
|
if u.Type == "offline" && float64(time.Now().Unix())-src > 6 {
|
||||||
|
39
resource/template/theme-default/service.html
vendored
39
resource/template/theme-default/service.html
vendored
@ -7,7 +7,6 @@
|
|||||||
<div class="nb-container">
|
<div class="nb-container">
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
<div class="service-status">
|
<div class="service-status">
|
||||||
|
|
||||||
<table class="ui celled table">
|
<table class="ui celled table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -30,12 +29,48 @@
|
|||||||
</div> {{end}}
|
</div> {{end}}
|
||||||
</td>
|
</td>
|
||||||
<td class="ui center aligned delay-today">
|
<td class="ui center aligned delay-today">
|
||||||
<i class="delay-today {{className (divU64 $service.CurrentUp (addU64 $service.CurrentUp $service.CurrentDown))}}"></i>
|
<i
|
||||||
|
class="delay-today {{className (divU64 $service.CurrentUp (addU64 $service.CurrentUp $service.CurrentDown))}}"></i>
|
||||||
{{statusName (divU64 $service.CurrentUp (addU64 $service.CurrentUp $service.CurrentDown))}}
|
{{statusName (divU64 $service.CurrentUp (addU64 $service.CurrentUp $service.CurrentDown))}}
|
||||||
</td>
|
</td>
|
||||||
</tr> {{end}}
|
</tr> {{end}}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
{{if .CycleTransferStats}}
|
||||||
|
|
||||||
|
<h2 style="text-align: center;">循环流量统计</h2>
|
||||||
|
|
||||||
|
<table class="ui celled table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="ui center aligned">ID</th>
|
||||||
|
<th class="ui center aligned">规则</th>
|
||||||
|
<th class="ui center aligned">服务器</th>
|
||||||
|
<th class="ui center aligned">自</th>
|
||||||
|
<th class="ui center aligned">至</th>
|
||||||
|
<th class="ui center aligned">下次刷新</th>
|
||||||
|
<th class="ui center aligned">流量</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range $id, $stats := .CycleTransferStats}}
|
||||||
|
{{range $innerId, $transfer := $stats.Transfer}}
|
||||||
|
<tr>
|
||||||
|
<td class="ui center aligned">{{$id}}</td>
|
||||||
|
<td class="ui center aligned">{{$stats.Name}}</td>
|
||||||
|
<td class="ui center aligned">{{index $stats.ServerName $innerId}}</td>
|
||||||
|
<td class="ui center aligned">{{$stats.From|tf}}</td>
|
||||||
|
<td class="ui center aligned">{{$stats.To|tf}}</td>
|
||||||
|
<td class="ui center aligned">{{(index $stats.NextUpdate $innerId)|tf}}</td>
|
||||||
|
<td class="ui center aligned">{{$transfer|bf}}</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,29 +16,54 @@ const (
|
|||||||
_RuleCheckPass
|
_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 {
|
type NotificationHistory struct {
|
||||||
Duration time.Duration
|
Duration time.Duration
|
||||||
Until time.Time
|
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() {
|
func AlertSentinelStart() {
|
||||||
alertsStore = make(map[uint64]map[uint64][][]interface{})
|
alertsStore = make(map[uint64]map[uint64][][]interface{})
|
||||||
alertsPrevState = make(map[uint64]map[uint64]uint)
|
alertsPrevState = make(map[uint64]map[uint64]uint)
|
||||||
alertsLock.Lock()
|
AlertsCycleTransferStatsStore = make(map[uint64]*model.CycleTransferStats)
|
||||||
if err := DB.Find(&alerts).Error; err != nil {
|
AlertsLock.Lock()
|
||||||
|
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++ {
|
||||||
alertsStore[alerts[i].ID] = make(map[uint64][][]interface{})
|
alertsStore[Alerts[i].ID] = make(map[uint64][][]interface{})
|
||||||
alertsPrevState[alerts[i].ID] = make(map[uint64]uint)
|
alertsPrevState[Alerts[i].ID] = make(map[uint64]uint)
|
||||||
|
addCycleTransferStatsInfo(Alerts[i])
|
||||||
}
|
}
|
||||||
alertsLock.Unlock()
|
AlertsLock.Unlock()
|
||||||
|
|
||||||
time.Sleep(time.Second * 10)
|
time.Sleep(time.Second * 10)
|
||||||
var lastPrint time.Time
|
var lastPrint time.Time
|
||||||
@ -59,52 +84,55 @@ func AlertSentinelStart() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func OnRefreshOrAddAlert(alert model.AlertRule) {
|
func OnRefreshOrAddAlert(alert model.AlertRule) {
|
||||||
alertsLock.Lock()
|
AlertsLock.Lock()
|
||||||
defer alertsLock.Unlock()
|
defer AlertsLock.Unlock()
|
||||||
delete(alertsStore, alert.ID)
|
delete(alertsStore, alert.ID)
|
||||||
delete(alertsPrevState, alert.ID)
|
delete(alertsPrevState, alert.ID)
|
||||||
var isEdit bool
|
var isEdit bool
|
||||||
for i := 0; i < len(alerts); i++ {
|
for i := 0; i < len(Alerts); i++ {
|
||||||
if alerts[i].ID == alert.ID {
|
if Alerts[i].ID == alert.ID {
|
||||||
alerts[i] = &alert
|
Alerts[i] = &alert
|
||||||
isEdit = true
|
isEdit = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !isEdit {
|
if !isEdit {
|
||||||
alerts = append(alerts, &alert)
|
Alerts = append(Alerts, &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)
|
||||||
|
delete(AlertsCycleTransferStatsStore, alert.ID)
|
||||||
|
addCycleTransferStatsInfo(&alert)
|
||||||
}
|
}
|
||||||
|
|
||||||
func OnDeleteAlert(id uint64) {
|
func OnDeleteAlert(id uint64) {
|
||||||
alertsLock.Lock()
|
AlertsLock.Lock()
|
||||||
defer alertsLock.Unlock()
|
defer AlertsLock.Unlock()
|
||||||
delete(alertsStore, id)
|
delete(alertsStore, id)
|
||||||
delete(alertsPrevState, id)
|
delete(alertsPrevState, id)
|
||||||
for i := 0; i < len(alerts); i++ {
|
for i := 0; i < len(Alerts); i++ {
|
||||||
if alerts[i].ID == id {
|
if Alerts[i].ID == id {
|
||||||
alerts = append(alerts[:i], alerts[i+1:]...)
|
Alerts = append(Alerts[:i], Alerts[i+1:]...)
|
||||||
i--
|
i--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
delete(AlertsCycleTransferStatsStore, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkStatus() {
|
func checkStatus() {
|
||||||
alertsLock.RLock()
|
AlertsLock.RLock()
|
||||||
defer alertsLock.RUnlock()
|
defer AlertsLock.RUnlock()
|
||||||
ServerLock.RLock()
|
ServerLock.RLock()
|
||||||
defer ServerLock.RUnlock()
|
defer ServerLock.RUnlock()
|
||||||
|
|
||||||
for _, alert := range alerts {
|
for _, alert := range Alerts {
|
||||||
// 跳过未启用
|
// 跳过未启用
|
||||||
if alert.Enable == nil || !*alert.Enable {
|
if !alert.Enabled() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, server := range 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, DB))
|
ID][server.ID], alert.Snapshot(AlertsCycleTransferStatsStore[alert.ID], server, DB))
|
||||||
// 发送通知,分为触发报警和恢复通知
|
// 发送通知,分为触发报警和恢复通知
|
||||||
max, passed := alert.Check(alertsStore[alert.ID][server.ID])
|
max, passed := alert.Check(alertsStore[alert.ID][server.ID])
|
||||||
if !passed {
|
if !passed {
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
pb "github.com/naiba/nezha/proto"
|
pb "github.com/naiba/nezha/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Version = "v0.11.2" // !!记得修改 README 中的 badge 版本!!
|
var Version = "v0.11.3" // !!记得修改 README 中的 badge 版本!!
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Conf *model.Config
|
Conf *model.Config
|
||||||
|
Loading…
Reference in New Issue
Block a user