💥 v2.0 必须更新面板,新增服务监控

This commit is contained in:
naiba 2021-01-16 00:45:49 +08:00
parent 0ce8017875
commit a41c792577
38 changed files with 1015 additions and 453 deletions

View File

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

View File

@ -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 变更通知功能
在后台设置界面启用。

View File

@ -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())
}
}
}

View File

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

View File

@ -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/**/*")

View File

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

View File

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

View File

@ -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()
}

View File

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

View File

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

2
go.mod
View File

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

6
go.sum
View File

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

View File

@ -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]))
}

View File

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

View File

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

114
model/host.go Normal file
View File

@ -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(),
}
}

View File

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

22
model/monitor_history.go Normal file
View File

@ -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(),
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 ? '修改<i class="edit icon"></i>' : '添加<i class="add icon"></i>')
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,

View File

@ -1,8 +1,7 @@
{{define "common/footer"}}
<div class="ui inverted vertical footer segment">
<div class="ui center aligned is-size-7 container">
Powered by <a href="https://github.com/naiba/nezha" style="color: white;" target="_blank">哪吒面板</a> build ·
{{.Version}}
Powered by <a href="https://github.com/naiba/nezha" style="color: white;" target="_blank">哪吒面板</a> {{.Version}}
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

View File

@ -9,7 +9,7 @@
<title>{{.Title}}</title>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.1/dist/semantic.min.css">
<link rel="stylesheet" type="text/css" href="/static/semantic-ui-alerts.min.css">
<link rel="stylesheet" type="text/css" href="/static/main.css?v202012252026">
<link rel="stylesheet" type="text/css" href="/static/main.css?v202101160044">
<link rel="shortcut icon" type="image/png" href="/static/logo.png" />
</head>

View File

@ -5,13 +5,14 @@
<img src="/static/logo.png">
</div>
<a class="item{{if eq .MatchedPath " /"}} active{{end}}" href="/">首页</a>
<a class="item{{if eq .MatchedPath " /service"}} active{{end}}" href="/service">服务状态</a>
{{if .Admin}}
<a class="item{{if eq .MatchedPath " /server"}} active{{end}}" href="/server">服务器</a>
<a class="item{{if eq .MatchedPath " /monitor"}} active{{end}}" href="/monitor">监控</a>
<a class="item{{if eq .MatchedPath " /notification"}} active{{end}}" href="/notification">通知</a>
<a class="item{{if eq .MatchedPath " /setting"}} active{{end}}" href="/setting">设置</a>
{{end}}
<div class="right menu">
<a class="item" href="https://github.com/naiba/nezha/issues" target="_blank">反馈</a>
<div class="item">
{{if .Admin}}
<div class="ui simple dropdown">

View File

@ -0,0 +1,31 @@
{{define "component/monitor"}}
<div class="ui tiny monitor modal transition hidden">
<div class="header">添加监控</div>
<div class="content">
<form id="monitorForm" class="ui form">
<input type="hidden" name="ID">
<div class="field">
<label>备注</label>
<input type="text" name="Name" placeholder="博客">
</div>
<div class="field">
<label>目标</label>
<input type="text" name="Target" placeholder="https://t.tt,t.tt,t.tt:80">
</div>
<div class="field">
<label>类型</label>
<select name="Type" class="ui fluid dropdown">
<option value="1">HTTP-GET</option>
<option value="2">ICMP-Ping</option>
<option value="3">TCP-Ping</option>
</select>
</div>
</form>
</div>
<div class=" actions">
<div class="ui negative button">取消</div>
<button class="ui positive right labeled icon button">确认<i class="checkmark icon"></i>
</button>
</div>
</div>
{{end}}

View File

@ -0,0 +1,49 @@
{{define "dashboard/monitor"}}
{{template "common/header" .}}
{{template "common/menu" .}}
<div class="nb-container">
<div class="ui container">
<div class="ui grid">
<div class="right floated right aligned twelve wide column">
<button class="ui right labeled positive icon button" onclick="addOrEditMonitor()"><i
class="add icon"></i> 添加监控
</button>
</div>
</div>
<table class="ui very basic table">
<thead>
<tr>
<th>ID</th>
<th>备注</th>
<th>类型</th>
<th>目标</th>
<th>管理</th>
</tr>
</thead>
<tbody>
{{range $monitor := .Monitors}}
<tr>
<td>{{$monitor.ID}}</td>
<td>{{$monitor.Name}}</td>
<td>{{$monitor.Target}}</td>
<td>{{$monitor.Type}}</td>
<td>
<div class="ui mini icon buttons">
<button class="ui button" onclick="addOrEditMonitor({{$monitor}})">
<i class="edit icon"></i>
</button>
<button class="ui button"
onclick="showConfirm('删除监控','确认删除此监控?',deleteRequest,'/api/monitor/'+{{$monitor.ID}})">
<i class="delete icon"></i>
</button>
</div>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
{{template "component/monitor"}}
{{template "common/footer" .}}
{{end}}

