Compare commits

..
23 Commits
Author SHA1 Message Date
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
naiba 83efdc6258 replace statifile cdn with bytedance cdn 2023-12-11 21:53:56 +08:00
github-actions[bot] bf58ad24b5 update contributors[no ci] 2023-12-08 13:17:02 +00:00
spiritlhl 53581d48fc 更新判断面板是否是通过docker安装的逻辑 (#311)
* 更新判断面板是否是通过docker安装的逻辑

* Updated logic for determining whether a panel is installed via docker or not
2023-12-08 21:16:36 +08:00
naiba b00a00e791 Add comment about missing riscv64 image in latest Alpine image 2023-12-08 09:55:17 +08:00
github-actions[bot] b91f29077c update contributors[no ci] 2023-12-07 13:59:01 +00:00
nap0oandSuperHsiao 99111a3115 improve server-status theme (#310)
* Add files via upload

* Add files via upload

* Add files via upload

* improve theme-server-status

1.前台分组展示agent
2.一些小优化

---------

Co-authored-by: SuperHsiao <[email protected]>
2023-12-07 21:58:42 +08:00
github-actions[bot] 30652c3d79 update contributors[no ci] 2023-12-07 13:41:51 +00:00
Mmx ac0103a1ee Improve: use alpine as dockerfile base image (#309)
* build: use alpine as base image

* build: use alpine edge instead of latest
2023-12-07 21:41:26 +08:00
github-actions[bot] f59b31590e update contributors[no ci] 2023-12-06 14:42:42 +00:00
Applexad 1bcddf1944 installer: fix docker compose command determination (#307) 2023-12-06 22:42:22 +08:00
卖女孩的小火柴 d790ca04d6 Fix select_version (#306) 2023-12-06 21:35:24 +08:00
github-actions[bot] 2106ca41e1 update contributors[no ci] 2023-12-06 01:35:54 +00:00
Mmx 927bf13ce4 fix: field name "VerifySSL" to "SkipVerifySSL" in Transport config (#305) 2023-12-06 09:35:38 +08:00
github-actions[bot] 859e263815 update contributors[no ci] 2023-12-05 15:23:19 +00:00
53 changed files with 2052 additions and 878 deletions
+10 -10
View File
@@ -1,18 +1,18 @@
FROM ubuntu:focal-20221130 # latest 镜像还没有 riscv64 https://hub.docker.com/_/alpine/tags
FROM alpine:edge
ENV TZ="Asia/Shanghai"
ARG TARGETOS ARG TARGETOS
ARG TARGETARCH ARG TARGETARCH
COPY ./script/entrypoint.sh /entrypoint.sh RUN apk update && \
apk upgrade --no-cache && \
apk add --no-cache tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo 'Asia/Shanghai' >/etc/timezone && \
rm -rf /var/cache/apk/*
RUN export DEBIAN_FRONTEND="noninteractive" && \ COPY ./script/entrypoint.sh /entrypoint.sh
apt update && apt install -y ca-certificates tzdata && \ RUN chmod +x /entrypoint.sh
update-ca-certificates && \
ln -fs /usr/share/zoneinfo/$TZ /etc/localtime && \
dpkg-reconfigure tzdata && \
chmod +x /entrypoint.sh
WORKDIR /dashboard WORKDIR /dashboard
COPY dist/dashboard-${TARGETOS}-${TARGETARCH} ./app COPY dist/dashboard-${TARGETOS}-${TARGETARCH} ./app
+65 -53
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.2-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,20 +65,23 @@ 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/CosmosZ-code" title="CosmosZ-code">
<img src="https://avatars.githubusercontent.com/u/81398224?v=4" width="50;" alt="CosmosZ-code"/>
</a>
<a href="https://github.com/uubulb" title="UUBulb">
<img src="https://avatars.githubusercontent.com/u/35923940?v=4" width="50;" alt="UUBulb"/>
</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"/>
</a> </a>
<a href="https://github.com/spiritLHLS" title="spiritlhl"> <a href="https://github.com/Mmx233" title="Mmx">
<img src="https://avatars.githubusercontent.com/u/103393591?v=4" width="50;" alt="spiritlhl"/> <img src="https://avatars.githubusercontent.com/u/36563672?v=4" width="50;" alt="Mmx"/>
</a>
<a href="https://github.com/applexad" title="Applexad">
<img src="https://avatars.githubusercontent.com/u/35923940?v=4" width="50;" alt="Applexad"/>
</a> </a>
<a href="https://github.com/rootmelo92118" title="rootmelo92118"> <a href="https://github.com/rootmelo92118" title="rootmelo92118">
<img src="https://avatars.githubusercontent.com/u/32770959?v=4" width="50;" alt="rootmelo92118"/> <img src="https://avatars.githubusercontent.com/u/32770959?v=4" width="50;" alt="rootmelo92118"/>
@@ -86,62 +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/Creling" title="Creling">
<img src="https://avatars.githubusercontent.com/u/71542548?v=4" width="50;" alt="Esdese"/> <img src="https://avatars.githubusercontent.com/u/43109504?v=4" width="50;" alt="Creling"/>
</a> </a>
<a href="https://github.com/GreenTeodoro839" title="GreenTeodoro839"> <a href="https://github.com/coreff" title="Core F">
<img src="https://avatars.githubusercontent.com/u/77104800?v=4" width="50;" alt="GreenTeodoro839"/> <img src="https://avatars.githubusercontent.com/u/38347122?v=4" width="50;" alt="Core F"/>
</a> </a>
<a href="https://github.com/techotaku" title="Ian Li"> <a href="https://github.com/colour93" title="玖叁">
<img src="https://avatars.githubusercontent.com/u/1948179?v=4" width="50;" alt="Ian Li"/> <img src="https://avatars.githubusercontent.com/u/64313711?v=4" width="50;" alt="玖叁"/>
</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/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/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/lvgj-stack" title="Ko no dio">
<img src="https://avatars.githubusercontent.com/u/38449861?v=4" width="50;" alt="Ko no dio"/>
</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}))
}
+110
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)
} }
@@ -127,6 +131,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 // 检查状态异常计数
+1 -7
View File
@@ -110,15 +110,9 @@ func (n *Notification) setRequestHeader(req *http.Request) error {
} }
func (ns *NotificationServerBundle) Send(message string) error { func (ns *NotificationServerBundle) Send(message string) error {
var verifySSL bool var client *http.Client
n := ns.Notification n := ns.Notification
if n.VerifySSL != nil && *n.VerifySSL { if n.VerifySSL != nil && *n.VerifySSL {
verifySSL = true
}
var client *http.Client
if verifySSL {
client = utils.HttpClient client = utils.HttpClient
} else { } else {
client = utils.HttpClientSkipTlsVerify client = utils.HttpClientSkipTlsVerify
+4 -4
View File
@@ -14,23 +14,23 @@ var (
func init() { func init() {
HttpClientSkipTlsVerify = httpClient(_httpClient{ HttpClientSkipTlsVerify = httpClient(_httpClient{
Transport: httpTransport(_httpTransport{ Transport: httpTransport(_httpTransport{
VerifySSL: true, SkipVerifySSL: true,
}), }),
}) })
HttpClient = httpClient(_httpClient{ HttpClient = httpClient(_httpClient{
Transport: httpTransport(_httpTransport{ Transport: httpTransport(_httpTransport{
VerifySSL: false, SkipVerifySSL: false,
}), }),
}) })
} }
type _httpTransport struct { type _httpTransport struct {
VerifySSL bool SkipVerifySSL bool
} }
func httpTransport(conf _httpTransport) *http.Transport { func httpTransport(conf _httpTransport) *http.Transport {
return &http.Transport{ return &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: conf.VerifySSL}, TLSClientConfig: &tls.Config{InsecureSkipVerify: conf.SkipVerifySSL},
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
} }
} }
+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 {
File diff suppressed because one or more lines are too long
+25
View File
@@ -27,6 +27,10 @@ body[theme="light"] .table {
background: #ffffff; background: #ffffff;
} }
body[theme="light"] .table>thead>tr>th.node-group-tag {
background-color:#ffffff;
}
body[theme="light"] .table-striped tbody > tr.even > td, body[theme="light"] .table-striped tbody > tr.even > td,
body[theme="light"] .table-striped tbody > tr.even > th { body[theme="light"] .table-striped tbody > tr.even > th {
background-color: #F9F9F9; background-color: #F9F9F9;
@@ -37,10 +41,25 @@ body[theme="light"] .table-striped tbody > tr.odd > th {
background-color: #FFF; background-color: #FFF;
} }
body[theme="light"] .table>tbody>tr>td,
body[theme="light"] .table>tbody>tr>th,
body[theme="light"] .table>tfoot>tr>td,
body[theme="light"] .table>tfoot>tr>th,
body[theme="light"] .table>thead>tr>td,
body[theme="light"] .table>thead>tr>th {
line-height:20px;
}
body[theme="light"] .progress-bar { body[theme="light"] .progress-bar {
color: #000; color: #000;
} }
body[theme="light"] .progress-offline .progress-bar-success {
background-image: linear-gradient(grey 0px, grey 100%);
}
}
body[theme="light"] .table-hover > tbody > tr:hover > td { body[theme="light"] .table-hover > tbody > tr:hover > td {
background: #E6E6E6; background: #E6E6E6;
} }
@@ -76,3 +95,9 @@ body[theme="light"] .service-status .warning {
body[theme="light"] .service-day-status-icon { body[theme="light"] .service-day-status-icon {
background-color: #ebebeb; background-color: #ebebeb;
} }
body[theme="light"] footer p a,
body[theme="light"] footer p a:hover {
color: #333333;
text-decoration: none;
}
+47 -81
View File
@@ -8,14 +8,27 @@ body {
min-height: 40px !important; min-height: 40px !important;
} }
.navbar-inner{
margin:0 auto;
}
.pl-md-unset {
max-width: 95vw;
}
.navbar-collapse:not([aria-expanded]) .navbar-nav .dropdown-toggle { .navbar-collapse:not([aria-expanded]) .navbar-nav .dropdown-toggle {
margin-top: 18px; margin-top: 18px;
padding: 0 !important; padding: 0 !important;
} }
.navbar-toggle {
margin-right:0
}
.navbar-brand { .navbar-brand {
font-size: 20px; font-size: 20px;
text-align: center; text-align: center;
padding:12px 0 0 0;
} }
.node-cell-expand { .node-cell-expand {
@@ -32,18 +45,17 @@ body {
padding-top: 10px; padding-top: 10px;
} }
.dropdown {
margin-right: 10px;
}
.navbar-inverse, .nav.navbar-nav { .navbar-inverse, .nav.navbar-nav {
background-image: linear-gradient(rgb(60, 60, 60) 0px, rgb(34, 34, 34) 100%) !important; background-image: linear-gradient(rgb(60, 60, 60) 0px, rgb(34, 34, 34) 100%) !important;
} }
.navbar-inverse .navbar-nav>li>a {
color:#f1f1f1;
}
.navbar-inverse .navbar-brand { .navbar-inverse .navbar-brand {
font-size: 20px; font-size: 20px;
} }
/* 导航部分 结束 */ /* 导航部分 结束 */
/* 正文部分 开始 */ /* 正文部分 开始 */
@@ -63,6 +75,15 @@ body {
text-align: left; text-align: left;
} }
.table>thead>tr>th{
border:none;
}
.table .node-group-tag {
font-size: 18px;
padding-bottom:15px;
}
.progress { .progress {
margin-bottom: 0; margin-bottom: 0;
} }
@@ -78,6 +99,10 @@ body {
border-top: 0 !important; border-top: 0 !important;
} }
.accordian-body{
margin: 10px 0px 10px 18px;
}
.node-cell.center { .node-cell.center {
text-align: center !important; text-align: center !important;
} }
@@ -147,104 +172,45 @@ body {
} }
/* 服务页 正文结束 */ /* 服务页 正文结束 */
@media only screen and (max-width: 1200px) { @media only screen and (max-width: 1200px) {
.node-cell.os, .accordian-body{
.node-cell.location, margin: 5px 0px 5px 10px;
.node-cell.uptime { }
display: none; .table .node-group-tag {
visibility: hidden; font-size:16px;
padding-bottom:6px;
} }
} }
@media only screen and (max-width: 720px) { @media only screen and (max-width: 720px) {
body { body {
font-size: 10px !important; font-size: 10px !important;
padding-top:60px !important;
} }
.content { .content {
padding: 0; padding: 0;
margin-bottom: 10px;
} }
.node-cell.os, .node-cell.os,
.node-cell.location,
.node-cell.uptime {
display: none;
visibility: hidden;
}
.service-day-status-icon {
width: 8px;
height: 8px;
margin-right: 2px;
border-radius: 3px;
}
}
@media only screen and (max-width: 620px) {
body {
font-size: 10px !important;
}
.content {
padding: 0;
}
.node-cell.type,
.node-cell.location,
.node-cell.uptime, .node-cell.uptime,
.node-cell.traffic{ .node-cell.traffic{
display: none; display: none;
visibility: hidden; visibility: hidden;
} }
.node-cell.location{
.service-day-status-icon { display: table-cell;
visibility: visible;
} }
} .node-cell-os-text,.node-cell-location-text{
@media only screen and (max-width: 533px) {
body {
font-size: 10px !important;
}
.content {
padding: 0;
}
.node-cell.os,
.node-cell.location,
.node-cell.uptime,
.node-cell.traffic {
display: none; display: none;
visibility: hidden; visibility: hidden;
} }
.accordian-body{
.service-day-status-icon { margin: 5px 0px 5px 10px;
} }
} .table .node-group-tag {
font-size:12px;
@media only screen and (max-width: 450px) { padding-bottom:6px;
body {
font-size: 10px !important;
}
.content {
padding: 0;
}
.node-cell.type,
.node-cell.location,
.node-cell.uptime,
.node-cell.traffic {
display: none;
visibility: hidden;
}
.node-cell.cpu,
.node-cell.ram,
.node-cell.hdd {
min-width: 25px;
max-width: 50px;
} }
} }
+4 -4
View File
@@ -6,11 +6,11 @@
{{.Version}}</small> {{.Version}}</small>
</div> </div>
</div> </div>
<script src="https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/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://cdn.staticfile.org/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 }});
+2 -2
View File
@@ -9,8 +9,8 @@
<meta content="telephone=no" name="format-detection"> <meta content="telephone=no" name="format-detection">
<title>{{.Title}}</title> <title>{{.Title}}</title>
<link rel="stylesheet" type="text/css" <link rel="stylesheet" type="text/css"
href="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.min.css"> href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/semantic-ui/2.4.1/semantic.min.css">
<link href="https://cdn.staticfile.org/font-logos/0.17/font-logos.min.css" type="text/css" <link href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/font-logos/0.17/font-logos.min.css" type="text/css"
rel="stylesheet" /> rel="stylesheet" />
<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?v2022042314"> <link rel="stylesheet" type="text/css" href="/static/main.css?v2022042314">
+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
@@ -85,7 +85,7 @@
</div> </div>
{{template "component/server" .}} {{template "component/server" .}}
{{template "common/footer" .}} {{template "common/footer" .}}
<script src="https://cdn.staticfile.org/clipboard.js/2.0.10/clipboard.min.js" type="application/javascript"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/clipboard.js/2.0.10/clipboard.min.js" type="application/javascript"></script>
<script> <script>
var clipboard = new ClipboardJS('.ui.icon.green.mini.button'); var clipboard = new ClipboardJS('.ui.icon.green.mini.button');
const checkBoxList = document.querySelectorAll('tbody > tr > td > input.nezha-servers[type=checkbox]') const checkBoxList = document.querySelectorAll('tbody > tr > td > input.nezha-servers[type=checkbox]')
+4 -4
View File
@@ -8,7 +8,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>tty@{{.ServerName}} - {{.Title}}</title> <title>tty@{{.ServerName}} - {{.Title}}</title>
<link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" /> <link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" />
<link href="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/xterm/4.11.0/xterm.css" type="text/css" rel="stylesheet"/> <link href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/xterm/4.11.0/xterm.css" type="text/css" rel="stylesheet"/>
</head> </head>
<style> <style>
@@ -28,9 +28,9 @@
<body onresize="onResize()"> <body onresize="onResize()">
<div id="terminal-container"></div> <div id="terminal-container"></div>
<script src="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/xterm/4.11.0/xterm.js" type="application/javascript"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/xterm/4.11.0/xterm.js" type="application/javascript"></script>
<script src="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/xterm/4.11.0/addons/attach/xterm-addon-attach.js"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/xterm/4.11.0/addons/attach/xterm-addon-attach.js"></script>
<script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/xterm/4.11.0/addons/fit/xterm-addon-fit.js"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/xterm/4.11.0/addons/fit/xterm-addon-fit.js"></script>
<script> <script>
let sendResizing = false; let sendResizing = false;
+4 -4
View File
@@ -8,11 +8,11 @@
</small> </small>
</div> </div>
</div> </div>
<script src="https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/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://cdn.staticfile.org/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 }});
+3 -3
View File
@@ -9,10 +9,10 @@
<meta content="telephone=no" name="format-detection"> <meta content="telephone=no" name="format-detection">
<title>{{.Title}}</title> <title>{{.Title}}</title>
<link rel="stylesheet" type="text/css" <link rel="stylesheet" type="text/css"
href="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.min.css"> href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/semantic-ui/2.4.1/semantic.min.css">
<link href="https://cdn.staticfile.org/font-logos/0.17/font-logos.min.css" type="text/css" <link href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/font-logos/0.17/font-logos.min.css" type="text/css"
rel="stylesheet" /> rel="stylesheet" />
<link href="https://cdn.staticfile.org/bootstrap-icons/1.10.3/font/bootstrap-icons.css" type="text/css" <link href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/bootstrap-icons/1.8.1/font/bootstrap-icons.min.css" type="text/css"
rel="stylesheet" /> rel="stylesheet" />
<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?v2022042314"> <link rel="stylesheet" type="text/css" href="/static/main.css?v2022042314">
+1 -1
View File
@@ -15,7 +15,7 @@
<div v-for="server in group.data" :id="server.ID" class="ui card"> <div v-for="server in group.data" :id="server.ID" class="ui card">
<div class="content" v-if="server.Host" style="margin-top: 10px; padding-bottom: 5px"> <div class="content" v-if="server.Host" style="margin-top: 10px; padding-bottom: 5px">
<div class="header"> <div class="header">
<img v-if="server.Host.CountryCode" style="border-radius: 50%;box-shadow:-1px -1px 2px #eee, 1px 1px 2px #000;width:19px;" :src="'https://cdn.staticfile.org/flag-icon-css/6.6.5/flags/1x1/'+server.Host.CountryCode + '.svg'" alt="国家"/>&nbsp;<i v-if='server.Host.Platform == "darwin"' <img v-if="server.Host.CountryCode" style="border-radius: 50%;box-shadow:-1px -1px 2px #eee, 1px 1px 2px #000;width:19px;" :src="'https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/flag-icon-css/4.1.5/flags/1x1/'+server.Host.CountryCode + '.svg'" alt="国家"/>&nbsp;<i v-if='server.Host.Platform == "darwin"'
class="apple icon"></i><i v-else-if='isWindowsPlatform(server.Host.Platform)' class="apple icon"></i><i v-else-if='isWindowsPlatform(server.Host.Platform)'
class="windows icon"></i><i v-else :class="'fl-' + getFontLogoClass(server.Host.Platform)"></i> class="windows icon"></i><i v-else :class="'fl-' + getFontLogoClass(server.Host.Platform)"></i>
@#server.Name + (server.live?'':'[{{tr "Offline"}}]')#@ @#server.Name + (server.live?'':'[{{tr "Offline"}}]')#@
+5 -4
View File
@@ -10,8 +10,8 @@
<link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" /> <link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" />
<link rel="stylesheet" href="/static/theme-daynight/css/main.css?v202108042286"> <link rel="stylesheet" href="/static/theme-daynight/css/main.css?v202108042286">
<link href="https://cdn.staticfile.org/font-awesome/6.0.0/css/all.min.css" rel="stylesheet"> <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://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/jquery/3.6.0/jquery.min.js"></script>
{{if ts .CustomCode}} {{if ts .CustomCode}}
{{.CustomCode|safe}} {{.CustomCode|safe}}
@@ -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}}
@@ -165,8 +166,8 @@
</footer> </footer>
<script src="/static/theme-daynight/js/main.js?v202102012266"></script> <script src="/static/theme-daynight/js/main.js?v202102012266"></script>
<script src="https://cdn.staticfile.org/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="https://cdn.staticfile.org/limonte-sweetalert2/11.4.4/sweetalert2.all.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> <script>
const initData = JSON.parse('{{.Servers}}').servers; const initData = JSON.parse('{{.Servers}}').servers;
+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}}
+6 -3
View File
@@ -8,9 +8,9 @@
<title>{{.Title}}</title> <title>{{.Title}}</title>
<link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" /> <link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" />
<link rel="stylesheet" href="/static/theme-daynight/css/service.css?v202108042286" /> <link rel="stylesheet" href="/static/theme-daynight/css/service.css?v202108042286" />
<link rel="stylesheet" href="https://cdn.staticfile.org/mdui/1.0.2/css/mdui.min.css"/> <link rel="stylesheet" href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/mdui/1.0.2/css/mdui.min.css"/>
<link href="https://cdn.staticfile.org/font-awesome/6.0.0/css/all.min.css" rel="stylesheet" /> <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://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/jquery/3.6.0/jquery.min.js"></script>
{{if ts .CustomCode}} {{if ts .CustomCode}}
{{.CustomCode|safe}} {{.CustomCode|safe}}
@@ -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}}
@@ -60,6 +61,7 @@
</section> </section>
<section class="monitor-container"> <section class="monitor-container">
{{range $service := .Services}} {{range $service := .Services}}
{{if $service.Monitor.EnableShowInService}}
<section class="monitor-info-container"> <section class="monitor-info-container">
<div class="monitor-state"> <div class="monitor-state">
<span class="monitor-state-dot {{className (divU64 $service.CurrentUp (addU64 $service.CurrentUp $service.CurrentDown))}}"></span> <span class="monitor-state-dot {{className (divU64 $service.CurrentUp (addU64 $service.CurrentUp $service.CurrentDown))}}"></span>
@@ -81,6 +83,7 @@
</div> </div>
</section> </section>
{{end}} {{end}}
{{end}}
</section> </section>
{{if .CycleTransferStats}} {{if .CycleTransferStats}}
<h2 class="mdui-m-t-5 mdui-text-center">{{tr "CycleTransferStats"}}</h2> <h2 class="mdui-m-t-5 mdui-text-center">{{tr "CycleTransferStats"}}</h2>
+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}}
+2 -1
View File
@@ -18,6 +18,7 @@
</thead> </thead>
<tbody> <tbody>
{{range $service := .Services}} {{range $service := .Services}}
{{if $service.Monitor.EnableShowInService}}
<tr> <tr>
<td class="ui center aligned">{{$service.Monitor.Name}}</td> <td class="ui center aligned">{{$service.Monitor.Name}}</td>
<td class="ui center aligned"> <td class="ui center aligned">
@@ -33,7 +34,7 @@
class="delay-today {{className (divU64 $service.CurrentUp (addU64 $service.CurrentUp $service.CurrentDown))}}"></i> class="delay-today {{className (divU64 $service.CurrentUp (addU64 $service.CurrentUp $service.CurrentDown))}}"></i>
{{statusName (divU64 $service.CurrentUp (addU64 $service.CurrentUp $service.CurrentDown))}} {{statusName (divU64 $service.CurrentUp (addU64 $service.CurrentUp $service.CurrentDown))}}
</td> </td>
</tr> {{end}} </tr> {{end}} {{end}}
</tbody> </tbody>
</table> </table>
+5 -5
View File
@@ -14,8 +14,8 @@
<meta http-equiv="Content-Language" content="zh" /> <meta http-equiv="Content-Language" content="zh" />
<!-- Styles --> <!-- Styles -->
<link rel="stylesheet" type="text/css" href="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.min.css"> <link rel="stylesheet" type="text/css" href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/semantic-ui/2.4.1/semantic.min.css">
<link rel="stylesheet" type="text/css" href="https://cdn.staticfile.org/font-awesome/6.0.0/css/all.min.css"> <link rel="stylesheet" type="text/css" href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/font-awesome/6.0.0/css/all.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" href="/static/theme-hotaru/css/core.css?v202012121912" type="text/css"> <link rel="stylesheet" href="/static/theme-hotaru/css/core.css?v202012121912" type="text/css">
<link rel="stylesheet" href="/static/theme-hotaru/css/main.css?v202101171153" type="text/css"> <link rel="stylesheet" href="/static/theme-hotaru/css/main.css?v202101171153" type="text/css">
@@ -141,10 +141,10 @@
<a href="/server">{{tr "AdminPanel"}}</a> <a href="/server">{{tr "AdminPanel"}}</a>
</p> </p>
</footer> </footer>
<script src="https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/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://cdn.staticfile.org/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> <script>
const initData = JSON.parse('{{.Servers}}').servers; const initData = JSON.parse('{{.Servers}}').servers;
var statusCards = new Vue({ var statusCards = new Vue({
+5 -5
View File
@@ -9,7 +9,7 @@
<link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" /> <link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" />
<!-- MDUI CSS --> <!-- MDUI CSS -->
<link rel="stylesheet" href="https://cdn.staticfile.org/mdui/1.0.2/css/mdui.min.css"/> <link rel="stylesheet" href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/mdui/1.0.2/css/mdui.min.css"/>
<link rel="stylesheet" href="/static/theme-mdui/mdui.css" type="text/css"> <link rel="stylesheet" href="/static/theme-mdui/mdui.css" type="text/css">
<style> <style>
.mdui-table td, .mdui-table th{padding: 6px;} .mdui-table td, .mdui-table th{padding: 6px;}
@@ -37,7 +37,7 @@
<div class="mdui-col" v-for='server in servers' :id="server.ID"> <div class="mdui-col" v-for='server in servers' :id="server.ID">
<div :class="'mdui-card mt' + (server.live?'':' offline')"> <div :class="'mdui-card mt' + (server.live?'':' offline')">
<div class="mdui-card-header"> <div class="mdui-card-header">
<img class="mdui-card-header-avatar" :src="'https://cdn.staticfile.org/flag-icon-css/4.1.5/flags/1x1/' + (server.Host.CountryCode?server.Host.CountryCode:'cn') + '.svg'"/> <img class="mdui-card-header-avatar" :src="'https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/flag-icon-css/4.1.5/flags/1x1/' + (server.Host.CountryCode?server.Host.CountryCode:'cn') + '.svg'"/>
<div class="mdui-card-header-title">@#server.Name#@</div> <div class="mdui-card-header-title">@#server.Name#@</div>
<div class="mdui-card-header-subtitle">@#server.Host.CountryCode.toUpperCase()#@ | @#server.Host.Platform#@ @#server.Host.PlatformVersion#@</div> <div class="mdui-card-header-subtitle">@#server.Host.CountryCode.toUpperCase()#@ | @#server.Host.Platform#@ @#server.Host.PlatformVersion#@</div>
</div> </div>
@@ -153,9 +153,9 @@
{{template "theme-mdui/footer" .}} {{template "theme-mdui/footer" .}}
<script src="/static/theme-mdui/mdui.js"></script> <script src="/static/theme-mdui/mdui.js"></script>
<script src="https://cdn.staticfile.org/mdui/1.0.2/js/mdui.min.js"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/mdui/1.0.2/js/mdui.min.js"></script>
<script src="https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/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> <script>
var container=document.querySelector("#container"); var container=document.querySelector("#container");
+4 -4
View File
@@ -9,7 +9,7 @@
<link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" /> <link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" />
<!-- MDUI CSS --> <!-- MDUI CSS -->
<link rel="stylesheet" href="https://cdn.staticfile.org/mdui/1.0.2/css/mdui.min.css"/> <link rel="stylesheet" href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/mdui/1.0.2/css/mdui.min.css"/>
<link rel="stylesheet" href="/static/theme-mdui/mdui.css" type="text/css"> <link rel="stylesheet" href="/static/theme-mdui/mdui.css" type="text/css">
{{if ts .CustomCode}} {{if ts .CustomCode}}
{{.CustomCode|safe}} {{.CustomCode|safe}}
@@ -116,9 +116,9 @@
{{template "theme-mdui/footer" .}} {{template "theme-mdui/footer" .}}
<script src="/static/theme-mdui/mdui.js"></script> <script src="/static/theme-mdui/mdui.js"></script>
<script src="https://cdn.staticfile.org/mdui/1.0.2/js/mdui.min.js"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/mdui/1.0.2/js/mdui.min.js"></script>
<script src="https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/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> <script>
var container=document.querySelector("#container"); var container=document.querySelector("#container");
container.style.minHeight=window.innerHeight-document.body.clientHeight+container.clientHeight+'px'; container.style.minHeight=window.innerHeight-document.body.clientHeight+container.clientHeight+'px';
+3 -3
View File
@@ -8,7 +8,7 @@
<title>{{.Title}}</title> <title>{{.Title}}</title>
<!-- MDUI CSS --> <!-- MDUI CSS -->
<link rel="stylesheet" href="https://cdn.staticfile.org/mdui/1.0.2/css/mdui.min.css"/> <link rel="stylesheet" href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/mdui/1.0.2/css/mdui.min.css"/>
<link rel="stylesheet" href="/static/theme-mdui/mdui.css" type="text/css"> <link rel="stylesheet" href="/static/theme-mdui/mdui.css" type="text/css">
{{if ts .CustomCode}} {{if ts .CustomCode}}
{{.CustomCode|safe}} {{.CustomCode|safe}}
@@ -34,8 +34,8 @@
</div> </div>
</div> </div>
<script src="https://cdn.staticfile.org/mdui/1.0.2/js/mdui.min.js"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/mdui/1.0.2/js/mdui.min.js"></script>
<script src="https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/jquery/3.6.0/jquery.min.js"></script>
<script> <script>
var $input = mdui.$('#container').find('.mdui-textfield-input'); var $input = mdui.$('#container').find('.mdui-textfield-input');
var $dialog = new mdui.Dialog(mdui.$('.mdui-dialog')); var $dialog = new mdui.Dialog(mdui.$('.mdui-dialog'));
+1 -2
View File
@@ -1,8 +1,7 @@
{{define "theme-server-status/content-footer"}} {{define "theme-server-status/content-footer"}}
<footer class="container" style="padding-bottom: 2rem;"> <footer class="container" style="padding-bottom: 2rem;">
<p style="text-align: center; font-size: 10px;"> <p style="text-align: center; font-size: 10px;">
{{ .Conf.Site.Brand }} | Theme <a href="https://github.com/cppla/ServerStatus">ServerStatus</a> | Powered by <a {{ .Conf.Site.Brand }} | Theme <a target="_blank" href="https://github.com/cppla/ServerStatus">ServerStatus</a> | Powered by <a target="_blank" href="https://github.com/naiba/nezha">{{tr "NezhaMonitoring"}}</a> {{.Version}}
href="https://github.com/naiba/nezha">{{tr "NezhaMonitoring"}}</a> {{.Version}}
</p> </p>
</footer> </footer>
{{end}} {{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}}
+7 -7
View File
@@ -1,7 +1,6 @@
{{define "theme-server-status/header"}} {{define "theme-server-status/header"}}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{{.Conf.Language}}"> <html lang="{{.Conf.Language}}">
<head> <head>
<title>{{ .Title }}</title> <title>{{ .Title }}</title>
<meta charset="utf-8"> <meta charset="utf-8">
@@ -9,11 +8,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/theme-server-status/css/bootstrap.min.css"> <link rel="stylesheet" href="/static/theme-server-status/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/theme-server-status/css/bootstrap-theme.min.css"> <link rel="stylesheet" href="/static/theme-server-status/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="/static/theme-server-status/css/main.css?v20231109"> <link rel="stylesheet" href="/static/theme-server-status/css/main.css?v20231207">
<link rel="stylesheet" href="/static/theme-server-status/css/dark.css"> <link rel="stylesheet" href="/static/theme-server-status/css/dark.css">
<link rel="stylesheet" href="/static/theme-server-status/css/light.css"> <link rel="stylesheet" href="/static/theme-server-status/css/light.css">
<link href="https://cdn.staticfile.org/font-logos/0.17/font-logos.min.css" type="text/css" rel="stylesheet" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css">
<link rel="stylesheet" type="text/css" href="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.min.css"> <link rel="stylesheet" href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/font-logos/0.17/font-logos.min.css">
<link rel="stylesheet" href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/semantic-ui/2.4.1/semantic.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/lipis/[email protected]/css/flag-icons.min.css">
<link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" /> <link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" />
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]> <!--[if lt IE 9]>
@@ -23,12 +24,11 @@
{{if ts .CustomCode}} {{if ts .CustomCode}}
{{.CustomCode|safe}} {{.CustomCode|safe}}
{{end}} {{end}}
<script src="/static/theme-server-status/js/jquery.min.js"></script> <script src="/static/theme-server-status/js/jquery.min.js"></script>
<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://cdn.staticfile.org/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}}
+79 -60
View File
@@ -2,26 +2,30 @@
{{template "theme-server-status/header" .}} {{template "theme-server-status/header" .}}
<div id="app"> <div id="app">
{{template "theme-server-status/content-nav" .}} {{template "theme-server-status/content-nav" .}}
<div class="container table-responsive content" style="max-width: 95vw"> <div class="container table-responsive content" style="max-width: 95vw" v-for="group in nodes">
<table class="table table-striped table-condensed table-hover"> <table class="table table-striped table-condensed table-hover">
<thead> <thead>
<tr> <tr>
<th class="node-cell status center">🍀 {{tr "Status"}}</th> <th class="node-group-tag" colspan="16" style="border:none;">@#(group.Tag!==''?group.Tag:'{{tr "Default"}}')#@</th>
<th class="node-cell name">🚀 {{tr "Name"}}</th> </th>
<th class="node-cell os">🗂 {{tr "Platform"}}</th> </tr>
<th class="node-cell location center">🌍 {{tr "Location"}}</th> <tr>
<th class="node-cell uptime center">⏱️ {{tr "Uptime"}}</th> <th class="node-cell status center">{{tr "Status"}}</th>
<th class="node-cell load center">📋{{tr "Load"}}</th> <th class="node-cell name center">{{tr "Name"}}</th>
<th class="node-cell network center">🚦 {{tr "NetSpeed"}}↓|↑</th> <th class="node-cell os center">{{tr "Platform"}}</th>
<th class="node-cell traffic center">📊 {{tr "NetTransfer"}}↓|↑</th> <th class="node-cell location center">{{tr "Location"}}</th>
<th class="node-cell cpu center">🎯 {{tr "CpuUsed"}}</th> <th class="node-cell uptime center">{{tr "Uptime"}}</th>
<th class="node-cell ram center">{{tr "MemUsed"}}</th> <th class="node-cell load center">{{tr "Load"}}</th>
<th class="node-cell hdd center">💾 {{tr "DiskUsed"}}</th> <th class="node-cell network center">{{tr "NetSpeed"}}↓|↑</th>
<th class="node-cell traffic center">{{tr "NetTransfer"}}↓|↑</th>
<th class="node-cell cpu center">{{tr "CpuUsed"}}</th>
<th class="node-cell ram center">{{tr "MemUsed"}}</th>
<th class="node-cell hdd center">{{tr "DiskUsed"}}</th>
</tr> </tr>
</thead> </thead>
<tbody id="servers"> <tbody id="servers">
<template v-for="(node,index) in nodes"> <template v-for="(node,index) in group.data">
<tr :id="'r'+index" data-toggle="collapse" :data-target="'#rt'+index" class="accordion-toggle" <tr :id="'r'+node.ID" data-toggle="collapse" :data-target="'#rt'+node.ID" class="accordion-toggle"
:class="index % 2 === 0 ? 'odd': 'even'"> :class="index % 2 === 0 ? 'odd': 'even'">
<td class="node-cell status center"> <td class="node-cell status center">
<div class="status-container"> <div class="status-container">
@@ -29,36 +33,36 @@
<div v-else class="status-icon offline"></div> <div v-else class="status-icon offline"></div>
</div> </div>
</td> </td>
<td class="node-cell name">@#node.name#@</td> <td class="node-cell name center">@#node.name#@</td>
<td class="node-cell os"> <td class="node-cell os center">
<i v-if='node.os == "darwin"' class="apple icon"></i> <i v-if='node.os == "darwin"' class="apple icon"></i>
<i v-else-if='isWindowsPlatform(node.host.Platform)' class="windows icon"></i> <i v-else-if='isWindowsPlatform(node.host.Platform)' class="windows icon"></i>
<i v-else :class="'fl-' + getFontLogoClass(node.host.Platform)"></i> <i v-else :class="'fl-' + getFontLogoClass(node.host.Platform)"></i>
@#node.os#@ <span class="node-cell-os-text">@#node.os#@</span>
</td> </td>
<td style="text-align: center;" class="node-cell location"> <td style="text-align: center;" class="node-cell location">
<i :class="node.location + ' flag'"></i>&nbsp; <i :class="'fi fi-' + node.location"></i>
<span class="text-uppercase">@#node.location#@</span> <span class="node-cell-location-text text-uppercase">&nbsp;@#node.location#@</span>
</td> </td>
<td style="text-align: center;" class="node-cell uptime">@#node.uptime#@</td> <td style="text-align: center;" class="node-cell uptime">@#node.uptime#@</td>
<td style="text-align: center;" class="node-cell load">@#node.load#@</td> <td style="text-align: center;" class="node-cell load">@#node.load#@</td>
<td style="text-align: center;" class="node-cell network">@#node.network#@</td> <td style="text-align: center;" class="node-cell network">@#node.network#@</td>
<td style="text-align: center;" class="node-cell traffic">@#node.traffic#@</td> <td style="text-align: center;" class="node-cell traffic">@#node.traffic#@</td>
<td class="node-cell cpu"> <td class="node-cell cpu">
<div class="progress"> <div :class="['progress', node.online ? 'progress-online' : 'progress-offline']">
<div :style="node.cpu.style" :class="node.cpu.class"><small>@#node.cpu.percent#@%</small> <div :style="node.cpu.style" :class="node.cpu.class"><small>@#node.cpu.percent#@%</small>
</div> </div>
</div> </div>
</td> </td>
<td class="node-cell memory"> <td class="node-cell memory">
<div class="progress"> <div :class="['progress', node.online ? 'progress-online' : 'progress-offline']">
<div :style="node.memory.style" :class="node.memory.class"> <div :style="node.memory.style" :class="node.memory.class">
<small>@#node.memory.percent#@%</small> <small>@#node.memory.percent#@%</small>
</div> </div>
</div> </div>
</td> </td>
<td class="node-cell hdd"> <td class="node-cell hdd">
<div class="progress"> <div :class="['progress', node.online ? 'progress-online' : 'progress-offline']">
<div :style="node.hdd.style" :class="node.hdd.class"><small>@#node.hdd.percent#@%</small> <div :style="node.hdd.style" :class="node.hdd.class"><small>@#node.hdd.percent#@%</small>
</div> </div>
</div> </div>
@@ -66,24 +70,16 @@
</tr> </tr>
<tr class="expandRow" :class="index % 2 === 0 ? 'odd': 'even'"> <tr class="expandRow" :class="index % 2 === 0 ? 'odd': 'even'">
<td colspan="16"> <td colspan="16">
<div class="accordian-body collapse" :id="'rt'+index"> <div class="accordian-body collapse" :id="'rt'+node.ID">
<div style="display: flex;align-items: center;justify-content: center;flex-direction: column;"> <div style="display: flex;left-items: center;justify-content: center;flex-direction: column; max-width: 89vw">
<div style="display: flex;align-items: flex-start;justify-content: center;flex-direction: column; width: 450px;max-width: 90vw">
<span class="node-cell-expand"> <span class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "Platform"}}:</span> <span class="node-cell-expand-label">{{tr "Platform"}}:</span>
@#node.host.Platform#@ @#node.host.Platform#@-@#node.host.PlatformVersion#@
</span> [<span v-if="node.host.Virtualization">@#node.host.Virtualization#@:</span>@#node.host.Arch#@]
<span class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "Location"}}:</span>
<i :class="node.location + ' flag'"></i>
</span> </span>
<span class="node-cell-expand" v-if="node.host.CPU"> <span class="node-cell-expand" v-if="node.host.CPU">
<span class="node-cell-expand-label">CPU:</span> <span class="node-cell-expand-label">CPU:</span>
@#node.host.CPU.join(",")#@ @#node.host.CPU.join(",")#@
</span>
<span class="node-cell-expand load">
<span class="node-cell-expand-label">{{tr "Load"}}:</span>
@#toFixed2(node.state.Load1)#@ / @#toFixed2(node.state.Load5)#@ /@#toFixed2(node.state.Load15)#@
</span> </span>
<span class="node-cell-expand"> <span class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "DiskUsed"}}:</span> <span class="node-cell-expand-label">{{tr "DiskUsed"}}:</span>
@@ -93,6 +89,11 @@
<span class="node-cell-expand-label">{{tr "MemUsed"}}:</span> <span class="node-cell-expand-label">{{tr "MemUsed"}}:</span>
@#formatByteSize(node.state.MemUsed)#@ / @#formatByteSize(node.host.MemTotal)#@(@#toFixed2(node.state.MemUsed / node.host.MemTotal * 100)#@%) @#formatByteSize(node.state.MemUsed)#@ / @#formatByteSize(node.host.MemTotal)#@(@#toFixed2(node.state.MemUsed / node.host.MemTotal * 100)#@%)
</span> </span>
<span class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "SwapUsed"}}:</span>
@#formatByteSize(node.state.SwapUsed)#@ / @#formatByteSize(node.host.SwapTotal)#@
<span v-if="node.host.SwapTotal">(@#toFixed2(node.state.SwapUsed / node.host.SwapTotal * 100)#@%)</span>
</span>
<span class="node-cell-expand"> <span class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "NetTransfer"}}:</span> <span class="node-cell-expand-label">{{tr "NetTransfer"}}:</span>
<i class="arrow alternate circle down outline icon" <i class="arrow alternate circle down outline icon"
@@ -100,22 +101,9 @@
<i class="arrow alternate circle up outline icon" <i class="arrow alternate circle up outline icon"
style="margin: 0"></i>@#formatByteSize(node.state.NetOutTransfer)#@ style="margin: 0"></i>@#formatByteSize(node.state.NetOutTransfer)#@
</span> </span>
<span class="node-cell-expand"> <span class="node-cell-expand load">
<span class="node-cell-expand-label">{{tr "SwapUsed"}}:</span> <span class="node-cell-expand-label">{{tr "Load"}}:</span>
@#formatByteSize(node.state.SwapUsed)#@ / @#formatByteSize(node.host.SwapTotal)#@ @#toFixed2(node.state.Load1)#@ / @#toFixed2(node.state.Load5)#@ / @#toFixed2(node.state.Load15)#@
<span v-if="node.host.SwapTotal">(@#toFixed2(node.state.SwapUsed / node.host.SwapTotal * 100)#@%)</span>
</span>
<span class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "BootTime"}}:</span>
@#formatTimestamp(node.host.BootTime)#@
</span>
<span class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "LastActive"}}:</span>
@#new Date(node.lastActive).toLocaleString()#@
</span>
<span class="node-cell-expand" v-if="node.host.Virtualization">
<span class="node-cell-expand-label">{{tr "Virtualization"}}:</span>
@#node.host.Virtualization#@
</span> </span>
<span class="node-cell-expand"> <span class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "ProcessCount"}}:</span> <span class="node-cell-expand-label">{{tr "ProcessCount"}}:</span>
@@ -125,19 +113,29 @@
<span class="node-cell-expand-label">{{tr "ConnCount"}}:</span> <span class="node-cell-expand-label">{{tr "ConnCount"}}:</span>
TCP @#node.state.TcpConnCount#@ / UDP @#node.state.UdpConnCount#@ TCP @#node.state.TcpConnCount#@ / UDP @#node.state.UdpConnCount#@
</span> </span>
</div> <span class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "BootTime"}}:</span>
@#formatTimestamp(node.host.BootTime)#@
</span>
<span class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "LastActive"}}:</span>
@#new Date(node.lastActive).toLocaleString()#@
</span>
<span class="node-cell-expand">
<span class="node-cell-expand-label">{{tr "Version"}}:</span>
@#node.host.Version#@
</span>
</div> </div>
</div> </div>
</td> </td>
</tr> </tr>
</template> </template>
</tbody> </tbody>
</table> </table>
<br/>
</div> </div>
{{template "theme-server-status/content-footer" .}} {{template "theme-server-status/content-footer" .}}
</div> </div>
<script> <script>
new Vue({ new Vue({
el: '#app', el: '#app',
@@ -148,7 +146,7 @@
mixins: [mixinsVue], mixins: [mixinsVue],
created() { created() {
const initData = JSON.parse('{{.Servers}}').servers; const initData = JSON.parse('{{.Servers}}').servers;
this.nodes = this.handleNodes(initData); this.nodes = groupingData(this.handleNodes(initData),"Tag");
this.initTheme() this.initTheme()
}, },
mounted() { mounted() {
@@ -233,17 +231,17 @@
return x !== "NaN undefined" ? x : '0B' return x !== "NaN undefined" ? x : '0B'
}, },
formatPercent(live, used, total) { formatPercent(live, used, total) {
const percent = live ? (this.toFixed2(used / total * 100) || 0) : 1 const percent = live ? (this.toFixed2(used / total * 100) || 0) : 0
return this.formatPercents(percent) return this.formatPercents(percent)
}, },
formatPercents(percent) { formatPercents(percent) {
if (percent <= 0) { if (percent <= 0) {
percent = 1; percent = 0;
} }
if (!this.cache[percent]) { if (!this.cache[percent]) {
this.cache[percent] = { this.cache[percent] = {
class: 'progress-bar progress-bar-success', class: 'progress-bar progress-bar-success',
style: `width: ${parseInt(percent)}%`, style: `width: ${parseInt(percent)+1}%`,
percent, percent,
} }
if (percent < 80) { if (percent < 80) {
@@ -282,7 +280,7 @@
data.servers[i].live = data.now - lastActive <= 10 * 1000; data.servers[i].live = data.now - lastActive <= 10 * 1000;
} }
} }
this.nodes = this.handleNodes(data.servers) this.nodes = groupingData(this.handleNodes(data.servers),"Tag");
} }
ws.onclose = () => { ws.onclose = () => {
setTimeout(function () { setTimeout(function () {
@@ -296,13 +294,14 @@
handleNodes(servers) { handleNodes(servers) {
let nodes = [] let nodes = []
servers?.forEach(server => { servers?.forEach(server => {
let platform = server.Host.Platform let platform = server.Host.Platform;
if (this.isWindowsPlatform(server.Host.Platform)) { if (this.isWindowsPlatform(platform)) {
platform = "windows" platform = "windows"
}else if (platform === "immortalwrt") { }else if (platform === "immortalwrt") {
platform = "openwrt" platform = "openwrt"
} }
let node = { let node = {
ID: server.ID,
name: server.Name, name: server.Name,
os: platform, os: platform,
location: server.Host.CountryCode, location: server.Host.CountryCode,
@@ -317,6 +316,7 @@
state: server.State, state: server.State,
host: server.Host, host: server.Host,
lastActive: server.LastActive, lastActive: server.LastActive,
Tag: server.Tag,
} }
nodes.push(node) nodes.push(node)
}) })
@@ -328,6 +328,25 @@
} }
}) })
function groupingData(data, field) {
let map = new Map();
let dest = [];
data.forEach(item => {
if (!map.has(item[field])) {
dest.push({
[field]: item[field],
data: [item]
});
map.set(item[field], item);
} else {
dest.find(dItem => dItem[field] === item[field]).data.push(item);
}
});
return dest;
}
</script> </script>
{{template "theme-server-status/footer" .}} {{template "theme-server-status/footer" .}}
{{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}}
+10 -9
View File
@@ -6,23 +6,23 @@
<table class="table table-striped table-condensed service-status"> <table class="table table-striped table-condensed service-status">
<thead> <thead>
<tr> <tr>
<th class="node-cell" style="min-width: 60px">🍀 {{tr "Status"}}</th> <th class="node-cell center" style="min-width:60px">{{tr "Status"}}</th>
<th class="node-cell" style="min-width: 60px">🚀 {{tr "Name"}}</th> <th class="node-cell center" style="min-width:50px">{{tr "Name"}}</th>
<th class="node-cell center">🗂 {{tr "Details"}}</th> <th class="node-cell center">{{tr "Details"}}</th>
<th class="node-cell center" style="min-width: 80px">⚡️{{tr "AverageLatency"}}</th> <th class="node-cell center" style="min-width:80px">{{tr "AverageLatency"}}</th>
<th class="node-cell center" style="min-width: 80px">⏱️ {{tr "30DaysOnline"}}</th> <th class="node-cell center" style="min-width:80px">{{tr "30DaysOnline"}}</th>
</tr> </tr>
</thead> </thead>
<tbody id="servers"> <tbody id="servers">
<template v-for="service in services"> <template v-for="service in services">
<tr> <tr v-if="service.enableShow === 'true'">
<td style="text-align: left" class="node-cell"> <td class="node-cell center">
<div class="delay-today"> <div class="delay-today">
<i class="delay-today" :class="service.health.className"></i> <i class="delay-today" :class="service.health.className"></i>
@#service.health.text#@ @#service.health.text#@
</div> </div>
</td> </td>
<td class="node-cell">@#service.name#@</td> <td class="node-cell center">@#service.name#@</td>
<td class="node-cell center"> <td class="node-cell center">
<template v-for="(item,index) in service.dayDetail"> <template v-for="(item,index) in service.dayDetail">
<div class="service-day-status-icon" :class="item.className" <div class="service-day-status-icon" :class="item.className"
@@ -44,7 +44,7 @@
</table> </table>
</div> </div>
<div class="container" style="margin-bottom: 20px;padding:unset;max-width: 95vw"> <div class="container" style="padding:unset;max-width: 95vw">
{{if .CycleTransferStats}} {{if .CycleTransferStats}}
<h4 style="text-align: center;">{{tr "CycleTransferStats"}}</h4> <h4 style="text-align: center;">{{tr "CycleTransferStats"}}</h4>
<div class="table-responsive content"> <div class="table-responsive content">
@@ -137,6 +137,7 @@
delay: '{{$service.Delay}}'.replaceAll("[","").replaceAll("]","").split(" "), delay: '{{$service.Delay}}'.replaceAll("[","").replaceAll("]","").split(" "),
up: '{{$service.Up}}'.replaceAll("[","").replaceAll("]","").split(" "), up: '{{$service.Up}}'.replaceAll("[","").replaceAll("]","").split(" "),
down: '{{$service.Down}}'.replaceAll("[","").replaceAll("]","").split(" "), down: '{{$service.Down}}'.replaceAll("[","").replaceAll("]","").split(" "),
enableShow: '{{$service.Monitor.EnableShowInService}}',
}) })
{{end}} {{end}}
// @formatter:on // @formatter:on
+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
+50 -9
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.2" 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() {
@@ -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,7 +576,7 @@ 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
@@ -794,11 +811,35 @@ clean_all() {
} }
select_version() { select_version() {
if command -v docker compose >/dev/null 2>&1; then DOCKER_COMPOSE_COMMAND=""
if docker compose ls | grep -qw "$NZ_DASHBOARD_PATH/docker-compose.yaml" >/dev/null 2>&1; then if docker compose version >/dev/null 2>&1; then
DOCKER_COMPOSE_COMMAND="docker compose"
if $DOCKER_COMPOSE_COMMAND ls | grep -qw "$NZ_DASHBOARD_PATH/docker-compose.yaml" >/dev/null 2>&1; then
NEZHA_IMAGES=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep -w "nezha-dashboard")
if [ -n "$NEZHA_IMAGES" ]; then
echo "存在带有 nezha-dashboard 仓库的 Docker 镜像:"
echo "$NEZHA_IMAGES"
IS_DOCKER_NEZHA=1 IS_DOCKER_NEZHA=1
return
else
echo "未找到带有 nezha-dashboard 仓库的 Docker 镜像。"
fi fi
fi fi
elif command -v docker-compose >/dev/null 2>&1; then
DOCKER_COMPOSE_COMMAND="docker-compose"
if $DOCKER_COMPOSE_COMMAND -f "$NZ_DASHBOARD_PATH/docker-compose.yaml" config >/dev/null 2>&1; then
NEZHA_IMAGES=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep -w "nezha-dashboard")
if [ -n "$NEZHA_IMAGES" ]; then
echo "存在带有 nezha-dashboard 仓库的 Docker 镜像:"
echo "$NEZHA_IMAGES"
IS_DOCKER_NEZHA=1
return
else
echo "未找到带有 nezha-dashboard 仓库的 Docker 镜像。"
fi
fi
fi
if [[ -f $NZ_DASHBOARD_PATH/app ]]; then if [[ -f $NZ_DASHBOARD_PATH/app ]]; then
IS_DOCKER_NEZHA=0 IS_DOCKER_NEZHA=0
else else
+49 -9
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.2" 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
} }
@@ -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,7 +573,7 @@ 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
@@ -792,11 +808,35 @@ clean_all() {
} }
select_version() { select_version() {
if command -v docker compose >/dev/null 2>&1; then DOCKER_COMPOSE_COMMAND=""
if docker compose ls | grep -qw "$NZ_DASHBOARD_PATH/docker-compose.yaml" >/dev/null 2>&1; then if docker compose version >/dev/null 2>&1; then
DOCKER_COMPOSE_COMMAND="docker compose"
if $DOCKER_COMPOSE_COMMAND ls | grep -qw "$NZ_DASHBOARD_PATH/docker-compose.yaml" >/dev/null 2>&1; then
NEZHA_IMAGES=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep -w "nezha-dashboard")
if [ -n "$NEZHA_IMAGES" ]; then
echo "Docker image with nezha-dashboard repository exists:"
echo "$NEZHA_IMAGES"
IS_DOCKER_NEZHA=1 IS_DOCKER_NEZHA=1
return
else
echo "No Docker images with the nezha-dashboard repository were found."
fi fi
fi fi
elif command -v docker-compose >/dev/null 2>&1; then
DOCKER_COMPOSE_COMMAND="docker-compose"
if $DOCKER_COMPOSE_COMMAND -f "$NZ_DASHBOARD_PATH/docker-compose.yaml" config >/dev/null 2>&1; then
NEZHA_IMAGES=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep -w "nezha-dashboard")
if [ -n "$NEZHA_IMAGES" ]; then
echo "Docker image with nezha-dashboard repository exists:"
echo "$NEZHA_IMAGES"
IS_DOCKER_NEZHA=1
return
else
echo "No Docker images with the nezha-dashboard repository were found."
fi
fi
fi
if [[ -f $NZ_DASHBOARD_PATH/app ]]; then if [[ -f $NZ_DASHBOARD_PATH/app ]]; then
IS_DOCKER_NEZHA=0 IS_DOCKER_NEZHA=0
else else
+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