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 @@


-    +   

:trollface: 哪吒监控 一站式轻监控轻运维系统。支持系统状态、HTTP(SSL 证书变更、即将到期、到期)、TCP、Ping 监控报警,命令批量执行和计划任务。

-\>> 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(`
操作失败

` + resp.message + `

`) - } - }).fail(function (err) { - form.append(`
网络错误

` + err.responseText + `

`) - }).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( + `
操作失败

` + + resp.message + + `

` + ); + } + }) + .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 @@ - + diff --git a/resource/template/common/header.html b/resource/template/common/header.html index 2f4d903..3e9e04c 100644 --- a/resource/template/common/header.html +++ b/resource/template/common/header.html @@ -9,7 +9,7 @@ {{.Title}} - + diff --git a/resource/template/common/menu.html b/resource/template/common/menu.html index e315049..fa373a1 100644 --- a/resource/template/common/menu.html +++ b/resource/template/common/menu.html @@ -14,7 +14,7 @@ {{else}} 首页 - 服务状态 + 服务 {{end}}