v0.6.1 优化CSS、服务报警、服务状态展示

This commit is contained in:
naiba 2021-04-19 23:11:00 +08:00
parent 114f47cb0f
commit d3c3e55c88
9 changed files with 87 additions and 46 deletions

View File

@ -1,7 +1,7 @@
<div align="center" style="background-color: white"> <div align="center" style="background-color: white">
<img width="500" style="max-width:100%" src="https://raw.githubusercontent.com/naiba/nezha/master/resource/static/brand.png" title="哪吒监控"> <img width="500" style="max-width:100%" src="https://raw.githubusercontent.com/naiba/nezha/master/resource/static/brand.png" title="哪吒监控">
<br><br> <br><br>
<img src="https://img.shields.io/github/workflow/status/naiba/nezha/Dashboard%20image?label=Dash%20v0.5.1&logo=github&style=for-the-badge">&nbsp;<img src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&label=Agent&style=for-the-badge&logo=github">&nbsp;<img src="https://img.shields.io/github/workflow/status/naiba/nezha/Agent%20release?label=Agent%20CI&logo=github&style=for-the-badge">&nbsp;<img src="https://img.shields.io/badge/Installer-v0.4.10-brightgreen?style=for-the-badge&logo=linux"> <img src="https://img.shields.io/github/workflow/status/naiba/nezha/Dashboard%20image?label=Dash%20v0.6.1&logo=github&style=for-the-badge">&nbsp;<img src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&label=Agent&style=for-the-badge&logo=github">&nbsp;<img src="https://img.shields.io/github/workflow/status/naiba/nezha/Agent%20release?label=Agent%20CI&logo=github&style=for-the-badge">&nbsp;<img src="https://img.shields.io/badge/Installer-v0.4.10-brightgreen?style=for-the-badge&logo=linux">
<br> <br>
<p>:trollface: 哪吒监控 一站式轻监控轻运维系统。支持系统状态、HTTP(SSL 证书变更、即将到期、到期)、TCP、Ping 监控报警,命令批量执行和计划任务。</p> <p>:trollface: 哪吒监控 一站式轻监控轻运维系统。支持系统状态、HTTP(SSL 证书变更、即将到期、到期)、TCP、Ping 监控报警,命令批量执行和计划任务。</p>
</div> </div>

View File

@ -54,7 +54,7 @@ func ServeWeb(port uint) {
} }
if a == 0 { if a == 0 {
// 这是从未在线的情况 // 这是从未在线的情况
return 1 / float32(b) * 100 return 0.00001 / float32(b) * 100
} }
return float32(a) / float32(b) * 100 return float32(a) / float32(b) * 100
}, },
@ -67,7 +67,7 @@ func ServeWeb(port uint) {
} }
if a == 0 { if a == 0 {
// 这是从未在线的情况 // 这是从未在线的情况
return 1 / float32(b) * 100 return 0.00001 / float32(b) * 100
} }
return float32(a) / float32(b) * 100 return float32(a) / float32(b) * 100
}, },

View File

