diff --git a/Dockerfile.dashboard b/Dockerfile.dashboard index 8e421fb..f025279 100644 --- a/Dockerfile.dashboard +++ b/Dockerfile.dashboard @@ -3,7 +3,7 @@ RUN apk --no-cache --no-progress add \ gcc git musl-dev WORKDIR /dashboard COPY . . -RUN cd cmd/dashboard && go build -o app -ldflags="-s -w -X github.com/naiba/nezha/service/dao.Version=$(git rev-parse HEAD)" +RUN cd cmd/dashboard && go build -o app -ldflags="-s -w" FROM alpine:latest RUN apk --no-cache --no-progress add \ diff --git a/README.md b/README.md index c449ec0..3798edd 100644 --- a/README.md +++ b/README.md @@ -179,3 +179,13 @@ URL 里面也可放置占位符,请求时会进行简单的字符串替换。 - [哪吒面板,一个便携服务器状态监控面板搭建教程,不想拥有一个自己的探针吗?](https://haoduck.com/644.html) - [哪吒面板:小鸡们的最佳探针](https://www.zhujizixun.com/2843.html) *(已过时)* - [>>更多教程](https://www.google.com/search?q=%22%E5%93%AA%E5%90%92%E9%9D%A2%E6%9D%BF%22+%22%E6%95%99%E7%A8%8B%22) (Google) + +## 变更日志 + +- `0.2.0` **重大更新** + + 增加了服务监控(TCP端口延迟、Ping、HTTP-SSL 证书)功能,此版本 Agent 与旧面板不兼容,而 Agent 是通过 GitHub Release 自动更新的 所以务必更新面板开启最新功能。 + +- `0.1.23` 新增 IP 变更通知功能 + + 在后台设置界面启用。 diff --git a/cmd/agent/main.go b/cmd/agent/main.go index 5aa7a22..4564339 100644 --- a/cmd/agent/main.go +++ b/cmd/agent/main.go @@ -2,13 +2,18 @@ package main import ( "context" + "errors" "fmt" - "io" "log" + "net" + "net/http" "os" + "strings" "time" "github.com/blang/semver" + "github.com/genkiroid/cert" + "github.com/go-ping/ping" "github.com/p14yground/go-github-selfupdate/selfupdate" "github.com/spf13/cobra" "google.golang.org/grpc" @@ -105,7 +110,6 @@ func run(cmd *cobra.Command, args []string) { var err error var conn *grpc.ClientConn - var hc pb.NezhaService_HeartbeatClient retry := func() { log.Println("Error to close connection ...") @@ -125,43 +129,90 @@ func run(cmd *cobra.Command, args []string) { } client = pb.NewNezhaServiceClient(conn) // 第一步注册 - _, err = client.Register(ctx, monitor.GetHost().PB()) + _, err = client.ReportSystemInfo(ctx, monitor.GetHost().PB()) if err != nil { - log.Printf("client.Register err: %v", err) + log.Printf("client.ReportSystemInfo err: %v", err) retry() continue } - // 心跳接收控制命令 - hc, err = client.Heartbeat(ctx, &pb.Beat{ - Timestamp: fmt.Sprintf("%v", time.Now()), - }) + // 执行 Task + tasks, err := client.RequestTask(ctx, monitor.GetHost().PB()) if err != nil { - log.Printf("client.Heartbeat err: %v", err) + log.Printf("client.RequestTask err: %v", err) retry() continue } - err = receiveCommand(hc) + err = receiveTasks(tasks) log.Printf("receiveCommand exit to main: %v", err) retry() } } -func receiveCommand(hc pb.NezhaService_HeartbeatClient) error { +func receiveTasks(tasks pb.NezhaService_RequestTaskClient) error { var err error - var action *pb.Command - defer log.Printf("receiveCommand exit %v %v => %v", time.Now(), action, err) + var task *pb.Task + defer log.Printf("receiveTasks exit %v %v => %v", time.Now(), task, err) for { - action, err = hc.Recv() - if err == io.EOF { - return nil - } + task, err = tasks.Recv() if err != nil { return err } - switch action.GetType() { + var result pb.TaskResult + result.Id = task.GetId() + switch task.GetType() { + case model.MonitorTypeHTTPGET: + start := time.Now() + resp, err := http.Get(task.GetData()) + if err == nil { + result.Delay = float32(time.Now().Sub(start).Microseconds()) / 1000.0 + if resp.StatusCode > 299 || resp.StatusCode < 200 { + err = errors.New("\n应用错误:" + resp.Status) + } + } + var certs cert.Certs + if err == nil { + if strings.HasPrefix(task.GetData(), "https://") { + certs, err = cert.NewCerts([]string{task.GetData()}) + } + } + if err == nil { + if len(certs) == 0 { + err = errors.New("\n获取SSL证书错误:未获取到证书") + } + } + if err == nil { + result.Data = certs[0].Issuer + result.Successful = true + } else { + result.Data = err.Error() + } + case model.MonitorTypeICMPPing: + pinger, err := ping.NewPinger(task.GetData()) + if err == nil { + pinger.Count = 10 + err = pinger.Run() // Blocks until finished. + } + if err == nil { + stat := pinger.Statistics() + result.Delay = float32(stat.AvgRtt.Microseconds()) / 1000.0 + result.Successful = true + } else { + result.Data = err.Error() + } + case model.MonitorTypeTCPPing: + start := time.Now() + conn, err := net.DialTimeout("tcp", task.GetData(), time.Second*10) + if err == nil { + conn.Close() + result.Delay = float32(time.Now().Sub(start).Microseconds()) / 1000.0 + result.Successful = true + } else { + result.Data = err.Error() + } default: - log.Printf("Unknown action: %v", action) + log.Printf("Unknown action: %v", task) } + client.ReportTask(ctx, &result) } } @@ -172,14 +223,14 @@ func reportState() { for { if client != nil { monitor.TrackNetworkSpeed() - _, err = client.ReportState(ctx, monitor.GetState(dao.ReportDelay).PB()) + _, err = client.ReportSystemState(ctx, monitor.GetState(dao.ReportDelay).PB()) if err != nil { log.Printf("reportState error %v", err) time.Sleep(delayWhenError) } if lastReportHostInfo.Before(time.Now().Add(-10 * time.Minute)) { lastReportHostInfo = time.Now() - client.Register(ctx, monitor.GetHost().PB()) + client.ReportSystemInfo(ctx, monitor.GetHost().PB()) } } } diff --git a/cmd/dashboard/controller/common_page.go b/cmd/dashboard/controller/common_page.go index f37e215..9e5094e 100644 --- a/cmd/dashboard/controller/common_page.go +++ b/cmd/dashboard/controller/common_page.go @@ -20,9 +20,64 @@ func (cp *commonPage) serve() { cr := cp.r.Group("") cr.Use(mygin.Authorize(mygin.AuthorizeOption{})) cr.GET("/", cp.home) + cr.GET("/service", cp.service) cr.GET("/ws", cp.ws) } +type ServiceItem struct { + Monitor model.Monitor + TotalUp uint64 + TotalDown uint64 + Delay *[30]float32 + Up *[30]int + Down *[30]int +} + +func (p *commonPage) service(c *gin.Context) { + 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) + + msm := make(map[uint64]*ServiceItem) + 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}, + } + } + + // 整合数据 + 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) + } + 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]++ + } + } + + u, ok := c.Get(model.CtxKeyAuthorizedUser) + data := mygin.CommonEnvironment(c, gin.H{ + "Title": "服务状态", + "Services": msm, + }) + if ok { + data["Admin"] = u + } + c.HTML(http.StatusOK, "theme-"+dao.Conf.Site.Theme+"/service", data) +} + func (cp *commonPage) home(c *gin.Context) { dao.ServerLock.RLock() defer dao.ServerLock.RUnlock() diff --git a/cmd/dashboard/controller/controller.go b/cmd/dashboard/controller/controller.go index 4c2c2ba..405a2b7 100644 --- a/cmd/dashboard/controller/controller.go +++ b/cmd/dashboard/controller/controller.go @@ -13,7 +13,6 @@ import ( "github.com/naiba/nezha/service/dao" ) -// ServeWeb .. func ServeWeb(port uint) { gin.SetMode(gin.ReleaseMode) if dao.Conf.Debug { @@ -43,6 +42,35 @@ func ServeWeb(port uint) { "ts": func(s string) string { return strings.TrimSpace(s) }, + "divU64": func(a, b uint64) float32 { + if b == 0 { + if a > 0 { + return 100 + } + return 0 + } + return float32(a) / float32(b) * 100 + }, + "div": func(a, b int) float32 { + if b == 0 { + if a > 0 { + return 100 + } + return 0 + } + return float32(a) / float32(b) * 100 + }, + "addU64": func(a, b uint64) uint64 { + return a + b + }, + "add": func(a, b int) int { + return a + b + }, + "dayBefore": func(i int) string { + year, month, day := time.Now().Date() + today := time.Date(year, month, day, 0, 0, 0, 0, time.Local) + return today.AddDate(0, 0, i-29).Format("1月2号") + }, }) r.Static("/static", "resource/static") r.LoadHTMLGlob("resource/template/**/*") diff --git a/cmd/dashboard/controller/member_api.go b/cmd/dashboard/controller/member_api.go index 5b1d83f..f848c51 100644 --- a/cmd/dashboard/controller/member_api.go +++ b/cmd/dashboard/controller/member_api.go @@ -33,6 +33,7 @@ func (ma *memberAPI) serve() { mr.POST("/logout", ma.logout) mr.POST("/server", ma.addOrEditServer) + mr.POST("/monitor", ma.addOrEditMonitor) mr.POST("/notification", ma.addOrEditNotification) mr.POST("/alert-rule", ma.addOrEditAlertRule) mr.POST("/setting", ma.updateSetting) @@ -64,6 +65,8 @@ func (ma *memberAPI) delete(c *gin.Context) { if err == nil { alertmanager.OnDeleteNotification(id) } + case "monitor": + err = dao.DB.Delete(&model.Monitor{}, "id = ?", id).Error case "alert-rule": err = dao.DB.Delete(&model.AlertRule{}, "id = ?", id).Error if err == nil { @@ -125,7 +128,7 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) { s.State = dao.ServerList[s.ID].State } else { s.Host = &model.Host{} - s.State = &model.State{} + s.State = &model.HostState{} } dao.ServerList[s.ID] = &s dao.ReSortServer() @@ -134,6 +137,42 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) { }) } +type monitorForm struct { + ID uint64 + Name string + Target string + Type uint8 +} + +func (ma *memberAPI) addOrEditMonitor(c *gin.Context) { + var mf monitorForm + var m model.Monitor + err := c.ShouldBindJSON(&mf) + if err == nil { + m.Name = mf.Name + m.Target = mf.Target + m.Type = mf.Type + m.ID = mf.ID + } + if err == nil { + if m.ID == 0 { + err = dao.DB.Create(&m).Error + } else { + err = dao.DB.Save(&m).Error + } + } + if err != nil { + c.JSON(http.StatusOK, model.Response{ + Code: http.StatusBadRequest, + Message: fmt.Sprintf("请求错误:%s", err), + }) + return + } + c.JSON(http.StatusOK, model.Response{ + Code: http.StatusOK, + }) +} + type notificationForm struct { ID uint64 Name string diff --git a/cmd/dashboard/controller/member_page.go b/cmd/dashboard/controller/member_page.go index 97c0cde..3fdb698 100644 --- a/cmd/dashboard/controller/member_page.go +++ b/cmd/dashboard/controller/member_page.go @@ -23,6 +23,7 @@ func (mp *memberPage) serve() { Redirect: "/login", })) mr.GET("/server", mp.server) + mr.GET("/monitor", mp.monitor) mr.GET("/notification", mp.notification) mr.GET("/setting", mp.setting) } @@ -36,6 +37,15 @@ 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, + })) +} + func (mp *memberPage) notification(c *gin.Context) { var nf []model.Notification dao.DB.Find(&nf) diff --git a/cmd/dashboard/main.go b/cmd/dashboard/main.go index ea3c50c..04462d5 100644 --- a/cmd/dashboard/main.go +++ b/cmd/dashboard/main.go @@ -34,14 +34,16 @@ func init() { } func initDB() { - dao.DB.AutoMigrate(model.Server{}, model.User{}, model.Notification{}, model.AlertRule{}) + dao.DB.AutoMigrate(model.Server{}, model.User{}, + model.Notification{}, model.AlertRule{}, model.Monitor{}, + model.MonitorHistory{}) // load cache var servers []model.Server dao.DB.Find(&servers) for _, s := range servers { innerS := s innerS.Host = &model.Host{} - innerS.State = &model.State{} + innerS.State = &model.HostState{} dao.ServerList[innerS.ID] = &innerS } dao.ReSortServer() @@ -50,5 +52,6 @@ func initDB() { func main() { go controller.ServeWeb(dao.Conf.HTTPPort) go rpc.ServeRPC(5555) + go rpc.DispatchTask(time.Minute * 10) alertmanager.Start() } diff --git a/cmd/dashboard/rpc/rpc.go b/cmd/dashboard/rpc/rpc.go index a11dc62..1c6b45b 100644 --- a/cmd/dashboard/rpc/rpc.go +++ b/cmd/dashboard/rpc/rpc.go @@ -2,15 +2,18 @@ package rpc import ( "fmt" + "log" "net" + "time" "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" ) -// ServeRPC ... func ServeRPC(port uint) { server := grpc.NewServer() pb.RegisterNezhaServiceServer(server, &rpcService.NezhaHandler{ @@ -22,3 +25,34 @@ func ServeRPC(port uint) { } server.Serve(listen) } + +func DispatchTask(duration time.Duration) { + var index uint64 = 0 + for { + var tasks []model.Monitor + var hasAliveAgent bool + dao.DB.Find(&tasks) + dao.ServerLock.RLock() + for i := 0; i < len(tasks); i++ { + if index >= uint64(len(dao.SortedServerList)) { + index = 0 + if !hasAliveAgent { + break + } + hasAliveAgent = false + } + if dao.SortedServerList[index].TaskStream == nil { + i-- + index++ + continue + } + hasAliveAgent = true + log.Println("DispatchTask 确认派发 >>>>>", i, index) + dao.SortedServerList[index].TaskStream.Send(tasks[i].PB()) + log.Println("DispatchTask 确认派发 <<<<<", i, index) + index++ + } + dao.ServerLock.RUnlock() + time.Sleep(duration) + } +} diff --git a/cmd/playground/main.go b/cmd/playground/main.go index 3fe3254..627d7ee 100644 --- a/cmd/playground/main.go +++ b/cmd/playground/main.go @@ -1,13 +1,38 @@ package main import ( + "fmt" "log" + "net" "os/exec" + "time" + "github.com/genkiroid/cert" + "github.com/go-ping/ping" "github.com/shirou/gopsutil/v3/disk" ) func main() { + conn, err := net.DialTimeout("tcp", "example.com:80", time.Second*10) + if err != nil { + panic(err) + } + println(conn) + pinger, err := ping.NewPinger("example.com") + if err != nil { + panic(err) + } + pinger.Count = 3 + err = pinger.Run() // Blocks until finished. + if err != nil { + panic(err) + } + fmt.Printf("%+v", pinger.Statistics()) + certs, err := cert.NewCerts([]string{"example.com"}) + if err != nil { + panic(err) + } + fmt.Println(certs) dparts, _ := disk.Partitions(false) for _, part := range dparts { u, _ := disk.Usage(part.Mountpoint) @@ -18,12 +43,12 @@ func main() { } func cmdExec() { - cmd := exec.Command("ping", "qiongbi.net", "-c2") + cmd := exec.Command("ping", "example.com", "-c2") output, err := cmd.Output() log.Println("output:", string(output)) log.Println("err:", err) - cmd = exec.Command("ping", "qiongbi", "-c2") + cmd = exec.Command("ping", "example", "-c2") output, err = cmd.Output() log.Println("output:", string(output)) log.Println("err:", err) diff --git a/go.mod b/go.mod index e406529..4ecd257 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,9 @@ require ( code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48 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-gonic/gin v1.6.3 + github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663 github.com/golang/protobuf v1.4.2 github.com/google/go-github v17.0.0+incompatible github.com/gorilla/websocket v1.4.2 diff --git a/go.sum b/go.sum index 47cb624..667f7d3 100644 --- a/go.sum +++ b/go.sum @@ -83,6 +83,8 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV 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/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= @@ -96,6 +98,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-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= @@ -417,6 +421,8 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R 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/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= diff --git a/model/alertrule.go b/model/alertrule.go index d8277f1..96dafd1 100644 --- a/model/alertrule.go +++ b/model/alertrule.go @@ -118,7 +118,7 @@ func (r *AlertRule) Check(points [][]interface{}) (int, string) { fail++ } } - if fail/total > 0.3 { + if fail/total > 0.5 { count++ dist.WriteString(fmt.Sprintf("%+v\n", r.Rules[i])) } diff --git a/model/common.go b/model/common.go index ada327d..e2cb6fb 100644 --- a/model/common.go +++ b/model/common.go @@ -2,13 +2,10 @@ package model import "time" -// CtxKeyAuthorizedUser .. const CtxKeyAuthorizedUser = "ckau" -// CtxKeyOauth2State .. const CtxKeyOauth2State = "cko2s" -// Common .. type Common struct { ID uint64 `gorm:"primary_key"` CreatedAt time.Time @@ -16,7 +13,6 @@ type Common struct { DeletedAt *time.Time `sql:"index"` } -// Response .. type Response struct { Code uint64 `json:"code,omitempty"` Message string `json:"message,omitempty"` diff --git a/model/config.go b/model/config.go index 616f92a..a3e12d1 100644 --- a/model/config.go +++ b/model/config.go @@ -29,7 +29,6 @@ type Config struct { v *viper.Viper } -// ReadInConfig .. func (c *Config) Read(path string) error { c.v = viper.New() c.v.SetConfigFile(path) diff --git a/model/host.go b/model/host.go new file mode 100644 index 0000000..3e1ebcb --- /dev/null +++ b/model/host.go @@ -0,0 +1,114 @@ +package model + +import ( + "fmt" + + pb "github.com/naiba/nezha/proto" +) + +const ( + _ = iota + + MTReportHostState +) + +type HostState struct { + CPU float64 + MemUsed uint64 + SwapUsed uint64 + DiskUsed uint64 + NetInTransfer uint64 + NetOutTransfer uint64 + NetInSpeed uint64 + NetOutSpeed uint64 + Uptime uint64 +} + +func (s *HostState) PB() *pb.State { + return &pb.State{ + Cpu: s.CPU, + MemUsed: s.MemUsed, + SwapUsed: s.SwapUsed, + DiskUsed: s.DiskUsed, + NetInTransfer: s.NetInTransfer, + NetOutTransfer: s.NetOutTransfer, + NetInSpeed: s.NetInSpeed, + NetOutSpeed: s.NetOutSpeed, + Uptime: s.Uptime, + } +} + +func PB2State(s *pb.State) HostState { + return HostState{ + CPU: s.GetCpu(), + MemUsed: s.GetMemUsed(), + SwapUsed: s.GetSwapUsed(), + DiskUsed: s.GetDiskUsed(), + NetInTransfer: s.GetNetInTransfer(), + NetOutTransfer: s.GetNetOutTransfer(), + NetInSpeed: s.GetNetInSpeed(), + NetOutSpeed: s.GetNetOutSpeed(), + Uptime: s.GetUptime(), + } +} + +type Host struct { + Platform string + PlatformVersion string + CPU []string + MemTotal uint64 + DiskTotal uint64 + SwapTotal uint64 + Arch string + Virtualization string + BootTime uint64 + IP string `json:"-"` + CountryCode string + Version string +} + +func (h *Host) PB() *pb.Host { + return &pb.Host{ + Platform: h.Platform, + PlatformVersion: h.PlatformVersion, + Cpu: h.CPU, + MemTotal: h.MemTotal, + DiskTotal: h.DiskTotal, + SwapTotal: h.SwapTotal, + Arch: h.Arch, + Virtualization: h.Virtualization, + BootTime: h.BootTime, + Ip: h.IP, + CountryCode: h.CountryCode, + Version: h.Version, + } +} + +func PB2Host(h *pb.Host) Host { + + cpuCount := make(map[string]int, 0) + cpus := h.GetCpu() + for _, u := range cpus { + cpuCount[u]++ + } + + var distCpu []string + for u, num := range cpuCount { + distCpu = append(distCpu, fmt.Sprintf("%sx%d", u, num)) + } + + return Host{ + Platform: h.GetPlatform(), + PlatformVersion: h.GetPlatformVersion(), + CPU: distCpu, + MemTotal: h.GetMemTotal(), + DiskTotal: h.GetDiskTotal(), + SwapTotal: h.GetSwapTotal(), + Arch: h.GetArch(), + Virtualization: h.GetVirtualization(), + BootTime: h.GetBootTime(), + IP: h.GetIp(), + CountryCode: h.GetCountryCode(), + Version: h.GetVersion(), + } +} diff --git a/model/monitor.go b/model/monitor.go index 3d01c4b..a01b9f1 100644 --- a/model/monitor.go +++ b/model/monitor.go @@ -1,120 +1,27 @@ package model import ( - "fmt" - pb "github.com/naiba/nezha/proto" ) const ( _ = iota - // MTReportState .. - MTReportState + MonitorTypeHTTPGET + MonitorTypeICMPPing + MonitorTypeTCPPing ) -// State .. -type State struct { - CPU float64 - MemUsed uint64 - SwapUsed uint64 - DiskUsed uint64 - NetInTransfer uint64 - NetOutTransfer uint64 - NetInSpeed uint64 - NetOutSpeed uint64 - Uptime uint64 +type Monitor struct { + Common + Name string + Type uint8 + Target string } -// PB .. -func (s *State) PB() *pb.State { - return &pb.State{ - Cpu: s.CPU, - MemUsed: s.MemUsed, - SwapUsed: s.SwapUsed, - DiskUsed: s.DiskUsed, - NetInTransfer: s.NetInTransfer, - NetOutTransfer: s.NetOutTransfer, - NetInSpeed: s.NetInSpeed, - NetOutSpeed: s.NetOutSpeed, - Uptime: s.Uptime, - } -} - -// PB2State .. -func PB2State(s *pb.State) State { - return State{ - CPU: s.GetCpu(), - MemUsed: s.GetMemUsed(), - SwapUsed: s.GetSwapUsed(), - DiskUsed: s.GetDiskUsed(), - NetInTransfer: s.GetNetInTransfer(), - NetOutTransfer: s.GetNetOutTransfer(), - NetInSpeed: s.GetNetInSpeed(), - NetOutSpeed: s.GetNetOutSpeed(), - Uptime: s.GetUptime(), - } -} - -// Host .. -type Host struct { - Platform string - PlatformVersion string - CPU []string - MemTotal uint64 - DiskTotal uint64 - SwapTotal uint64 - Arch string - Virtualization string - BootTime uint64 - IP string `json:"-"` - CountryCode string - Version string -} - -// PB .. -func (h *Host) PB() *pb.Host { - return &pb.Host{ - Platform: h.Platform, - PlatformVersion: h.PlatformVersion, - Cpu: h.CPU, - MemTotal: h.MemTotal, - DiskTotal: h.DiskTotal, - SwapTotal: h.SwapTotal, - Arch: h.Arch, - Virtualization: h.Virtualization, - BootTime: h.BootTime, - Ip: h.IP, - CountryCode: h.CountryCode, - Version: h.Version, - } -} - -// PB2Host ... -func PB2Host(h *pb.Host) Host { - - cpuCount := make(map[string]int, 0) - cpus := h.GetCpu() - for _, u := range cpus { - cpuCount[u]++ - } - - var distCpu []string - for u, num := range cpuCount { - distCpu = append(distCpu, fmt.Sprintf("%sx%d", u, num)) - } - - return Host{ - Platform: h.GetPlatform(), - PlatformVersion: h.GetPlatformVersion(), - CPU: distCpu, - MemTotal: h.GetMemTotal(), - DiskTotal: h.GetDiskTotal(), - SwapTotal: h.GetSwapTotal(), - Arch: h.GetArch(), - Virtualization: h.GetVirtualization(), - BootTime: h.GetBootTime(), - IP: h.GetIp(), - CountryCode: h.GetCountryCode(), - Version: h.GetVersion(), +func (m *Monitor) PB() *pb.Task { + return &pb.Task{ + Id: m.ID, + Type: uint64(m.Type), + Data: m.Target, } } diff --git a/model/monitor_history.go b/model/monitor_history.go new file mode 100644 index 0000000..6e3f431 --- /dev/null +++ b/model/monitor_history.go @@ -0,0 +1,22 @@ +package model + +import ( + pb "github.com/naiba/nezha/proto" +) + +type MonitorHistory struct { + Common + MonitorID uint64 + Delay float32 // 延迟,毫秒 + Data string + Successful bool // 是否成功 +} + +func PB2MonitorHistory(r *pb.TaskResult) MonitorHistory { + return MonitorHistory{ + Delay: r.GetDelay(), + Successful: r.GetSuccessful(), + MonitorID: r.GetId(), + Data: r.GetData(), + } +} diff --git a/model/server.go b/model/server.go index e4eeacd..d382a75 100644 --- a/model/server.go +++ b/model/server.go @@ -8,19 +8,18 @@ import ( pb "github.com/naiba/nezha/proto" ) -// Server .. type Server struct { Common Name string DisplayIndex int // 展示权重,越大越靠前 Secret string `json:"-"` Tag string - Host *Host `gorm:"-"` - State *State `gorm:"-"` + Host *Host `gorm:"-"` + State *HostState `gorm:"-"` LastActive time.Time - Stream pb.NezhaService_HeartbeatServer `gorm:"-" json:"-"` - StreamClose chan<- error `gorm:"-" json:"-"` + TaskClose chan error `gorm:"-" json:"-"` + TaskStream pb.NezhaService_RequestTaskServer `gorm:"-" json:"-"` } func (s Server) Marshal() template.JS { diff --git a/model/user.go b/model/user.go index ea9333f..1e01a1a 100644 --- a/model/user.go +++ b/model/user.go @@ -8,7 +8,6 @@ import ( "github.com/naiba/com" ) -// User ... type User struct { Common Login string `gorm:"UNIQUE_INDEX" json:"login,omitempty"` // 登录名 @@ -26,7 +25,6 @@ type User struct { TeamsID []uint64 `gorm:"-"` } -// NewUserFromGitHub .. func NewUserFromGitHub(gu *github.User) User { var u User u.ID = uint64(gu.GetID()) @@ -45,7 +43,6 @@ func NewUserFromGitHub(gu *github.User) User { return u } -// IssueNewToken ... func (u *User) IssueNewToken() { u.Token = com.MD5(fmt.Sprintf("%d%d%s", time.Now().UnixNano(), u.ID, u.Login)) u.TokenExpired = time.Now().AddDate(0, 2, 0) diff --git a/pkg/mygin/auth.go b/pkg/mygin/auth.go index 5370c4d..d376472 100644 --- a/pkg/mygin/auth.go +++ b/pkg/mygin/auth.go @@ -11,7 +11,6 @@ import ( "github.com/naiba/nezha/service/dao" ) -// AuthorizeOption .. type AuthorizeOption struct { Guest bool Member bool @@ -21,7 +20,6 @@ type AuthorizeOption struct { Btn string } -// Authorize .. func Authorize(opt AuthorizeOption) func(*gin.Context) { return func(c *gin.Context) { token, err := c.Cookie(dao.Conf.Site.CookieName) diff --git a/pkg/mygin/error.go b/pkg/mygin/error.go index 040c303..0b0e2fd 100644 --- a/pkg/mygin/error.go +++ b/pkg/mygin/error.go @@ -8,7 +8,6 @@ import ( "github.com/naiba/nezha/model" ) -// ErrInfo .. type ErrInfo struct { Code uint64 Title string @@ -17,7 +16,6 @@ type ErrInfo struct { Btn string } -// ShowErrorPage .. func ShowErrorPage(c *gin.Context, i ErrInfo, isPage bool) { if isPage { c.HTML(http.StatusOK, "dashboard/error", CommonEnvironment(c, gin.H{ diff --git a/pkg/mygin/mygin.go b/pkg/mygin/mygin.go index eaae823..c2e984d 100644 --- a/pkg/mygin/mygin.go +++ b/pkg/mygin/mygin.go @@ -10,7 +10,6 @@ import ( "github.com/naiba/nezha/service/dao" ) -// CommonEnvironment .. func CommonEnvironment(c *gin.Context, data map[string]interface{}) gin.H { data["MatchedPath"] = c.MustGet("MatchedPath") data["Version"] = dao.Version @@ -27,7 +26,6 @@ func CommonEnvironment(c *gin.Context, data map[string]interface{}) gin.H { return data } -// RecordPath .. func RecordPath(c *gin.Context) { url := c.Request.URL.String() for _, p := range c.Params { diff --git a/proto/nezha.pb.go b/proto/nezha.pb.go index afa07a1..0217cf5 100644 --- a/proto/nezha.pb.go +++ b/proto/nezha.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 -// protoc v3.13.0 +// protoc v3.14.0 // source: proto/nezha.proto package proto @@ -275,6 +275,148 @@ func (x *State) GetUptime() uint64 { return 0 } +type Task struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Type uint64 `protobuf:"varint,2,opt,name=type,proto3" json:"type,omitempty"` + Data string `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *Task) Reset() { + *x = Task{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_nezha_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Task) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Task) ProtoMessage() {} + +func (x *Task) ProtoReflect() protoreflect.Message { + mi := &file_proto_nezha_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Task.ProtoReflect.Descriptor instead. +func (*Task) Descriptor() ([]byte, []int) { + return file_proto_nezha_proto_rawDescGZIP(), []int{2} +} + +func (x *Task) GetId() uint64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *Task) GetType() uint64 { + if x != nil { + return x.Type + } + return 0 +} + +func (x *Task) GetData() string { + if x != nil { + return x.Data + } + return "" +} + +type TaskResult struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Type uint64 `protobuf:"varint,2,opt,name=type,proto3" json:"type,omitempty"` + Delay float32 `protobuf:"fixed32,3,opt,name=delay,proto3" json:"delay,omitempty"` + Data string `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"` + Successful bool `protobuf:"varint,5,opt,name=successful,proto3" json:"successful,omitempty"` +} + +func (x *TaskResult) Reset() { + *x = TaskResult{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_nezha_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TaskResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TaskResult) ProtoMessage() {} + +func (x *TaskResult) ProtoReflect() protoreflect.Message { + mi := &file_proto_nezha_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TaskResult.ProtoReflect.Descriptor instead. +func (*TaskResult) Descriptor() ([]byte, []int) { + return file_proto_nezha_proto_rawDescGZIP(), []int{3} +} + +func (x *TaskResult) GetId() uint64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *TaskResult) GetType() uint64 { + if x != nil { + return x.Type + } + return 0 +} + +func (x *TaskResult) GetDelay() float32 { + if x != nil { + return x.Delay + } + return 0 +} + +func (x *TaskResult) GetData() string { + if x != nil { + return x.Data + } + return "" +} + +func (x *TaskResult) GetSuccessful() bool { + if x != nil { + return x.Successful + } + return false +} + type Receipt struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -286,7 +428,7 @@ type Receipt struct { func (x *Receipt) Reset() { *x = Receipt{} if protoimpl.UnsafeEnabled { - mi := &file_proto_nezha_proto_msgTypes[2] + mi := &file_proto_nezha_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -299,7 +441,7 @@ func (x *Receipt) String() string { func (*Receipt) ProtoMessage() {} func (x *Receipt) ProtoReflect() protoreflect.Message { - mi := &file_proto_nezha_proto_msgTypes[2] + mi := &file_proto_nezha_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -312,7 +454,7 @@ func (x *Receipt) ProtoReflect() protoreflect.Message { // Deprecated: Use Receipt.ProtoReflect.Descriptor instead. func (*Receipt) Descriptor() ([]byte, []int) { - return file_proto_nezha_proto_rawDescGZIP(), []int{2} + return file_proto_nezha_proto_rawDescGZIP(), []int{4} } func (x *Receipt) GetProced() bool { @@ -322,108 +464,6 @@ func (x *Receipt) GetProced() bool { return false } -type Beat struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Timestamp string `protobuf:"bytes,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` -} - -func (x *Beat) Reset() { - *x = Beat{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_nezha_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Beat) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Beat) ProtoMessage() {} - -func (x *Beat) ProtoReflect() protoreflect.Message { - mi := &file_proto_nezha_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Beat.ProtoReflect.Descriptor instead. -func (*Beat) Descriptor() ([]byte, []int) { - return file_proto_nezha_proto_rawDescGZIP(), []int{3} -} - -func (x *Beat) GetTimestamp() string { - if x != nil { - return x.Timestamp - } - return "" -} - -type Command struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Type uint64 `protobuf:"varint,1,opt,name=type,proto3" json:"type,omitempty"` - Data string `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` -} - -func (x *Command) Reset() { - *x = Command{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_nezha_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Command) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Command) ProtoMessage() {} - -func (x *Command) ProtoReflect() protoreflect.Message { - mi := &file_proto_nezha_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Command.ProtoReflect.Descriptor instead. -func (*Command) Descriptor() ([]byte, []int) { - return file_proto_nezha_proto_rawDescGZIP(), []int{4} -} - -func (x *Command) GetType() uint64 { - if x != nil { - return x.Type - } - return 0 -} - -func (x *Command) GetData() string { - if x != nil { - return x.Data - } - return "" -} - var File_proto_nezha_proto protoreflect.FileDescriptor var file_proto_nezha_proto_rawDesc = []byte{ @@ -468,25 +508,36 @@ var file_proto_nezha_proto_rawDesc = []byte{ 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6e, 0x65, 0x74, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x70, 0x65, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6e, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x53, 0x70, 0x65, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, - 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x21, - 0x0a, 0x07, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x6f, - 0x63, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x70, 0x72, 0x6f, 0x63, 0x65, - 0x64, 0x22, 0x24, 0x0a, 0x04, 0x42, 0x65, 0x61, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x31, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x32, 0x96, 0x01, 0x0a, 0x0c, 0x4e, - 0x65, 0x7a, 0x68, 0x61, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2c, 0x0a, 0x09, 0x48, - 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x42, 0x65, 0x61, 0x74, 0x1a, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x22, 0x00, 0x30, 0x01, 0x12, 0x2d, 0x0a, 0x0b, 0x52, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x1a, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, - 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x22, 0x00, 0x12, 0x29, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, - 0x73, 0x74, 0x65, 0x72, 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x6f, 0x73, - 0x74, 0x1a, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, - 0x74, 0x22, 0x00, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x3e, + 0x0a, 0x04, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, + 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x7a, + 0x0a, 0x0a, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, + 0x05, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x75, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, + 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x22, 0x21, 0x0a, 0x07, 0x52, 0x65, + 0x63, 0x65, 0x69, 0x70, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x64, 0x32, 0xd6, 0x01, + 0x0a, 0x0c, 0x4e, 0x65, 0x7a, 0x68, 0x61, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, + 0x0a, 0x11, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x12, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x1a, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, + 0x74, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x10, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x79, 0x73, + 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x48, 0x6f, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, + 0x65, 0x69, 0x70, 0x74, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x0a, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x54, 0x61, 0x73, 0x6b, 0x12, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x61, 0x73, + 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x1a, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x22, 0x00, 0x12, 0x2b, 0x0a, 0x0b, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x61, + 0x73, 0x6b, 0x22, 0x00, 0x30, 0x01, 0x42, 0x07, 0x5a, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -503,21 +554,23 @@ func file_proto_nezha_proto_rawDescGZIP() []byte { var file_proto_nezha_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_proto_nezha_proto_goTypes = []interface{}{ - (*Host)(nil), // 0: proto.Host - (*State)(nil), // 1: proto.State - (*Receipt)(nil), // 2: proto.Receipt - (*Beat)(nil), // 3: proto.Beat - (*Command)(nil), // 4: proto.Command + (*Host)(nil), // 0: proto.Host + (*State)(nil), // 1: proto.State + (*Task)(nil), // 2: proto.Task + (*TaskResult)(nil), // 3: proto.TaskResult + (*Receipt)(nil), // 4: proto.Receipt } var file_proto_nezha_proto_depIdxs = []int32{ - 3, // 0: proto.NezhaService.Heartbeat:input_type -> proto.Beat - 1, // 1: proto.NezhaService.ReportState:input_type -> proto.State - 0, // 2: proto.NezhaService.Register:input_type -> proto.Host - 4, // 3: proto.NezhaService.Heartbeat:output_type -> proto.Command - 2, // 4: proto.NezhaService.ReportState:output_type -> proto.Receipt - 2, // 5: proto.NezhaService.Register:output_type -> proto.Receipt - 3, // [3:6] is the sub-list for method output_type - 0, // [0:3] is the sub-list for method input_type + 1, // 0: proto.NezhaService.ReportSystemState:input_type -> proto.State + 0, // 1: proto.NezhaService.ReportSystemInfo:input_type -> proto.Host + 3, // 2: proto.NezhaService.ReportTask:input_type -> proto.TaskResult + 0, // 3: proto.NezhaService.RequestTask:input_type -> proto.Host + 4, // 4: proto.NezhaService.ReportSystemState:output_type -> proto.Receipt + 4, // 5: proto.NezhaService.ReportSystemInfo:output_type -> proto.Receipt + 4, // 6: proto.NezhaService.ReportTask:output_type -> proto.Receipt + 2, // 7: proto.NezhaService.RequestTask:output_type -> proto.Task + 4, // [4:8] is the sub-list for method output_type + 0, // [0:4] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name @@ -554,7 +607,7 @@ func file_proto_nezha_proto_init() { } } file_proto_nezha_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Receipt); i { + switch v := v.(*Task); i { case 0: return &v.state case 1: @@ -566,7 +619,7 @@ func file_proto_nezha_proto_init() { } } file_proto_nezha_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Beat); i { + switch v := v.(*TaskResult); i { case 0: return &v.state case 1: @@ -578,7 +631,7 @@ func file_proto_nezha_proto_init() { } } file_proto_nezha_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Command); i { + switch v := v.(*Receipt); i { case 0: return &v.state case 1: @@ -622,9 +675,10 @@ const _ = grpc.SupportPackageIsVersion6 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type NezhaServiceClient interface { - Heartbeat(ctx context.Context, in *Beat, opts ...grpc.CallOption) (NezhaService_HeartbeatClient, error) - ReportState(ctx context.Context, in *State, opts ...grpc.CallOption) (*Receipt, error) - Register(ctx context.Context, in *Host, opts ...grpc.CallOption) (*Receipt, error) + ReportSystemState(ctx context.Context, in *State, opts ...grpc.CallOption) (*Receipt, error) + ReportSystemInfo(ctx context.Context, in *Host, opts ...grpc.CallOption) (*Receipt, error) + ReportTask(ctx context.Context, in *TaskResult, opts ...grpc.CallOption) (*Receipt, error) + RequestTask(ctx context.Context, in *Host, opts ...grpc.CallOption) (NezhaService_RequestTaskClient, error) } type nezhaServiceClient struct { @@ -635,12 +689,39 @@ func NewNezhaServiceClient(cc grpc.ClientConnInterface) NezhaServiceClient { return &nezhaServiceClient{cc} } -func (c *nezhaServiceClient) Heartbeat(ctx context.Context, in *Beat, opts ...grpc.CallOption) (NezhaService_HeartbeatClient, error) { - stream, err := c.cc.NewStream(ctx, &_NezhaService_serviceDesc.Streams[0], "/proto.NezhaService/Heartbeat", opts...) +func (c *nezhaServiceClient) ReportSystemState(ctx context.Context, in *State, opts ...grpc.CallOption) (*Receipt, error) { + out := new(Receipt) + err := c.cc.Invoke(ctx, "/proto.NezhaService/ReportSystemState", in, out, opts...) if err != nil { return nil, err } - x := &nezhaServiceHeartbeatClient{stream} + return out, nil +} + +func (c *nezhaServiceClient) ReportSystemInfo(ctx context.Context, in *Host, opts ...grpc.CallOption) (*Receipt, error) { + out := new(Receipt) + err := c.cc.Invoke(ctx, "/proto.NezhaService/ReportSystemInfo", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nezhaServiceClient) ReportTask(ctx context.Context, in *TaskResult, opts ...grpc.CallOption) (*Receipt, error) { + out := new(Receipt) + err := c.cc.Invoke(ctx, "/proto.NezhaService/ReportTask", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nezhaServiceClient) RequestTask(ctx context.Context, in *Host, opts ...grpc.CallOption) (NezhaService_RequestTaskClient, error) { + stream, err := c.cc.NewStream(ctx, &_NezhaService_serviceDesc.Streams[0], "/proto.NezhaService/RequestTask", opts...) + if err != nil { + return nil, err + } + x := &nezhaServiceRequestTaskClient{stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } @@ -650,140 +731,148 @@ func (c *nezhaServiceClient) Heartbeat(ctx context.Context, in *Beat, opts ...gr return x, nil } -type NezhaService_HeartbeatClient interface { - Recv() (*Command, error) +type NezhaService_RequestTaskClient interface { + Recv() (*Task, error) grpc.ClientStream } -type nezhaServiceHeartbeatClient struct { +type nezhaServiceRequestTaskClient struct { grpc.ClientStream } -func (x *nezhaServiceHeartbeatClient) Recv() (*Command, error) { - m := new(Command) +func (x *nezhaServiceRequestTaskClient) Recv() (*Task, error) { + m := new(Task) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } -func (c *nezhaServiceClient) ReportState(ctx context.Context, in *State, opts ...grpc.CallOption) (*Receipt, error) { - out := new(Receipt) - err := c.cc.Invoke(ctx, "/proto.NezhaService/ReportState", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *nezhaServiceClient) Register(ctx context.Context, in *Host, opts ...grpc.CallOption) (*Receipt, error) { - out := new(Receipt) - err := c.cc.Invoke(ctx, "/proto.NezhaService/Register", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - // NezhaServiceServer is the server API for NezhaService service. type NezhaServiceServer interface { - Heartbeat(*Beat, NezhaService_HeartbeatServer) error - ReportState(context.Context, *State) (*Receipt, error) - Register(context.Context, *Host) (*Receipt, error) + ReportSystemState(context.Context, *State) (*Receipt, error) + ReportSystemInfo(context.Context, *Host) (*Receipt, error) + ReportTask(context.Context, *TaskResult) (*Receipt, error) + RequestTask(*Host, NezhaService_RequestTaskServer) error } // UnimplementedNezhaServiceServer can be embedded to have forward compatible implementations. type UnimplementedNezhaServiceServer struct { } -func (*UnimplementedNezhaServiceServer) Heartbeat(*Beat, NezhaService_HeartbeatServer) error { - return status.Errorf(codes.Unimplemented, "method Heartbeat not implemented") +func (*UnimplementedNezhaServiceServer) ReportSystemState(context.Context, *State) (*Receipt, error) { + return nil, status.Errorf(codes.Unimplemented, "method ReportSystemState not implemented") } -func (*UnimplementedNezhaServiceServer) ReportState(context.Context, *State) (*Receipt, error) { - return nil, status.Errorf(codes.Unimplemented, "method ReportState not implemented") +func (*UnimplementedNezhaServiceServer) ReportSystemInfo(context.Context, *Host) (*Receipt, error) { + return nil, status.Errorf(codes.Unimplemented, "method ReportSystemInfo not implemented") } -func (*UnimplementedNezhaServiceServer) Register(context.Context, *Host) (*Receipt, error) { - return nil, status.Errorf(codes.Unimplemented, "method Register not implemented") +func (*UnimplementedNezhaServiceServer) ReportTask(context.Context, *TaskResult) (*Receipt, error) { + return nil, status.Errorf(codes.Unimplemented, "method ReportTask not implemented") +} +func (*UnimplementedNezhaServiceServer) RequestTask(*Host, NezhaService_RequestTaskServer) error { + return status.Errorf(codes.Unimplemented, "method RequestTask not implemented") } func RegisterNezhaServiceServer(s *grpc.Server, srv NezhaServiceServer) { s.RegisterService(&_NezhaService_serviceDesc, srv) } -func _NezhaService_Heartbeat_Handler(srv interface{}, stream grpc.ServerStream) error { - m := new(Beat) - if err := stream.RecvMsg(m); err != nil { - return err - } - return srv.(NezhaServiceServer).Heartbeat(m, &nezhaServiceHeartbeatServer{stream}) -} - -type NezhaService_HeartbeatServer interface { - Send(*Command) error - grpc.ServerStream -} - -type nezhaServiceHeartbeatServer struct { - grpc.ServerStream -} - -func (x *nezhaServiceHeartbeatServer) Send(m *Command) error { - return x.ServerStream.SendMsg(m) -} - -func _NezhaService_ReportState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { +func _NezhaService_ReportSystemState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(State) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(NezhaServiceServer).ReportState(ctx, in) + return srv.(NezhaServiceServer).ReportSystemState(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/proto.NezhaService/ReportState", + FullMethod: "/proto.NezhaService/ReportSystemState", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(NezhaServiceServer).ReportState(ctx, req.(*State)) + return srv.(NezhaServiceServer).ReportSystemState(ctx, req.(*State)) } return interceptor(ctx, in, info, handler) } -func _NezhaService_Register_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { +func _NezhaService_ReportSystemInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Host) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(NezhaServiceServer).Register(ctx, in) + return srv.(NezhaServiceServer).ReportSystemInfo(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/proto.NezhaService/Register", + FullMethod: "/proto.NezhaService/ReportSystemInfo", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(NezhaServiceServer).Register(ctx, req.(*Host)) + return srv.(NezhaServiceServer).ReportSystemInfo(ctx, req.(*Host)) } return interceptor(ctx, in, info, handler) } +func _NezhaService_ReportTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TaskResult) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NezhaServiceServer).ReportTask(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.NezhaService/ReportTask", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NezhaServiceServer).ReportTask(ctx, req.(*TaskResult)) + } + return interceptor(ctx, in, info, handler) +} + +func _NezhaService_RequestTask_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(Host) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(NezhaServiceServer).RequestTask(m, &nezhaServiceRequestTaskServer{stream}) +} + +type NezhaService_RequestTaskServer interface { + Send(*Task) error + grpc.ServerStream +} + +type nezhaServiceRequestTaskServer struct { + grpc.ServerStream +} + +func (x *nezhaServiceRequestTaskServer) Send(m *Task) error { + return x.ServerStream.SendMsg(m) +} + var _NezhaService_serviceDesc = grpc.ServiceDesc{ ServiceName: "proto.NezhaService", HandlerType: (*NezhaServiceServer)(nil), Methods: []grpc.MethodDesc{ { - MethodName: "ReportState", - Handler: _NezhaService_ReportState_Handler, + MethodName: "ReportSystemState", + Handler: _NezhaService_ReportSystemState_Handler, }, { - MethodName: "Register", - Handler: _NezhaService_Register_Handler, + MethodName: "ReportSystemInfo", + Handler: _NezhaService_ReportSystemInfo_Handler, + }, + { + MethodName: "ReportTask", + Handler: _NezhaService_ReportTask_Handler, }, }, Streams: []grpc.StreamDesc{ { - StreamName: "Heartbeat", - Handler: _NezhaService_Heartbeat_Handler, + StreamName: "RequestTask", + Handler: _NezhaService_RequestTask_Handler, ServerStreams: true, }, }, diff --git a/proto/nezha.proto b/proto/nezha.proto index feb37d4..372825a 100644 --- a/proto/nezha.proto +++ b/proto/nezha.proto @@ -1,11 +1,13 @@ syntax = "proto3"; +option go_package = "proto"; package proto; service NezhaService { - rpc Heartbeat(Beat)returns(stream Command){} - rpc ReportState(State)returns(Receipt){} - rpc Register(Host)returns(Receipt){} + rpc ReportSystemState(State)returns(Receipt){} + rpc ReportSystemInfo(Host)returns(Receipt){} + rpc ReportTask(TaskResult)returns(Receipt){} + rpc RequestTask(Host)returns(stream Task){} } message Host { @@ -35,15 +37,20 @@ message State { uint64 uptime = 10; } +message Task { + uint64 id = 1; + uint64 type = 2; + string data = 3; +} + +message TaskResult { + uint64 id = 1; + uint64 type = 2; + float delay = 3; + string data = 4; + bool successful = 5; +} + message Receipt{ bool proced = 1; } - -message Beat { - string timestamp = 1; -} - -message Command { - uint64 type = 1; - string data = 2; -} diff --git a/resource/static/main.css b/resource/static/main.css index 9f09963..53f3b03 100644 --- a/resource/static/main.css +++ b/resource/static/main.css @@ -76,4 +76,57 @@ line-height: 1.75em; color: rgba(255, 255, 255, .7); font-weight: 700; +} + +.service-status .round>i { + width: 1rem; + height: 1rem; + border-radius: .5rem; + display: inline-block; + margin-right: .3rem; + background-color: slategray; +} + +.service-status .danger.button { + background-color: crimson; +} + +.service-status .good.button { + background-color: rgb(0, 235, 139); +} + +.service-status .warning.button { + background-color: orange; +} + +.service-status .danger>i { + background-color: crimson; +} + +.service-status .good>i { + background-color: rgb(0, 235, 139); +} + +.service-status .warning>i { + background-color: orange; +} + +.service-status .three.column p { + display: inline-block; +} + +.service-status .three.column p:last-child { + float: right; + font-size: smaller; +} + +.service-status .eleven.column { + text-align: center; +} + +.service-status .eleven.column>.ui.button { + width: 8px !important; + padding: unset !important; + margin-top: unset !important; + margin-bottom: unset !important; } \ No newline at end of file diff --git a/resource/static/main.js b/resource/static/main.js index 2e14494..00f35a9 100644 --- a/resource/static/main.js +++ b/resource/static/main.js @@ -39,7 +39,7 @@ function showFormModal(modelSelector, formID, URL, getData) { obj[item.name] = (item.name.endsWith('_id') || item.name === 'id' || item.name === 'ID' || item.name === 'RequestType' || item.name === 'RequestMethod' || - item.name === 'DisplayIndex') ? parseInt(item.value) : item.value; + item.name === 'DisplayIndex' || item.name === 'Type') ? parseInt(item.value) : item.value; return obj; }, {}); $.post(URL, JSON.stringify(data)).done(function (resp) { @@ -118,6 +118,17 @@ function addOrEditServer(server) { 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') +} + function deleteRequest(api) { $.ajax({ url: api, diff --git a/resource/template/common/footer.html b/resource/template/common/footer.html index 6726aee..01291fb 100644 --- a/resource/template/common/footer.html +++ b/resource/template/common/footer.html @@ -1,8 +1,7 @@ {{define "common/footer"}} diff --git a/resource/template/common/header.html b/resource/template/common/header.html index bc65924..6bf4743 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 723b8d8..3cc678b 100644 --- a/resource/template/common/menu.html +++ b/resource/template/common/menu.html @@ -5,13 +5,14 @@ 首页 + 服务状态 {{if .Admin}} 服务器 + 监控 通知 设置 {{end}}