Compare commits

...
11 Commits
Author SHA1 Message Date
Ko no dioandLvGJ fdb2fd8839 fix: revert service html and block service in backend (#318)
Co-authored-by: LvGJ <[email protected]>
2024-02-14 13:12:24 +08:00
github-actions[bot] f3fac9325c update contributors[no ci] 2024-02-13 15:09:58 +00:00
Ko no dioandLvGJ 626613441d fix: add toggle to service (#317)
* fix: short nework word

* fix: add toggle on service

* fix: delete console log update main.js version

---------

Co-authored-by: LvGJ <[email protected]>
2024-02-13 23:09:40 +08:00
github-actions[bot] b86b73a433 update contributors[no ci] 2024-02-12 06:16:26 +00:00
Ko no dioandLvGJ e8b8e59bd7 feat: add network monitor hitory (#316) · 三网ping
* feat: add network monitor hitory

* fix: revert proto change and add indexStore

* fix: update monitor delete unuse monitor history

* fix: delete unuse monitor type

---------

Co-authored-by: LvGJ <[email protected]>
2024-02-12 14:16:04 +08:00
naiba c9bcba6f28 ♻️ 更换可用镜像地址 2024-02-09 20:59:22 +08:00
github-actions[bot] ad9dc5470a update contributors[no ci] 2024-01-18 05:35:21 +00:00
黑歌 8539fb6559 replace daocloud with fgit (#315)
* Update add custom mirror

* Update add custom mirror

* bump install version
2024-01-18 13:34:58 +08:00
github-actions[bot] 11bcc47d49 update contributors[no ci] 2023-12-26 02:26:27 +00:00
Crazy bb789f7652 add supervise daemon for alpine (#313)
alpine在自动更新agent以后不会自动重启agent,导致批量掉线,添加守护进程以后问题解决
2023-12-26 10:26:11 +08:00
dependabot[bot] 51c1e41123 Bump golang.org/x/crypto from 0.15.0 to 0.17.0 (#312)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.15.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.15.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-19 09:43:33 +08:00
34 changed files with 1726 additions and 632 deletions
+60 -54
View File
@@ -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 alt="GitHub release (with filter)" src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&style=for-the-badge&logo=github&label=Dashboard">&nbsp;<img src="https://img.shields.io/github/v/release/nezhahq/agent?color=brightgreen&label=Agent&style=for-the-badge&logo=github">&nbsp;<img src="https://img.shields.io/github/actions/workflow/status/nezhahq/agent/agent.yml?label=Agent%20CI&logo=github&style=for-the-badge">&nbsp;<img src="https://img.shields.io/badge/Installer-v0.15.4-brightgreen?style=for-the-badge&logo=linux"> <img alt="GitHub release (with filter)" src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&style=for-the-badge&logo=github&label=Dashboard">&nbsp;<img src="https://img.shields.io/github/v/release/nezhahq/agent?color=brightgreen&label=Agent&style=for-the-badge&logo=github">&nbsp;<img src="https://img.shields.io/github/actions/workflow/status/nezhahq/agent/agent.yml?label=Agent%20CI&logo=github&style=for-the-badge">&nbsp;<img src="https://img.shields.io/badge/Installer-v0.15.6-brightgreen?style=for-the-badge&logo=linux">
<br> <br>
<br> <br>
<p>:trollface: <b>Nezha Monitoring: Self-hostable, lightweight, servers and websites monitoring and O&M tool.</b></p> <p>:trollface: <b>Nezha Monitoring: Self-hostable, lightweight, servers and websites monitoring and O&M tool.</b></p>
@@ -65,17 +65,17 @@ You can change the dashboard language in the settings page (`/setting`) after th
<a href="https://github.com/cantoblanco" title="Kris"> <a href="https://github.com/cantoblanco" title="Kris">
<img src="https://avatars.githubusercontent.com/u/116849421?v=4" width="50;" alt="Kris"/> <img src="https://avatars.githubusercontent.com/u/116849421?v=4" width="50;" alt="Kris"/>
</a> </a>
<a href="https://github.com/CosmosZ-code" title="CosmosZ-code"> <a href="https://github.com/spiritLHLS" title="spiritlhl">
<img src="https://avatars.githubusercontent.com/u/81398224?v=4" width="50;" alt="CosmosZ-code"/> <img src="https://avatars.githubusercontent.com/u/103393591?v=4" width="50;" alt="spiritlhl"/>
</a> </a>
<a href="https://github.com/liuyanxi975" title="刘颜溪"> <a href="https://github.com/liuyanxi975" title="刘颜溪">
<img src="https://avatars.githubusercontent.com/u/24417037?v=4" width="50;" alt="刘颜溪"/> <img src="https://avatars.githubusercontent.com/u/24417037?v=4" width="50;" alt="刘颜溪"/>
</a> </a>
<a href="https://github.com/spiritLHLS" title="spiritlhl"> <a href="https://github.com/CosmosZ-code" title="CosmosZ-code">
<img src="https://avatars.githubusercontent.com/u/103393591?v=4" width="50;" alt="spiritlhl"/> <img src="https://avatars.githubusercontent.com/u/81398224?v=4" width="50;" alt="CosmosZ-code"/>
</a> </a>
<a href="https://github.com/applexad" title="Applexad"> <a href="https://github.com/uubulb" title="UUBulb">
<img src="https://avatars.githubusercontent.com/u/35923940?v=4" width="50;" alt="Applexad"/> <img src="https://avatars.githubusercontent.com/u/35923940?v=4" width="50;" alt="UUBulb"/>
</a> </a>
<a href="https://github.com/hhhkkk520" title="Kris"> <a href="https://github.com/hhhkkk520" title="Kris">
<img src="https://avatars.githubusercontent.com/u/52115472?v=4" width="50;" alt="Kris"/> <img src="https://avatars.githubusercontent.com/u/52115472?v=4" width="50;" alt="Kris"/>
@@ -89,65 +89,71 @@ You can change the dashboard language in the settings page (`/setting`) after th
<a href="https://github.com/1ridic" title="1ridic"> <a href="https://github.com/1ridic" title="1ridic">
<img src="https://avatars.githubusercontent.com/u/88495501?v=4" width="50;" alt="1ridic"/> <img src="https://avatars.githubusercontent.com/u/88495501?v=4" width="50;" alt="1ridic"/>
</a> </a>
<a href="https://github.com/coreff" title="Core F"> <a href="https://github.com/iilemon" title="Sean">
<img src="https://avatars.githubusercontent.com/u/38347122?v=4" width="50;" alt="Core F"/> <img src="https://avatars.githubusercontent.com/u/33201711?v=4" width="50;" alt="Sean"/>
</a>
<a href="https://github.com/Creling" title="Creling">
<img src="https://avatars.githubusercontent.com/u/43109504?v=4" width="50;" alt="Creling"/>
</a>
<a href="https://github.com/ch8o" title="no-name-now">
<img src="https://avatars.githubusercontent.com/u/9103372?v=4" width="50;" alt="no-name-now"/>
</a> </a>
<a href="https://github.com/fscarmen" title="fscarmen"> <a href="https://github.com/fscarmen" title="fscarmen">
<img src="https://avatars.githubusercontent.com/u/62703343?v=4" width="50;" alt="fscarmen"/> <img src="https://avatars.githubusercontent.com/u/62703343?v=4" width="50;" alt="fscarmen"/>
</a> </a>
<a href="https://github.com/iilemon" title="Sean"> <a href="https://github.com/ch8o" title="no-name-now">
<img src="https://avatars.githubusercontent.com/u/33201711?v=4" width="50;" alt="Sean"/> <img src="https://avatars.githubusercontent.com/u/9103372?v=4" width="50;" alt="no-name-now"/>
</a> </a>
<a href="https://github.com/Es-dese" title="Esdese"> <a href="https://github.com/lvgj-stack" title="Ko no dio">
<img src="https://avatars.githubusercontent.com/u/71542548?v=4" width="50;" alt="Esdese"/> <img src="https://avatars.githubusercontent.com/u/38449861?v=4" width="50;" alt="Ko no dio"/>
</a> </a>
<a href="https://github.com/GreenTeodoro839" title="GreenTeodoro839"> <a href="https://github.com/Creling" title="Creling">
<img src="https://avatars.githubusercontent.com/u/77104800?v=4" width="50;" alt="GreenTeodoro839"/> <img src="https://avatars.githubusercontent.com/u/43109504?v=4" width="50;" alt="Creling"/>
</a> </a>
<a href="https://github.com/techotaku" title="Ian Li"> <a href="https://github.com/coreff" title="Core F">
<img src="https://avatars.githubusercontent.com/u/1948179?v=4" width="50;" alt="Ian Li"/> <img src="https://avatars.githubusercontent.com/u/38347122?v=4" width="50;" alt="Core F"/>
</a> </a>
<a href="https://github.com/KorenKrita" title="KorenKrita"> <a href="https://github.com/colour93" title="玖叁">
<img src="https://avatars.githubusercontent.com/u/22239339?v=4" width="50;" alt="KorenKrita"/> <img src="https://avatars.githubusercontent.com/u/64313711?v=4" width="50;" alt="玖叁"/>
</a>
<a href="https://github.com/MartijnLindeman" title="Martijn Lindeman">
<img src="https://avatars.githubusercontent.com/u/78365708?v=4" width="50;" alt="Martijn Lindeman"/>
</a>
<a href="https://github.com/nickfox-taterli" title="Tater Li">
<img src="https://avatars.githubusercontent.com/u/19658596?v=4" width="50;" alt="Tater Li"/>
</a>
<a href="https://github.com/hmsjy2017" title="Tony">
<img src="https://avatars.githubusercontent.com/u/42692274?v=4" width="50;" alt="Tony"/>
</a>
<a href="https://github.com/adminsama" title="adminsama">
<img src="https://avatars.githubusercontent.com/u/60880076?v=4" width="50;" alt="adminsama"/>
</a>
<a href="https://github.com/acgpiano" title="Acgpiano">
<img src="https://avatars.githubusercontent.com/u/15900800?v=4" width="50;" alt="Acgpiano"/>
</a>
<a href="https://github.com/guoyongchang" title="guoyongchang">
<img src="https://avatars.githubusercontent.com/u/10484506?v=4" width="50;" alt="guoyongchang"/>
</a>
<a href="https://github.com/yuanweize" title="I">
<img src="https://avatars.githubusercontent.com/u/30067203?v=4" width="50;" alt="I"/>
</a>
<a href="https://github.com/nap0o" title="nap0o">
<img src="https://avatars.githubusercontent.com/u/144927971?v=4" width="50;" alt="nap0o"/>
</a>
<a href="https://github.com/unclezs" title="unclezs">
<img src="https://avatars.githubusercontent.com/u/42318775?v=4" width="50;" alt="unclezs"/>
</a> </a>
<a href="https://github.com/ysicing" title="缘生"> <a href="https://github.com/ysicing" title="缘生">
<img src="https://avatars.githubusercontent.com/u/8605565?v=4" width="50;" alt="缘生"/> <img src="https://avatars.githubusercontent.com/u/8605565?v=4" width="50;" alt="缘生"/>
</a> </a>
<a href="https://github.com/colour93" title="玖叁"> <a href="https://github.com/unclezs" title="unclezs">
<img src="https://avatars.githubusercontent.com/u/64313711?v=4" width="50;" alt="玖叁"/> <img src="https://avatars.githubusercontent.com/u/42318775?v=4" width="50;" alt="unclezs"/>
</a>
<a href="https://github.com/nap0o" title="nap0o">
<img src="https://avatars.githubusercontent.com/u/144927971?v=4" width="50;" alt="nap0o"/>
</a>
<a href="https://github.com/yuanweize" title="I">
<img src="https://avatars.githubusercontent.com/u/30067203?v=4" width="50;" alt="I"/>
</a>
<a href="https://github.com/guoyongchang" title="guoyongchang">
<img src="https://avatars.githubusercontent.com/u/10484506?v=4" width="50;" alt="guoyongchang"/>
</a>
<a href="https://github.com/acgpiano" title="Acgpiano">
<img src="https://avatars.githubusercontent.com/u/15900800?v=4" width="50;" alt="Acgpiano"/>
</a>
<a href="https://github.com/adminsama" title="adminsama">
<img src="https://avatars.githubusercontent.com/u/60880076?v=4" width="50;" alt="adminsama"/>
</a>
<a href="https://github.com/hmsjy2017" title="Tony">
<img src="https://avatars.githubusercontent.com/u/42692274?v=4" width="50;" alt="Tony"/>
</a>
<a href="https://github.com/nickfox-taterli" title="Tater Li">
<img src="https://avatars.githubusercontent.com/u/19658596?v=4" width="50;" alt="Tater Li"/>
</a>
<a href="https://github.com/MartijnLindeman" title="Martijn Lindeman">
<img src="https://avatars.githubusercontent.com/u/78365708?v=4" width="50;" alt="Martijn Lindeman"/>
</a>
<a href="https://github.com/KorenKrita" title="KorenKrita">
<img src="https://avatars.githubusercontent.com/u/22239339?v=4" width="50;" alt="KorenKrita"/>
</a>
<a href="https://github.com/techotaku" title="Ian Li">
<img src="https://avatars.githubusercontent.com/u/1948179?v=4" width="50;" alt="Ian Li"/>
</a>
<a href="https://github.com/GreenTeodoro839" title="GreenTeodoro839">
<img src="https://avatars.githubusercontent.com/u/77104800?v=4" width="50;" alt="GreenTeodoro839"/>
</a>
<a href="https://github.com/Es-dese" title="Esdese">
<img src="https://avatars.githubusercontent.com/u/71542548?v=4" width="50;" alt="Esdese"/>
</a>
<a href="https://github.com/wwng2333" title="Crazy">
<img src="https://avatars.githubusercontent.com/u/17147265?v=4" width="50;" alt="Crazy"/>
</a><!--GAMFC_DELIMITER_END--> </a><!--GAMFC_DELIMITER_END-->
## Star History ## Star History
+25 -4
View File
@@ -1,11 +1,13 @@
package controller package controller
import ( import (
"github.com/gin-gonic/gin"
"github.com/naiba/nezha/pkg/mygin"
"github.com/naiba/nezha/service/singleton"
"strconv" "strconv"
"strings" "strings"
"github.com/gin-gonic/gin"
"github.com/naiba/nezha/pkg/mygin"
"github.com/naiba/nezha/service/singleton"
) )
type apiV1 struct { type apiV1 struct {
@@ -25,7 +27,8 @@ func (v *apiV1) serve() {
})) }))
r.GET("/server/list", v.serverList) r.GET("/server/list", v.serverList)
r.GET("/server/details", v.serverDetails) r.GET("/server/details", v.serverDetails)
mr := v.r.Group("monitor")
mr.GET("/:id", v.monitorHistoriesById)
} }
// serverList 获取服务器列表 不传入Query参数则获取全部 // serverList 获取服务器列表 不传入Query参数则获取全部
@@ -65,3 +68,21 @@ func (v *apiV1) serverDetails(c *gin.Context) {
} }
c.JSON(200, singleton.ServerAPI.GetAllStatus()) c.JSON(200, singleton.ServerAPI.GetAllStatus())
} }
func (v *apiV1) monitorHistoriesById(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
c.AbortWithStatusJSON(400, gin.H{"code": 400, "message": "id参数错误"})
return
}
server, ok := singleton.ServerList[id]
if !ok {
c.AbortWithStatusJSON(404, gin.H{
"code": 404,
"message": "id不存在",
})
return
}
c.JSON(200, singleton.MonitorAPI.GetMonitorHistories(map[string]any{"server_id": server.ID}))
}
+115
View File
@@ -5,6 +5,7 @@ import (
"log" "log"
"net/http" "net/http"
"regexp" "regexp"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -48,6 +49,9 @@ func (cp *commonPage) serve() {
cr.Use(cp.checkViewPassword) // 前端查看密码鉴权 cr.Use(cp.checkViewPassword) // 前端查看密码鉴权
cr.GET("/", cp.home) cr.GET("/", cp.home)
cr.GET("/service", cp.service) cr.GET("/service", cp.service)
// TODO: 界面直接跳转使用该接口
cr.GET("/network/:id", cp.network)
cr.GET("/network", cp.network)
cr.GET("/ws", cp.ws) cr.GET("/ws", cp.ws)
cr.POST("/terminal", cp.createTerminal) cr.POST("/terminal", cp.createTerminal)
} }
@@ -114,6 +118,11 @@ func (p *commonPage) service(c *gin.Context) {
var statsStore map[uint64]model.CycleTransferStats var statsStore map[uint64]model.CycleTransferStats
copier.Copy(&stats, singleton.ServiceSentinelShared.LoadStats()) copier.Copy(&stats, singleton.ServiceSentinelShared.LoadStats())
copier.Copy(&statsStore, singleton.AlertsCycleTransferStatsStore) copier.Copy(&statsStore, singleton.AlertsCycleTransferStatsStore)
for k, service := range stats {
if !service.Monitor.EnableShowInService {
delete(stats, k)
}
}
return []interface { return []interface {
}{ }{
stats, statsStore, stats, statsStore,
@@ -127,6 +136,112 @@ func (p *commonPage) service(c *gin.Context) {
})) }))
} }
func (cp *commonPage) network(c *gin.Context) {
var (
monitorHistory *model.MonitorHistory
servers []*model.Server
serverIdsWithMonitor []uint64
monitorInfos = []byte("{}")
id uint64
)
if len(singleton.SortedServerList) > 0 {
id = singleton.SortedServerList[0].ID
}
if err := singleton.DB.Model(&model.MonitorHistory{}).Select("monitor_id, server_id").
Where("monitor_id != 0 and server_id != 0").Limit(1).First(&monitorHistory).Error; err != nil {
mygin.ShowErrorPage(c, mygin.ErrInfo{
Code: http.StatusForbidden,
Title: "请求失败",
Msg: "请求参数有误:" + "server monitor history not found",
Link: "/",
Btn: "返回重试",
}, true)
return
} else {
if monitorHistory == nil || monitorHistory.ServerID == 0 {
if len(singleton.SortedServerList) > 0 {
id = singleton.SortedServerList[0].ID
}
} else {
id = monitorHistory.ServerID
}
}
idStr := c.Param("id")
if idStr != "" {
var err error
id, err = strconv.ParseUint(idStr, 10, 64)
if err != nil {
mygin.ShowErrorPage(c, mygin.ErrInfo{
Code: http.StatusForbidden,
Title: "请求失败",
Msg: "请求参数有误:" + err.Error(),
Link: "/",
Btn: "返回重试",
}, true)
return
}
_, ok := singleton.ServerList[id]
if !ok {
mygin.ShowErrorPage(c, mygin.ErrInfo{
Code: http.StatusForbidden,
Title: "请求失败",
Msg: "请求参数有误:" + "server id not found",
Link: "/",
Btn: "返回重试",
}, true)
return
}
}
monitorHistories := singleton.MonitorAPI.GetMonitorHistories(map[string]any{"server_id": id})
monitorInfos, _ = utils.Json.Marshal(monitorHistories)
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
_, isViewPasswordVerfied := c.Get(model.CtxKeyViewPasswordVerified)
if err := singleton.DB.Model(&model.MonitorHistory{}).
Select("distinct(server_id)").
Where("server_id != 0").
Find(&serverIdsWithMonitor).
Error; err != nil {
mygin.ShowErrorPage(c, mygin.ErrInfo{
Code: http.StatusForbidden,
Title: "请求失败",
Msg: "请求参数有误:" + "no server with monitor histories",
Link: "/",
Btn: "返回重试",
}, true)
return
}
if isMember || isViewPasswordVerfied {
for _, server := range singleton.SortedServerList {
for _, id := range serverIdsWithMonitor {
if server.ID == id {
servers = append(servers, server)
}
}
}
} else {
for _, server := range singleton.SortedServerListForGuest {
for _, id := range serverIdsWithMonitor {
if server.ID == id {
servers = append(servers, server)
}
}
}
}
serversBytes, _ := utils.Json.Marshal(Data{
Now: time.Now().Unix() * 1000,
Servers: servers,
})
c.HTML(http.StatusOK, "theme-"+singleton.Conf.Site.Theme+"/network", mygin.CommonEnvironment(c, gin.H{
"Servers": string(serversBytes),
"MonitorInfos": string(monitorInfos),
"CustomCode": singleton.Conf.Site.CustomCode,
"MaxTCPPingValue": singleton.Conf.MaxTCPPingValue,
}))
}
func (cp *commonPage) getServerStat(c *gin.Context) ([]byte, error) { func (cp *commonPage) getServerStat(c *gin.Context) ([]byte, error) {
v, err, _ := cp.requestGroup.Do("serverStats", func() (any, error) { v, err, _ := cp.requestGroup.Do("serverStats", func() (any, error) {
singleton.SortedServerLock.RLock() singleton.SortedServerLock.RLock()
+18
View File
@@ -11,6 +11,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
"gorm.io/gorm"
"github.com/naiba/nezha/model" "github.com/naiba/nezha/model"
"github.com/naiba/nezha/pkg/mygin" "github.com/naiba/nezha/pkg/mygin"
@@ -185,7 +186,17 @@ func (ma *memberAPI) delete(c *gin.Context) {
var err error var err error
switch c.Param("model") { switch c.Param("model") {
case "server": case "server":
err := singleton.DB.Transaction(func(tx *gorm.DB) error {
err = singleton.DB.Unscoped().Delete(&model.Server{}, "id = ?", id).Error err = singleton.DB.Unscoped().Delete(&model.Server{}, "id = ?", id).Error
if err != nil {
return err
}
err = singleton.DB.Unscoped().Delete(&model.MonitorHistory{}, "server_id = ?", id).Error
if err != nil {
return err
}
return nil
})
if err == nil { if err == nil {
// 删除服务器 // 删除服务器
singleton.ServerLock.Lock() singleton.ServerLock.Lock()
@@ -383,6 +394,7 @@ type monitorForm struct {
MaxLatency float32 MaxLatency float32
LatencyNotify string LatencyNotify string
EnableTriggerTask string EnableTriggerTask string
EnableShowInService string
FailTriggerTasksRaw string FailTriggerTasksRaw string
RecoverTriggerTasksRaw string RecoverTriggerTasksRaw string
} }
@@ -404,6 +416,7 @@ func (ma *memberAPI) addOrEditMonitor(c *gin.Context) {
m.LatencyNotify = mf.LatencyNotify == "on" m.LatencyNotify = mf.LatencyNotify == "on"
m.MinLatency = mf.MinLatency m.MinLatency = mf.MinLatency
m.MaxLatency = mf.MaxLatency m.MaxLatency = mf.MaxLatency
m.EnableShowInService = mf.EnableShowInService == "on"
m.EnableTriggerTask = mf.EnableTriggerTask == "on" m.EnableTriggerTask = mf.EnableTriggerTask == "on"
m.RecoverTriggerTasksRaw = mf.RecoverTriggerTasksRaw m.RecoverTriggerTasksRaw = mf.RecoverTriggerTasksRaw
m.FailTriggerTasksRaw = mf.FailTriggerTasksRaw m.FailTriggerTasksRaw = mf.FailTriggerTasksRaw
@@ -427,6 +440,11 @@ func (ma *memberAPI) addOrEditMonitor(c *gin.Context) {
err = singleton.DB.Save(&m).Error err = singleton.DB.Save(&m).Error
} }
} }
if m.Cover == 0 {
err = singleton.DB.Unscoped().Delete(&model.MonitorHistory{}, "monitor_id = ? and server_id in (?)", m.ID, strings.Split(m.SkipServersRaw[1:len(m.SkipServersRaw)-1], ",")).Error
} else {
err = singleton.DB.Unscoped().Delete(&model.MonitorHistory{}, "monitor_id = ? and server_id not in (?)", m.ID, strings.Split(m.SkipServersRaw[1:len(m.SkipServersRaw)-1], ",")).Error
}
} }
if err == nil { if err == nil {
err = singleton.ServiceSentinelShared.OnMonitorUpdate(m) err = singleton.ServiceSentinelShared.OnMonitorUpdate(m)
+12 -2
View File
@@ -49,10 +49,20 @@ func DispatchTask(serviceSentinelDispatchBus <-chan model.Monitor) {
workedServerIndex++ workedServerIndex++
continue continue
} }
// 找到合适机器执行任务,跳出循环 if task.Cover == model.MonitorCoverIgnoreAll && task.SkipServers[singleton.SortedServerList[workedServerIndex].ID] {
singleton.SortedServerList[workedServerIndex].TaskStream.Send(task.PB()) singleton.SortedServerList[workedServerIndex].TaskStream.Send(task.PB())
workedServerIndex++ workedServerIndex++
break continue
}
if task.Cover == model.MonitorCoverAll && !task.SkipServers[singleton.SortedServerList[workedServerIndex].ID] {
singleton.SortedServerList[workedServerIndex].TaskStream.Send(task.PB())
workedServerIndex++
continue
}
// 找到合适机器执行任务,跳出循环
// singleton.SortedServerList[workedServerIndex].TaskStream.Send(task.PB())
// workedServerIndex++
// break
} }
singleton.SortedServerLock.RUnlock() singleton.SortedServerLock.RUnlock()
} }
+2 -2
View File
@@ -24,7 +24,7 @@ require (
github.com/spf13/viper v1.17.0 github.com/spf13/viper v1.17.0
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
github.com/xanzy/go-gitlab v0.94.0 github.com/xanzy/go-gitlab v0.94.0
golang.org/x/crypto v0.15.0 golang.org/x/crypto v0.17.0
golang.org/x/oauth2 v0.14.0 golang.org/x/oauth2 v0.14.0
golang.org/x/sync v0.5.0 golang.org/x/sync v0.5.0
golang.org/x/text v0.14.0 golang.org/x/text v0.14.0
@@ -80,7 +80,7 @@ require (
golang.org/x/arch v0.3.0 // indirect golang.org/x/arch v0.3.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.18.0 // indirect golang.org/x/net v0.18.0 // indirect
golang.org/x/sys v0.14.0 // indirect golang.org/x/sys v0.15.0 // indirect
golang.org/x/time v0.3.0 // indirect golang.org/x/time v0.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect
+6 -6
View File
@@ -339,8 +339,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -478,12 +478,12 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+8
View File
@@ -110,6 +110,8 @@ type Config struct {
v *viper.Viper v *viper.Viper
IgnoredIPNotificationServerIDs map[uint64]bool // [ServerID] -> bool(值为true代表当前ServerID在特定服务器列表内) IgnoredIPNotificationServerIDs map[uint64]bool // [ServerID] -> bool(值为true代表当前ServerID在特定服务器列表内)
MaxTCPPingValue int32
AvgPingCount int
} }
// Read 读取配置文件并应用 // Read 读取配置文件并应用
@@ -144,6 +146,12 @@ func (c *Config) Read(path string) error {
if c.Location == "" { if c.Location == "" {
c.Location = "Asia/Shanghai" c.Location = "Asia/Shanghai"
} }
if c.MaxTCPPingValue == 0 {
c.MaxTCPPingValue = 300
}
if c.AvgPingCount == 0 {
c.AvgPingCount = 2
}
c.updateIgnoredIPNotificationID() c.updateIgnoredIPNotificationID()
return nil return nil
+4 -2
View File
@@ -4,10 +4,11 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/naiba/nezha/pkg/utils"
pb "github.com/naiba/nezha/proto"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
"gorm.io/gorm" "gorm.io/gorm"
"github.com/naiba/nezha/pkg/utils"
pb "github.com/naiba/nezha/proto"
) )
const ( const (
@@ -49,6 +50,7 @@ type Monitor struct {
Cover uint8 Cover uint8
EnableTriggerTask bool `gorm:"default: false"` EnableTriggerTask bool `gorm:"default: false"`
EnableShowInService bool `gorm:"default: false"`
FailTriggerTasksRaw string `gorm:"default:'[]'"` FailTriggerTasksRaw string `gorm:"default:'[]'"`
RecoverTriggerTasksRaw string `gorm:"default:'[]'"` RecoverTriggerTasksRaw string `gorm:"default:'[]'"`
FailTriggerTasks []uint64 `gorm:"-" json:"-"` // 失败时执行的触发任务id FailTriggerTasks []uint64 `gorm:"-" json:"-"` // 失败时执行的触发任务id
+9
View File
@@ -1,9 +1,18 @@
package model package model
const (
Cycle = iota
Hour
Day
Week
Month
)
// MonitorHistory 历史监控记录 // MonitorHistory 历史监控记录
type MonitorHistory struct { type MonitorHistory struct {
Common Common
MonitorID uint64 MonitorID uint64
ServerID uint64
AvgDelay float32 // 平均延迟,毫秒 AvgDelay float32 // 平均延迟,毫秒
Up uint64 // 检查状态良好计数 Up uint64 // 检查状态良好计数
Down uint64 // 检查状态异常计数 Down uint64 // 检查状态异常计数
+6
View File
@@ -609,3 +609,9 @@ other = "Hide for Guest"
[Menu] [Menu]
other = "Menu" other = "Menu"
[NetworkSpiter]
other = "Network"
[EnableShowInService]
other = "Enable Show in Service"
+6
View File
@@ -609,3 +609,9 @@ other = "Ocultar para Invitados"
[Menu] [Menu]
other = "Menú" other = "Menú"
[NetworkSpiter]
other = "Red"
[EnableShowInService]
other = "Mostrar en servicio"
+6
View File
@@ -609,3 +609,9 @@ other = "对游客隐藏"
[Menu] [Menu]
other = "菜单" other = "菜单"
[NetworkSpiter]
other = "网络"
[EnableShowInService]
other = "在服务中显示"
+6
View File
@@ -609,3 +609,9 @@ other = "對遊客隱藏"
[Menu] [Menu]
other = "菜單" other = "菜單"
[NetworkSpiter]
other = "網絡"
[EnableShowInService]
other = "在服務中顯示"
+5
View File
@@ -339,6 +339,11 @@ function addOrEditMonitor(monitor) {
modal.find("select[name=Type]").val(monitor ? monitor.Type : 1); modal.find("select[name=Type]").val(monitor ? monitor.Type : 1);
modal.find("select[name=Cover]").val(monitor ? monitor.Cover : 0); modal.find("select[name=Cover]").val(monitor ? monitor.Cover : 0);
modal.find("input[name=NotificationTag]").val(monitor ? monitor.NotificationTag : null); modal.find("input[name=NotificationTag]").val(monitor ? monitor.NotificationTag : null);
if (monitor && monitor.EnableShowInService) {
modal.find(".ui.nb-show-in-service.checkbox").checkbox("set checked")
} else {
modal.find(".ui.nb-show-in-service.checkbox").checkbox("set unchecked")
}
if (monitor && monitor.Notify) { if (monitor && monitor.Notify) {
modal.find(".ui.nb-notify.checkbox").checkbox("set checked"); modal.find(".ui.nb-notify.checkbox").checkbox("set checked");
} else { } else {
+1 -1
View File
@@ -10,7 +10,7 @@
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/semantic-ui/2.4.1/semantic.min.js"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/semantic-ui/2.4.1/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://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/vue/2.6.14/vue.min.js"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/vue/2.6.14/vue.min.js"></script>
<script src="/static/main.js?v20230614"></script> <script src="/static/main.js?v20240213"></script>
<script> <script>
(function () { (function () {
updateLang({{.LANG }}); updateLang({{.LANG }});
+1
View File
@@ -15,6 +15,7 @@
{{else}} {{else}}
<a class='item{{if eq .MatchedPath "/"}} active{{end}}' href="/"><i class="home icon"></i>{{tr "Home"}}</a> <a class='item{{if eq .MatchedPath "/"}} active{{end}}' href="/"><i class="home icon"></i>{{tr "Home"}}</a>
<a class='item{{if eq .MatchedPath "/service"}} active{{end}}' href="/service"><i class="rss icon"></i>{{tr "Services"}}</a> <a class='item{{if eq .MatchedPath "/service"}} active{{end}}' href="/service"><i class="rss icon"></i>{{tr "Services"}}</a>
<a class='item{{if eq .MatchedPath "/network"}} active{{end}}' href="/network"><i class="server icon"></i>{{tr "NetworkSpiter"}}</a>
{{end}} {{end}}
<div class="right menu"> <div class="right menu">
<div class="item"> <div class="item">
+7
View File
@@ -24,6 +24,12 @@
<option value="3">TCP-Ping</option> <option value="3">TCP-Ping</option>
</select> </select>
</div> </div>
<div class="field">
<div class="ui nb-show-in-service checkbox">
<input name="EnableShowInService" type="checkbox" tabindex="0" class="hidden" />
<label>{{tr "EnableShowInService"}}</label>
</div>
</div>
<div class="field"> <div class="field">
<label>{{tr "Duration"}}</label> <label>{{tr "Duration"}}</label>
<input type="number" name="Duration" placeholder="{{tr "Seconds"}}" /> <input type="number" name="Duration" placeholder="{{tr "Seconds"}}" />
@@ -110,3 +116,4 @@
</div> </div>
</div> </div>
{{end}} {{end}}
+1 -1
View File
@@ -12,7 +12,7 @@
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/semantic-ui/2.4.1/semantic.min.js"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/semantic-ui/2.4.1/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://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/vue/2.6.14/vue.min.js"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/vue/2.6.14/vue.min.js"></script>
<script src="/static/main.js?v20230614"></script> <script src="/static/main.js?v20240213"></script>
<script> <script>
(function () { (function () {
updateLang({{.LANG }}); updateLang({{.LANG }});
+1
View File
@@ -39,6 +39,7 @@
<ul> <ul>
<li><a href="/">{{tr "Home"}}</a></li> <li><a href="/">{{tr "Home"}}</a></li>
<li><a href="/service">{{tr "Services"}}</a></li> <li><a href="/service">{{tr "Services"}}</a></li>
<li><a href="/network">{{tr "NetworkSpiter"}}</a></li>
{{if .Admin}} {{if .Admin}}
<li><a href="/server">{{tr "AdminPanel"}}</a></li> <li><a href="/server">{{tr "AdminPanel"}}</a></li>
{{else}} {{else}}
+268
View File
@@ -0,0 +1,268 @@
{{define "theme-daynight/network"}}
<!doctype html>
<html lang="{{.Conf.Language}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>{{.Title}}</title>
<link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" />
<link rel="stylesheet" href="/static/theme-daynight/css/main.css?v202108042286">
<link href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/jquery/3.6.0/jquery.min.js"></script>
<script src="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/echarts/5.3.0-rc.1/echarts.min.js"></script>
{{if ts .CustomCode}}
{{.CustomCode|safe}}
{{end}}
</head>
<body data-theme="light" data-gridlist="grid">
<header>
<section class="nav-bar clearfix">
<figure class="logo">
<a href="/">
<img src="/static/logo.svg?v20210804" alt='{{tr "NezhaMonitoring"}}' width="50" height="50">
</a>
<a href="/">{{.Conf.Site.Brand}}</a>
</figure>
<div class="icon-container">
<div class="row cf">
<div class="three col">
<div class="hamburger" id="hamburger-icon"><span class="line"></span><span
class="line"></span><span class="line"></span></div>
</div>
</div>
</div>
<nav class="nav-menu">
<ul>
<li><a href="/">{{tr "Home"}}</a></li>
<li><a href="/service">{{tr "Services"}}</a></li>
<li><a href="/network">{{tr "NetworkSpiter"}}</a></li>
{{if .Admin}}
<li><a href="/server">{{tr "AdminPanel"}}</a></li>
{{else}}
<li><a href="/login">{{tr "Login"}}</a></li>
{{end}}
</ul>
</nav>
</section>
</header>
<main>
<div id="network">
<div class="server-info-container" v-for='server in servers' :id="server.ID" style="font-size: .6em">
<div class="info-body" @click="redirectNetwork(server.ID)">
<ul class="server-info-body-container">
<li>
<h3>@#server.Name#@</h3>
</li>
<li><img :src="'/static/theme-daynight/img/flag/'+(server.Host&&server.Host.CountryCode?server.Host.CountryCode.toUpperCase():'CN')+'.png'"
:title="server.Host.CountryCode.toUpperCase()" /></li>
</ul>
</div>
</div>
</div>
<div class="network-chart" style="height: 800px;overflow: hidden">
<div id="monitor-info-container" style="height: 520px;max-width: 1400px">
</div>
</div>
<div class="sidebar-container">
<ul>
<li><i class="fas fa-sun" title='{{tr "LightMode"}}'></i><span>{{tr "LightMode"}}</span></li>
<li><i class="fas fa-moon" title='{{tr "DarkMode"}}'></i><span>{{tr "DarkMode"}}</span></li>
<li><i class="fas fa-th" title='{{tr "GridLayout"}}'></i><span>{{tr "GridLayout"}}</span></li>
<li><i class="fas fa-list-ul" title='{{tr "ListLayout"}}'></i><span>{{tr "ListLayout"}}</span></li>
</ul>
</div>
</main>
<section class="dark-light-toggle">
<label class="switcher">
<input type="checkbox" name="theme" id="dark-light" />
<div>
<i class="fas fa-adjust"></i>
</div>
</label>
</section>
<!-- Back to top button -->
<a id="back-to-top"></a>
<footer>
<div class="footer-container">
<div><a href="https://github.com/naiba/nezha" target="_blank">Powered by {{tr "NezhaMonitoring"}} · {{.Version}}</a>
<p>&copy; <span id="copyright-date">
<script>document.getElementById('copyright-date').appendChild(document.createTextNode(new Date().getFullYear()))</script>
</span> · <a href="https://blog.jackiesung.com" target="_blank">Theme designed by Jackie Sung</a>
</p>
</div>
</div>
</footer>
<script src="/static/theme-daynight/js/main.js?v202102012266"></script>
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/vue/2.6.14/vue.min.js"></script>
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/limonte-sweetalert2/11.4.4/sweetalert2.all.min.js"></script>
<script>
const monitorInfo = JSON.parse('{{.MonitorInfos}}');
const initData = JSON.parse('{{.Servers}}').servers;
let MaxTCPPingValue = {{.MaxTCPPingValue}};
if (MaxTCPPingValue == null) {
MaxTCPPingValue = 300;
}
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('monitor-info-container'));
// 使用刚指定的配置项和数据显示图表。
var statusCards = new Vue({
el: '#network',
delimiters: ['@#', '#@'],
data: {
servers: initData,
cache: [],
option: {
tooltip: {
trigger: 'axis',
position: function (pt) {
return [pt[0], '10%'];
},
formatter: function(params){
let result = params[0].axisValueLabel + "<br />";
params.forEach(function(item){
result += item.marker + item.seriesName + ": " + item.value[1].toFixed(2) + " ms<br />";
})
return result;
},
confine: true,
transitionDuration: 0
},
title: {
left: 'center',
text: "",
textStyle: {}
},
legend: {
top: '5%',
data: [],
textStyle: {
fontSize: 14
}
},
toolbox: {
feature: {
dataZoom: {
yAxisIndex: 'none'
},
restore: {},
saveAsImage: {}
}
},
dataZoom: [
{
start: 94,
end: 100
}
],
xAxis: {
type: 'time',
boundaryGap: false
},
yAxis: {
type: 'value',
boundaryGap: [0, '100%']
},
series: [],
}
},
mounted() {
this.DarkMode();
this.parseMonitorInfo(monitorInfo);
},
methods: {
DarkMode() {
const hour = new Date(Date.now()).getHours()
if (hour > 17 || hour < 4) {
document.querySelector("input[name=theme]").checked = true;
document.getElementsByTagName("BODY")[0].setAttribute('data-theme', 'dark');
document.getElementById("monitor-info-container").style.backgroundColor = "#1E1E1E";
}
},
redirectNetwork(id) {
this.getMonitorHistory(id)
.then(function(monitorInfo) {
var vm = network.__vue__;
vm.parseMonitorInfo(monitorInfo);
})
.catch(function(error){
window.location.href = "/404";
})
},
getMonitorHistory(id) {
return $.ajax({
url: "/api/v1/monitor/"+id,
method: "GET"
});
},
parseMonitorInfo(monitorInfo) {
let tSeries = [];
let tLegendData = [];
for (let i = 0; i < monitorInfo.result.length; i++) {
let loss = 0;
let data = [];
for (let j = 0; j < monitorInfo.result[i].created_at.length; j++) {
avgDelay = monitorInfo.result[i].avg_delay[j];
if (avgDelay > 0.9 * MaxTCPPingValue) {
loss += 1
}
data.push([monitorInfo.result[i].created_at[j], avgDelay]);
}
lossRate = ((loss / monitorInfo.result[i].created_at.length) * 100).toFixed(1);
legendName = monitorInfo.result[i].monitor_name +" "+ lossRate + "%";
tLegendData.push(legendName);
tSeries.push({
name: legendName,
type: 'line',
smooth: true,
symbol: 'none',
data: data
});
}
this.option.title.text = monitorInfo.result[0].server_name;
this.option.series = tSeries;
this.option.legend.data = tLegendData;
const hour = new Date(Date.now()).getHours()
if (hour > 17 || hour < 4) {
this.option.legend.textStyle.color = "#F1F1F2";
this.option.title.textStyle.color = "#ccc";
}
myChart.clear();
myChart.setOption(this.option);
}
}
});
</script>
</body>
<style>
#network {
display: grid;
/*grid-template-columns: repeat(5, 1fr);*/
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
grid-gap: 1em;
width: 100%;
width: calc(100vw - 6em);
max-width: 1400px;
margin: 1em auto;
align-content: start;
}
#monitor-info-container{
margin: 0em auto;
align-content: start;
background-color: #F1F1F2;
}
</style>
</html>
{{end}}
+1
View File
@@ -43,6 +43,7 @@
<ul> <ul>
<li><a href="/">{{tr "Home"}}</a></li> <li><a href="/">{{tr "Home"}}</a></li>
<li><a href="/service">{{tr "Services"}}</a></li> <li><a href="/service">{{tr "Services"}}</a></li>
<li><a href="/network">{{tr "NetworkSpiter"}}</a></li>
{{if .Admin}} {{if .Admin}}
<li><a href="/server">{{tr "AdminPanel"}}</a></li> <li><a href="/server">{{tr "AdminPanel"}}</a></li>
{{else}} {{else}}
+219
View File
@@ -0,0 +1,219 @@
{{define "theme-default/network"}}
{{template "common/header" .}}
{{if ts .CustomCode}}
{{.CustomCode|safe}}
{{end}}
{{template "common/menu" .}}
<div class="nb-container" id="app">
<div class="ui container">
<div class="service-status">
<table class="ui celled table">
<button class="ui nezha-primary-btn button"
v-for="server in servers"
style="margin-top: 3px"
@click="redirectNetwork(server.ID)">
@#server.Name#@ &nbsp;<i :class="server.Host.CountryCode + ' flag'"></i>
</button>
</table>
</div>
</div>
<div class="ui container" style="max-width: 95vw">
<div ref="chartDom" style="border-radius: 28px; margin-top: 15px;height: 520px;max-width: 1400px;overflow: hidden"></div>
</div>
</div>
{{template "common/footer" .}}
<script src="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/echarts/5.3.0-rc.1/echarts.min.js"></script>
<script>
const monitorInfo = JSON.parse('{{.MonitorInfos}}');
const initData = JSON.parse('{{.Servers}}').servers;
let MaxTCPPingValue = {{.MaxTCPPingValue}};
if (MaxTCPPingValue == null) {
MaxTCPPingValue = 300;
}
new Vue({
el: '#app',
delimiters: ['@#', '#@'],
data: {
servers: initData,
option: {
tooltip: {
trigger: 'axis',
position: function (pt) {
return [pt[0], '10%'];
},
formatter: function(params){
let result = params[0].axisValueLabel + "<br />";
params.forEach(function(item){
result += item.marker + item.seriesName + ": " + item.value[1].toFixed(2) + " ms<br />";
})
return result;
},
confine: true,
transitionDuration: 0
},
title: {
left: 'center',
text: "",
textStyle: {}
},
legend: {
top: '5%',
data: [],
textStyle: {
fontSize: 14
}
},
backgroundColor: 'rgba(255, 255, 255, 0.8)',
toolbox: {
feature: {
dataZoom: {
yAxisIndex: 'none'
},
restore: {},
saveAsImage: {}
}
},
dataZoom: [
{
start: 94,
end: 100
}
],
xAxis: {
type: 'time',
boundaryGap: false
},
yAxis: {
type: 'value',
boundaryGap: [0, '100%']
},
series: [],
},
chartOnOff: true,
},
mounted() {
this.renderChart();
this.parseMonitorInfo(monitorInfo);
},
methods: {
getFontLogoClass(str) {
if (["almalinux",
"alpine",
"aosc",
"apple",
"archlinux",
"archlabs",
"artix",
"budgie",
"centos",
"coreos",
"debian",
"deepin",
"devuan",
"docker",
"elementary",
"fedora",
"ferris",
"flathub",
"freebsd",
"gentoo",
"gnu-guix",
"illumos",
"kali-linux",
"linuxmint",
"mageia",
"mandriva",
"manjaro",
"nixos",
"openbsd",
"opensuse",
"pop-os",
"raspberry-pi",
"redhat",
"rocky-linux",
"sabayon",
"slackware",
"snappy",
"solus",
"tux",
"ubuntu",
"void",
"zorin"].indexOf(str)
> -1) {
return str;
}
if (['openwrt', 'linux', "immortalwrt"].indexOf(str) > -1) {
return 'tux';
}
if (str == 'amazon') {
return 'redhat';
}
if (str == 'arch') {
return 'archlinux';
}
return '';
},
redirectNetwork(id) {
this.getMonitorHistory(id)
.then(function(monitorInfo) {
var vm = app.__vue__;
vm.parseMonitorInfo(monitorInfo);
})
.catch(function(error){
window.location.href = "/404";
})
},
getMonitorHistory(id) {
return $.ajax({
url: "/api/v1/monitor/"+id,
method: "GET"
});
},
parseMonitorInfo(monitorInfo) {
let tSeries = [];
let tLegendData = [];
for (let i = 0; i < monitorInfo.result.length; i++) {
let loss = 0;
let data = [];
for (let j = 0; j < monitorInfo.result[i].created_at.length; j++) {
avgDelay = monitorInfo.result[i].avg_delay[j];
if (avgDelay > 0.9 * MaxTCPPingValue) {
loss += 1
}
data.push([monitorInfo.result[i].created_at[j], avgDelay]);
}
lossRate = ((loss / monitorInfo.result[i].created_at.length) * 100).toFixed(1);
legendName = monitorInfo.result[i].monitor_name +" "+ lossRate + "%";
tLegendData.push(legendName);
tSeries.push({
name: legendName,
type: 'line',
smooth: true,
symbol: 'none',
data: data
});
}
this.option.title.text = monitorInfo.result[0].server_name;
this.option.series = tSeries;
this.option.legend.data = tLegendData;
this.myChart.clear();
this.myChart.setOption(this.option);
},
isWindowsPlatform(str) {
return str.includes('Windows')
},
renderChart() {
this.myChart = echarts.init(this.$refs.chartDom);
this.myChart.setOption(this.option);
},
},
beforeDestroy() {
this.myChart.dispose();
this.myChart = null;
},
});
</script>
{{end}}
@@ -18,6 +18,7 @@
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
<li><a href="/">{{tr "Home" }}</a></li> <li><a href="/">{{tr "Home" }}</a></li>
<li><a href="/service">{{tr "Services" }}</a></li> <li><a href="/service">{{tr "Services" }}</a></li>
<li><a href="/network">{{tr "NetworkSpiter" }}</a></li>
</ul> </ul>
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li class="dropdown"> <li class="dropdown">
@@ -45,3 +46,4 @@
</div> </div>
</div> </div>
{{end}} {{end}}
+1
View File
@@ -28,6 +28,7 @@
<script src="/static/theme-server-status/js/bootstrap.min.js"></script> <script src="/static/theme-server-status/js/bootstrap.min.js"></script>
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/vue/2.6.14/vue.min.js"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/vue/2.6.14/vue.min.js"></script>
<script src="/static/theme-server-status/js/mixin.js"></script> <script src="/static/theme-server-status/js/mixin.js"></script>
<script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/echarts/5.3.0-rc.1/echarts.min.js"></script>
</head> </head>
<body> <body>
{{end}} {{end}}
+217
View File
@@ -0,0 +1,217 @@
{{define "theme-server-status/network"}}
{{template "theme-server-status/header" .}}
<div id="app">
{{template "theme-server-status/content-nav" .}}
<div class="container table-responsive content" style="max-width: 95vw">
<table class="table table-striped table-condensed table-hover">
<button class="ui nezha-primary-btn button"
v-for="server in servers"
style="margin-top: 3px"
@click="redirectNetwork(server.ID)">
@#server.Name#@&nbsp;&nbsp;<i :class="'fi fi-' + server.Host.CountryCode"></i><span class="node-cell-location-text text-uppercase">&nbsp;&nbsp;@#server.Host.CountryCode#@</span>
</button>
</table>
</div>
<div class="container table-responsive content" style="max-width: 95vw">
<div ref="chartDom" style="border-radius: 28px; margin-top: 15px;height: 520px;max-width: 1400px;overflow: hidden"></div>
</div>
{{template "theme-server-status/content-footer" .}}
</div>
<script>
const monitorInfo = JSON.parse('{{.MonitorInfos}}');
const initData = JSON.parse('{{.Servers}}').servers;
let MaxTCPPingValue = {{.MaxTCPPingValue}};
if (MaxTCPPingValue == null) {
MaxTCPPingValue = 300;
}
new Vue({
el: '#app',
delimiters: ['@#', '#@'],
data: {
servers: initData,
option: {
tooltip: {
trigger: 'axis',
position: function (pt) {
return [pt[0], '10%'];
},
formatter: function(params){
let result = params[0].axisValueLabel + "<br />";
params.forEach(function(item){
result += item.marker + item.seriesName + ": " + item.value[1].toFixed(2) + " ms<br />";
})
return result;
},
confine: true,
transitionDuration: 0
},
title: {
left: 'center',
text: "",
textStyle: {}
},
legend: {
top: '5%',
data: [],
textStyle: {
fontSize: 14
}
},
backgroundColor: 'rgba(255, 255, 255, 0.8)',
toolbox: {
feature: {
dataZoom: {
yAxisIndex: 'none'
},
restore: {},
saveAsImage: {}
}
},
dataZoom: [
{
start: 94,
end: 100
}
],
xAxis: {
type: 'time',
boundaryGap: false
},
yAxis: {
type: 'value',
boundaryGap: [0, '100%']
},
series: [],
},
chartOnOff: true,
},
mixins: [mixinsVue],
created() {
this.initTheme();
},
mounted() {
this.renderChart();
this.parseMonitorInfo(monitorInfo);
},
methods: {
getFontLogoClass(str) {
if (["almalinux",
"alpine",
"aosc",
"apple",
"archlinux",
"archlabs",
"artix",
"budgie",
"centos",
"coreos",
"debian",
"deepin",
"devuan",
"docker",
"elementary",
"fedora",
"ferris",
"flathub",
"freebsd",
"gentoo",
"gnu-guix",
"illumos",
"kali-linux",
"linuxmint",
"mageia",
"mandriva",
"manjaro",
"nixos",
"openbsd",
"opensuse",
"pop-os",
"raspberry-pi",
"redhat",
"rocky-linux",
"sabayon",
"slackware",
"snappy",
"solus",
"tux",
"ubuntu",
"void",
"zorin"].indexOf(str)
> -1) {
return str;
}
if (['openwrt', 'linux', "immortalwrt"].indexOf(str) > -1) {
return 'tux';
}
if (str == 'amazon') {
return 'redhat';
}
if (str == 'arch') {
return 'archlinux';
}
return '';
},
redirectNetwork(id) {
this.getMonitorHistory(id)
.then(function(monitorInfo) {
var vm = app.__vue__;
vm.parseMonitorInfo(monitorInfo);
})
.catch(function(error){
window.location.href = "/404";
})
},
getMonitorHistory(id) {
return $.ajax({
url: "/api/v1/monitor/"+id,
method: "GET"
});
},
parseMonitorInfo(monitorInfo) {
let tSeries = [];
let tLegendData = [];
for (let i = 0; i < monitorInfo.result.length; i++) {
let loss = 0;
let data = [];
for (let j = 0; j < monitorInfo.result[i].created_at.length; j++) {
avgDelay = monitorInfo.result[i].avg_delay[j];
if (avgDelay > 0.9 * MaxTCPPingValue) {
loss += 1
}
data.push([monitorInfo.result[i].created_at[j], avgDelay]);
}
lossRate = ((loss / monitorInfo.result[i].created_at.length) * 100).toFixed(1);
legendName = monitorInfo.result[i].monitor_name +" "+ lossRate + "%";
tLegendData.push(legendName);
tSeries.push({
name: legendName,
type: 'line',
smooth: true,
symbol: 'none',
data: data
});
}
this.option.title.text = monitorInfo.result[0].server_name;
this.option.series = tSeries;
this.option.legend.data = tLegendData;
this.myChart.clear();
this.myChart.setOption(this.option);
},
isWindowsPlatform(str) {
return str.includes('Windows')
},
renderChart() {
this.myChart = echarts.init(this.$refs.chartDom);
this.myChart.setOption(this.option);
},
},
beforeDestroy() {
this.myChart.dispose();
this.myChart = null;
},
});
</script>
{{template "theme-server-status/footer" .}}
{{end}}
+2 -2
View File
@@ -38,8 +38,8 @@ $download = "https://github.com/$agentrepo/releases/download/$agenttag/$file"
$nssmdownload="https://github.com/$nssmrepo/releases/download/$nssmtag/nssm.zip" $nssmdownload="https://github.com/$nssmrepo/releases/download/$nssmtag/nssm.zip"
Write-Host "Location:$region,connect directly!" -BackgroundColor DarkRed -ForegroundColor Green Write-Host "Location:$region,connect directly!" -BackgroundColor DarkRed -ForegroundColor Green
}else{ }else{
$download = "https://dn-dao-github-mirror.daocloud.io/$agentrepo/releases/download/$agenttag/$file" $download = "https://kkgithub.com/$agentrepo/releases/download/$agenttag/$file"
$nssmdownload="https://dn-dao-github-mirror.daocloud.io/$nssmrepo/releases/download/$nssmtag/nssm.zip" $nssmdownload="https://kkgithub.com/$nssmrepo/releases/download/$nssmtag/nssm.zip"
Write-Host "Location:CN,use mirror address" -BackgroundColor DarkRed -ForegroundColor Green Write-Host "Location:CN,use mirror address" -BackgroundColor DarkRed -ForegroundColor Green
} }
echo $download echo $download
+39 -22
View File
@@ -14,7 +14,7 @@ NZ_AGENT_SERVICE="/etc/systemd/system/nezha-agent.service"
NZ_AGENT_SERVICERC="/etc/init.d/nezha-agent" NZ_AGENT_SERVICERC="/etc/init.d/nezha-agent"
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service" NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard" NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
NZ_VERSION="v0.15.4" NZ_VERSION="v0.15.6"
red='\033[0;31m' red='\033[0;31m'
green='\033[0;32m' green='\033[0;32m'
@@ -50,7 +50,7 @@ pre_check() {
if [[ -z "${CN}" ]]; then if [[ -z "${CN}" ]]; then
if [[ $(curl -m 10 -s https://ipapi.co/json | grep 'China') != "" ]]; then if [[ $(curl -m 10 -s https://ipapi.co/json | grep 'China') != "" ]]; then
echo "根据ipapi.co提供的信息,当前IP可能在中国" echo "根据ipapi.co提供的信息,当前IP可能在中国"
read -e -r -p "是否选用中国镜像完成安装? [Y/n] " input read -e -r -p "是否选用中国镜像完成安装? [Y/n] (自定义镜像输入 3):" input
case $input in case $input in
[yY][eE][sS] | [yY]) [yY][eE][sS] | [yY])
echo "使用中国镜像" echo "使用中国镜像"
@@ -60,6 +60,17 @@ pre_check() {
[nN][oO] | [nN]) [nN][oO] | [nN])
echo "不使用中国镜像" echo "不使用中国镜像"
;; ;;
[3])
echo "使用自定义镜像"
read -e -r -p "请输入自定义镜像 (例如:dn-dao-github-mirror.daocloud.io),留空为不使用: " input
case $input in
*)
CUSTOM_MIRROR=$input
;;
esac
;;
*) *)
echo "使用中国镜像" echo "使用中国镜像"
CN=true CN=true
@@ -68,21 +79,28 @@ pre_check() {
fi fi
fi fi
if [[ -n "${CUSTOM_MIRROR}" ]]; then
GITHUB_RAW_URL="gitee.com/naibahq/nezha/raw/master"
GITHUB_URL=$CUSTOM_MIRROR
Get_Docker_URL="get.docker.com"
Get_Docker_Argu=" -s docker --mirror Aliyun"
Docker_IMG="registry.cn-shanghai.aliyuncs.com\/naibahq\/nezha-dashboard"
else
if [[ -z "${CN}" ]]; then if [[ -z "${CN}" ]]; then
GITHUB_RAW_URL="raw.githubusercontent.com/naiba/nezha/master" GITHUB_RAW_URL="raw.githubusercontent.com/naiba/nezha/master"
GITHUB_URL="github.com" GITHUB_URL="github.com"
Get_Docker_URL="get.docker.com" Get_Docker_URL="get.docker.com"
Get_Docker_Argu=" " Get_Docker_Argu=" "
Docker_IMG="ghcr.io\/naiba\/nezha-dashboard" Docker_IMG="ghcr.io\/naiba\/nezha-dashboard"
GITHUB_RELEASE_URL="github.com/naiba/nezha/releases/latest/download"
else else
GITHUB_RAW_URL="gitee.com/naibahq/nezha/raw/master" GITHUB_RAW_URL="gitee.com/naibahq/nezha/raw/master"
GITHUB_URL="dn-dao-github-mirror.daocloud.io" GITHUB_URL="kkgithub.com"
Get_Docker_URL="get.docker.com" Get_Docker_URL="get.docker.com"
Get_Docker_Argu=" -s docker --mirror Aliyun" Get_Docker_Argu=" -s docker --mirror Aliyun"
Docker_IMG="registry.cn-shanghai.aliyuncs.com\/naibahq\/nezha-dashboard" Docker_IMG="registry.cn-shanghai.aliyuncs.com\/naibahq\/nezha-dashboard"
GITHUB_RELEASE_URL="hub.fgit.cf/naiba/nezha/releases/latest/download"
fi fi
fi
} }
confirm() { confirm() {
@@ -130,7 +148,7 @@ install_base() {
(install_soft curl wget git unzip) (install_soft curl wget git unzip)
} }
install_arch(){ install_arch() {
echo -e "${green}提示: ${plain} Arch安装libselinux需添加nezha-agent用户,安装完会自动删除,建议手动检查一次\n" echo -e "${green}提示: ${plain} Arch安装libselinux需添加nezha-agent用户,安装完会自动删除,建议手动检查一次\n"
read -e -r -p "是否安装libselinux? [Y/n] " input read -e -r -p "是否安装libselinux? [Y/n] " input
case $input in case $input in
@@ -141,7 +159,7 @@ install_arch(){
cd /tmp; git clone https://aur.archlinux.org/libsepol.git; cd libsepol; makepkg -si --noconfirm --asdeps; cd ..; cd /tmp; git clone https://aur.archlinux.org/libsepol.git; cd libsepol; makepkg -si --noconfirm --asdeps; cd ..;
git clone https://aur.archlinux.org/libselinux.git; cd libselinux; makepkg -si --noconfirm; cd ..; git clone https://aur.archlinux.org/libselinux.git; cd libselinux; makepkg -si --noconfirm; cd ..;
rm -rf libsepol libselinux' rm -rf libsepol libselinux'
sed -i '/nezha-agent/d' /etc/sudoers && sleep 30s && killall -u nezha-agent&&userdel nezha-agent sed -i '/nezha-agent/d' /etc/sudoers && sleep 30s && killall -u nezha-agent && userdel nezha-agent
echo -e "${red}提示: ${plain}已删除用户nezha-agent,请务必手动核查一遍!\n" echo -e "${red}提示: ${plain}已删除用户nezha-agent,请务必手动核查一遍!\n"
;; ;;
[nN][oO] | [nN]) [nN][oO] | [nN])
@@ -249,11 +267,11 @@ install_dashboard_standalone() {
fi fi
} }
selinux(){ selinux() {
#判断当前的状态 #判断当前的状态
if [ "$os_alpine" != 1 ];then if [ "$os_alpine" != 1 ]; then
getenforce | grep '[Ee]nfor' getenforce | grep '[Ee]nfor'
if [ $? -eq 0 ];then if [ $? -eq 0 ]; then
echo -e "SELinux是开启状态,正在关闭!" echo -e "SELinux是开启状态,正在关闭!"
setenforce 0 &>/dev/null setenforce 0 &>/dev/null
find_key="SELINUX=" find_key="SELINUX="
@@ -314,7 +332,7 @@ install_agent() {
modify_agent_config() { modify_agent_config() {
echo -e "> 修改Agent配置" echo -e "> 修改Agent配置"
if [ "$os_alpine" != 1 ];then if [ "$os_alpine" != 1 ]; then
wget -t 2 -T 10 -O $NZ_AGENT_SERVICE https://${GITHUB_RAW_URL}/script/nezha-agent.service >/dev/null 2>&1 wget -t 2 -T 10 -O $NZ_AGENT_SERVICE https://${GITHUB_RAW_URL}/script/nezha-agent.service >/dev/null 2>&1
if [[ $? != 0 ]]; then if [[ $? != 0 ]]; then
echo -e "${red}文件下载失败,请检查本机能否连接 ${GITHUB_RAW_URL}${plain}" echo -e "${red}文件下载失败,请检查本机能否连接 ${GITHUB_RAW_URL}${plain}"
@@ -335,7 +353,7 @@ modify_agent_config() {
read -ep "请输入面板RPC端口 (默认值 5555): " nz_grpc_port && read -ep "请输入面板RPC端口 (默认值 5555): " nz_grpc_port &&
read -ep "请输入Agent 密钥: " nz_client_secret && read -ep "请输入Agent 密钥: " nz_client_secret &&
read -ep "是否启用针对 gRPC 端口的 SSL/TLS加密 (--tls),需要请按 [y],默认是不需要,不理解用户可回车跳过: " nz_grpc_proxy read -ep "是否启用针对 gRPC 端口的 SSL/TLS加密 (--tls),需要请按 [y],默认是不需要,不理解用户可回车跳过: " nz_grpc_proxy
grep -qiw 'Y' <<< "${nz_grpc_proxy}" && args='--tls' grep -qiw 'Y' <<<"${nz_grpc_proxy}" && args='--tls'
if [[ -z "${nz_grpc_host}" || -z "${nz_client_secret}" ]]; then if [[ -z "${nz_grpc_host}" || -z "${nz_client_secret}" ]]; then
echo -e "${red}所有选项都不能为空${plain}" echo -e "${red}所有选项都不能为空${plain}"
before_show_menu before_show_menu
@@ -354,7 +372,7 @@ modify_agent_config() {
fi fi
fi fi
if [ "$os_alpine" != 1 ];then if [ "$os_alpine" != 1 ]; then
sed -i "s/nz_grpc_host/${nz_grpc_host}/" ${NZ_AGENT_SERVICE} sed -i "s/nz_grpc_host/${nz_grpc_host}/" ${NZ_AGENT_SERVICE}
sed -i "s/nz_grpc_port/${nz_grpc_port}/" ${NZ_AGENT_SERVICE} sed -i "s/nz_grpc_port/${nz_grpc_port}/" ${NZ_AGENT_SERVICE}
sed -i "s/nz_client_secret/${nz_client_secret}/" ${NZ_AGENT_SERVICE} sed -i "s/nz_client_secret/${nz_client_secret}/" ${NZ_AGENT_SERVICE}
@@ -368,7 +386,7 @@ modify_agent_config() {
echo -e "Agent配置 ${green}修改成功,请稍等重启生效${plain}" echo -e "Agent配置 ${green}修改成功,请稍等重启生效${plain}"
if [ "$os_alpine" != 1 ];then if [ "$os_alpine" != 1 ]; then
systemctl daemon-reload systemctl daemon-reload
systemctl enable nezha-agent systemctl enable nezha-agent
systemctl restart nezha-agent systemctl restart nezha-agent
@@ -499,7 +517,7 @@ modify_dashboard_config_standalone() {
echo -e "正在下载服务文件" echo -e "正在下载服务文件"
if [ "$os_alpine" != 1 ];then if [ "$os_alpine" != 1 ]; then
wget -t 2 -T 10 -O $NZ_DASHBOARD_SERVICE https://${GITHUB_RAW_URL}/script/nezha-dashboard.service >/dev/null 2>&1 wget -t 2 -T 10 -O $NZ_DASHBOARD_SERVICE https://${GITHUB_RAW_URL}/script/nezha-dashboard.service >/dev/null 2>&1
else else
wget -t 2 -T 10 -O $NZ_DASHBOARD_SERVICERC https://${GITHUB_RAW_URL}/script/nezha-dashboard >/dev/null 2>&1 wget -t 2 -T 10 -O $NZ_DASHBOARD_SERVICERC https://${GITHUB_RAW_URL}/script/nezha-dashboard >/dev/null 2>&1
@@ -510,7 +528,6 @@ modify_dashboard_config_standalone() {
fi fi
fi fi
echo -e "面板配置 ${green}修改成功,请稍等重启生效${plain}" echo -e "面板配置 ${green}修改成功,请稍等重启生效${plain}"
restart_and_update_standalone restart_and_update_standalone
@@ -559,9 +576,9 @@ restart_and_update_standalone() {
rc-service nezha-dashboard stop rc-service nezha-dashboard stop
fi fi
wget -qO app.zip $GITHUB_RELEASE_URL/dashboard-linux-$os_arch.zip >/dev/null 2>&1 && unzip -qq app.zip && mv dist/dashboard-linux-$os_arch app && rm -r app.zip dist wget -qO app.zip https://${GITHUB_URL}/naiba/nezha/releases/latest/download/dashboard-linux-$os_arch.zip >/dev/null 2>&1 && unzip -qq app.zip && mv dist/dashboard-linux-$os_arch app && rm -r app.zip dist
if [ "$os_alpine" != 1 ];then if [ "$os_alpine" != 1 ]; then
systemctl daemon-reload systemctl daemon-reload
systemctl enable nezha-dashboard systemctl enable nezha-dashboard
systemctl restart nezha-dashboard systemctl restart nezha-dashboard
@@ -704,8 +721,8 @@ uninstall_dashboard() {
fi fi
rm -rf $NZ_DASHBOARD_PATH rm -rf $NZ_DASHBOARD_PATH
docker rmi -f ghcr.io/naiba/nezha-dashboard > /dev/null 2>&1 docker rmi -f ghcr.io/naiba/nezha-dashboard >/dev/null 2>&1
docker rmi -f registry.cn-shanghai.aliyuncs.com/naibahq/nezha-dashboard > /dev/null 2>&1 docker rmi -f registry.cn-shanghai.aliyuncs.com/naibahq/nezha-dashboard >/dev/null 2>&1
clean_all clean_all
if [[ $# == 0 ]]; then if [[ $# == 0 ]]; then
@@ -754,7 +771,7 @@ show_agent_log() {
uninstall_agent() { uninstall_agent() {
echo -e "> 卸载Agent" echo -e "> 卸载Agent"
if [ "$os_alpine" != 1 ];then if [ "$os_alpine" != 1 ]; then
systemctl disable nezha-agent.service systemctl disable nezha-agent.service
systemctl stop nezha-agent.service systemctl stop nezha-agent.service
rm -rf $NZ_AGENT_SERVICE rm -rf $NZ_AGENT_SERVICE
@@ -776,7 +793,7 @@ uninstall_agent() {
restart_agent() { restart_agent() {
echo -e "> 重启Agent" echo -e "> 重启Agent"
if [ "$os_alpine" != 1 ];then if [ "$os_alpine" != 1 ]; then
systemctl restart nezha-agent.service systemctl restart nezha-agent.service
else else
rc-service nezha-agent restart rc-service nezha-agent restart
+38 -22
View File
@@ -14,7 +14,7 @@ NZ_AGENT_SERVICE="/etc/systemd/system/nezha-agent.service"
NZ_AGENT_SERVICERC="/etc/init.d/nezha-agent" NZ_AGENT_SERVICERC="/etc/init.d/nezha-agent"
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service" NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard" NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
NZ_VERSION="v0.15.4" NZ_VERSION="v0.15.6"
red='\033[0;31m' red='\033[0;31m'
green='\033[0;32m' green='\033[0;32m'
@@ -50,7 +50,7 @@ pre_check() {
if [[ -z "${CN}" ]]; then if [[ -z "${CN}" ]]; then
if [[ $(curl -m 10 -s https://ipapi.co/json | grep 'China') != "" ]]; then if [[ $(curl -m 10 -s https://ipapi.co/json | grep 'China') != "" ]]; then
echo "According to the information provided by ipapi.co, the current IP may be in China" echo "According to the information provided by ipapi.co, the current IP may be in China"
read -e -r -p "Is the installation done with a Chinese Mirror? [Y/n] " input read -e -r -p "Is the installation done with a Chinese Mirror? [Y/n] (Custom Mirror Input 3):" input
case $input in case $input in
[yY][eE][sS] | [yY]) [yY][eE][sS] | [yY])
echo "Use Chinese Mirror" echo "Use Chinese Mirror"
@@ -60,6 +60,17 @@ pre_check() {
[nN][oO] | [nN]) [nN][oO] | [nN])
echo "No Use Chinese Mirror" echo "No Use Chinese Mirror"
;; ;;
[3])
echo "Use Custom Mirror"
read -e -r -p "Please enter a custom image (e.g. :dn-dao-github-mirror.daocloud.io), leave blank to nouse: " input
case $input in
*)
CUSTOM_MIRROR=$input
;;
esac
;;
*) *)
echo "No Use Chinese Mirror" echo "No Use Chinese Mirror"
;; ;;
@@ -67,20 +78,26 @@ pre_check() {
fi fi
fi fi
if [[ -n "${CUSTOM_MIRROR}" ]]; then
GITHUB_RAW_URL="gitee.com/naibahq/nezha/raw/master"
GITHUB_URL=$CUSTOM_MIRROR
Get_Docker_URL="get.docker.com"
Get_Docker_Argu=" -s docker --mirror Aliyun"
Docker_IMG="registry.cn-shanghai.aliyuncs.com\/naibahq\/nezha-dashboard"
else
if [[ -z "${CN}" ]]; then if [[ -z "${CN}" ]]; then
GITHUB_RAW_URL="raw.githubusercontent.com/naiba/nezha/master" GITHUB_RAW_URL="raw.githubusercontent.com/naiba/nezha/master"
GITHUB_URL="github.com" GITHUB_URL="github.com"
Get_Docker_URL="get.docker.com" Get_Docker_URL="get.docker.com"
Get_Docker_Argu=" " Get_Docker_Argu=" "
Docker_IMG="ghcr.io\/naiba\/nezha-dashboard" Docker_IMG="ghcr.io\/naiba\/nezha-dashboard"
GITHUB_RELEASE_URL="github.com/naiba/nezha/releases/latest/download"
else else
GITHUB_RAW_URL="gitee.com/naibahq/nezha/raw/master" GITHUB_RAW_URL="gitee.com/naibahq/nezha/raw/master"
GITHUB_URL="dn-dao-github-mirror.daocloud.io" GITHUB_URL="kkgithub.com"
Get_Docker_URL="get.docker.com" Get_Docker_URL="get.docker.com"
Get_Docker_Argu=" -s docker --mirror Aliyun" Get_Docker_Argu=" -s docker --mirror Aliyun"
Docker_IMG="registry.cn-shanghai.aliyuncs.com\/naibahq\/nezha-dashboard" Docker_IMG="registry.cn-shanghai.aliyuncs.com\/naibahq\/nezha-dashboard"
GITHUB_RELEASE_URL="hub.fgit.cf/naiba/nezha/releases/latest/download" fi
fi fi
} }
@@ -129,7 +146,7 @@ install_base() {
(install_soft curl wget git unzip tzdata) (install_soft curl wget git unzip tzdata)
} }
install_arch(){ install_arch() {
echo -e "${green}Info: ${plain} Archlinux needs to add nezha-agent user to install libselinux. It will be deleted automatically after installation. It is recommended to check manually\n" echo -e "${green}Info: ${plain} Archlinux needs to add nezha-agent user to install libselinux. It will be deleted automatically after installation. It is recommended to check manually\n"
read -e -r -p "Do you need to install libselinux? [Y/n] " input read -e -r -p "Do you need to install libselinux? [Y/n] " input
case $input in case $input in
@@ -140,7 +157,7 @@ install_arch(){
cd /tmp; git clone https://aur.archlinux.org/libsepol.git; cd libsepol; makepkg -si --noconfirm --asdeps; cd ..; cd /tmp; git clone https://aur.archlinux.org/libsepol.git; cd libsepol; makepkg -si --noconfirm --asdeps; cd ..;
git clone https://aur.archlinux.org/libselinux.git; cd libselinux; makepkg -si --noconfirm; cd ..; git clone https://aur.archlinux.org/libselinux.git; cd libselinux; makepkg -si --noconfirm; cd ..;
rm -rf libsepol libselinux' rm -rf libsepol libselinux'
sed -i '/nezha-agent/d' /etc/sudoers && sleep 30s && killall -u nezha-agent&&userdel nezha-agent sed -i '/nezha-agent/d' /etc/sudoers && sleep 30s && killall -u nezha-agent && userdel nezha-agent
echo -e "${red}Info: ${plain}user nezha-agent has been deleted, Be sure to check it manually!\n" echo -e "${red}Info: ${plain}user nezha-agent has been deleted, Be sure to check it manually!\n"
;; ;;
[nN][oO] | [nN]) [nN][oO] | [nN])
@@ -247,11 +264,11 @@ install_dashboard_standalone() {
fi fi
} }
selinux(){ selinux() {
#Check SELinux #Check SELinux
if [ "$os_alpine" != 1 ];then if [ "$os_alpine" != 1 ]; then
getenforce | grep '[Ee]nfor' getenforce | grep '[Ee]nfor'
if [ $? -eq 0 ];then if [ $? -eq 0 ]; then
echo -e "SELinux runningclosing now" echo -e "SELinux runningclosing now"
setenforce 0 &>/dev/null setenforce 0 &>/dev/null
find_key="SELINUX=" find_key="SELINUX="
@@ -312,7 +329,7 @@ install_agent() {
modify_agent_config() { modify_agent_config() {
echo -e "> Modify Agent Configuration" echo -e "> Modify Agent Configuration"
if [ "$os_alpine" != 1 ];then if [ "$os_alpine" != 1 ]; then
wget -t 2 -T 10 -O $NZ_AGENT_SERVICE https://${GITHUB_RAW_URL}/script/nezha-agent.service >/dev/null 2>&1 wget -t 2 -T 10 -O $NZ_AGENT_SERVICE https://${GITHUB_RAW_URL}/script/nezha-agent.service >/dev/null 2>&1
if [[ $? != 0 ]]; then if [[ $? != 0 ]]; then
echo -e "${red}Fail to download service, please check if the network can link ${GITHUB_RAW_URL}${plain}" echo -e "${red}Fail to download service, please check if the network can link ${GITHUB_RAW_URL}${plain}"
@@ -333,7 +350,7 @@ modify_agent_config() {
read -ep "Please enter the panel RPC port (default 5555): " nz_grpc_port && read -ep "Please enter the panel RPC port (default 5555): " nz_grpc_port &&
read -ep "Please enter the Agent secret: " nz_client_secret && read -ep "Please enter the Agent secret: " nz_client_secret &&
read -ep "Do you want to enable SSL/TLS encryption for the gRPC port (--tls)? Press [y] if yes, the default is not required, and users can press Enter to skip if you don't understand: " nz_grpc_proxy read -ep "Do you want to enable SSL/TLS encryption for the gRPC port (--tls)? Press [y] if yes, the default is not required, and users can press Enter to skip if you don't understand: " nz_grpc_proxy
grep -qiw 'Y' <<< "${nz_grpc_proxy}" && args='--tls' grep -qiw 'Y' <<<"${nz_grpc_proxy}" && args='--tls'
if [[ -z "${nz_grpc_host}" || -z "${nz_client_secret}" ]]; then if [[ -z "${nz_grpc_host}" || -z "${nz_client_secret}" ]]; then
echo -e "${red}All options cannot be empty${plain}" echo -e "${red}All options cannot be empty${plain}"
before_show_menu before_show_menu
@@ -352,7 +369,7 @@ modify_agent_config() {
fi fi
fi fi
if [ "$os_alpine" != 1 ];then if [ "$os_alpine" != 1 ]; then
sed -i "s/nz_grpc_host/${nz_grpc_host}/" ${NZ_AGENT_SERVICE} sed -i "s/nz_grpc_host/${nz_grpc_host}/" ${NZ_AGENT_SERVICE}
sed -i "s/nz_grpc_port/${nz_grpc_port}/" ${NZ_AGENT_SERVICE} sed -i "s/nz_grpc_port/${nz_grpc_port}/" ${NZ_AGENT_SERVICE}
sed -i "s/nz_client_secret/${nz_client_secret}/" ${NZ_AGENT_SERVICE} sed -i "s/nz_client_secret/${nz_client_secret}/" ${NZ_AGENT_SERVICE}
@@ -366,7 +383,7 @@ modify_agent_config() {
echo -e "Agent configuration ${green} modified successfully, please wait for agent self-restart to take effect${plain}" echo -e "Agent configuration ${green} modified successfully, please wait for agent self-restart to take effect${plain}"
if [ "$os_alpine" != 1 ];then if [ "$os_alpine" != 1 ]; then
systemctl daemon-reload systemctl daemon-reload
systemctl enable nezha-agent systemctl enable nezha-agent
systemctl restart nezha-agent systemctl restart nezha-agent
@@ -497,7 +514,7 @@ modify_dashboard_config_standalone() {
echo -e "Downloading service file" echo -e "Downloading service file"
if [ "$os_alpine" != 1 ];then if [ "$os_alpine" != 1 ]; then
wget -t 2 -T 10 -O $NZ_DASHBOARD_SERVICE https://${GITHUB_RAW_URL}/script/nezha-dashboard.service >/dev/null 2>&1 wget -t 2 -T 10 -O $NZ_DASHBOARD_SERVICE https://${GITHUB_RAW_URL}/script/nezha-dashboard.service >/dev/null 2>&1
else else
wget -t 2 -T 10 -O $NZ_DASHBOARD_SERVICERC https://${GITHUB_RAW_URL}/script/nezha-dashboard >/dev/null 2>&1 wget -t 2 -T 10 -O $NZ_DASHBOARD_SERVICERC https://${GITHUB_RAW_URL}/script/nezha-dashboard >/dev/null 2>&1
@@ -508,7 +525,6 @@ modify_dashboard_config_standalone() {
fi fi
fi fi
echo -e "Dashboard configuration ${green} modified successfully, please wait for Dashboard self-restart to take effect${plain}" echo -e "Dashboard configuration ${green} modified successfully, please wait for Dashboard self-restart to take effect${plain}"
restart_and_update_standalone restart_and_update_standalone
@@ -557,9 +573,9 @@ restart_and_update_standalone() {
rc-service nezha-dashboard stop rc-service nezha-dashboard stop
fi fi
wget -qO app.zip $GITHUB_RELEASE_URL/dashboard-linux-$os_arch.zip >/dev/null 2>&1 && unzip -qq app.zip && mv dist/dashboard-linux-$os_arch app && rm -r app.zip dist wget -qO app.zip https://${GITHUB_URL}/naiba/nezha/releases/latest/download/dashboard-linux-$os_arch.zip >/dev/null 2>&1 && unzip -qq app.zip && mv dist/dashboard-linux-$os_arch app && rm -r app.zip dist
if [ "$os_alpine" != 1 ];then if [ "$os_alpine" != 1 ]; then
systemctl daemon-reload systemctl daemon-reload
systemctl enable nezha-dashboard systemctl enable nezha-dashboard
systemctl restart nezha-dashboard systemctl restart nezha-dashboard
@@ -702,8 +718,8 @@ uninstall_dashboard() {
fi fi
rm -rf $NZ_DASHBOARD_PATH rm -rf $NZ_DASHBOARD_PATH
docker rmi -f ghcr.io/naiba/nezha-dashboard > /dev/null 2>&1 docker rmi -f ghcr.io/naiba/nezha-dashboard >/dev/null 2>&1
docker rmi -f registry.cn-shanghai.aliyuncs.com/naibahq/nezha-dashboard > /dev/null 2>&1 docker rmi -f registry.cn-shanghai.aliyuncs.com/naibahq/nezha-dashboard >/dev/null 2>&1
clean_all clean_all
if [[ $# == 0 ]]; then if [[ $# == 0 ]]; then
@@ -752,7 +768,7 @@ show_agent_log() {
uninstall_agent() { uninstall_agent() {
echo -e "> Uninstall Agent" echo -e "> Uninstall Agent"
if [ "$os_alpine" != 1 ];then if [ "$os_alpine" != 1 ]; then
systemctl disable nezha-agent.service systemctl disable nezha-agent.service
systemctl stop nezha-agent.service systemctl stop nezha-agent.service
rm -rf $NZ_AGENT_SERVICE rm -rf $NZ_AGENT_SERVICE
@@ -774,7 +790,7 @@ uninstall_agent() {
restart_agent() { restart_agent() {
echo -e "> Restart Agent" echo -e "> Restart Agent"
if [ "$os_alpine" != 1 ];then if [ "$os_alpine" != 1 ]; then
systemctl restart nezha-agent.service systemctl restart nezha-agent.service
else else
rc-service nezha-agent restart rc-service nezha-agent restart
+3
View File
@@ -1,4 +1,7 @@
#!/sbin/openrc-run #!/sbin/openrc-run
supervisor=supervise-daemon
name=nezha-agent
supervise_daemon_args="--stdout /var/log/${name}.log --stderr /var/log/${name}.err"
SERVER="nz_grpc_host:nz_grpc_port" #Dashboard 地址 ip:port SERVER="nz_grpc_host:nz_grpc_port" #Dashboard 地址 ip:port
SECRET="nz_client_secret" #SECRET SECRET="nz_client_secret" #SECRET
NZ_BASE_PATH="/opt/nezha" NZ_BASE_PATH="/opt/nezha"
+65 -1
View File
@@ -1,9 +1,11 @@
package singleton package singleton
import ( import (
"sync"
"time"
"github.com/naiba/nezha/model" "github.com/naiba/nezha/model"
"github.com/naiba/nezha/pkg/utils" "github.com/naiba/nezha/pkg/utils"
"sync"
) )
var ( var (
@@ -12,6 +14,9 @@ var (
ApiLock sync.RWMutex ApiLock sync.RWMutex
ServerAPI = &ServerAPIService{} ServerAPI = &ServerAPIService{}
MonitorAPI = &MonitorAPIService{}
once = &sync.Once{}
) )
type ServerAPIService struct{} type ServerAPIService struct{}
@@ -51,6 +56,23 @@ type ServerInfoResponse struct {
Result []*CommonServerInfo `json:"result"` Result []*CommonServerInfo `json:"result"`
} }
type MonitorAPIService struct {
}
type MonitorInfoResponse struct {
CommonResponse
Result []*MonitorInfo `json:"result"`
}
type MonitorInfo struct {
MonitorID uint64 `json:"monitor_id"`
ServerID uint64 `json:"server_id"`
MonitorName string `json:"monitor_name"`
ServerName string `json:"server_name"`
CreatedAt []int64 `json:"created_at"`
AvgDelay []float32 `json:"avg_delay"`
}
func InitAPI() { func InitAPI() {
ApiTokenList = make(map[string]*model.ApiToken) ApiTokenList = make(map[string]*model.ApiToken)
UserIDToApiTokenList = make(map[uint64][]string) UserIDToApiTokenList = make(map[uint64][]string)
@@ -203,3 +225,45 @@ func (s *ServerAPIService) GetAllList() *ServerInfoResponse {
} }
return res return res
} }
func (m *MonitorAPIService) GetMonitorHistories(query map[string]any) *MonitorInfoResponse {
var (
resultMap = make(map[uint64]*MonitorInfo)
monitorHistories []*model.MonitorHistory
sortedMonitorIDs []uint64
)
res := &MonitorInfoResponse{
CommonResponse: CommonResponse{
Code: 0,
Message: "success",
},
}
if err := DB.Model(&model.MonitorHistory{}).Select("monitor_id, created_at, server_id, avg_delay").
Where(query).Where("created_at >= ?", time.Now().Add(-24*time.Hour)).Order("monitor_id, created_at").
Scan(&monitorHistories).Error; err != nil {
res.CommonResponse = CommonResponse{
Code: 500,
Message: err.Error(),
}
} else {
for _, history := range monitorHistories {
infos, ok := resultMap[history.MonitorID]
if !ok {
infos = &MonitorInfo{
MonitorID: history.MonitorID,
ServerID: history.ServerID,
MonitorName: ServiceSentinelShared.monitors[history.MonitorID].Name,
ServerName: ServerList[history.ServerID].Name,
}
resultMap[history.MonitorID] = infos
sortedMonitorIDs = append(sortedMonitorIDs, history.MonitorID)
}
infos.CreatedAt = append(infos.CreatedAt, history.CreatedAt.Truncate(time.Minute).Unix()*1000)
infos.AvgDelay = append(infos.AvgDelay, history.AvgDelay)
}
for _, monitorID := range sortedMonitorIDs {
res.Result = append(res.Result, resultMap[monitorID])
}
}
return res
}
+62 -7
View File
@@ -8,9 +8,10 @@ import (
"sync" "sync"
"time" "time"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/naiba/nezha/model" "github.com/naiba/nezha/model"
pb "github.com/naiba/nezha/proto" pb "github.com/naiba/nezha/proto"
"github.com/nicksnyder/go-i18n/v2/i18n"
) )
const ( const (
@@ -36,12 +37,13 @@ func NewServiceSentinel(serviceSentinelDispatchBus chan<- model.Monitor) {
ServiceSentinelShared = &ServiceSentinel{ ServiceSentinelShared = &ServiceSentinel{
serviceReportChannel: make(chan ReportData, 200), serviceReportChannel: make(chan ReportData, 200),
serviceStatusToday: make(map[uint64]*_TodayStatsOfMonitor), serviceStatusToday: make(map[uint64]*_TodayStatsOfMonitor),
serviceCurrentStatusIndex: make(map[uint64]int), serviceCurrentStatusIndex: make(map[uint64]*indexStore),
serviceCurrentStatusData: make(map[uint64][]*pb.TaskResult), serviceCurrentStatusData: make(map[uint64][]*pb.TaskResult),
lastStatus: make(map[uint64]int), lastStatus: make(map[uint64]int),
serviceResponseDataStoreCurrentUp: make(map[uint64]uint64), serviceResponseDataStoreCurrentUp: make(map[uint64]uint64),
serviceResponseDataStoreCurrentDown: make(map[uint64]uint64), serviceResponseDataStoreCurrentDown: make(map[uint64]uint64),
serviceResponseDataStoreCurrentAvgDelay: make(map[uint64]float32), serviceResponseDataStoreCurrentAvgDelay: make(map[uint64]float32),
serviceResponsePing: make(map[uint64]map[uint64]*pingStore),
monitors: make(map[uint64]*model.Monitor), monitors: make(map[uint64]*model.Monitor),
sslCertCache: make(map[uint64]string), sslCertCache: make(map[uint64]string),
// 30天数据缓存 // 30天数据缓存
@@ -95,11 +97,12 @@ type ServiceSentinel struct {
serviceResponseDataStoreLock sync.RWMutex serviceResponseDataStoreLock sync.RWMutex
serviceStatusToday map[uint64]*_TodayStatsOfMonitor // [monitor_id] -> _TodayStatsOfMonitor serviceStatusToday map[uint64]*_TodayStatsOfMonitor // [monitor_id] -> _TodayStatsOfMonitor
serviceCurrentStatusIndex map[uint64]int // [monitor_id] -> 该监控ID对应的 serviceCurrentStatusData 的最新索引下标 serviceCurrentStatusIndex map[uint64]*indexStore // [monitor_id] -> 该监控ID对应的 serviceCurrentStatusData 的最新索引下标
serviceCurrentStatusData map[uint64][]*pb.TaskResult // [monitor_id] -> []model.MonitorHistory serviceCurrentStatusData map[uint64][]*pb.TaskResult // [monitor_id] -> []model.MonitorHistory
serviceResponseDataStoreCurrentUp map[uint64]uint64 // [monitor_id] -> 当前服务在线计数 serviceResponseDataStoreCurrentUp map[uint64]uint64 // [monitor_id] -> 当前服务在线计数
serviceResponseDataStoreCurrentDown map[uint64]uint64 // [monitor_id] -> 当前服务离线计数 serviceResponseDataStoreCurrentDown map[uint64]uint64 // [monitor_id] -> 当前服务离线计数
serviceResponseDataStoreCurrentAvgDelay map[uint64]float32 // [monitor_id] -> 当前服务离线计数 serviceResponseDataStoreCurrentAvgDelay map[uint64]float32 // [monitor_id] -> 当前服务离线计数
serviceResponsePing map[uint64]map[uint64]*pingStore // [monitor_id] -> ClientID -> delay
lastStatus map[uint64]int lastStatus map[uint64]int
sslCertCache map[uint64]string sslCertCache map[uint64]string
@@ -111,6 +114,16 @@ type ServiceSentinel struct {
monthlyStatus map[uint64]*model.ServiceItemResponse // [monitor_id] -> model.ServiceItemResponse monthlyStatus map[uint64]*model.ServiceItemResponse // [monitor_id] -> model.ServiceItemResponse
} }
type indexStore struct {
index int
t time.Time
}
type pingStore struct {
count int
ping float32
}
func (ss *ServiceSentinel) refreshMonthlyServiceStatus() { func (ss *ServiceSentinel) refreshMonthlyServiceStatus() {
// 刷新数据防止无人访问 // 刷新数据防止无人访问
ss.LoadStats() ss.LoadStats()
@@ -326,6 +339,34 @@ func (ss *ServiceSentinel) worker() {
continue continue
} }
mh := r.Data mh := r.Data
if mh.Type == model.TaskTypeTCPPing || mh.Type == model.TaskTypeICMPPing {
monitorTcpMap, ok := ss.serviceResponsePing[mh.GetId()]
if !ok {
monitorTcpMap = make(map[uint64]*pingStore)
ss.serviceResponsePing[mh.GetId()] = monitorTcpMap
}
ts, ok := monitorTcpMap[r.Reporter]
if !ok {
ts = &pingStore{}
}
ts.count++
ts.ping = (ts.ping*float32(ts.count-1) + mh.Delay) / float32(ts.count)
if ts.count == Conf.AvgPingCount {
if ts.ping > float32(Conf.MaxTCPPingValue) {
ts.ping = float32(Conf.MaxTCPPingValue)
}
ts.count = 0
if err := DB.Create(&model.MonitorHistory{
MonitorID: mh.GetId(),
AvgDelay: ts.ping,
Data: mh.Data,
ServerID: r.Reporter,
}).Error; err != nil {
log.Println("NEZHA>> 服务监控数据持久化失败:", err)
}
}
monitorTcpMap[r.Reporter] = ts
}
ss.serviceResponseDataStoreLock.Lock() ss.serviceResponseDataStoreLock.Lock()
// 写入当天状态 // 写入当天状态
if mh.Successful { if mh.Successful {
@@ -336,9 +377,20 @@ func (ss *ServiceSentinel) worker() {
} else { } else {
ss.serviceStatusToday[mh.GetId()].Down++ ss.serviceStatusToday[mh.GetId()].Down++
} }
currentTime := time.Now()
if ss.serviceCurrentStatusIndex[mh.GetId()] == nil {
ss.serviceCurrentStatusIndex[mh.GetId()] = &indexStore{
t: currentTime,
index: 0,
}
}
// 写入当前数据 // 写入当前数据
ss.serviceCurrentStatusData[mh.GetId()][ss.serviceCurrentStatusIndex[mh.GetId()]] = mh if ss.serviceCurrentStatusIndex[mh.GetId()].t.Before(currentTime) {
ss.serviceCurrentStatusIndex[mh.GetId()]++ ss.serviceCurrentStatusIndex[mh.GetId()].t = currentTime.Add(30 * time.Second)
ss.serviceCurrentStatusData[mh.GetId()][ss.serviceCurrentStatusIndex[mh.GetId()].index] = mh
ss.serviceCurrentStatusIndex[mh.GetId()].index++
}
// 更新当前状态 // 更新当前状态
ss.serviceResponseDataStoreCurrentUp[mh.GetId()] = 0 ss.serviceResponseDataStoreCurrentUp[mh.GetId()] = 0
@@ -365,8 +417,11 @@ func (ss *ServiceSentinel) worker() {
stateCode := GetStatusCode(upPercent) stateCode := GetStatusCode(upPercent)
// 数据持久化 // 数据持久化
if ss.serviceCurrentStatusIndex[mh.GetId()] == _CurrentStatusSize { if ss.serviceCurrentStatusIndex[mh.GetId()].index == _CurrentStatusSize {
ss.serviceCurrentStatusIndex[mh.GetId()] = 0 ss.serviceCurrentStatusIndex[mh.GetId()] = &indexStore{
index: 0,
t: currentTime,
}
if err := DB.Create(&model.MonitorHistory{ if err := DB.Create(&model.MonitorHistory{
MonitorID: mh.GetId(), MonitorID: mh.GetId(),
AvgDelay: ss.serviceResponseDataStoreCurrentAvgDelay[mh.GetId()], AvgDelay: ss.serviceResponseDataStoreCurrentAvgDelay[mh.GetId()],
+4
View File
@@ -99,6 +99,10 @@ func RecordTransferHourlyUsage() {
func CleanMonitorHistory() { func CleanMonitorHistory() {
// 清理已被删除的服务器的监控记录与流量记录 // 清理已被删除的服务器的监控记录与流量记录
DB.Unscoped().Delete(&model.MonitorHistory{}, "created_at < ? OR monitor_id NOT IN (SELECT `id` FROM monitors)", time.Now().AddDate(0, 0, -30)) DB.Unscoped().Delete(&model.MonitorHistory{}, "created_at < ? OR monitor_id NOT IN (SELECT `id` FROM monitors)", time.Now().AddDate(0, 0, -30))
// 由于网络监控记录的数据较多,并且前端仅使用了 1 天的数据
// 考虑到 sqlite 数据量问题,仅保留一天数据,
// server_id = 0 的数据会用于/service页面的可用性展示
DB.Unscoped().Delete(&model.MonitorHistory{}, "(created_at < ? AND server_id != 0) OR monitor_id NOT IN (SELECT `id` FROM monitors)", time.Now().AddDate(0, 0, -1))
DB.Unscoped().Delete(&model.Transfer{}, "server_id NOT IN (SELECT `id` FROM servers)") DB.Unscoped().Delete(&model.Transfer{}, "server_id NOT IN (SELECT `id` FROM servers)")
// 计算可清理流量记录的时长 // 计算可清理流量记录的时长
var allServerKeep time.Time var allServerKeep time.Time