mirror of
https://github.com/nezhahq/nezha.git
synced 2025-01-22 12:48:14 -05:00
⚡️ pprof 性能调优 / 计划任务支持秒级
This commit is contained in:
parent
1f1e0b6db7
commit
47dfa4777b
3
.gitignore
vendored
3
.gitignore
vendored
@ -10,8 +10,11 @@
|
|||||||
|
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
*.out
|
*.out
|
||||||
|
*.pprof
|
||||||
.idea
|
.idea
|
||||||
/data
|
/data
|
||||||
/dist
|
/dist
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/main
|
/main
|
||||||
|
/cmd/agent/main
|
||||||
|
/cmd/dashboard/main
|
30
README.md
30
README.md
@ -4,7 +4,7 @@
|
|||||||
<br>
|
<br>
|
||||||
<small><i>LOGO designed by <a href="https://xio.ng" target="_blank">熊大</a> .</i></small>
|
<small><i>LOGO designed by <a href="https://xio.ng" target="_blank">熊大</a> .</i></small>
|
||||||
<br><br>
|
<br><br>
|
||||||
<img src="https://img.shields.io/github/workflow/status/naiba/nezha/Dashboard%20image?label=Dash%20v0.10.1&logo=github&style=for-the-badge"> <img src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&label=Agent&style=for-the-badge&logo=github"> <img src="https://img.shields.io/github/workflow/status/naiba/nezha/Agent%20release?label=Agent%20CI&logo=github&style=for-the-badge"> <img src="https://img.shields.io/badge/Installer-v0.7.0-brightgreen?style=for-the-badge&logo=linux">
|
<img src="https://img.shields.io/github/workflow/status/naiba/nezha/Dashboard%20image?label=Dash%20v0.10.2&logo=github&style=for-the-badge"> <img src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&label=Agent&style=for-the-badge&logo=github"> <img src="https://img.shields.io/github/workflow/status/naiba/nezha/Agent%20release?label=Agent%20CI&logo=github&style=for-the-badge"> <img src="https://img.shields.io/badge/Installer-v0.7.0-brightgreen?style=for-the-badge&logo=linux">
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
<p>:trollface: <b>哪吒监控</b> 一站式轻监控轻运维系统。支持系统状态、HTTP(SSL 证书变更、即将到期、到期)、TCP、Ping 监控报警,命令批量执行和计划任务。</p>
|
<p>:trollface: <b>哪吒监控</b> 一站式轻监控轻运维系统。支持系统状态、HTTP(SSL 证书变更、即将到期、到期)、TCP、Ping 监控报警,命令批量执行和计划任务。</p>
|
||||||
@ -14,9 +14,9 @@
|
|||||||
|
|
||||||
\>> [我们的用户](https://www.google.com/search?q="powered+by+哪吒监控"&filter=0) (Google)
|
\>> [我们的用户](https://www.google.com/search?q="powered+by+哪吒监控"&filter=0) (Google)
|
||||||
|
|
||||||
| 默认主题 | DayNight [@JackieSung](https://github.com/JackieSung4ev) | hotaru |
|
| 默认主题 | DayNight [@JackieSung](https://github.com/JackieSung4ev) | hotaru |
|
||||||
| ------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------- |
|
| ----------------------------------------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------- |
|
||||||
| ![首页截图1](https://s3.ax1x.com/2020/12/07/DvTCwD.jpg) | <img src="https://s3.ax1x.com/2021/01/20/sfJv2q.jpg"/> | <img src="https://s3.ax1x.com/2020/12/09/rPF4xJ.png" width="1600px" /> |
|
| ![默认主题](resource/template/theme-default/screenshot.png) | ![daynight](resource/template/theme-daynight/screenshot.png) | <img src="resource/template/theme-hotaru/screenshot.png" width="1600px" /> |
|
||||||
|
|
||||||
## 安装脚本
|
## 安装脚本
|
||||||
|
|
||||||
@ -36,11 +36,12 @@ CN=true sudo ./nezha.sh
|
|||||||
|
|
||||||
_\* 使用 WatchTower 可以自动更新面板,Windows 终端可以使用 nssm 配置自启动(见尾部教程)_
|
_\* 使用 WatchTower 可以自动更新面板,Windows 终端可以使用 nssm 配置自启动(见尾部教程)_
|
||||||
|
|
||||||
### 特殊技能
|
### 增强配置
|
||||||
|
|
||||||
通过执行 `./nezha-agent --help` 查看支持的参数,如果你使用一键脚本,可以编辑 `/etc/systemd/system/nezha-agent.service`,在 `ExecStart=` 这一行的末尾加上
|
通过执行 `./nezha-agent --help` 查看支持的参数,如果你使用一键脚本,可以编辑 `/etc/systemd/system/nezha-agent.service`,在 `ExecStart=` 这一行的末尾加上
|
||||||
|
|
||||||
- `--skip-conn` 不监控连接数,机场/连接密集型机器推荐设置,不然比较占 CPU([shirou/gopsutil/issues#220](https://github.com/shirou/gopsutil/issues/220))
|
- `--skip-conn` 不监控连接数,机场/连接密集型机器推荐设置,不然比较占 CPU([shirou/gopsutil/issues#220](https://github.com/shirou/gopsutil/issues/220))
|
||||||
|
- `--skip-procs` 不监控进程数,也可以降低 agent 占用
|
||||||
- `--disable-auto-update` 禁止 Agent 自动更新
|
- `--disable-auto-update` 禁止 Agent 自动更新
|
||||||
|
|
||||||
## 功能说明
|
## 功能说明
|
||||||
@ -230,7 +231,7 @@ URL 里面也可放置占位符,请求时会进行简单的字符串替换。
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Agent 不断重启/无法启动 ?</summary>
|
<summary>Agent不上线不启动自检流程</summary>
|
||||||
|
|
||||||
1. 直接执行 `/opt/nezha/agent/nezha-agent -s 面板IP或非CDN域名:面板RPC端口 -p Agent密钥 -d` 查看日志是否是 DNS 问题。
|
1. 直接执行 `/opt/nezha/agent/nezha-agent -s 面板IP或非CDN域名:面板RPC端口 -p Agent密钥 -d` 查看日志是否是 DNS 问题。
|
||||||
2. `nc -v 域名/IP 面板RPC端口` 或者 `telnet 域名/IP 面板RPC端口` 检验是否是网络问题,检查本机与面板服务器出入站防火墙,如果单机无法判断可借助 <https://port.ping.pe/> 提供的端口检查工具进行检测。
|
2. `nc -v 域名/IP 面板RPC端口` 或者 `telnet 域名/IP 面板RPC端口` 检验是否是网络问题,检查本机与面板服务器出入站防火墙,如果单机无法判断可借助 <https://port.ping.pe/> 提供的端口检查工具进行检测。
|
||||||
@ -243,7 +244,7 @@ URL 里面也可放置占位符,请求时会进行简单的字符串替换。
|
|||||||
|
|
||||||
首先在 release 下载对应的二进制解压 tar.gz 包后放置到 `/root`,然后 `chmod +x /root/nezha-agent` 赋予执行权限,然后创建 `/etc/init.d/nezha-service`:
|
首先在 release 下载对应的二进制解压 tar.gz 包后放置到 `/root`,然后 `chmod +x /root/nezha-agent` 赋予执行权限,然后创建 `/etc/init.d/nezha-service`:
|
||||||
|
|
||||||
```
|
```shell
|
||||||
#!/bin/sh /etc/rc.common
|
#!/bin/sh /etc/rc.common
|
||||||
|
|
||||||
START=99
|
START=99
|
||||||
@ -272,8 +273,9 @@ restart() {
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>实时通道断开/终端连接失败</summary>
|
<summary>实时通道断开/网页终端连接失败</summary>
|
||||||
使用反向代理时需要针对 `/ws` 路径的 WebSocket 进行特别配置以支持实时更新服务器状态。
|
|
||||||
|
使用反向代理时需要针对 `/ws`,`/terminal` 路径的 WebSocket 进行特别配置以支持实时更新服务器状态和 **WebSSH**。
|
||||||
|
|
||||||
- Nginx(宝塔):在你的 nginx 配置文件中加入以下代码
|
- Nginx(宝塔):在你的 nginx 配置文件中加入以下代码
|
||||||
|
|
||||||
@ -285,7 +287,6 @@ restart() {
|
|||||||
|
|
||||||
location ~ ^/(ws|terminal/.+)$ {
|
location ~ ^/(ws|terminal/.+)$ {
|
||||||
proxy_pass http://ip:站点访问端口;
|
proxy_pass http://ip:站点访问端口;
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection "Upgrade";
|
proxy_set_header Connection "Upgrade";
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
@ -295,6 +296,15 @@ restart() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
如果非宝塔,还要在 `server{}` 中添加上这一段
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
location / {
|
||||||
|
proxy_pass http://ip:站点访问端口;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
- CaddyServer v1(v2 无需特别配置)
|
- CaddyServer v1(v2 无需特别配置)
|
||||||
|
|
||||||
```Caddyfile
|
```Caddyfile
|
||||||
|
@ -30,25 +30,21 @@ import (
|
|||||||
|
|
||||||
type AgentConfig struct {
|
type AgentConfig struct {
|
||||||
SkipConnectionCount bool
|
SkipConnectionCount bool
|
||||||
|
SkipProcsCount bool
|
||||||
DisableAutoUpdate bool
|
DisableAutoUpdate bool
|
||||||
Debug bool
|
Debug bool
|
||||||
Server string
|
Server string
|
||||||
ClientSecret string
|
ClientSecret string
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
http.DefaultClient.Timeout = time.Second * 5
|
|
||||||
flag.CommandLine.ParseErrorsWhitelist.UnknownFlags = true
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version string
|
version string
|
||||||
agentConf AgentConfig
|
client pb.NezhaServiceClient
|
||||||
|
inited bool
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
client pb.NezhaServiceClient
|
agentConf AgentConfig
|
||||||
inited bool
|
|
||||||
updateCh = make(chan struct{}) // Agent 自动更新间隔
|
updateCh = make(chan struct{}) // Agent 自动更新间隔
|
||||||
httpClient = &http.Client{
|
httpClient = &http.Client{
|
||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
@ -63,6 +59,11 @@ const (
|
|||||||
networkTimeOut = time.Second * 5 // 普通网络超时
|
networkTimeOut = time.Second * 5 // 普通网络超时
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
http.DefaultClient.Timeout = time.Second * 5
|
||||||
|
flag.CommandLine.ParseErrorsWhitelist.UnknownFlags = true
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// 来自于 GoReleaser 的版本号
|
// 来自于 GoReleaser 的版本号
|
||||||
monitor.Version = version
|
monitor.Version = version
|
||||||
@ -71,6 +72,7 @@ func main() {
|
|||||||
flag.StringVarP(&agentConf.Server, "server", "s", "localhost:5555", "管理面板RPC端口")
|
flag.StringVarP(&agentConf.Server, "server", "s", "localhost:5555", "管理面板RPC端口")
|
||||||
flag.StringVarP(&agentConf.ClientSecret, "password", "p", "", "Agent连接Secret")
|
flag.StringVarP(&agentConf.ClientSecret, "password", "p", "", "Agent连接Secret")
|
||||||
flag.BoolVar(&agentConf.SkipConnectionCount, "skip-conn", false, "不监控连接数")
|
flag.BoolVar(&agentConf.SkipConnectionCount, "skip-conn", false, "不监控连接数")
|
||||||
|
flag.BoolVar(&agentConf.SkipProcsCount, "skip-procs", false, "不监控进程数")
|
||||||
flag.BoolVar(&agentConf.DisableAutoUpdate, "disable-auto-update", false, "禁用自动升级")
|
flag.BoolVar(&agentConf.DisableAutoUpdate, "disable-auto-update", false, "禁用自动升级")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@ -88,7 +90,6 @@ func run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
go pty.DownloadDependency()
|
go pty.DownloadDependency()
|
||||||
|
|
||||||
// 上报服务器信息
|
// 上报服务器信息
|
||||||
go reportState()
|
go reportState()
|
||||||
// 更新IP信息
|
// 更新IP信息
|
||||||
@ -168,8 +169,8 @@ func receiveTasks(tasks pb.NezhaService_RequestTaskClient) error {
|
|||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if recover() != nil {
|
if err := recover(); err != nil {
|
||||||
println("task panic", task)
|
println("task panic", task, err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
doTask(task)
|
doTask(task)
|
||||||
@ -209,7 +210,7 @@ func reportState() {
|
|||||||
if client != nil && inited {
|
if client != nil && inited {
|
||||||
monitor.TrackNetworkSpeed()
|
monitor.TrackNetworkSpeed()
|
||||||
timeOutCtx, cancel := context.WithTimeout(context.Background(), networkTimeOut)
|
timeOutCtx, cancel := context.WithTimeout(context.Background(), networkTimeOut)
|
||||||
_, err = client.ReportSystemState(timeOutCtx, monitor.GetState(agentConf.SkipConnectionCount).PB())
|
_, err = client.ReportSystemState(timeOutCtx, monitor.GetState(agentConf.SkipConnectionCount, agentConf.SkipProcsCount).PB())
|
||||||
cancel()
|
cancel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("reportState error", err)
|
println("reportState error", err)
|
||||||
@ -229,7 +230,7 @@ func doSelfUpdate() {
|
|||||||
println("检查更新:", v)
|
println("检查更新:", v)
|
||||||
latest, err := selfupdate.UpdateSelf(v, "naiba/nezha")
|
latest, err := selfupdate.UpdateSelf(v, "naiba/nezha")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("自动更新失败:", err)
|
println("更新失败:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !latest.Version.Equals(v) {
|
if !latest.Version.Equals(v) {
|
||||||
@ -282,7 +283,7 @@ func handleHttpGetTask(task *pb.Task, result *pb.TaskResult) {
|
|||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// 检查 SSL 证书信息
|
// 检查 SSL 证书信息
|
||||||
if len(resp.TLS.PeerCertificates) > 0 {
|
if resp.TLS != nil && len(resp.TLS.PeerCertificates) > 0 {
|
||||||
c := resp.TLS.PeerCertificates[0]
|
c := resp.TLS.PeerCertificates[0]
|
||||||
result.Data = c.Issuer.CommonName + "|" + c.NotAfter.In(time.Local).String()
|
result.Data = c.Issuer.CommonName + "|" + c.NotAfter.In(time.Local).String()
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -15,20 +14,27 @@ import (
|
|||||||
"github.com/shirou/gopsutil/v3/load"
|
"github.com/shirou/gopsutil/v3/load"
|
||||||
"github.com/shirou/gopsutil/v3/mem"
|
"github.com/shirou/gopsutil/v3/mem"
|
||||||
"github.com/shirou/gopsutil/v3/net"
|
"github.com/shirou/gopsutil/v3/net"
|
||||||
|
"github.com/shirou/gopsutil/v3/process"
|
||||||
|
|
||||||
"github.com/naiba/nezha/model"
|
"github.com/naiba/nezha/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Version string = "debug"
|
var (
|
||||||
var netInSpeed, netOutSpeed, netInTransfer, netOutTransfer, lastUpdate uint64
|
Version string = "debug"
|
||||||
var expectDiskFsTypes = []string{
|
expectDiskFsTypes = []string{
|
||||||
"apfs", "ext4", "ext3", "ext2", "f2fs", "reiserfs", "jfs", "btrfs",
|
"apfs", "ext4", "ext3", "ext2", "f2fs", "reiserfs", "jfs", "btrfs",
|
||||||
"fuseblk", "zfs", "simfs", "ntfs", "fat32", "exfat", "xfs", "fuse.rclone",
|
"fuseblk", "zfs", "simfs", "ntfs", "fat32", "exfat", "xfs", "fuse.rclone",
|
||||||
}
|
}
|
||||||
var excludeNetInterfaces = []string{
|
excludeNetInterfaces = []string{
|
||||||
"lo", "tun", "docker", "veth", "br-", "vmbr", "vnet", "kube",
|
"lo", "tun", "docker", "veth", "br-", "vmbr", "vnet", "kube",
|
||||||
}
|
}
|
||||||
var getMacDiskNo = regexp.MustCompile(`\/dev\/disk(\d)s.*`)
|
getMacDiskNo = regexp.MustCompile(`\/dev\/disk(\d)s.*`)
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
netInSpeed, netOutSpeed, netInTransfer, netOutTransfer, lastUpdateNetStats uint64
|
||||||
|
cachedBootTime time.Time
|
||||||
|
)
|
||||||
|
|
||||||
func GetHost() *model.Host {
|
func GetHost() *model.Host {
|
||||||
hi, _ := host.Info()
|
hi, _ := host.Info()
|
||||||
@ -58,6 +64,8 @@ func GetHost() *model.Host {
|
|||||||
swapMemTotal = mv.SwapTotal
|
swapMemTotal = mv.SwapTotal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cachedBootTime = time.Now().Add(time.Duration(-1 * int64(hi.BootTime*1000)))
|
||||||
|
|
||||||
return &model.Host{
|
return &model.Host{
|
||||||
Platform: hi.OS,
|
Platform: hi.OS,
|
||||||
PlatformVersion: hi.PlatformVersion,
|
PlatformVersion: hi.PlatformVersion,
|
||||||
@ -74,8 +82,12 @@ func GetHost() *model.Host {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetState(skipConnectionCount bool) *model.HostState {
|
func GetState(skipConnectionCount bool, skipProcsCount bool) *model.HostState {
|
||||||
hi, _ := host.Info()
|
var procs []int32
|
||||||
|
if !skipProcsCount {
|
||||||
|
procs, _ = process.Pids()
|
||||||
|
}
|
||||||
|
|
||||||
mv, _ := mem.VirtualMemory()
|
mv, _ := mem.VirtualMemory()
|
||||||
|
|
||||||
var swapMemUsed uint64
|
var swapMemUsed uint64
|
||||||
@ -92,11 +104,11 @@ func GetState(skipConnectionCount bool) *model.HostState {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
cpuPercent = cp[0]
|
cpuPercent = cp[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
_, diskUsed := getDiskTotalAndUsed()
|
_, diskUsed := getDiskTotalAndUsed()
|
||||||
loadStat, _ := load.Avg()
|
loadStat, _ := load.Avg()
|
||||||
|
|
||||||
var tcpConnCount, udpConnCount uint64
|
var tcpConnCount, udpConnCount uint64
|
||||||
|
|
||||||
if !skipConnectionCount {
|
if !skipConnectionCount {
|
||||||
conns, _ := net.Connections("all")
|
conns, _ := net.Connections("all")
|
||||||
for i := 0; i < len(conns); i++ {
|
for i := 0; i < len(conns); i++ {
|
||||||
@ -114,17 +126,17 @@ func GetState(skipConnectionCount bool) *model.HostState {
|
|||||||
MemUsed: mv.Total - mv.Available,
|
MemUsed: mv.Total - mv.Available,
|
||||||
SwapUsed: swapMemUsed,
|
SwapUsed: swapMemUsed,
|
||||||
DiskUsed: diskUsed,
|
DiskUsed: diskUsed,
|
||||||
NetInTransfer: atomic.LoadUint64(&netInTransfer),
|
NetInTransfer: netInTransfer,
|
||||||
NetOutTransfer: atomic.LoadUint64(&netOutTransfer),
|
NetOutTransfer: netOutTransfer,
|
||||||
NetInSpeed: atomic.LoadUint64(&netInSpeed),
|
NetInSpeed: netInSpeed,
|
||||||
NetOutSpeed: atomic.LoadUint64(&netOutSpeed),
|
NetOutSpeed: netOutSpeed,
|
||||||
Uptime: hi.Uptime,
|
Uptime: uint64(time.Since(cachedBootTime).Seconds()),
|
||||||
Load1: loadStat.Load1,
|
Load1: loadStat.Load1,
|
||||||
Load5: loadStat.Load5,
|
Load5: loadStat.Load5,
|
||||||
Load15: loadStat.Load15,
|
Load15: loadStat.Load15,
|
||||||
TcpConnCount: tcpConnCount,
|
TcpConnCount: tcpConnCount,
|
||||||
UdpConnCount: udpConnCount,
|
UdpConnCount: udpConnCount,
|
||||||
ProcessCount: hi.Procs,
|
ProcessCount: uint64(len(procs)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,14 +152,14 @@ func TrackNetworkSpeed() {
|
|||||||
innerNetOutTransfer += v.BytesSent
|
innerNetOutTransfer += v.BytesSent
|
||||||
}
|
}
|
||||||
now := uint64(time.Now().Unix())
|
now := uint64(time.Now().Unix())
|
||||||
diff := now - atomic.LoadUint64(&lastUpdate)
|
diff := now - lastUpdateNetStats
|
||||||
if diff > 0 {
|
if diff > 0 {
|
||||||
atomic.StoreUint64(&netInSpeed, (innerNetInTransfer-atomic.LoadUint64(&netInTransfer))/diff)
|
netInSpeed = (innerNetInTransfer - netInTransfer) / diff
|
||||||
atomic.StoreUint64(&netOutSpeed, (innerNetOutTransfer-atomic.LoadUint64(&netOutTransfer))/diff)
|
netOutSpeed = (innerNetOutTransfer - netOutTransfer) / diff
|
||||||
}
|
}
|
||||||
atomic.StoreUint64(&netInTransfer, innerNetInTransfer)
|
netInTransfer = innerNetInTransfer
|
||||||
atomic.StoreUint64(&netOutTransfer, innerNetOutTransfer)
|
netOutTransfer = innerNetOutTransfer
|
||||||
atomic.StoreUint64(&lastUpdate, now)
|
lastUpdateNetStats = now
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,13 +35,21 @@ func UpdateIP() {
|
|||||||
for {
|
for {
|
||||||
ipv4 := fetchGeoIP(geoIPApiList, false)
|
ipv4 := fetchGeoIP(geoIPApiList, false)
|
||||||
ipv6 := fetchGeoIP(geoIPApiList, true)
|
ipv6 := fetchGeoIP(geoIPApiList, true)
|
||||||
cachedIP = fmt.Sprintf("ip(v4:%s,v6:[%s])", ipv4.IP, ipv6.IP)
|
if ipv4.IP == "" && ipv6.IP == "" {
|
||||||
|
time.Sleep(time.Minute)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ipv4.IP == "" || ipv6.IP == "" {
|
||||||
|
cachedIP = fmt.Sprintf("%s%s", ipv4.IP, ipv6.IP)
|
||||||
|
} else {
|
||||||
|
cachedIP = fmt.Sprintf("%s/%s", ipv4.IP, ipv6.IP)
|
||||||
|
}
|
||||||
if ipv4.CountryCode != "" {
|
if ipv4.CountryCode != "" {
|
||||||
cachedCountry = ipv4.CountryCode
|
cachedCountry = ipv4.CountryCode
|
||||||
} else if ipv6.CountryCode != "" {
|
} else if ipv6.CountryCode != "" {
|
||||||
cachedCountry = ipv6.CountryCode
|
cachedCountry = ipv6.CountryCode
|
||||||
}
|
}
|
||||||
time.Sleep(time.Minute * 10)
|
time.Sleep(time.Minute * 30)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ func ServeWeb(port uint) *http.Server {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
r.Static("/static", "resource/static")
|
r.Static("/static", "resource/static")
|
||||||
r.LoadHTMLGlob("resource/template/**/*")
|
r.LoadHTMLGlob("resource/template/**/*.html")
|
||||||
routers(r)
|
routers(r)
|
||||||
|
|
||||||
page404 := func(c *gin.Context) {
|
page404 := func(c *gin.Context) {
|
||||||
|
@ -267,19 +267,22 @@ func (ma *memberAPI) addOrEditCron(c *gin.Context) {
|
|||||||
cr.Cover = cf.Cover
|
cr.Cover = cf.Cover
|
||||||
err = json.Unmarshal([]byte(cf.ServersRaw), &cr.Servers)
|
err = json.Unmarshal([]byte(cf.ServersRaw), &cr.Servers)
|
||||||
}
|
}
|
||||||
if err == nil {
|
tx := dao.DB.Begin()
|
||||||
_, err = cron.ParseStandard(cr.Scheduler)
|
|
||||||
}
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if cf.ID == 0 {
|
if cf.ID == 0 {
|
||||||
err = dao.DB.Create(&cr).Error
|
err = tx.Create(&cr).Error
|
||||||
} else {
|
} else {
|
||||||
err = dao.DB.Save(&cr).Error
|
err = tx.Save(&cr).Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
cr.CronID, err = dao.Cron.AddFunc(cr.Scheduler, dao.CronTrigger(cr))
|
cr.CronID, err = dao.Cron.AddFunc(cr.Scheduler, dao.CronTrigger(cr))
|
||||||
}
|
}
|
||||||
|
if err == nil {
|
||||||
|
err = tx.Commit().Error
|
||||||
|
} else {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusOK, model.Response{
|
c.JSON(http.StatusOK, model.Response{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
|
@ -25,7 +25,7 @@ func init() {
|
|||||||
|
|
||||||
// 初始化 dao 包
|
// 初始化 dao 包
|
||||||
dao.Conf = &model.Config{}
|
dao.Conf = &model.Config{}
|
||||||
dao.Cron = cron.New(cron.WithLocation(shanghai))
|
dao.Cron = cron.New(cron.WithSeconds(), cron.WithLocation(shanghai))
|
||||||
dao.Crons = make(map[uint64]*model.Cron)
|
dao.Crons = make(map[uint64]*model.Cron)
|
||||||
dao.ServerList = make(map[uint64]*model.Server)
|
dao.ServerList = make(map[uint64]*model.Server)
|
||||||
dao.SecretToID = make(map[string]uint64)
|
dao.SecretToID = make(map[string]uint64)
|
||||||
@ -60,13 +60,13 @@ func initSystem() {
|
|||||||
loadCrons() //加载计划任务
|
loadCrons() //加载计划任务
|
||||||
|
|
||||||
// 清理 服务请求记录 和 流量记录 的旧数据
|
// 清理 服务请求记录 和 流量记录 的旧数据
|
||||||
_, err := dao.Cron.AddFunc("30 3 * * *", cleanMonitorHistory)
|
_, err := dao.Cron.AddFunc("0 30 3 * * *", cleanMonitorHistory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 流量记录打点
|
// 流量记录打点
|
||||||
_, err = dao.Cron.AddFunc("0 * * * *", recordTransferHourlyUsage)
|
_, err = dao.Cron.AddFunc("0 0 * * * *", recordTransferHourlyUsage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -162,10 +162,11 @@ func loadCrons() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cr.CronID, err = dao.Cron.AddFunc(cr.Scheduler, dao.CronTrigger(cr))
|
cr.CronID, err = dao.Cron.AddFunc(cr.Scheduler, dao.CronTrigger(cr))
|
||||||
if err != nil {
|
if err == nil {
|
||||||
panic(err)
|
dao.Crons[cr.ID] = &cr
|
||||||
|
} else {
|
||||||
|
log.Println("NEZHA>> 计划任务调度失败", cr, err)
|
||||||
}
|
}
|
||||||
dao.Crons[cr.ID] = &cr
|
|
||||||
}
|
}
|
||||||
dao.Cron.Start()
|
dao.Cron.Start()
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,25 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/naiba/nezha/cmd/agent/pty"
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
pty.DownloadDependency()
|
c := cron.New(cron.WithSeconds())
|
||||||
|
_, err := c.AddFunc("* * * * * *", func() {
|
||||||
|
log.Println("bingo second")
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_, err = c.AddFunc("* * * * *", func() {
|
||||||
|
log.Println("bingo minute")
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
c.Start()
|
||||||
|
select {}
|
||||||
}
|
}
|
||||||
|
5
go.mod
5
go.mod
@ -7,7 +7,7 @@ require (
|
|||||||
github.com/artdarek/go-unzip v1.0.0
|
github.com/artdarek/go-unzip v1.0.0
|
||||||
github.com/blang/semver v3.5.1+incompatible
|
github.com/blang/semver v3.5.1+incompatible
|
||||||
github.com/creack/pty v1.1.14
|
github.com/creack/pty v1.1.14
|
||||||
github.com/fsnotify/fsnotify v1.4.9
|
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||||
github.com/gin-contrib/pprof v1.3.0
|
github.com/gin-contrib/pprof v1.3.0
|
||||||
github.com/gin-gonic/gin v1.7.0
|
github.com/gin-gonic/gin v1.7.0
|
||||||
github.com/go-ping/ping v0.0.0-20210407214646-e4e642a95741
|
github.com/go-ping/ping v0.0.0-20210407214646-e4e642a95741
|
||||||
@ -23,13 +23,14 @@ require (
|
|||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v3 v3.21.7
|
github.com/shirou/gopsutil/v3 v3.21.8
|
||||||
github.com/spf13/pflag v1.0.3
|
github.com/spf13/pflag v1.0.3
|
||||||
github.com/spf13/viper v1.7.1
|
github.com/spf13/viper v1.7.1
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||||
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6 // indirect
|
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6 // indirect
|
||||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
|
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
|
||||||
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect
|
||||||
google.golang.org/grpc v1.33.1
|
google.golang.org/grpc v1.33.1
|
||||||
google.golang.org/protobuf v1.25.0
|
google.golang.org/protobuf v1.25.0
|
||||||
gopkg.in/yaml.v2 v2.2.8
|
gopkg.in/yaml.v2 v2.2.8
|
||||||
|
17
go.sum
17
go.sum
@ -290,8 +290,8 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
|
|||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
github.com/shirou/gopsutil/v3 v3.21.7 h1:PnTqQamUjwEDSgn+nBGu0qSDV/CfvyiR/gwTH3i7HTU=
|
github.com/shirou/gopsutil/v3 v3.21.8 h1:nKct+uP0TV8DjjNiHanKf8SAuub+GNsbrOtM9Nl9biA=
|
||||||
github.com/shirou/gopsutil/v3 v3.21.7/go.mod h1:RGl11Y7XMTQPmHh8F0ayC6haKNBgH4PXMJuTAcMOlz4=
|
github.com/shirou/gopsutil/v3 v3.21.8/go.mod h1:YWp/H8Qs5fVmf17v7JNZzA0mPJ+mS2e9JdiUF9LlKzQ=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
@ -320,10 +320,10 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s
|
|||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/tcnksm/go-gitconfig v0.1.2 h1:iiDhRitByXAEyjgBqsKi9QU4o2TNtv9kPP3RgPgXBPw=
|
github.com/tcnksm/go-gitconfig v0.1.2 h1:iiDhRitByXAEyjgBqsKi9QU4o2TNtv9kPP3RgPgXBPw=
|
||||||
github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4UsLGSItWYCpE=
|
github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4UsLGSItWYCpE=
|
||||||
github.com/tklauser/go-sysconf v0.3.7 h1:HT7h4+536gjqeq1ZIJPgOl1rg1XFatQGVZWp7Py53eg=
|
github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
|
||||||
github.com/tklauser/go-sysconf v0.3.7/go.mod h1:JZIdXh4RmBvZDBZ41ld2bGxRV3n4daiiqA3skYhAoQ4=
|
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
|
||||||
github.com/tklauser/numcpus v0.2.3 h1:nQ0QYpiritP6ViFhrKYsiv6VVxOpum2Gks5GhnJbS/8=
|
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
|
||||||
github.com/tklauser/numcpus v0.2.3/go.mod h1:vpEPS/JC+oZGGQ/My/vJnNsvMDQL6PwOqt8dsCw5j+E=
|
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
@ -469,8 +469,9 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo=
|
||||||
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
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/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=
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
@ -68,13 +66,6 @@ func (c *Config) Read(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.updateIgnoredIPNotificationID()
|
c.updateIgnoredIPNotificationID()
|
||||||
|
|
||||||
c.v.OnConfigChange(func(in fsnotify.Event) {
|
|
||||||
c.v.Unmarshal(c)
|
|
||||||
fmt.Println("配置文件更新,重载配置", c)
|
|
||||||
})
|
|
||||||
|
|
||||||
go c.v.WatchConfig()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
resource/template/common/menu.html
vendored
2
resource/template/common/menu.html
vendored
@ -5,7 +5,7 @@
|
|||||||
<img src="/static/logo.svg?v20210804">
|
<img src="/static/logo.svg?v20210804">
|
||||||
</div>
|
</div>
|
||||||
{{if .IsAdminPage}}
|
{{if .IsAdminPage}}
|
||||||
<a class='item{{if eq .MatchedPath "/server"}} active{{end}}' href="/server"><i class="server icon"></i>资产</a>
|
<a class='item{{if eq .MatchedPath "/server"}} active{{end}}' href="/server"><i class="server icon"></i>主机</a>
|
||||||
<a class='item{{if eq .MatchedPath "/monitor"}} active{{end}}' href="/monitor"><i class="rss icon"></i>服务</a>
|
<a class='item{{if eq .MatchedPath "/monitor"}} active{{end}}' href="/monitor"><i class="rss icon"></i>服务</a>
|
||||||
<a class='item{{if eq .MatchedPath "/cron"}} active{{end}}' href="/cron"><i class="clock icon"></i>任务</a>
|
<a class='item{{if eq .MatchedPath "/cron"}} active{{end}}' href="/cron"><i class="clock icon"></i>任务</a>
|
||||||
<a class='item{{if eq .MatchedPath "/notification"}} active{{end}}' href="/notification"><i class="bell icon"></i>报警</a>
|
<a class='item{{if eq .MatchedPath "/notification"}} active{{end}}' href="/notification"><i class="bell icon"></i>报警</a>
|
||||||
|
4
resource/template/component/cron.html
vendored
4
resource/template/component/cron.html
vendored
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>计划</label>
|
<label>计划</label>
|
||||||
<input type="text" name="Scheduler" placeholder="0 3 * * *(每天3点)">
|
<input type="text" name="Scheduler" placeholder="0 0 3 * * *(每天3点)">
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>命令</label>
|
<label>命令</label>
|
||||||
@ -41,7 +41,7 @@
|
|||||||
</form>
|
</form>
|
||||||
<div class="ui warning message">
|
<div class="ui warning message">
|
||||||
<p>
|
<p>
|
||||||
计划的格式为:<code>* * * * *</code> 分 时 天 月 星期,详情见 <a
|
计划的格式为:<code>* * * * * *</code> 秒 分 时 天 月 星期,详情见 <a
|
||||||
href="https://pkg.go.dev/github.com/robfig/cron/v3#hdr-CRON_Expression_Format"
|
href="https://pkg.go.dev/github.com/robfig/cron/v3#hdr-CRON_Expression_Format"
|
||||||
target="_blank">计划表达式格式</a><br>
|
target="_blank">计划表达式格式</a><br>
|
||||||
命令:就像写 shell/bat 脚本一样,但是不推荐换行,多个命令使用 <code>&&</code>/<code>&</code> 连接,如果遇到 xxx 命令找不到,可能是
|
命令:就像写 shell/bat 脚本一样,但是不推荐换行,多个命令使用 <code>&&</code>/<code>&</code> 连接,如果遇到 xxx 命令找不到,可能是
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<div class="ui grid">
|
<div class="ui grid">
|
||||||
<div class="right floated right aligned twelve wide column">
|
<div class="right floated right aligned twelve wide column">
|
||||||
<button class="ui right labeled nezha-primary-btn icon button" onclick="addOrEditMonitor()">
|
<button class="ui right labeled nezha-primary-btn icon button" onclick="addOrEditMonitor()">
|
||||||
<i class="add icon"></i> 添加监控
|
<i class="add icon"></i> 添加服务
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -43,7 +43,7 @@
|
|||||||
<i class="edit icon"></i>
|
<i class="edit icon"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="ui button"
|
<button class="ui button"
|
||||||
onclick="showConfirm('删除监控','确认删除此监控?',deleteRequest,'/api/monitor/'+{{$monitor.ID}})">
|
onclick="showConfirm('删除服务','确认删除此服务?',deleteRequest,'/api/monitor/'+{{$monitor.ID}})">
|
||||||
<i class="trash alternate outline icon"></i>
|
<i class="trash alternate outline icon"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<div class="ui grid">
|
<div class="ui grid">
|
||||||
<div class="right floated right aligned twelve wide column">
|
<div class="right floated right aligned twelve wide column">
|
||||||
<button class="ui right labeled nezha-primary-btn icon button" onclick="addOrEditServer()"><i
|
<button class="ui right labeled nezha-primary-btn icon button" onclick="addOrEditServer()"><i
|
||||||
class="add icon"></i> 添加服务器
|
class="add icon"></i> 添加主机
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -56,7 +56,7 @@
|
|||||||
<i class="edit icon"></i>
|
<i class="edit icon"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="ui button"
|
<button class="ui button"
|
||||||
onclick="showConfirm('删除节点','确认删除此监控节点?',deleteRequest,'/api/server/'+{{$server.ID}})">
|
onclick="showConfirm('删除主机','确认删除此主机?',deleteRequest,'/api/server/'+{{$server.ID}})">
|
||||||
<i class="trash alternate outline icon"></i>
|
<i class="trash alternate outline icon"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
BIN
resource/template/theme-daynight/screenshot.png
vendored
Normal file
BIN
resource/template/theme-daynight/screenshot.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 481 KiB |
1
resource/template/theme-default/README.md
vendored
1
resource/template/theme-default/README.md
vendored
@ -1 +0,0 @@
|
|||||||
# 默认主题
|
|
BIN
resource/template/theme-default/screenshot.png
vendored
Normal file
BIN
resource/template/theme-default/screenshot.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 856 KiB |
BIN
resource/template/theme-hotaru/screenshot.png
vendored
Normal file
BIN
resource/template/theme-hotaru/screenshot.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 MiB |
@ -13,7 +13,7 @@ import (
|
|||||||
pb "github.com/naiba/nezha/proto"
|
pb "github.com/naiba/nezha/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Version = "v0.10.1" // !!记得修改 README 中的 badge 版本!!
|
var Version = "v0.10.2" // !!记得修改 README 中的 badge 版本!!
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Conf *model.Config
|
Conf *model.Config
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
|
|
||||||
"github.com/naiba/nezha/model"
|
"github.com/naiba/nezha/model"
|
||||||
pb "github.com/naiba/nezha/proto"
|
pb "github.com/naiba/nezha/proto"
|
||||||
"github.com/robfig/cron/v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const _CurrentStatusSize = 30 // 统计 15 分钟内的数据为当前状态
|
const _CurrentStatusSize = 30 // 统计 15 分钟内的数据为当前状态
|
||||||
@ -42,11 +41,9 @@ func NewServiceSentinel(serviceSentinelDispatchBus chan<- model.Monitor) {
|
|||||||
sslCertCache: make(map[uint64]string),
|
sslCertCache: make(map[uint64]string),
|
||||||
// 30天数据缓存
|
// 30天数据缓存
|
||||||
monthlyStatus: make(map[uint64]*model.ServiceItemResponse),
|
monthlyStatus: make(map[uint64]*model.ServiceItemResponse),
|
||||||
dispatchCron: cron.New(cron.WithSeconds()),
|
|
||||||
dispatchBus: serviceSentinelDispatchBus,
|
dispatchBus: serviceSentinelDispatchBus,
|
||||||
}
|
}
|
||||||
ServiceSentinelShared.loadMonitorHistory()
|
ServiceSentinelShared.loadMonitorHistory()
|
||||||
ServiceSentinelShared.dispatchCron.Start()
|
|
||||||
|
|
||||||
year, month, day := time.Now().Date()
|
year, month, day := time.Now().Date()
|
||||||
today := time.Date(year, month, day, 0, 0, 0, 0, time.Local)
|
today := time.Date(year, month, day, 0, 0, 0, 0, time.Local)
|
||||||
@ -75,7 +72,7 @@ func NewServiceSentinel(serviceSentinelDispatchBus chan<- model.Monitor) {
|
|||||||
go ServiceSentinelShared.worker()
|
go ServiceSentinelShared.worker()
|
||||||
|
|
||||||
// 每日将游标往后推一天
|
// 每日将游标往后推一天
|
||||||
_, err := Cron.AddFunc("0 0 * * *", ServiceSentinelShared.refreshMonthlyServiceStatus)
|
_, err := Cron.AddFunc("0 0 0 * * *", ServiceSentinelShared.refreshMonthlyServiceStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -102,8 +99,7 @@ type ServiceSentinel struct {
|
|||||||
monthlyStatusLock sync.Mutex
|
monthlyStatusLock sync.Mutex
|
||||||
monthlyStatus map[uint64]*model.ServiceItemResponse
|
monthlyStatus map[uint64]*model.ServiceItemResponse
|
||||||
// 服务监控调度计划任务
|
// 服务监控调度计划任务
|
||||||
dispatchCron *cron.Cron
|
dispatchBus chan<- model.Monitor
|
||||||
dispatchBus chan<- model.Monitor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *ServiceSentinel) refreshMonthlyServiceStatus() {
|
func (ss *ServiceSentinel) refreshMonthlyServiceStatus() {
|
||||||
@ -147,7 +143,7 @@ func (ss *ServiceSentinel) loadMonitorHistory() {
|
|||||||
ss.monitors = make(map[uint64]*model.Monitor)
|
ss.monitors = make(map[uint64]*model.Monitor)
|
||||||
for i := 0; i < len(monitors); i++ {
|
for i := 0; i < len(monitors); i++ {
|
||||||
task := *monitors[i]
|
task := *monitors[i]
|
||||||
monitors[i].CronJobID, err = ss.dispatchCron.AddFunc(task.CronSpec(), func() {
|
monitors[i].CronJobID, err = Cron.AddFunc(task.CronSpec(), func() {
|
||||||
ss.dispatchBus <- task
|
ss.dispatchBus <- task
|
||||||
})
|
})
|
||||||
log.Println("NEZHA>> 服务监控任务", monitors[i].ID, monitors[i].Name, monitors[i].CronJobID)
|
log.Println("NEZHA>> 服务监控任务", monitors[i].ID, monitors[i].Name, monitors[i].CronJobID)
|
||||||
@ -194,7 +190,7 @@ func (ss *ServiceSentinel) OnMonitorUpdate(m model.Monitor) error {
|
|||||||
defer ss.monitorsLock.Unlock()
|
defer ss.monitorsLock.Unlock()
|
||||||
var err error
|
var err error
|
||||||
// 写入新任务
|
// 写入新任务
|
||||||
m.CronJobID, err = ss.dispatchCron.AddFunc(m.CronSpec(), func() {
|
m.CronJobID, err = Cron.AddFunc(m.CronSpec(), func() {
|
||||||
ss.dispatchBus <- m
|
ss.dispatchBus <- m
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -202,7 +198,7 @@ func (ss *ServiceSentinel) OnMonitorUpdate(m model.Monitor) error {
|
|||||||
}
|
}
|
||||||
if ss.monitors[m.ID] != nil {
|
if ss.monitors[m.ID] != nil {
|
||||||
// 停掉旧任务
|
// 停掉旧任务
|
||||||
ss.dispatchCron.Remove(ss.monitors[m.ID].CronJobID)
|
Cron.Remove(ss.monitors[m.ID].CronJobID)
|
||||||
} else {
|
} else {
|
||||||
// 新任务初始化数据
|
// 新任务初始化数据
|
||||||
ss.monthlyStatusLock.Lock()
|
ss.monthlyStatusLock.Lock()
|
||||||
@ -236,7 +232,7 @@ func (ss *ServiceSentinel) OnMonitorDelete(id uint64) {
|
|||||||
ss.monitorsLock.Lock()
|
ss.monitorsLock.Lock()
|
||||||
defer ss.monitorsLock.Unlock()
|
defer ss.monitorsLock.Unlock()
|
||||||
// 停掉定时任务
|
// 停掉定时任务
|
||||||
ss.dispatchCron.Remove(ss.monitors[id].CronJobID)
|
Cron.Remove(ss.monitors[id].CronJobID)
|
||||||
delete(ss.monitors, id)
|
delete(ss.monitors, id)
|
||||||
ss.monthlyStatusLock.Lock()
|
ss.monthlyStatusLock.Lock()
|
||||||
defer ss.monthlyStatusLock.Unlock()
|
defer ss.monthlyStatusLock.Unlock()
|
||||||
|
Loading…
Reference in New Issue
Block a user