@ -110,6 +110,6 @@ func loadCrons() {
func main() { func main() {
go controller.ServeWeb(dao.Conf.HTTPPort) go controller.ServeWeb(dao.Conf.HTTPPort)
go rpc.ServeRPC(dao.Conf.GRPCPort) go rpc.ServeRPC(dao.Conf.GRPCPort)
go rpc.DispatchTask(time.Minute * 3) go rpc.DispatchTask(time.Second * 30)
dao.AlertSentinelStart() dao.AlertSentinelStart()
} }

View File

@ -10,6 +10,7 @@ import (
"os/exec" "os/exec"
"time" "time"
"github.com/genkiroid/cert"
"github.com/go-ping/ping" "github.com/go-ping/ping"
"github.com/naiba/nezha/pkg/utils" "github.com/naiba/nezha/pkg/utils"
"github.com/shirou/gopsutil/v3/cpu" "github.com/shirou/gopsutil/v3/cpu"
@ -20,8 +21,8 @@ import (
func main() { func main() {
// icmp() // icmp()
// tcpping() // tcpping()
// httpWithSSLInfo() httpWithSSLInfo()
sysinfo() // sysinfo()
// cmdExec() // cmdExec()
} }
@ -72,11 +73,12 @@ func httpWithSSLInfo() {
httpClient := &http.Client{Transport: transCfg, CheckRedirect: func(req *http.Request, via []*http.Request) error { httpClient := &http.Client{Transport: transCfg, CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse return http.ErrUseLastResponse
}} }}
resp, err := httpClient.Get("http://mail.nai.ba") url := "https://ops.naibahq.com"
fmt.Println(err, resp.StatusCode) resp, err := httpClient.Get(url)
fmt.Println(err, resp)
// SSL 证书信息获取 // SSL 证书信息获取
// c := cert.NewCert("expired-ecc-dv.ssl.com") c := cert.NewCert(url[8:])
// fmt.Println(c.Error) fmt.Println(c.Error)
} }
func icmp() { func icmp() {

View File

@ -6,7 +6,7 @@
.nb-container { .nb-container {
padding-top: 75px; padding-top: 75px;
min-height: 100%; min-height: 100vh;
padding-bottom: 55px; padding-bottom: 55px;
margin-bottom: -47px; margin-bottom: -47px;
} }

View File

@ -9,7 +9,7 @@
<title>{{.Title}}</title> <title>{{.Title}}</title>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.1/dist/semantic.min.css"> <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.1/dist/semantic.min.css">
<link rel="stylesheet" type="text/css" href="/static/semantic-ui-alerts.min.css"> <link rel="stylesheet" type="text/css" href="/static/semantic-ui-alerts.min.css">
<link rel="stylesheet" type="text/css" href="/static/main.css?v202102051040"> <link rel="stylesheet" type="text/css" href="/static/main.css?v202104192145">
<link rel="shortcut icon" type="image/png" href="/static/logo.png?v20210320" /> <link rel="shortcut icon" type="image/png" href="/static/logo.png?v20210320" />
</head> </head>

View File

@ -14,7 +14,7 @@
</a> </a>
{{else}} {{else}}
<a class='item{{if eq .MatchedPath "/"}} active{{end}}' href="/"><i class="home icon"></i>首页</a> <a class='item{{if eq .MatchedPath "/"}} active{{end}}' href="/"><i class="home icon"></i>首页</a>
<a class='item{{if eq .MatchedPath "/service"}} active{{end}}' href="/service"><i class="rss icon"></i>服务状态</a> <a class='item{{if eq .MatchedPath "/service"}} active{{end}}' href="/service"><i class="rss icon"></i>服务</a>
{{end}} {{end}}
<div class="right menu"> <div class="right menu">
<div class="item"> <div class="item">

View File

@ -13,7 +13,7 @@ import (
pb "github.com/naiba/nezha/proto" pb "github.com/naiba/nezha/proto"
) )
var Version = "v0.5.1" // !!记得修改 README 重的 badge 版本!! var Version = "v0.6.1" // !!记得修改 README 重的 badge 版本!!
const ( const (
SnapshotDelay = 3 SnapshotDelay = 3

View File

@ -15,15 +15,22 @@ var ServiceSentinelShared *ServiceSentinel
func NewServiceSentinel() { func NewServiceSentinel() {
ServiceSentinelShared = &ServiceSentinel{ ServiceSentinelShared = &ServiceSentinel{
serviceResponseChannel: make(chan *pb.TaskResult, 200), serviceResponseChannel: make(chan *pb.TaskResult, 200),
serviceResponseDataStoreToday: make(map[uint64][]model.MonitorHistory), serviceResponseDataStoreTodaySavedIndex: make(map[uint64]int),
lastStatus: make(map[uint64]string), serviceCurrentStatusIndex: make(map[uint64]int),
serviceResponseDataStoreCurrentUp: make(map[uint64]uint64), serviceCurrentStatusData: make(map[uint64][]model.MonitorHistory),
serviceResponseDataStoreCurrentDown: make(map[uint64]uint64), serviceResponseDataStoreTodayLastSave: make(map[uint64]time.Time),
monitors: make(map[uint64]model.Monitor), latestDate: make(map[uint64]string),
latestDate: time.Now().Format("02-Jan-06"), lastStatus: make(map[uint64]string),
serviceResponseDataStoreCurrentUp: make(map[uint64]uint64),
serviceResponseDataStoreCurrentDown: make(map[uint64]uint64),
monitors: make(map[uint64]model.Monitor),
serviceResponseDataStoreToday: make(map[uint64][]model.MonitorHistory),
} }
ServiceSentinelShared.OnMonitorUpdate() ServiceSentinelShared.OnMonitorUpdate()
for k := range ServiceSentinelShared.monitors {
ServiceSentinelShared.latestDate[k] = time.Now().Format("02-Jan-06")
}
go ServiceSentinelShared.worker() go ServiceSentinelShared.worker()
} }
@ -32,17 +39,19 @@ func NewServiceSentinel() {
需要记录上一次的状态信息 需要记录上一次的状态信息
*/ */
type ServiceSentinel struct { type ServiceSentinel struct {
latestDate string
serviceResponseDataStoreTodaySavedIndex int
serviceResponseDataStoreTodayLastSave time.Time
serviceResponseDataStoreLock sync.RWMutex serviceResponseDataStoreLock sync.RWMutex
monitorsLock sync.RWMutex monitorsLock sync.RWMutex
serviceResponseChannel chan *pb.TaskResult serviceResponseChannel chan *pb.TaskResult
serviceResponseDataStoreTodaySavedIndex map[uint64]int
serviceCurrentStatusIndex map[uint64]int
serviceCurrentStatusData map[uint64][]model.MonitorHistory
serviceResponseDataStoreTodayLastSave map[uint64]time.Time
latestDate map[uint64]string
lastStatus map[uint64]string lastStatus map[uint64]string
monitors map[uint64]model.Monitor
serviceResponseDataStoreToday map[uint64][]model.MonitorHistory
serviceResponseDataStoreCurrentUp map[uint64]uint64 serviceResponseDataStoreCurrentUp map[uint64]uint64
serviceResponseDataStoreCurrentDown map[uint64]uint64 serviceResponseDataStoreCurrentDown map[uint64]uint64
monitors map[uint64]model.Monitor
serviceResponseDataStoreToday map[uint64][]model.MonitorHistory
} }
func (ss *ServiceSentinel) Dispatch(r *pb.TaskResult) { func (ss *ServiceSentinel) Dispatch(r *pb.TaskResult) {
@ -67,19 +76,28 @@ func (ss *ServiceSentinel) OnMonitorUpdate() {
ss.monitors = make(map[uint64]model.Monitor) ss.monitors = make(map[uint64]model.Monitor)
for i := 0; i < len(monitors); i++ { for i := 0; i < len(monitors); i++ {
ss.monitors[monitors[i].ID] = monitors[i] ss.monitors[monitors[i].ID] = monitors[i]
if len(ss.serviceCurrentStatusData[monitors[i].ID]) == 0 {
ss.serviceCurrentStatusData[monitors[i].ID] = make([]model.MonitorHistory, 20)
}
} }
} }
func (ss *ServiceSentinel) OnMonitorDelete(id uint64) { func (ss *ServiceSentinel) OnMonitorDelete(id uint64) {
ss.serviceResponseDataStoreLock.Lock() ss.serviceResponseDataStoreLock.Lock()
defer ss.serviceResponseDataStoreLock.Unlock() defer ss.serviceResponseDataStoreLock.Unlock()
delete(ss.serviceResponseDataStoreCurrentDown, id) delete(ss.serviceResponseDataStoreTodaySavedIndex, id)
delete(ss.serviceResponseDataStoreCurrentUp, id) delete(ss.serviceCurrentStatusIndex, id)
delete(ss.serviceResponseDataStoreToday, id) delete(ss.serviceCurrentStatusData, id)
delete(ss.serviceResponseDataStoreTodayLastSave, id)
delete(ss.latestDate, id)
delete(ss.lastStatus, id) delete(ss.lastStatus, id)
delete(ss.serviceResponseDataStoreCurrentUp, id)
delete(ss.serviceResponseDataStoreCurrentDown, id)
delete(ss.serviceResponseDataStoreToday, id)
ss.monitorsLock.Lock() ss.monitorsLock.Lock()
defer ss.monitorsLock.Unlock() defer ss.monitorsLock.Unlock()
delete(ss.monitors, id) delete(ss.monitors, id)
Cache.Delete(model.CacheKeyServicePage)
} }
func (ss *ServiceSentinel) LoadStats() map[uint64]*model.ServiceItemResponse { func (ss *ServiceSentinel) LoadStats() map[uint64]*model.ServiceItemResponse {
@ -124,11 +142,16 @@ func (ss *ServiceSentinel) LoadStats() map[uint64]*model.ServiceItemResponse {
// 最新一天的数据 // 最新一天的数据
ss.serviceResponseDataStoreLock.RLock() ss.serviceResponseDataStoreLock.RLock()
defer ss.serviceResponseDataStoreLock.RUnlock() defer ss.serviceResponseDataStoreLock.RUnlock()
for k, v := range ss.serviceResponseDataStoreToday { for k := range ss.monitors {
if msm[k] == nil { if msm[k] == nil {
msm[k] = &model.ServiceItemResponse{} msm[k] = &model.ServiceItemResponse{
Up: new([30]int),
Down: new([30]int),
Delay: new([30]float32),
}
} }
msm[k].Monitor = ss.monitors[k] msm[k].Monitor = ss.monitors[k]
v := ss.serviceResponseDataStoreToday[k]
for i := 0; i < len(v); i++ { for i := 0; i < len(v); i++ {
if v[i].Successful { if v[i].Successful {
msm[k].Up[29]++ msm[k].Up[29]++
@ -163,44 +186,60 @@ func getStateStr(percent uint64) string {
func (ss *ServiceSentinel) worker() { func (ss *ServiceSentinel) worker() {
for r := range ss.serviceResponseChannel { for r := range ss.serviceResponseChannel {
if ss.monitors[r.GetId()].ID == 0 {
continue
}
mh := model.PB2MonitorHistory(r) mh := model.PB2MonitorHistory(r)
ss.serviceResponseDataStoreLock.Lock() ss.serviceResponseDataStoreLock.Lock()
// 先查看是否到下一天 // 先查看是否到下一天
nowDate := time.Now().Format("02-Jan-06") nowDate := time.Now().Format("02-Jan-06")
if nowDate != ss.latestDate { if nowDate != ss.latestDate[mh.MonitorID] {
ss.latestDate = nowDate ss.latestDate[mh.MonitorID] = nowDate
dataToSave := ss.serviceResponseDataStoreToday[mh.MonitorID][ss.serviceResponseDataStoreTodaySavedIndex:] dataToSave := ss.serviceResponseDataStoreToday[mh.MonitorID][ss.serviceResponseDataStoreTodaySavedIndex[mh.MonitorID]:]
if err := DB.Create(&dataToSave).Error; err != nil { if err := DB.Create(&dataToSave).Error; err != nil {
log.Println(err) log.Println(err)
} }
ss.serviceResponseDataStoreTodaySavedIndex = 0 ss.serviceResponseDataStoreTodaySavedIndex[mh.MonitorID] = 0
ss.serviceResponseDataStoreToday[mh.MonitorID] = []model.MonitorHistory{} ss.serviceResponseDataStoreToday[mh.MonitorID] = []model.MonitorHistory{}
ss.serviceResponseDataStoreCurrentDown[mh.MonitorID] = 0 ss.serviceResponseDataStoreCurrentDown[mh.MonitorID] = 0
ss.serviceResponseDataStoreCurrentUp[mh.MonitorID] = 0 ss.serviceResponseDataStoreCurrentUp[mh.MonitorID] = 0
ss.serviceResponseDataStoreTodayLastSave = time.Now() ss.serviceResponseDataStoreTodayLastSave[mh.MonitorID] = time.Now()
} }
// 储存至当日数据 // 储存至当日数据
ss.serviceResponseDataStoreToday[mh.MonitorID] = append(ss.serviceResponseDataStoreToday[mh.MonitorID], mh) ss.serviceResponseDataStoreToday[mh.MonitorID] = append(ss.serviceResponseDataStoreToday[mh.MonitorID], mh)
// 每20分钟入库一次 // 每20分钟入库一次
if time.Now().After(ss.serviceResponseDataStoreTodayLastSave.Add(time.Minute * 20)) { if time.Now().After(ss.serviceResponseDataStoreTodayLastSave[mh.MonitorID].Add(time.Minute * 20)) {
ss.serviceResponseDataStoreTodayLastSave = time.Now() ss.serviceResponseDataStoreTodayLastSave[mh.MonitorID] = time.Now()
dataToSave := ss.serviceResponseDataStoreToday[mh.MonitorID][ss.serviceResponseDataStoreTodaySavedIndex:] dataToSave := ss.serviceResponseDataStoreToday[mh.MonitorID][ss.serviceResponseDataStoreTodaySavedIndex[mh.MonitorID]:]
if err := DB.Create(&dataToSave).Error; err != nil { if err := DB.Create(&dataToSave).Error; err != nil {
log.Println(err) log.Println(err)
} }
ss.serviceResponseDataStoreTodaySavedIndex = len(ss.serviceResponseDataStoreToday[mh.MonitorID]) ss.serviceResponseDataStoreTodaySavedIndex[mh.MonitorID] = len(ss.serviceResponseDataStoreToday[mh.MonitorID])
}
// 写入当前数据
ss.serviceCurrentStatusData[mh.MonitorID][ss.serviceCurrentStatusIndex[mh.MonitorID]] = mh
ss.serviceCurrentStatusIndex[mh.MonitorID]++
if ss.serviceCurrentStatusIndex[mh.MonitorID] == 20 {
ss.serviceCurrentStatusIndex[mh.MonitorID] = 0
} }
// 更新当前状态 // 更新当前状态
ss.serviceResponseDataStoreCurrentUp[mh.MonitorID] = 0 ss.serviceResponseDataStoreCurrentUp[mh.MonitorID] = 0
ss.serviceResponseDataStoreCurrentDown[mh.MonitorID] = 0 ss.serviceResponseDataStoreCurrentDown[mh.MonitorID] = 0
for i := len(ss.serviceResponseDataStoreToday[mh.MonitorID]) - 1; i >= 0 && i >= len(ss.serviceResponseDataStoreToday[mh.MonitorID])-20; i-- { for i := 0; i < len(ss.serviceCurrentStatusData[mh.MonitorID]); i++ {
if ss.serviceResponseDataStoreToday[mh.MonitorID][i].Successful { if ss.serviceCurrentStatusData[mh.MonitorID][i].MonitorID > 0 {
ss.serviceResponseDataStoreCurrentUp[mh.MonitorID]++ if ss.serviceCurrentStatusData[mh.MonitorID][i].Successful {
} else { ss.serviceResponseDataStoreCurrentUp[mh.MonitorID]++
ss.serviceResponseDataStoreCurrentDown[mh.MonitorID]++ } else {
ss.serviceResponseDataStoreCurrentDown[mh.MonitorID]++
}
} }
} }
stateStr := getStateStr(ss.serviceResponseDataStoreCurrentUp[mh.MonitorID] * 100 / (ss.serviceResponseDataStoreCurrentDown[mh.MonitorID] + ss.serviceResponseDataStoreCurrentUp[mh.MonitorID])) var upPercent uint64 = 0
if ss.serviceResponseDataStoreCurrentDown[mh.MonitorID]+ss.serviceResponseDataStoreCurrentUp[mh.MonitorID] > 0 {
upPercent = ss.serviceResponseDataStoreCurrentUp[mh.MonitorID] * 100 / (ss.serviceResponseDataStoreCurrentDown[mh.MonitorID] + ss.serviceResponseDataStoreCurrentUp[mh.MonitorID])
}
stateStr := getStateStr(upPercent)
log.Println(ss.monitors[mh.MonitorID].Target, stateStr)
if stateStr == "故障" || stateStr != ss.lastStatus[mh.MonitorID] { if stateStr == "故障" || stateStr != ss.lastStatus[mh.MonitorID] {
ss.monitorsLock.RLock() ss.monitorsLock.RLock()
isSendNotification := (ss.lastStatus[mh.MonitorID] != "" || stateStr == "故障") && ss.monitors[mh.MonitorID].Notify isSendNotification := (ss.lastStatus[mh.MonitorID] != "" || stateStr == "故障") && ss.monitors[mh.MonitorID].Notify