diff --git a/.github/workflows/agent.yml b/.github/workflows/agent.yml
index d738124..6749fa6 100644
--- a/.github/workflows/agent.yml
+++ b/.github/workflows/agent.yml
@@ -10,6 +10,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
+ - name: Download UPX
+ run: |
+ wget https://github.com/upx/upx/releases/download/v3.95/upx-3.95-amd64_linux.tar.xz
+ tar --strip-components=1 -xf upx-3.95-amd64_linux.tar.xz && sudo mv upx /usr/bin/
+ git reset --hard
+ git clean -f -d
+ upx --version
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
if: startsWith(github.ref, 'refs/tags/')
diff --git a/.github/workflows/dashboard.yml b/.github/workflows/dashboard.yml
index cc28105..6940ccd 100644
--- a/.github/workflows/dashboard.yml
+++ b/.github/workflows/dashboard.yml
@@ -10,6 +10,7 @@ on:
- "script/**"
- "*.md"
- ".*"
+ - ".github/workflows/agent.yml"
jobs:
deploy:
diff --git a/.goreleaser.yml b/.goreleaser.yml
index 4342d76..fc6ee0a 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -15,11 +15,19 @@ builds:
- 386
- amd64
- mips
- - mips64
gomips:
- softfloat
+ ignore:
+ - goos: windows
+ goarch: arm
+ - goos: windows
+ goarch: arm64
main: ./cmd/agent
binary: nezha-agent
+ # 为路由器、开发板缩小二进制体积
+ hooks:
+ post:
+ - upx --best "{{.Path}}"
checksum:
name_template: "checksums.txt"
snapshot:
diff --git a/README.md b/README.md
index c6a6675..d38bff2 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,14 @@
-\>> QQ 交流群: ~~955957790~~ 已解散,**自2021年3月26起不再提供技术支持,接受PR。**
-\>> 交流论坛:正在选型搭建中…… 欢迎想建设社区的铁子与奶爸取得联系。
+\>> 交流论坛:[打杂社区](https://daza.net/c/nezha) (Lemmy)
+
+\>> QQ 交流群:872069346 **加群要求:已搭建好哪吒监控 & 有 2+ 服务器**
\>> [我们的用户](https://www.google.com/search?q="powered+by+哪吒监控%7C哪吒面板"&filter=0) (Google)
@@ -17,7 +18,7 @@
## 安装脚本
-**推荐配置:** 安装前解析 _两个域名_ 到面板服务器,一个作为 _公开访问_ ,可以 **接入 CDN**,比如 (status.nai.ba);另外一个作为安装 Agent 时连接 Dashboard 使用,**不能接入 CDN** 直接暴露面板主机 IP,比如(randomdashboard.nai.ba)。
+**推荐配置:** 安装前准备 _两个域名_,一个可以 **接入 CDN** 作为 _公开访问_,比如 (status.nai.ba);另外一个解析到面板服务器作为 Agent 连接 Dashboard 使用,**不能接入 CDN** 直接暴露面板主机 IP,比如(randomdashboard.nai.ba)。
```shell
curl -L https://raw.githubusercontent.com/naiba/nezha/master/script/install.sh -o nezha.sh && chmod +x nezha.sh
@@ -58,11 +59,13 @@ URL 里面也可放置占位符,请求时会进行简单的字符串替换。
1. 添加通知方式
- server 酱示例
+
- 名称:server 酱
- URL:https://sc.ftqq.com/SCUrandomkeys.send?text=#NEZHA#
- 请求方式: GET
- 请求类型: 默认
- Body: 空
+
- wxpusher 示例,需要关注你的应用
- 名称: wxpusher
@@ -72,6 +75,7 @@ URL 里面也可放置占位符,请求时会进行简单的字符串替换。
- Body: `{"appToken":"你的appToken","topicIds":[],"content":"#NEZHA#","contentType":"1","uids":["你的uid"]}`
- telegram 示例 [@haitau](https://github.com/haitau) 贡献
+
- 名称:telegram 机器人消息通知
- URL:https://api.telegram.org/botXXXXXX/sendMessage?chat_id=YYYYYY&text=#NEZHA#
- 请求方式: GET
@@ -121,7 +125,17 @@ URL 里面也可放置占位符,请求时会进行简单的字符串替换。
```
-- 默认主题修改 LOGO、移除版权示例(来自 [@iLay1678](https://github.com/iLay1678),欢迎 PR)
+- DayNight 主题更改进度条颜色示例(来自 [@hyt-allen-xu](https://github.com/hyt-allen-xu))
+
+ ```
+
+ ```
+
+- 默认主题修改 LOGO、移除版权示例(来自 [@iLay1678](https://github.com/iLay1678))
```
- ```
+ ```
+
+ ```
@@ -253,4 +279,4 @@ restart() {
- [哪吒探针 - Windows 客户端安装](https://nyko.me/2020/12/13/nezha-windows-client.html)
- [哪吒监控,一个便携服务器状态监控面板搭建教程,不想拥有一个自己的探针吗?](https://haoduck.com/644.html)
- [哪吒监控:小鸡们的最佳探针](https://www.zhujizixun.com/2843.html) _(已过时)_
-- [>>更多教程](https://www.google.com/search?q="哪吒"%2B"面板%7C监控%7C探针"+"教程") (Google)
+- [>>更多教程](https://www.google.com/search?q="哪吒"%2B"面板%7C监控%7C探针"+"教程") (Google)
\ No newline at end of file
diff --git a/cmd/agent/main.go b/cmd/agent/main.go
index 2239204..215fdf7 100644
--- a/cmd/agent/main.go
+++ b/cmd/agent/main.go
@@ -9,9 +9,9 @@ import (
"log"
"net"
"net/http"
+ "net/url"
"os"
"os/exec"
- "strings"
"time"
"github.com/blang/semver"
@@ -35,11 +35,10 @@ var (
)
var (
- reporting bool
client pb.NezhaServiceClient
ctx = context.Background()
- delayWhenError = time.Second * 10 // Agent 重连间隔
- updateCh = make(chan struct{}, 0) // Agent 自动更新间隔
+ delayWhenError = time.Second * 10 // Agent 重连间隔
+ updateCh = make(chan struct{}) // Agent 自动更新间隔
httpClient = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
@@ -56,17 +55,16 @@ func doSelfUpdate() {
updateCh <- struct{}{}
}()
v := semver.MustParse(version)
- log.Println("Check update", v)
+ println("Check update", v)
latest, err := selfupdate.UpdateSelf(v, "naiba/nezha")
if err != nil {
- log.Println("Binary update failed:", err)
+ println("Binary update failed:", err)
return
}
if latest.Version.Equals(v) {
- // latest version is the same as current version. It means current binary is up to date.
- log.Println("Current binary is the latest version", version)
+ println("Current binary is up to date", version)
} else {
- log.Println("Successfully updated to version", latest.Version)
+ println("Upgrade successfully", latest.Version)
os.Exit(1)
}
}
@@ -81,7 +79,7 @@ func main() {
var debug bool
flag.String("i", "", "unused 旧Agent配置兼容")
- flag.BoolVar(&debug, "d", false, "允许不安全连接")
+ flag.BoolVar(&debug, "d", false, "开启调试信息")
flag.StringVar(&server, "s", "localhost:5555", "管理面板RPC端口")
flag.StringVar(&clientSecret, "p", "", "Agent连接Secret")
flag.Parse()
@@ -121,18 +119,18 @@ func run() {
var conn *grpc.ClientConn
retry := func() {
- log.Println("Error to close connection ...")
+ println("Error to close connection ...")
if conn != nil {
conn.Close()
}
time.Sleep(delayWhenError)
- log.Println("Try to reconnect ...")
+ println("Try to reconnect ...")
}
for {
conn, err = grpc.Dial(server, grpc.WithInsecure(), grpc.WithPerRPCCredentials(&auth))
if err != nil {
- log.Printf("grpc.Dial err: %v", err)
+ println("grpc.Dial err: ", err)
retry()
continue
}
@@ -140,26 +138,26 @@ func run() {
// 第一步注册
_, err = client.ReportSystemInfo(ctx, monitor.GetHost().PB())
if err != nil {
- log.Printf("client.ReportSystemInfo err: %v", err)
+ println("client.ReportSystemInfo err: ", err)
retry()
continue
}
// 执行 Task
tasks, err := client.RequestTask(ctx, monitor.GetHost().PB())
if err != nil {
- log.Printf("client.RequestTask err: %v", err)
+ println("client.RequestTask err: ", err)
retry()
continue
}
err = receiveTasks(tasks)
- log.Printf("receiveTasks exit to main: %v", err)
+ println("receiveTasks exit to main: ", err)
retry()
}
}
func receiveTasks(tasks pb.NezhaService_RequestTaskClient) error {
var err error
- defer log.Printf("receiveTasks exit %v => %v", time.Now(), err)
+ defer println("receiveTasks exit", time.Now(), "=>", err)
for {
var task *pb.Task
task, err = tasks.Recv()
@@ -179,24 +177,32 @@ func doTask(task *pb.Task) {
start := time.Now()
resp, err := httpClient.Get(task.GetData())
if err == nil {
- result.Delay = float32(time.Now().Sub(start).Microseconds()) / 1000.0
+ // 检查 HTTP Response 状态
+ result.Delay = float32(time.Since(start).Microseconds()) / 1000.0
if resp.StatusCode > 399 || resp.StatusCode < 200 {
err = errors.New("\n应用错误:" + resp.Status)
}
}
if err == nil {
- if strings.HasPrefix(task.GetData(), "https://") {
- c := cert.NewCert(task.GetData()[8:])
- if c.Error != "" {
- result.Data = "SSL证书错误:" + c.Error
+ // 检查 SSL 证书信息
+ serviceUrl, err := url.Parse(task.GetData())
+ if err == nil {
+ if serviceUrl.Scheme == "https" {
+ c := cert.NewCert(serviceUrl.Host)
+ if c.Error != "" {
+ result.Data = "SSL证书错误:" + c.Error
+ } else {
+ result.Data = c.Issuer + "|" + c.NotAfter
+ result.Successful = true
+ }
} else {
- result.Data = c.Issuer + "|" + c.NotAfter
result.Successful = true
}
} else {
- result.Successful = true
+ result.Data = "URL解析错误:" + err.Error()
}
} else {
+ // HTTP 请求失败
result.Data = err.Error()
}
case model.TaskTypeICMPPing:
@@ -219,7 +225,7 @@ func doTask(task *pb.Task) {
if err == nil {
conn.Write([]byte("ping\n"))
conn.Close()
- result.Delay = float32(time.Now().Sub(start).Microseconds()) / 1000.0
+ result.Delay = float32(time.Since(start).Microseconds()) / 1000.0
result.Successful = true
} else {
result.Data = err.Error()
@@ -260,9 +266,9 @@ func doTask(task *pb.Task) {
result.Data = string(output)
result.Successful = true
}
- result.Delay = float32(time.Now().Sub(startedAt).Seconds())
+ result.Delay = float32(time.Since(startedAt).Seconds())
default:
- log.Printf("Unknown action: %v", task)
+ println("Unknown action: ", task)
}
client.ReportTask(ctx, &result)
}
@@ -270,13 +276,13 @@ func doTask(task *pb.Task) {
func reportState() {
var lastReportHostInfo time.Time
var err error
- defer log.Printf("reportState exit %v => %v", time.Now(), err)
+ defer println("reportState exit", time.Now(), "=>", err)
for {
if client != nil {
monitor.TrackNetworkSpeed()
_, err = client.ReportSystemState(ctx, monitor.GetState(dao.ReportDelay).PB())
if err != nil {
- log.Printf("reportState error %v", err)
+ println("reportState error", err)
time.Sleep(delayWhenError)
}
if lastReportHostInfo.Before(time.Now().Add(-10 * time.Minute)) {
@@ -286,3 +292,9 @@ func reportState() {
}
}
}
+
+func println(v ...interface{}) {
+ if dao.Conf.Debug {
+ log.Println(v...)
+ }
+}
diff --git a/cmd/agent/monitor/monitor.go b/cmd/agent/monitor/monitor.go
index b8a1c7b..3d4dc88 100644
--- a/cmd/agent/monitor/monitor.go
+++ b/cmd/agent/monitor/monitor.go
@@ -2,6 +2,7 @@ package monitor
import (
"fmt"
+ "regexp"
"strings"
"sync/atomic"
"time"
@@ -17,12 +18,19 @@ import (
)
var netInSpeed, netOutSpeed, netInTransfer, netOutTransfer, lastUpdate uint64
+var expectDiskFsTypes = []string{
+ "apfs", "ext4", "ext3", "ext2", "f2fs", "reiserfs", "jfs", "btrfs", "fuseblk", "zfs", "simfs", "ntfs", "fat32", "exfat", "xfs",
+}
+var excludeNetInterfaces = []string{
+ "lo", "tun", "docker", "veth", "br-", "vmbr", "vnet", "kube",
+}
+var getMacDiskNo = regexp.MustCompile(`\/dev\/disk(\d)s.*`)
func GetHost() *model.Host {
hi, _ := host.Info()
var cpuType string
if hi.VirtualizationSystem != "" {
- cpuType = "Virtual"
+ cpuType = "Vrtual"
} else {
cpuType = "Physical"
}
@@ -37,15 +45,15 @@ func GetHost() *model.Host {
}
mv, _ := mem.VirtualMemory()
ms, _ := mem.SwapMemory()
- u, _ := disk.Usage("/")
+ diskTotal, _ := getDiskTotalAndUsed()
return &model.Host{
Platform: hi.OS,
PlatformVersion: hi.PlatformVersion,
CPU: cpus,
MemTotal: mv.Total,
- DiskTotal: u.Total,
SwapTotal: ms.Total,
+ DiskTotal: diskTotal,
Arch: hi.KernelArch,
Virtualization: hi.VirtualizationSystem,
BootTime: hi.BootTime,
@@ -57,23 +65,19 @@ func GetHost() *model.Host {
func GetState(delay int64) *model.HostState {
hi, _ := host.Info()
- // Memory
mv, _ := mem.VirtualMemory()
ms, _ := mem.SwapMemory()
- // CPU
var cpuPercent float64
cp, err := cpu.Percent(time.Second*time.Duration(delay), false)
if err == nil {
cpuPercent = cp[0]
}
- // Disk
- u, _ := disk.Usage("/")
-
+ _, diskUsed := getDiskTotalAndUsed()
return &model.HostState{
CPU: cpuPercent,
MemUsed: mv.Used,
SwapUsed: ms.Used,
- DiskUsed: u.Used,
+ DiskUsed: diskUsed,
NetInTransfer: atomic.LoadUint64(&netInTransfer),
NetOutTransfer: atomic.LoadUint64(&netOutTransfer),
NetInSpeed: atomic.LoadUint64(&netInSpeed),
@@ -84,10 +88,15 @@ func GetState(delay int64) *model.HostState {
func TrackNetworkSpeed() {
var innerNetInTransfer, innerNetOutTransfer uint64
- nc, err := net.IOCounters(false)
+ nc, err := net.IOCounters(true)
if err == nil {
- innerNetInTransfer += nc[0].BytesRecv
- innerNetOutTransfer += nc[0].BytesSent
+ for _, v := range nc {
+ if isListContainsStr(excludeNetInterfaces, v.Name) {
+ continue
+ }
+ innerNetInTransfer += v.BytesRecv
+ innerNetOutTransfer += v.BytesSent
+ }
now := uint64(time.Now().Unix())
diff := now - atomic.LoadUint64(&lastUpdate)
if diff > 0 {
@@ -99,3 +108,40 @@ func TrackNetworkSpeed() {
atomic.StoreUint64(&lastUpdate, now)
}
}
+
+func getDiskTotalAndUsed() (total uint64, used uint64) {
+ diskList, _ := disk.Partitions(false)
+ devices := make(map[string]string)
+ countedDiskForMac := make(map[string]struct{})
+ for _, d := range diskList {
+ fsType := strings.ToLower(d.Fstype)
+ // 不统计 K8s 的虚拟挂载点:https://github.com/shirou/gopsutil/issues/1007
+ if devices[d.Device] == "" && isListContainsStr(expectDiskFsTypes, fsType) && !strings.Contains(d.Mountpoint, "/var/lib/kubelet") {
+ devices[d.Device] = d.Mountpoint
+ }
+ }
+ for device, mountPath := range devices {
+ diskUsageOf, _ := disk.Usage(mountPath)
+ // 这里是针对 Mac 机器的处理,https://github.com/giampaolo/psutil/issues/1509
+ matches := getMacDiskNo.FindStringSubmatch(device)
+ if len(matches) == 2 {
+ if _, has := countedDiskForMac[matches[1]]; !has {
+ countedDiskForMac[matches[1]] = struct{}{}
+ total += diskUsageOf.Total
+ }
+ } else {
+ total += diskUsageOf.Total
+ }
+ used += diskUsageOf.Used
+ }
+ return
+}
+
+func isListContainsStr(list []string, str string) bool {
+ for i := 0; i < len(list); i++ {
+ if strings.Contains(list[i], str) {
+ return true
+ }
+ }
+ return false
+}
diff --git a/cmd/dashboard/controller/common_page.go b/cmd/dashboard/controller/common_page.go
index 68a0693..2d9cca3 100644
--- a/cmd/dashboard/controller/common_page.go
+++ b/cmd/dashboard/controller/common_page.go
@@ -3,7 +3,6 @@ package controller
import (
"errors"
"fmt"
- "log"
"net/http"
"time"
@@ -80,79 +79,8 @@ func (p *commonPage) checkViewPassword(c *gin.Context) {
c.Next()
}
-type ServiceItem struct {
- Monitor model.Monitor
- TotalUp uint64
- TotalDown uint64
- CurrentUp uint64
- CurrentDown uint64
- Delay *[30]float32
- Up *[30]int
- Down *[30]int
-}
-
func (p *commonPage) service(c *gin.Context) {
- var msm map[uint64]*ServiceItem
-
- var cached bool
- if _, has := c.Get(model.CtxKeyAuthorizedUser); !has {
- data, has := dao.Cache.Get(model.CacheKeyServicePage)
- if has {
- log.Println("use cache")
- msm = data.(map[uint64]*ServiceItem)
- cached = true
- }
- }
-
- if !cached {
- msm = make(map[uint64]*ServiceItem)
- var ms []model.Monitor
- dao.DB.Find(&ms)
- year, month, day := time.Now().Date()
- today := time.Date(year, month, day, 0, 0, 0, 0, time.Local)
- var mhs []model.MonitorHistory
- dao.DB.Where("created_at >= ?", today.AddDate(0, 0, -29)).Find(&mhs)
-
- for i := 0; i < len(ms); i++ {
- msm[ms[i].ID] = &ServiceItem{
- Monitor: ms[i],
- Delay: &[30]float32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- Up: &[30]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- Down: &[30]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- }
- }
- // 整合数据
- todayStatus := make(map[uint64][]bool)
- for i := 0; i < len(mhs); i++ {
- dayIndex := 29
- if mhs[i].CreatedAt.Before(today) {
- dayIndex = 28 - (int(today.Sub(mhs[i].CreatedAt).Hours()) / 24)
- } else {
- todayStatus[mhs[i].MonitorID] = append(todayStatus[mhs[i].MonitorID], mhs[i].Successful)
- }
- if mhs[i].Successful {
- msm[mhs[i].MonitorID].TotalUp++
- msm[mhs[i].MonitorID].Delay[dayIndex] = (msm[mhs[i].MonitorID].Delay[dayIndex]*float32(msm[mhs[i].MonitorID].Up[dayIndex]) + mhs[i].Delay) / float32(msm[mhs[i].MonitorID].Up[dayIndex]+1)
- msm[mhs[i].MonitorID].Up[dayIndex]++
- } else {
- msm[mhs[i].MonitorID].TotalDown++
- msm[mhs[i].MonitorID].Down[dayIndex]++
- }
- }
- // 当日最后 20 个采样作为当前状态
- for _, m := range msm {
- for i := len(todayStatus[m.Monitor.ID]) - 1; i >= 0 && i >= (len(todayStatus[m.Monitor.ID])-1-20); i-- {
- if todayStatus[m.Monitor.ID][i] {
- m.CurrentUp++
- } else {
- m.CurrentDown++
- }
- }
- }
- // 未登录人员缓存十分钟
- dao.Cache.Set(model.CacheKeyServicePage, msm, time.Minute*10)
- }
-
+ msm := dao.ServiceSentinelShared.LoadStats()
c.HTML(http.StatusOK, "theme-"+dao.Conf.Site.Theme+"/service", mygin.CommonEnvironment(c, gin.H{
"Title": "服务状态",
"Services": msm,
diff --git a/cmd/dashboard/controller/controller.go b/cmd/dashboard/controller/controller.go
index 484dc07..3d2a811 100644
--- a/cmd/dashboard/controller/controller.go
+++ b/cmd/dashboard/controller/controller.go
@@ -7,6 +7,7 @@ import (
"time"
"code.cloudfoundry.org/bytefmt"
+ "github.com/gin-contrib/pprof"
"github.com/gin-gonic/gin"
"github.com/naiba/nezha/pkg/mygin"
@@ -15,10 +16,11 @@ import (
func ServeWeb(port uint) {
gin.SetMode(gin.ReleaseMode)
+ r := gin.Default()
if dao.Conf.Debug {
gin.SetMode(gin.DebugMode)
+ pprof.Register(r)
}
- r := gin.Default()
r.Use(mygin.RecordPath)
r.SetFuncMap(template.FuncMap{
"tf": func(t time.Time) string {
@@ -54,7 +56,7 @@ func ServeWeb(port uint) {
}
if a == 0 {
// 这是从未在线的情况
- return 1 / float32(b) * 100
+ return 0.00001 / float32(b) * 100
}
return float32(a) / float32(b) * 100
},
@@ -67,7 +69,7 @@ func ServeWeb(port uint) {
}
if a == 0 {
// 这是从未在线的情况
- return 1 / float32(b) * 100
+ return 0.00001 / float32(b) * 100
}
return float32(a) / float32(b) * 100
},
diff --git a/cmd/dashboard/controller/guest_page.go b/cmd/dashboard/controller/guest_page.go
index 668c5fe..c5db724 100644
--- a/cmd/dashboard/controller/guest_page.go
+++ b/cmd/dashboard/controller/guest_page.go
@@ -7,8 +7,6 @@ import (
"github.com/naiba/nezha/model"
"github.com/naiba/nezha/pkg/mygin"
"github.com/naiba/nezha/service/dao"
- "golang.org/x/oauth2"
- "golang.org/x/oauth2/github"
)
type guestPage struct {
@@ -27,31 +25,22 @@ func (gp *guestPage) serve() {
gr.GET("/login", gp.login)
- var endPoint oauth2.Endpoint
-
- if dao.Conf.Oauth2.Type == model.ConfigTypeGitee {
- endPoint = oauth2.Endpoint{
- AuthURL: "https://gitee.com/oauth/authorize",
- TokenURL: "https://gitee.com/oauth/token",
- }
- } else {
- endPoint = github.Endpoint
- }
-
oauth := &oauth2controller{
- oauth2Config: &oauth2.Config{
- ClientID: dao.Conf.Oauth2.ClientID,
- ClientSecret: dao.Conf.Oauth2.ClientSecret,
- Scopes: []string{},
- Endpoint: endPoint,
- },
r: gr,
}
oauth.serve()
}
func (gp *guestPage) login(c *gin.Context) {
+ LoginType := "GitHub"
+ RegistrationLink := "https://github.com/join"
+ if dao.Conf.Oauth2.Type == model.ConfigTypeGitee {
+ LoginType = "Gitee"
+ RegistrationLink = "https://gitee.com/signup"
+ }
c.HTML(http.StatusOK, "dashboard/login", mygin.CommonEnvironment(c, gin.H{
- "Title": "登录",
+ "Title": "登录",
+ "LoginType": LoginType,
+ "RegistrationLink": RegistrationLink,
}))
}
diff --git a/cmd/dashboard/controller/member_api.go b/cmd/dashboard/controller/member_api.go
index e557cc9..e3480c1 100644
--- a/cmd/dashboard/controller/member_api.go
+++ b/cmd/dashboard/controller/member_api.go
@@ -6,6 +6,7 @@ import (
"fmt"
"net/http"
"strconv"
+ "strings"
"time"
"github.com/gin-gonic/gin"
@@ -73,6 +74,7 @@ func (ma *memberAPI) delete(c *gin.Context) {
case "monitor":
err = dao.DB.Delete(&model.Monitor{}, "id = ?", id).Error
if err == nil {
+ dao.ServiceSentinelShared.OnMonitorDelete(id)
err = dao.DB.Delete(&model.MonitorHistory{}, "monitor_id = ?", id).Error
}
case "cron":
@@ -190,10 +192,12 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) {
}
type monitorForm struct {
- ID uint64
- Name string
- Target string
- Type uint8
+ ID uint64
+ Name string
+ Target string
+ Type uint8
+ Notify string
+ SkipServersRaw string
}
func (ma *memberAPI) addOrEditMonitor(c *gin.Context) {
@@ -202,9 +206,11 @@ func (ma *memberAPI) addOrEditMonitor(c *gin.Context) {
err := c.ShouldBindJSON(&mf)
if err == nil {
m.Name = mf.Name
- m.Target = mf.Target
+ m.Target = strings.TrimSpace(mf.Target)
m.Type = mf.Type
m.ID = mf.ID
+ m.SkipServersRaw = mf.SkipServersRaw
+ m.Notify = mf.Notify == "on"
}
if err == nil {
if m.ID == 0 {
@@ -219,6 +225,8 @@ func (ma *memberAPI) addOrEditMonitor(c *gin.Context) {
Message: fmt.Sprintf("请求错误:%s", err),
})
return
+ } else {
+ dao.ServiceSentinelShared.OnMonitorUpdate()
}
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
diff --git a/cmd/dashboard/controller/member_page.go b/cmd/dashboard/controller/member_page.go
index 4e2e04e..577a43c 100644
--- a/cmd/dashboard/controller/member_page.go
+++ b/cmd/dashboard/controller/member_page.go
@@ -39,11 +39,9 @@ func (mp *memberPage) server(c *gin.Context) {
}
func (mp *memberPage) monitor(c *gin.Context) {
- var monitors []model.Monitor
- dao.DB.Find(&monitors)
c.HTML(http.StatusOK, "dashboard/monitor", mygin.CommonEnvironment(c, gin.H{
"Title": "服务监控",
- "Monitors": monitors,
+ "Monitors": dao.ServiceSentinelShared.Monitors(),
}))
}
diff --git a/cmd/dashboard/controller/oauth2.go b/cmd/dashboard/controller/oauth2.go
index 773df9c..a57cb20 100644
--- a/cmd/dashboard/controller/oauth2.go
+++ b/cmd/dashboard/controller/oauth2.go
@@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
- "log"
"net/http"
"strings"
@@ -12,6 +11,7 @@ import (
"github.com/google/go-github/github"
GitHubAPI "github.com/google/go-github/github"
"golang.org/x/oauth2"
+ GitHubOauth2 "golang.org/x/oauth2/github"
"github.com/naiba/nezha/model"
"github.com/naiba/nezha/pkg/mygin"
@@ -20,8 +20,7 @@ import (
)
type oauth2controller struct {
- oauth2Config *oauth2.Config
- r gin.IRoutes
+ r gin.IRoutes
}
func (oa *oauth2controller) serve() {
@@ -29,39 +28,58 @@ func (oa *oauth2controller) serve() {
oa.r.GET("/oauth2/callback", oa.callback)
}
-func (oa *oauth2controller) fillRedirectURL(c *gin.Context) {
+func (oa *oauth2controller) getCommonOauth2Config(c *gin.Context) *oauth2.Config {
+ var endPoint oauth2.Endpoint
+ if dao.Conf.Oauth2.Type == model.ConfigTypeGitee {
+ endPoint = oauth2.Endpoint{
+ AuthURL: "https://gitee.com/oauth/authorize",
+ TokenURL: "https://gitee.com/oauth/token",
+ }
+ } else {
+ endPoint = GitHubOauth2.Endpoint
+ }
+
+ return &oauth2.Config{
+ ClientID: dao.Conf.Oauth2.ClientID,
+ ClientSecret: dao.Conf.Oauth2.ClientSecret,
+ Scopes: []string{},
+ Endpoint: endPoint,
+ RedirectURL: oa.getRedirectURL(c),
+ }
+}
+
+func (oa *oauth2controller) getRedirectURL(c *gin.Context) string {
schame := "http://"
if strings.HasPrefix(c.Request.Referer(), "https://") {
schame = "https://"
}
- oa.oauth2Config.RedirectURL = schame + c.Request.Host + "/oauth2/callback"
+ return schame + c.Request.Host + "/oauth2/callback"
}
func (oa *oauth2controller) login(c *gin.Context) {
- oa.fillRedirectURL(c)
state := utils.RandStringBytesMaskImprSrcUnsafe(6)
dao.Cache.Set(fmt.Sprintf("%s%s", model.CacheKeyOauth2State, c.ClientIP()), state, 0)
- url := oa.oauth2Config.AuthCodeURL(state, oauth2.AccessTypeOnline)
+ url := oa.getCommonOauth2Config(c).AuthCodeURL(state, oauth2.AccessTypeOnline)
c.Redirect(http.StatusFound, url)
}
func (oa *oauth2controller) callback(c *gin.Context) {
- oa.fillRedirectURL(c)
var err error
// 验证登录跳转时的 State
state, ok := dao.Cache.Get(fmt.Sprintf("%s%s", model.CacheKeyOauth2State, c.ClientIP()))
if !ok || state.(string) != c.Query("state") {
err = errors.New("非法的登录方式")
}
+ oauth2Config := oa.getCommonOauth2Config(c)
ctx := context.Background()
var otk *oauth2.Token
if err == nil {
- otk, err = oa.oauth2Config.Exchange(ctx, c.Query("code"))
+ otk, err = oauth2Config.Exchange(ctx, c.Query("code"))
}
var client *GitHubAPI.Client
if err == nil {
- oc := oa.oauth2Config.Client(ctx, otk)
- if dao.Conf.Oauth2.Type == "gitee" {
+ oc := oauth2Config.Client(ctx, otk)
+ if dao.Conf.Oauth2.Type == model.ConfigTypeGitee {
client, err = GitHubAPI.NewEnterpriseClient("https://gitee.com/api/v5/", "https://gitee.com/api/v5/", oc)
} else {
client = GitHubAPI.NewClient(oc)
@@ -71,7 +89,6 @@ func (oa *oauth2controller) callback(c *gin.Context) {
if err == nil {
gu, _, err = client.Users.Get(ctx, "")
}
- log.Printf("%+v", gu)
if err != nil {
mygin.ShowErrorPage(c, mygin.ErrInfo{
Code: http.StatusBadRequest,
diff --git a/cmd/dashboard/main.go b/cmd/dashboard/main.go
index 934ccbf..a2c4fa8 100644
--- a/cmd/dashboard/main.go
+++ b/cmd/dashboard/main.go
@@ -52,6 +52,7 @@ func initSystem() {
dao.DB.AutoMigrate(model.Server{}, model.User{},
model.Notification{}, model.AlertRule{}, model.Monitor{},
model.MonitorHistory{}, model.Cron{})
+ dao.NewServiceSentinel()
loadServers() //加载服务器列表
loadCrons() //加载计划任务
@@ -109,6 +110,6 @@ func loadCrons() {
func main() {
go controller.ServeWeb(dao.Conf.HTTPPort)
go rpc.ServeRPC(dao.Conf.GRPCPort)
- go rpc.DispatchTask(time.Minute * 3)
+ go rpc.DispatchTask(time.Second * 30)
dao.AlertSentinelStart()
}
diff --git a/cmd/dashboard/rpc/rpc.go b/cmd/dashboard/rpc/rpc.go
index 2aaf9fb..19a3fb4 100644
--- a/cmd/dashboard/rpc/rpc.go
+++ b/cmd/dashboard/rpc/rpc.go
@@ -7,7 +7,6 @@ import (
"google.golang.org/grpc"
- "github.com/naiba/nezha/model"
pb "github.com/naiba/nezha/proto"
"github.com/naiba/nezha/service/dao"
rpcService "github.com/naiba/nezha/service/rpc"
@@ -28,9 +27,8 @@ func ServeRPC(port uint) {
func DispatchTask(duration time.Duration) {
var index uint64 = 0
for {
- var tasks []model.Monitor
var hasAliveAgent bool
- dao.DB.Find(&tasks)
+ tasks := dao.ServiceSentinelShared.Monitors()
dao.SortedServerLock.RLock()
startedAt := time.Now()
for i := 0; i < len(tasks); i++ {
@@ -41,7 +39,9 @@ func DispatchTask(duration time.Duration) {
}
hasAliveAgent = false
}
- if dao.SortedServerList[index].TaskStream == nil {
+ // 1. 如果此任务不可使用此服务器请求,跳过这个服务器(有些 IPv6 only 开了 NAT64 的机器请求 IPv4 总会出问题)
+ // 2. 如果服务器不在线,跳过这个服务器
+ if tasks[i].SkipServers[dao.SortedServerList[index].ID] || dao.SortedServerList[index].TaskStream == nil {
i--
index++
continue
diff --git a/cmd/playground/main.go b/cmd/playground/main.go
index 2baeaaa..1bb569e 100644
--- a/cmd/playground/main.go
+++ b/cmd/playground/main.go
@@ -10,6 +10,7 @@ import (
"os/exec"
"time"
+ "github.com/genkiroid/cert"
"github.com/go-ping/ping"
"github.com/naiba/nezha/pkg/utils"
"github.com/shirou/gopsutil/v3/cpu"
@@ -20,8 +21,8 @@ import (
func main() {
// icmp()
// tcpping()
- // httpWithSSLInfo()
- sysinfo()
+ httpWithSSLInfo()
+ // sysinfo()
// cmdExec()
}
@@ -33,7 +34,7 @@ func tcpping() {
}
conn.Write([]byte("ping\n"))
conn.Close()
- fmt.Println(time.Now().Sub(start).Microseconds(), float32(time.Now().Sub(start).Microseconds())/1000.0)
+ fmt.Println(time.Since(start).Microseconds(), float32(time.Since(start).Microseconds())/1000.0)
}
func sysinfo() {
@@ -53,7 +54,6 @@ func sysinfo() {
for model, count := range cpuModelCount {
cpus = append(cpus, fmt.Sprintf("%s %d %s Core", model, count, cpuType))
}
- log.Println(cpus)
os.Exit(0)
// 硬盘信息,不使用的原因是会重复统计 Linux、Mac
dparts, _ := disk.Partitions(false)
@@ -73,11 +73,12 @@ func httpWithSSLInfo() {
httpClient := &http.Client{Transport: transCfg, CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}}
- resp, err := httpClient.Get("http://mail.nai.ba")
- fmt.Println(err, resp.StatusCode)
+ url := "https://ops.naibahq.com"
+ resp, err := httpClient.Get(url)
+ fmt.Println(err, resp)
// SSL 证书信息获取
- // c := cert.NewCert("expired-ecc-dv.ssl.com")
- // fmt.Println(c.Error)
+ c := cert.NewCert(url[8:])
+ fmt.Println(c.Error)
}
func icmp() {
diff --git a/go.mod b/go.mod
index 7c175f4..66b7cef 100644
--- a/go.mod
+++ b/go.mod
@@ -7,8 +7,9 @@ require (
github.com/blang/semver v3.5.1+incompatible
github.com/fsnotify/fsnotify v1.4.9
github.com/genkiroid/cert v0.0.0-20191007122723-897560fbbe50
+ github.com/gin-contrib/pprof v1.3.0
github.com/gin-gonic/gin v1.6.3
- github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663
+ github.com/go-ping/ping v0.0.0-20210407214646-e4e642a95741
github.com/golang/protobuf v1.4.2
github.com/google/go-github v17.0.0+incompatible
github.com/gorilla/websocket v1.4.2
@@ -17,14 +18,16 @@ require (
github.com/p14yground/go-github-selfupdate v1.2.3-0.20210119020835-db3523c6834b
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/robfig/cron/v3 v3.0.1
- github.com/shirou/gopsutil/v3 v3.20.11
+ github.com/shirou/gopsutil/v3 v3.21.3
github.com/spf13/viper v1.7.1
github.com/stretchr/testify v1.6.1
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
+ golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6 // indirect
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
+ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 // indirect
google.golang.org/grpc v1.33.1
google.golang.org/protobuf v1.25.0
gopkg.in/yaml.v2 v2.2.8
gorm.io/driver/sqlite v1.1.4
- gorm.io/gorm v1.20.8
+ gorm.io/gorm v1.21.8
)
diff --git a/go.sum b/go.sum
index 19cfe08..37484d2 100644
--- a/go.sum
+++ b/go.sum
@@ -63,7 +63,6 @@ github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
-github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -74,15 +73,17 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
-github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/genkiroid/cert v0.0.0-20191007122723-897560fbbe50 h1:vLwmYBduhnWWqShoUGbVgDulhcLdanoYtCQxYMzwaqQ=
github.com/genkiroid/cert v0.0.0-20191007122723-897560fbbe50/go.mod h1:Pb7nyGYAfDyE/IkU6AJeRshIFko0wJC9cOqeYzYQffk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/gin-contrib/pprof v1.3.0 h1:G9eK6HnbkSqDZBYbzG4wrjCsA4e+cvYAHUZw6W+W9K0=
+github.com/gin-contrib/pprof v1.3.0/go.mod h1:waMjT1H9b179t3CxuG1cV3DHpga6ybizwfBaM5OXaB0=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@@ -93,8 +94,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
-github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663 h1:jI2GiiRh+pPbey52EVmbU6kuLiXqwy4CXZ4gwUBj8Y0=
-github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663/go.mod h1:35JbSyV/BYqHwwRA6Zr1uVDm1637YlNOU61wI797NPI=
+github.com/go-ping/ping v0.0.0-20210407214646-e4e642a95741 h1:b0sLP++Tsle+s57tqg5sUk1/OQsC6yMCciVeqNzOcwU=
+github.com/go-ping/ping v0.0.0-20210407214646-e4e642a95741/go.mod h1:35JbSyV/BYqHwwRA6Zr1uVDm1637YlNOU61wI797NPI=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
@@ -106,7 +107,6 @@ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -121,7 +121,6 @@ github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
@@ -131,19 +130,16 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
-github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -200,8 +196,9 @@ github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7V
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
-github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
+github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
@@ -282,8 +279,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/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/shirou/gopsutil/v3 v3.20.11 h1:NeVf1K0cgxsWz+N3671ojRptdgzvp7BXL3KV21R0JnA=
-github.com/shirou/gopsutil/v3 v3.20.11/go.mod h1:igHnfak0qnw1biGeI2qKQvu0ZkwvEkUcCLlYhZzdr/4=
+github.com/shirou/gopsutil/v3 v3.21.3 h1:wgcdAHZS2H6qy4JFewVTtqfiYxFzCeEJod/mLztdPG8=
+github.com/shirou/gopsutil/v3 v3.21.3/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw=
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/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
@@ -305,7 +302,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -313,6 +309,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/tcnksm/go-gitconfig v0.1.2 h1:iiDhRitByXAEyjgBqsKi9QU4o2TNtv9kPP3RgPgXBPw=
github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4UsLGSItWYCpE=
+github.com/tklauser/go-sysconf v0.3.4 h1:HT8SVixZd3IzLdfs/xlpq0jeSfTX57g1v6wB1EuzV7M=
+github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek=
+github.com/tklauser/numcpus v0.2.1 h1:ct88eFm+Q7m2ZfXJdan1xYoXKlmwsfP+k88q05KvlZc=
+github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8=
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/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
@@ -380,7 +380,6 @@ golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -395,7 +394,6 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
@@ -403,14 +401,13 @@ golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6 h1:0PC75Fz/kyMGhL0e1QnypqK2kQMqKt9csD1GnMJR+Zk=
+golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -445,34 +442,33 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 h1:B6caxRw+hozq68X2MY7jEpZh/cr4/aHLv9xU8Kkadrw=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5 h1:iCaAy5bMeEvwANu3YnJfWwI0kWAGkEa2RXPdweI/ysk=
-golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 h1:iGu644GcxtEcrInvDsQRCwJjtCIOlT2V7IRt6ah2Whw=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+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.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
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.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -521,7 +517,6 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -543,10 +538,8 @@ google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSr
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
@@ -557,7 +550,6 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
@@ -576,7 +568,6 @@ google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
-google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
@@ -589,7 +580,6 @@ google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
@@ -610,7 +600,6 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -624,9 +613,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -635,8 +622,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
-gorm.io/gorm v1.20.8 h1:iToaOdZgjNvlc44NFkxfLa3U9q63qwaxt0FdNCiwOMs=
-gorm.io/gorm v1.20.8/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
+gorm.io/gorm v1.21.8 h1:2CEwZSzogdhsKPlJ9OvBKTdlWIpELXb6HbfLfMNhSYI=
+gorm.io/gorm v1.21.8/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/model/api.go b/model/api.go
new file mode 100644
index 0000000..039429c
--- /dev/null
+++ b/model/api.go
@@ -0,0 +1,12 @@
+package model
+
+type ServiceItemResponse struct {
+ Monitor Monitor
+ TotalUp uint64
+ TotalDown uint64
+ CurrentUp uint64
+ CurrentDown uint64
+ Delay *[30]float32
+ Up *[30]int
+ Down *[30]int
+}
diff --git a/model/monitor.go b/model/monitor.go
index 7b56253..49ceb3d 100644
--- a/model/monitor.go
+++ b/model/monitor.go
@@ -1,7 +1,10 @@
package model
import (
+ "encoding/json"
+
pb "github.com/naiba/nezha/proto"
+ "gorm.io/gorm"
)
const (
@@ -14,9 +17,13 @@ const (
type Monitor struct {
Common
- Name string
- Type uint8
- Target string
+ Name string
+ Type uint8
+ Target string
+ SkipServersRaw string
+ Notify bool
+
+ SkipServers map[uint64]bool `gorm:"-" json:"-"`
}
func (m *Monitor) PB() *pb.Task {
@@ -26,3 +33,15 @@ func (m *Monitor) PB() *pb.Task {
Data: m.Target,
}
}
+
+func (m *Monitor) AfterFind(tx *gorm.DB) error {
+ var skipServers []uint64
+ if err := json.Unmarshal([]byte(m.SkipServersRaw), &skipServers); err != nil {
+ return err
+ }
+ m.SkipServers = make(map[uint64]bool)
+ for i := 0; i < len(skipServers); i++ {
+ m.SkipServers[skipServers[i]] = true
+ }
+ return nil
+}
diff --git a/resource/static/main.css b/resource/static/main.css
index 2c0aff9..7513463 100644
--- a/resource/static/main.css
+++ b/resource/static/main.css
@@ -6,7 +6,7 @@
.nb-container {
padding-top: 75px;
- min-height: 100%;
+ min-height: 100vh;
padding-bottom: 55px;
margin-bottom: -47px;
}
diff --git a/resource/static/main.js b/resource/static/main.js
index 8f93b3f..30343fd 100644
--- a/resource/static/main.js
+++ b/resource/static/main.js
@@ -1,273 +1,371 @@
function readableBytes(bytes) {
- var i = Math.floor(Math.log(bytes) / Math.log(1024)),
- sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
- return (bytes / Math.pow(1024, i)).toFixed(0) + ' ' + sizes[i];
+ var i = Math.floor(Math.log(bytes) / Math.log(1024)),
+ sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
+ return (bytes / Math.pow(1024, i)).toFixed(0) + " " + sizes[i];
}
-const confirmBtn = $('.mini.confirm.modal .positive.button')
+const confirmBtn = $(".mini.confirm.modal .positive.button");
function showConfirm(title, content, callFn, extData) {
- const modal = $('.mini.confirm.modal')
- modal.children('.header').text(title)
- modal.children('.content').text(content)
- if (confirmBtn.hasClass('loading')) {
- return false
- }
- modal.modal({
- closable: true,
- onApprove: function () {
- confirmBtn.toggleClass('loading')
- callFn(extData)
- return false
- }
- }).modal('show')
+ const modal = $(".mini.confirm.modal");
+ modal.children(".header").text(title);
+ modal.children(".content").text(content);
+ if (confirmBtn.hasClass("loading")) {
+ return false;
+ }
+ modal
+ .modal({
+ closable: true,
+ onApprove: function () {
+ confirmBtn.toggleClass("loading");
+ callFn(extData);
+ return false;
+ },
+ })
+ .modal("show");
}
function showFormModal(modelSelector, formID, URL, getData) {
- $(modelSelector).modal({
- closable: true,
- onApprove: function () {
- let success = false
- const btn = $(modelSelector + ' .positive.button')
- const form = $(modelSelector + ' form')
- if (btn.hasClass('loading')) {
- return success
- }
- form.children('.message').remove()
- btn.toggleClass('loading')
- const data = getData ? getData() : $(formID).serializeArray().reduce(function (obj, item) {
+ $(modelSelector)
+ .modal({
+ closable: true,
+ onApprove: function () {
+ let success = false;
+ const btn = $(modelSelector + " .positive.button");
+ const form = $(modelSelector + " form");
+ if (btn.hasClass("loading")) {
+ return success;
+ }
+ form.children(".message").remove();
+ btn.toggleClass("loading");
+ const data = getData
+ ? getData()
+ : $(formID)
+ .serializeArray()
+ .reduce(function (obj, item) {
// ID 类的数据
- if ((item.name.endsWith('_id') ||
- item.name === 'id' || item.name === 'ID' ||
- item.name === 'RequestType' || item.name === 'RequestMethod' ||
- item.name === 'DisplayIndex' || item.name === 'Type')) {
- obj[item.name] = parseInt(item.value);
+ if (
+ item.name.endsWith("_id") ||
+ item.name === "id" ||
+ item.name === "ID" ||
+ item.name === "RequestType" ||
+ item.name === "RequestMethod" ||
+ item.name === "DisplayIndex" ||
+ item.name === "Type"
+ ) {
+ obj[item.name] = parseInt(item.value);
} else {
- obj[item.name] = item.value;
+ obj[item.name] = item.value;
}
- if (item.name == 'ServersRaw') {
- if (item.value.length > 2) {
- obj[item.name] = '[' + item.value.substr(3, item.value.length - 1) + ']'
- }
+ if (item.name.endsWith("ServersRaw")) {
+ if (item.value.length > 2) {
+ obj[item.name] = JSON.stringify(
+ [...item.value.matchAll(/\d+/gm)].map((k) =>
+ parseInt(k[0])
+ )
+ );
+ }
}
return obj;
- }, {});
- $.post(URL, JSON.stringify(data)).done(function (resp) {
- if (resp.code == 200) {
- if (resp.message) {
- $.suiAlert({
- title: '操作成功',
- type: 'success',
- description: resp.message,
- time: '3',
- position: 'top-center',
- });
- }
- window.location.reload()
- } else {
- form.append(``)
- }
- }).fail(function (err) {
- form.append(``)
- }).always(function () {
- btn.toggleClass('loading')
- });
- return success
- }
- }).modal('show')
+ }, {});
+ $.post(URL, JSON.stringify(data))
+ .done(function (resp) {
+ if (resp.code == 200) {
+ if (resp.message) {
+ $.suiAlert({
+ title: "操作成功",
+ type: "success",
+ description: resp.message,
+ time: "3",
+ position: "top-center",
+ });
+ }
+ window.location.reload();
+ } else {
+ form.append(
+ ``
+ );
+ }
+ })
+ .fail(function (err) {
+ form.append(
+ `` +
+ err.responseText +
+ `
`
+ );
+ })
+ .always(function () {
+ btn.toggleClass("loading");
+ });
+ return success;
+ },
+ })
+ .modal("show");
}
function addOrEditAlertRule(rule) {
- const modal = $('.rule.modal')
- modal.children('.header').text((rule ? '修改' : '添加') + '报警规则')
- modal.find('.positive.button').html(rule ? '修改' : '添加')
- modal.find('input[name=ID]').val(rule ? rule.ID : null)
- modal.find('input[name=Name]').val(rule ? rule.Name : null)
- modal.find('textarea[name=RulesRaw]').val(rule ? rule.RulesRaw : null)
- if (rule && rule.Enable) {
- modal.find('.ui.rule-enable.checkbox').checkbox('set checked')
- } else {
- modal.find('.ui.rule-enable.checkbox').checkbox('set unchecked')
- }
- showFormModal('.rule.modal', '#ruleForm', '/api/alert-rule')
+ const modal = $(".rule.modal");
+ modal.children(".header").text((rule ? "修改" : "添加") + "报警规则");
+ modal
+ .find(".positive.button")
+ .html(
+ rule ? '修改' : '添加'
+ );
+ modal.find("input[name=ID]").val(rule ? rule.ID : null);
+ modal.find("input[name=Name]").val(rule ? rule.Name : null);
+ modal.find("textarea[name=RulesRaw]").val(rule ? rule.RulesRaw : null);
+ if (rule && rule.Enable) {
+ modal.find(".ui.rule-enable.checkbox").checkbox("set checked");
+ } else {
+ modal.find(".ui.rule-enable.checkbox").checkbox("set unchecked");
+ }
+ showFormModal(".rule.modal", "#ruleForm", "/api/alert-rule");
}
function addOrEditNotification(notification) {
- const modal = $('.notification.modal')
- modal.children('.header').text((notification ? '修改' : '添加') + '通知方式')
- modal.find('.positive.button').html(notification ? '修改' : '添加')
- modal.find('input[name=ID]').val(notification ? notification.ID : null)
- modal.find('input[name=Name]').val(notification ? notification.Name : null)
- modal.find('input[name=URL]').val(notification ? notification.URL : null)
- modal.find('textarea[name=RequestBody]').val(notification ? notification.RequestBody : null)
- modal.find('select[name=RequestMethod]').val(notification ? notification.RequestMethod : 1)
- modal.find('select[name=RequestType]').val(notification ? notification.RequestType : 1)
- if (notification && notification.VerifySSL) {
- modal.find('.ui.nf-ssl.checkbox').checkbox('set checked')
- } else {
- modal.find('.ui.nf-ssl.checkbox').checkbox('set unchecked')
- }
- showFormModal('.notification.modal', '#notificationForm', '/api/notification')
+ const modal = $(".notification.modal");
+ modal.children(".header").text((notification ? "修改" : "添加") + "通知方式");
+ modal
+ .find(".positive.button")
+ .html(
+ notification
+ ? '修改'
+ : '添加'
+ );
+ modal.find("input[name=ID]").val(notification ? notification.ID : null);
+ modal.find("input[name=Name]").val(notification ? notification.Name : null);
+ modal.find("input[name=URL]").val(notification ? notification.URL : null);
+ modal
+ .find("textarea[name=RequestBody]")
+ .val(notification ? notification.RequestBody : null);
+ modal
+ .find("select[name=RequestMethod]")
+ .val(notification ? notification.RequestMethod : 1);
+ modal
+ .find("select[name=RequestType]")
+ .val(notification ? notification.RequestType : 1);
+ if (notification && notification.VerifySSL) {
+ modal.find(".ui.nf-ssl.checkbox").checkbox("set checked");
+ } else {
+ modal.find(".ui.nf-ssl.checkbox").checkbox("set unchecked");
+ }
+ showFormModal(
+ ".notification.modal",
+ "#notificationForm",
+ "/api/notification"
+ );
}
function addOrEditServer(server) {
- const modal = $('.server.modal')
- modal.children('.header').text((server ? '修改' : '添加') + '服务器')
- modal.find('.positive.button').html(server ? '修改' : '添加')
- modal.find('input[name=id]').val(server ? server.ID : null)
- modal.find('input[name=name]').val(server ? server.Name : null)
- modal.find('input[name=Tag]').val(server ? server.Tag : null)
- modal.find('input[name=DisplayIndex]').val(server ? server.DisplayIndex : null)
- modal.find('textarea[name=Note]').val(server ? server.Note : null)
- if (server) {
- modal.find('.secret.field').attr('style', '')
- modal.find('input[name=secret]').val(server.Secret)
- } else {
- modal.find('.secret.field').attr('style', 'display:none')
- modal.find('input[name=secret]').val('')
- }
- showFormModal('.server.modal', '#serverForm', '/api/server')
+ const modal = $(".server.modal");
+ modal.children(".header").text((server ? "修改" : "添加") + "服务器");
+ modal
+ .find(".positive.button")
+ .html(
+ server ? '修改' : '添加'
+ );
+ modal.find("input[name=id]").val(server ? server.ID : null);
+ modal.find("input[name=name]").val(server ? server.Name : null);
+ modal.find("input[name=Tag]").val(server ? server.Tag : null);
+ modal
+ .find("input[name=DisplayIndex]")
+ .val(server ? server.DisplayIndex : null);
+ modal.find("textarea[name=Note]").val(server ? server.Note : null);
+ if (server) {
+ modal.find(".secret.field").attr("style", "");
+ modal.find("input[name=secret]").val(server.Secret);
+ } else {
+ modal.find(".secret.field").attr("style", "display:none");
+ modal.find("input[name=secret]").val("");
+ }
+ showFormModal(".server.modal", "#serverForm", "/api/server");
}
function addOrEditMonitor(monitor) {
- const modal = $('.monitor.modal')
- modal.children('.header').text((monitor ? '修改' : '添加') + '监控')
- modal.find('.positive.button').html(monitor ? '修改' : '添加')
- modal.find('input[name=ID]').val(monitor ? monitor.ID : null)
- modal.find('input[name=Name]').val(monitor ? monitor.Name : null)
- modal.find('input[name=Target]').val(monitor ? monitor.Target : null)
- modal.find('select[name=Type]').val(monitor ? monitor.Type : 1)
- showFormModal('.monitor.modal', '#monitorForm', '/api/monitor')
+ const modal = $(".monitor.modal");
+ modal.children(".header").text((monitor ? "修改" : "添加") + "监控");
+ modal
+ .find(".positive.button")
+ .html(
+ monitor ? '修改' : '添加'
+ );
+ modal.find("input[name=ID]").val(monitor ? monitor.ID : null);
+ modal.find("input[name=Name]").val(monitor ? monitor.Name : null);
+ modal.find("input[name=Target]").val(monitor ? monitor.Target : null);
+ modal.find("select[name=Type]").val(monitor ? monitor.Type : 1);
+ if (monitor && monitor.Notify) {
+ modal.find(".ui.nb-notify.checkbox").checkbox("set checked");
+ } else {
+ modal.find(".ui.nb-notify.checkbox").checkbox("set unchecked");
+ }
+ var servers;
+ if (monitor) {
+ servers = monitor.SkipServersRaw;
+ const serverList = JSON.parse(servers || "[]");
+ const node = modal.find("i.dropdown.icon");
+ for (let i = 0; i < serverList.length; i++) {
+ node.after(
+ 'ID:' +
+ serverList[i] +
+ ''
+ );
+ }
+ }
+ modal
+ .find("input[name=SkipServersRaw]")
+ .val(monitor ? "[]," + servers.substr(1, servers.length - 2) : "[]");
+ showFormModal(".monitor.modal", "#monitorForm", "/api/monitor");
}
function addOrEditCron(cron) {
- const modal = $('.cron.modal')
- modal.children('.header').text((cron ? '修改' : '添加') + '计划任务')
- modal.find('.positive.button').html(cron ? '修改' : '添加')
- modal.find('input[name=ID]').val(cron ? cron.ID : null)
- modal.find('input[name=Name]').val(cron ? cron.Name : null)
- modal.find('input[name=Scheduler]').val(cron ? cron.Scheduler : null)
- modal.find('a.ui.label.visible').each((i, el) => {
- el.remove()
- })
- var servers
- if (cron) {
- servers = cron.ServersRaw
- serverList = JSON.parse(servers)
- const node = modal.find('i.dropdown.icon')
- for (let i = 0; i < serverList.length; i++) {
- node.after('ID:' + serverList[i] + '')
- }
+ const modal = $(".cron.modal");
+ modal.children(".header").text((cron ? "修改" : "添加") + "计划任务");
+ modal
+ .find(".positive.button")
+ .html(
+ cron ? '修改' : '添加'
+ );
+ modal.find("input[name=ID]").val(cron ? cron.ID : null);
+ modal.find("input[name=Name]").val(cron ? cron.Name : null);
+ modal.find("input[name=Scheduler]").val(cron ? cron.Scheduler : null);
+ modal.find("a.ui.label.visible").each((i, el) => {
+ el.remove();
+ });
+ var servers;
+ if (cron) {
+ servers = cron.ServersRaw;
+ const serverList = JSON.parse(servers || "[]");
+ const node = modal.find("i.dropdown.icon");
+ for (let i = 0; i < serverList.length; i++) {
+ node.after(
+ 'ID:' +
+ serverList[i] +
+ ''
+ );
}
- modal.find('input[name=ServersRaw]').val(cron ? '[],' + servers.substr(1, servers.length - 2) : '[]')
- modal.find('textarea[name=Command]').val(cron ? cron.Command : null)
- if (cron && cron.PushSuccessful) {
- modal.find('.ui.push-successful.checkbox').checkbox('set checked')
- } else {
- modal.find('.ui.push-successful.checkbox').checkbox('set unchecked')
- }
- showFormModal('.cron.modal', '#cronForm', '/api/cron')
+ }
+ modal
+ .find("input[name=ServersRaw]")
+ .val(cron ? "[]," + servers.substr(1, servers.length - 2) : "[]");
+ modal.find("textarea[name=Command]").val(cron ? cron.Command : null);
+ if (cron && cron.PushSuccessful) {
+ modal.find(".ui.push-successful.checkbox").checkbox("set checked");
+ } else {
+ modal.find(".ui.push-successful.checkbox").checkbox("set unchecked");
+ }
+ showFormModal(".cron.modal", "#cronForm", "/api/cron");
}
function deleteRequest(api) {
- $.ajax({
- url: api,
- type: 'DELETE',
- }).done(resp => {
- if (resp.code == 200) {
- if (resp.message) {
- alert(resp.message)
- } else {
- alert('删除成功')
- }
- window.location.reload()
+ $.ajax({
+ url: api,
+ type: "DELETE",
+ })
+ .done((resp) => {
+ if (resp.code == 200) {
+ if (resp.message) {
+ alert(resp.message);
} else {
- alert('删除失败 ' + resp.code + ':' + resp.message)
- confirmBtn.toggleClass('loading')
+ alert("删除成功");
}
- }).fail(err => {
- alert('网络错误:' + err.responseText)
+ window.location.reload();
+ } else {
+ alert("删除失败 " + resp.code + ":" + resp.message);
+ confirmBtn.toggleClass("loading");
+ }
+ })
+ .fail((err) => {
+ alert("网络错误:" + err.responseText);
});
}
function manualTrigger(btn, cronId) {
- $(btn).toggleClass('loading')
- $.ajax({
- url: '/api/cron/' + cronId + '/manual',
- type: 'GET',
- }).done(resp => {
- $(btn).toggleClass('loading')
- if (resp.code == 200) {
- $.suiAlert({
- title: '触发成功,等待执行结果',
- type: 'success',
- description: resp.message,
- time: '3',
- position: 'top-center',
- });
- } else {
- $.suiAlert({
- title: '触发失败 ',
- type: 'error',
- description: resp.code + ':' + resp.message,
- time: '3',
- position: 'top-center',
- });
- }
- }).fail(err => {
- $(btn).toggleClass('loading')
+ $(btn).toggleClass("loading");
+ $.ajax({
+ url: "/api/cron/" + cronId + "/manual",
+ type: "GET",
+ })
+ .done((resp) => {
+ $(btn).toggleClass("loading");
+ if (resp.code == 200) {
$.suiAlert({
- title: '触发失败 ',
- type: 'error',
- description: '网络错误:' + err.responseText,
- time: '3',
- position: 'top-center',
+ title: "触发成功,等待执行结果",
+ type: "success",
+ description: resp.message,
+ time: "3",
+ position: "top-center",
});
+ } else {
+ $.suiAlert({
+ title: "触发失败 ",
+ type: "error",
+ description: resp.code + ":" + resp.message,
+ time: "3",
+ position: "top-center",
+ });
+ }
+ })
+ .fail((err) => {
+ $(btn).toggleClass("loading");
+ $.suiAlert({
+ title: "触发失败 ",
+ type: "error",
+ description: "网络错误:" + err.responseText,
+ time: "3",
+ position: "top-center",
+ });
});
}
function logout(id) {
- $.post('/api/logout', JSON.stringify({ id: id })).done(function (resp) {
- if (resp.code == 200) {
- $.suiAlert({
- title: '注销成功',
- type: 'success',
- description: '如需继续访问请使用 GitHub 再次登录',
- time: '3',
- position: 'top-center',
- });
- window.location.reload()
- } else {
- $.suiAlert({
- title: '注销失败',
- description: resp.code + ':' + resp.message,
- type: 'error',
- time: '3',
- position: 'top-center',
- });
- }
- }).fail(function (err) {
+ $.post("/api/logout", JSON.stringify({ id: id }))
+ .done(function (resp) {
+ if (resp.code == 200) {
$.suiAlert({
- title: '网络错误',
- description: err.responseText,
- type: 'error',
- time: '3',
- position: 'top-center',
+ title: "注销成功",
+ type: "success",
+ description: "如需继续访问请使用 GitHub 再次登录",
+ time: "3",
+ position: "top-center",
});
+ window.location.reload();
+ } else {
+ $.suiAlert({
+ title: "注销失败",
+ description: resp.code + ":" + resp.message,
+ type: "error",
+ time: "3",
+ position: "top-center",
+ });
+ }
})
+ .fail(function (err) {
+ $.suiAlert({
+ title: "网络错误",
+ description: err.responseText,
+ type: "error",
+ time: "3",
+ position: "top-center",
+ });
+ });
}
$(document).ready(() => {
- try {
- $('.ui.servers.search.dropdown').dropdown({
- clearable: true,
- apiSettings: {
- url: '/api/search-server?word={query}',
- cache: false,
- },
- })
- } catch (error) {
- }
-})
\ No newline at end of file
+ try {
+ $(".ui.servers.search.dropdown").dropdown({
+ clearable: true,
+ apiSettings: {
+ url: "/api/search-server?word={query}",
+ cache: false,
+ },
+ });
+ } catch (error) {}
+});
diff --git a/resource/template/common/footer.html b/resource/template/common/footer.html
index e84a4a8..4f594c6 100644
--- a/resource/template/common/footer.html
+++ b/resource/template/common/footer.html
@@ -9,7 +9,7 @@
-
+