View File

@ -14,8 +14,8 @@
</div>
<div class="field">
<select name="Theme">
<option value="default">默认主题</option>
<option value="hotaru">CakeMine-Hotaru</option>
<option value="default"{{if eq .Conf.Site.Theme "default"}} selected="selected"{{end}}>默认主题</option>
<option value="hotaru"{{if eq .Conf.Site.Theme "hotaru"}} selected="selected"{{end}}>CokeMine Hotaru</option>
</select>
</div>
<div class="field">

View File

@ -0,0 +1,42 @@
{{define "theme-default/service"}}
{{template "common/header" .}}
{{template "common/menu" .}}
<div class="nb-container">
<div class="ui container">
<div class="ui segment service-status">
{{range $service := .Services}}
<div class="ui grid">
<div class="three wide column">
<p>{{$service.Monitor.Name}}</p>
<p>30天在线率{{divU64 $service.TotalDown (addU64 $service.TotalUp $service.TotalDown)}}%</p>
</div>
<div class="eleven wide column">
{{range $i,$d := $service.Delay}}
<div class="ui icon button{{if gt (add (index $service.Up $i) (index $service.Down $i)) 0}}
{{if gt (div (index $service.Down $i) (add (index $service.Up $i) (index $service.Down $i))) 30.0}}danger
{{else if gt (div (index $service.Down $i) (add (index $service.Up $i) (index $service.Down $i))) 10.0}}
warning{{else}}good{{end}}
{{end}}" data-tooltip="{{dayBefore $i}},平均延迟:{{$d}}ms">
<i class="delay"></i>
</div>
{{end}}
</div>
<div class="two wide column round{{if gt (addU64 $service.TotalUp $service.TotalDown) 0}}
{{if gt (divU64 $service.TotalDown (addU64 $service.TotalUp $service.TotalDown)) 30.0}}danger{{else if gt (divU64 $service.TotalDown (addU64 $service.TotalUp $service.TotalDown)) 10.0}}warning{{else}}good{{end}}
{{end}}">
<i></i>
{{if gt (addU64 $service.TotalUp $service.TotalDown) 0}}
{{if gt (divU64 $service.TotalDown (addU64 $service.TotalUp $service.TotalDown)) 30.0}}故障
{{else if gt (divU64 $service.TotalDown (addU64 $service.TotalUp $service.TotalDown)) 10.0}}
低可用{{else}}良好{{end}}
{{else}}无数据
{{end}}
</div>
</div>
<div class="ui divider"></div>
{{end}}
</div>
</div>
</div>
{{template "common/footer" .}}
{{end}}

View File

@ -8,7 +8,6 @@ import (
"gorm.io/gorm"
"github.com/naiba/nezha/model"
pb "github.com/naiba/nezha/proto"
)
const (
@ -16,30 +15,18 @@ const (
ReportDelay = 2
)
// Conf ..
var Conf *model.Config
// Cache ..
var Cache *cache.Cache
// DB ..
var DB *gorm.DB
// ServerList ..
var ServerList map[uint64]*model.Server
var SortedServerList []*model.Server
// ServerLock ..
var ServerLock sync.RWMutex
// Version ..
var Version = "debug"
func init() {
if len(Version) > 7 {
Version = Version[:7]
}
}
var Version = "v0.2.0"
func ReSortServer() {
SortedServerList = []*model.Server{}
@ -51,19 +38,3 @@ func ReSortServer() {
return SortedServerList[i].DisplayIndex > SortedServerList[j].DisplayIndex
})
}
// SendCommand ..
func SendCommand(cmd *pb.Command) {
ServerLock.RLock()
defer ServerLock.RUnlock()
var err error
for _, server := range ServerList {
if server.Stream != nil {
err = server.Stream.Send(cmd)
if err != nil {
close(server.StreamClose)
server.Stream = nil
}
}
}
}

View File

@ -26,7 +26,6 @@ type ipDotSbGeoIP struct {
var netInSpeed, netOutSpeed, netInTransfer, netOutTransfer, lastUpdate uint64
// GetHost ..
func GetHost() *model.Host {
hi, _ := host.Info()
var cpus []string
@ -60,8 +59,7 @@ func GetHost() *model.Host {
}
}
// GetState ..
func GetState(delay int64) *model.State {
func GetState(delay int64) *model.HostState {
hi, _ := host.Info()
// Memory
mv, _ := mem.VirtualMemory()
@ -75,7 +73,7 @@ func GetState(delay int64) *model.State {
// Disk
u, _ := disk.Usage("/")
return &model.State{
return &model.HostState{
CPU: cpuPercent,
MemUsed: mv.Used,
SwapUsed: ms.Used,
@ -88,7 +86,6 @@ func GetState(delay int64) *model.State {
}
}
// TrackNetworkSpeed ..
func TrackNetworkSpeed() {
var innerNetInTransfer, innerNetOutTransfer uint64
nc, err := net.IOCounters(false)

View File

@ -10,23 +10,19 @@ import (
"google.golang.org/grpc/status"
)
// AuthHandler ..
type AuthHandler struct {
ClientID string
ClientSecret string
}
// GetRequestMetadata ..
func (a *AuthHandler) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{"client_id": a.ClientID, "client_secret": a.ClientSecret}, nil
}
// RequireTransportSecurity ..
func (a *AuthHandler) RequireTransportSecurity() bool {
return !dao.Conf.Debug
}
// Check ..
func (a *AuthHandler) Check(ctx context.Context) (clientID uint64, err error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {

View File

@ -3,7 +3,6 @@ package rpc
import (
"context"
"fmt"
"log"
"time"
"github.com/naiba/nezha/model"
@ -12,13 +11,60 @@ import (
"github.com/naiba/nezha/service/dao"
)
// NezhaHandler ..
type NezhaHandler struct {
Auth *AuthHandler
}
// ReportState ..
func (s *NezhaHandler) ReportState(c context.Context, r *pb.State) (*pb.Receipt, error) {
func (s *NezhaHandler) ReportTask(c context.Context, r *pb.TaskResult) (*pb.Receipt, error) {
var err error
if _, err = s.Auth.Check(c); err != nil {
return nil, err
}
if r.GetType() == model.MonitorTypeHTTPGET {
// SSL 证书变更报警
var last model.MonitorHistory
if err := dao.DB.Where("monitor_id = ?", r.GetId()).Order("id DESC").First(&last).Error; err == nil {
if last.Data != "" && last.Data != r.GetData() {
var monitor model.Monitor
dao.DB.First(&monitor, "id = ?", last.MonitorID)
alertmanager.SendNotification(fmt.Sprintf(
"监控:%s SSL证书变更%s%s。",
monitor.Name, last.Data, r.GetData()))
}
}
}
// 存入历史记录
mh := model.PB2MonitorHistory(r)
if err := dao.DB.Create(&mh).Error; err != nil {
return nil, err
}
// 更新最后检测时间
var m model.Monitor
m.ID = r.GetId()
if err := dao.DB.Model(&m).Update("last_check", time.Now()).Error; err != nil {
return nil, err
}
return &pb.Receipt{Proced: true}, nil
}
func (s *NezhaHandler) RequestTask(h *pb.Host, stream pb.NezhaService_RequestTaskServer) error {
var clientID uint64
var err error
if clientID, err = s.Auth.Check(stream.Context()); err != nil {
return err
}
closeCh := make(chan error)
dao.ServerLock.Lock()
dao.ServerList[clientID].TaskStream = stream
dao.ServerList[clientID].TaskClose = closeCh
dao.ServerLock.Unlock()
select {
case err = <-closeCh:
return err
}
}
func (s *NezhaHandler) ReportSystemState(c context.Context, r *pb.State) (*pb.Receipt, error) {
var clientID uint64
var err error
if clientID, err = s.Auth.Check(c); err != nil {
@ -32,28 +78,7 @@ func (s *NezhaHandler) ReportState(c context.Context, r *pb.State) (*pb.Receipt,
return &pb.Receipt{Proced: true}, nil
}
// Heartbeat ..
func (s *NezhaHandler) Heartbeat(r *pb.Beat, stream pb.NezhaService_HeartbeatServer) error {
var clientID uint64
var err error
defer log.Printf("Heartbeat exit server:%v err:%v", clientID, err)
if clientID, err = s.Auth.Check(stream.Context()); err != nil {
return err
}
// 放入在线服务器列表
dao.ServerLock.RLock()
closeCh := make(chan error)
dao.ServerList[clientID].StreamClose = closeCh
dao.ServerList[clientID].Stream = stream
dao.ServerLock.RUnlock()
select {
case err = <-closeCh:
return err
}
}
// Register ..
func (s *NezhaHandler) Register(c context.Context, r *pb.Host) (*pb.Receipt, error) {
func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Receipt, error) {
var clientID uint64
var err error
if clientID, err = s.Auth.Check(c); err != nil {