Merge branch 'master' of https://github.com/naiba/nezha into naiba-master
This commit is contained in:
		
						commit
						3f6931e2a4
					
				
							
								
								
									
										12
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								README.md
									
									
									
									
									
								
							@ -4,7 +4,7 @@
 | 
			
		||||
  <br>
 | 
			
		||||
  <small><i>LOGO designed by <a href="https://xio.ng" target="_blank">熊大</a> .</i></small>
 | 
			
		||||
  <br><br>
 | 
			
		||||
<img src="https://img.shields.io/github/workflow/status/naiba/nezha/Dashboard%20image?label=Dash%20v0.12.3&logo=github&style=for-the-badge"> <img src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&label=Agent&style=for-the-badge&logo=github"> <img src="https://img.shields.io/github/workflow/status/naiba/nezha/Agent%20release?label=Agent%20CI&logo=github&style=for-the-badge"> <img src="https://img.shields.io/badge/Installer-v0.8.1-brightgreen?style=for-the-badge&logo=linux">
 | 
			
		||||
<img src="https://img.shields.io/github/workflow/status/naiba/nezha/Dashboard%20image?label=Dash%20v0.12.6&logo=github&style=for-the-badge"> <img src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&label=Agent&style=for-the-badge&logo=github"> <img src="https://img.shields.io/github/workflow/status/naiba/nezha/Agent%20release?label=Agent%20CI&logo=github&style=for-the-badge"> <img src="https://img.shields.io/badge/Installer-v0.8.1-brightgreen?style=for-the-badge&logo=linux">
 | 
			
		||||
  <br>
 | 
			
		||||
  <br>
 | 
			
		||||
  <p>:trollface: <b>哪吒监控</b> 一站式轻监控轻运维系统。支持系统状态、HTTP(SSL 证书变更、即将到期、到期)、TCP、Ping 监控报警,计划任务和在线终端。</p>
 | 
			
		||||
@ -14,11 +14,11 @@
 | 
			
		||||
 | 
			
		||||
\>> [我们的用户](https://www.google.com/search?q="powered+by+哪吒监控"&filter=0) (Google)
 | 
			
		||||
 | 
			
		||||
| 默认主题                                                                                 | DayNight [@JackieSung](https://github.com/JackieSung4ev)     | hotaru                                                                     |
 | 
			
		||||
| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------- |
 | 
			
		||||
|                               |  | <img src="resource/template/theme-hotaru/screenshot.png" width="1600px" /> |
 | 
			
		||||
| <div align="center"><b>默认主题魔改 <a href="https://ii.do/43.html">[教程]</a></b></div> |                                                              |                                                                            |
 | 
			
		||||
|        |                                                              |                                                                            |
 | 
			
		||||
| 默认主题                                                                                 | DayNight [@JackieSung](https://github.com/JackieSung4ev)                                               | hotaru                                                                     |
 | 
			
		||||
| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- |
 | 
			
		||||
|                               | <img src="resource/template/theme-daynight/screenshot.png" width="3000px"/>                            | <img src="resource/template/theme-hotaru/screenshot.png" width="1500px" /> |
 | 
			
		||||
| <div align="center"><b>默认主题魔改 <a href="https://ii.do/43.html">[教程]</a></b></div> | <div align="center"><b>Neko Mdui <a href="https://github.com/MikoyChinese">@MikoyChinese</a></b></div> |                                                                            |
 | 
			
		||||
|        |                                               |                                                                            |
 | 
			
		||||
 | 
			
		||||
## 安装脚本
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ import (
 | 
			
		||||
	"github.com/naiba/nezha/model"
 | 
			
		||||
	"github.com/naiba/nezha/pkg/mygin"
 | 
			
		||||
	"github.com/naiba/nezha/proto"
 | 
			
		||||
	"github.com/naiba/nezha/service/dao"
 | 
			
		||||
	"github.com/naiba/nezha/service/singleton"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type terminalContext struct {
 | 
			
		||||
@ -54,7 +54,7 @@ func (p *commonPage) issueViewPassword(c *gin.Context) {
 | 
			
		||||
	var vpf viewPasswordForm
 | 
			
		||||
	err := c.ShouldBind(&vpf)
 | 
			
		||||
	var hash []byte
 | 
			
		||||
	if err == nil && vpf.Password != dao.Conf.Site.ViewPassword {
 | 
			
		||||
	if err == nil && vpf.Password != singleton.Conf.Site.ViewPassword {
 | 
			
		||||
		err = errors.New("查看密码错误")
 | 
			
		||||
	}
 | 
			
		||||
	if err == nil {
 | 
			
		||||
@ -69,12 +69,12 @@ func (p *commonPage) issueViewPassword(c *gin.Context) {
 | 
			
		||||
		c.Abort()
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	c.SetCookie(dao.Conf.Site.CookieName+"-vp", string(hash), 60*60*24, "", "", false, false)
 | 
			
		||||
	c.SetCookie(singleton.Conf.Site.CookieName+"-vp", string(hash), 60*60*24, "", "", false, false)
 | 
			
		||||
	c.Redirect(http.StatusFound, c.Request.Referer())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *commonPage) checkViewPassword(c *gin.Context) {
 | 
			
		||||
	if dao.Conf.Site.ViewPassword == "" {
 | 
			
		||||
	if singleton.Conf.Site.ViewPassword == "" {
 | 
			
		||||
		c.Next()
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@ -84,11 +84,11 @@ func (p *commonPage) checkViewPassword(c *gin.Context) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 验证查看密码
 | 
			
		||||
	viewPassword, _ := c.Cookie(dao.Conf.Site.CookieName + "-vp")
 | 
			
		||||
	if err := bcrypt.CompareHashAndPassword([]byte(viewPassword), []byte(dao.Conf.Site.ViewPassword)); err != nil {
 | 
			
		||||
		c.HTML(http.StatusOK, "theme-"+dao.Conf.Site.Theme+"/viewpassword", mygin.CommonEnvironment(c, gin.H{
 | 
			
		||||
	viewPassword, _ := c.Cookie(singleton.Conf.Site.CookieName + "-vp")
 | 
			
		||||
	if err := bcrypt.CompareHashAndPassword([]byte(viewPassword), []byte(singleton.Conf.Site.ViewPassword)); err != nil {
 | 
			
		||||
		c.HTML(http.StatusOK, "theme-"+singleton.Conf.Site.Theme+"/viewpassword", mygin.CommonEnvironment(c, gin.H{
 | 
			
		||||
			"Title":      "验证查看密码",
 | 
			
		||||
			"CustomCode": dao.Conf.Site.CustomCode,
 | 
			
		||||
			"CustomCode": singleton.Conf.Site.CustomCode,
 | 
			
		||||
		}))
 | 
			
		||||
		c.Abort()
 | 
			
		||||
		return
 | 
			
		||||
@ -98,22 +98,22 @@ func (p *commonPage) checkViewPassword(c *gin.Context) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *commonPage) service(c *gin.Context) {
 | 
			
		||||
	dao.AlertsLock.RLock()
 | 
			
		||||
	defer dao.AlertsLock.RUnlock()
 | 
			
		||||
	c.HTML(http.StatusOK, "theme-"+dao.Conf.Site.Theme+"/service", mygin.CommonEnvironment(c, gin.H{
 | 
			
		||||
	singleton.AlertsLock.RLock()
 | 
			
		||||
	defer singleton.AlertsLock.RUnlock()
 | 
			
		||||
	c.HTML(http.StatusOK, "theme-"+singleton.Conf.Site.Theme+"/service", mygin.CommonEnvironment(c, gin.H{
 | 
			
		||||
		"Title":              "服务状态",
 | 
			
		||||
		"Services":           dao.ServiceSentinelShared.LoadStats(),
 | 
			
		||||
		"CycleTransferStats": dao.AlertsCycleTransferStatsStore,
 | 
			
		||||
		"CustomCode":         dao.Conf.Site.CustomCode,
 | 
			
		||||
		"Services":           singleton.ServiceSentinelShared.LoadStats(),
 | 
			
		||||
		"CycleTransferStats": singleton.AlertsCycleTransferStatsStore,
 | 
			
		||||
		"CustomCode":         singleton.Conf.Site.CustomCode,
 | 
			
		||||
	}))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (cp *commonPage) home(c *gin.Context) {
 | 
			
		||||
	dao.SortedServerLock.RLock()
 | 
			
		||||
	defer dao.SortedServerLock.RUnlock()
 | 
			
		||||
	c.HTML(http.StatusOK, "theme-"+dao.Conf.Site.Theme+"/home", mygin.CommonEnvironment(c, gin.H{
 | 
			
		||||
		"Servers":    dao.SortedServerList,
 | 
			
		||||
		"CustomCode": dao.Conf.Site.CustomCode,
 | 
			
		||||
	singleton.SortedServerLock.RLock()
 | 
			
		||||
	defer singleton.SortedServerLock.RUnlock()
 | 
			
		||||
	c.HTML(http.StatusOK, "theme-"+singleton.Conf.Site.Theme+"/home", mygin.CommonEnvironment(c, gin.H{
 | 
			
		||||
		"Servers":    singleton.SortedServerList,
 | 
			
		||||
		"CustomCode": singleton.Conf.Site.CustomCode,
 | 
			
		||||
	}))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -142,12 +142,12 @@ func (cp *commonPage) ws(c *gin.Context) {
 | 
			
		||||
	defer conn.Close()
 | 
			
		||||
	count := 0
 | 
			
		||||
	for {
 | 
			
		||||
		dao.SortedServerLock.RLock()
 | 
			
		||||
		singleton.SortedServerLock.RLock()
 | 
			
		||||
		err = conn.WriteJSON(Data{
 | 
			
		||||
			Now:     time.Now().Unix() * 1000,
 | 
			
		||||
			Servers: dao.SortedServerList,
 | 
			
		||||
			Servers: singleton.SortedServerList,
 | 
			
		||||
		})
 | 
			
		||||
		dao.SortedServerLock.RUnlock()
 | 
			
		||||
		singleton.SortedServerLock.RUnlock()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
@ -190,9 +190,9 @@ func (cp *commonPage) terminal(c *gin.Context) {
 | 
			
		||||
	var isAgent bool
 | 
			
		||||
 | 
			
		||||
	if _, authorized := c.Get(model.CtxKeyAuthorizedUser); !authorized {
 | 
			
		||||
		dao.ServerLock.RLock()
 | 
			
		||||
		_, hasID := dao.SecretToID[c.Request.Header.Get("Secret")]
 | 
			
		||||
		dao.ServerLock.RUnlock()
 | 
			
		||||
		singleton.ServerLock.RLock()
 | 
			
		||||
		_, hasID := singleton.SecretToID[c.Request.Header.Get("Secret")]
 | 
			
		||||
		singleton.ServerLock.RUnlock()
 | 
			
		||||
		if !hasID {
 | 
			
		||||
			mygin.ShowErrorPage(c, mygin.ErrInfo{
 | 
			
		||||
				Code:  http.StatusForbidden,
 | 
			
		||||
@ -225,9 +225,9 @@ func (cp *commonPage) terminal(c *gin.Context) {
 | 
			
		||||
		}
 | 
			
		||||
		isAgent = true
 | 
			
		||||
	} else {
 | 
			
		||||
		dao.ServerLock.RLock()
 | 
			
		||||
		server := dao.ServerList[terminal.serverID]
 | 
			
		||||
		dao.ServerLock.RUnlock()
 | 
			
		||||
		singleton.ServerLock.RLock()
 | 
			
		||||
		server := singleton.ServerList[terminal.serverID]
 | 
			
		||||
		singleton.ServerLock.RUnlock()
 | 
			
		||||
		if server == nil || server.TaskStream == nil {
 | 
			
		||||
			mygin.ShowErrorPage(c, mygin.ErrInfo{
 | 
			
		||||
				Code:  http.StatusForbidden,
 | 
			
		||||
@ -407,9 +407,9 @@ func (cp *commonPage) createTerminal(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dao.ServerLock.RLock()
 | 
			
		||||
	server := dao.ServerList[createTerminalReq.ID]
 | 
			
		||||
	dao.ServerLock.RUnlock()
 | 
			
		||||
	singleton.ServerLock.RLock()
 | 
			
		||||
	server := singleton.ServerList[createTerminalReq.ID]
 | 
			
		||||
	singleton.ServerLock.RUnlock()
 | 
			
		||||
	if server == nil || server.TaskStream == nil {
 | 
			
		||||
		mygin.ShowErrorPage(c, mygin.ErrInfo{
 | 
			
		||||
			Code:  http.StatusForbidden,
 | 
			
		||||
 | 
			
		||||
@ -14,13 +14,13 @@ import (
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
 | 
			
		||||
	"github.com/naiba/nezha/pkg/mygin"
 | 
			
		||||
	"github.com/naiba/nezha/service/dao"
 | 
			
		||||
	"github.com/naiba/nezha/service/singleton"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func ServeWeb(port uint) *http.Server {
 | 
			
		||||
	gin.SetMode(gin.ReleaseMode)
 | 
			
		||||
	r := gin.Default()
 | 
			
		||||
	if dao.Conf.Debug {
 | 
			
		||||
	if singleton.Conf.Debug {
 | 
			
		||||
		gin.SetMode(gin.DebugMode)
 | 
			
		||||
		pprof.Register(r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -4,9 +4,10 @@ import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
 | 
			
		||||
	"github.com/naiba/nezha/model"
 | 
			
		||||
	"github.com/naiba/nezha/pkg/mygin"
 | 
			
		||||
	"github.com/naiba/nezha/service/dao"
 | 
			
		||||
	"github.com/naiba/nezha/service/singleton"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type guestPage struct {
 | 
			
		||||
@ -34,7 +35,7 @@ func (gp *guestPage) serve() {
 | 
			
		||||
func (gp *guestPage) login(c *gin.Context) {
 | 
			
		||||
	LoginType := "GitHub"
 | 
			
		||||
	RegistrationLink := "https://github.com/join"
 | 
			
		||||
	if dao.Conf.Oauth2.Type == model.ConfigTypeGitee {
 | 
			
		||||
	if singleton.Conf.Oauth2.Type == model.ConfigTypeGitee {
 | 
			
		||||
		LoginType = "Gitee"
 | 
			
		||||
		RegistrationLink = "https://gitee.com/signup"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,7 @@ import (
 | 
			
		||||
	"github.com/naiba/nezha/pkg/mygin"
 | 
			
		||||
	"github.com/naiba/nezha/pkg/utils"
 | 
			
		||||
	"github.com/naiba/nezha/proto"
 | 
			
		||||
	"github.com/naiba/nezha/service/dao"
 | 
			
		||||
	"github.com/naiba/nezha/service/singleton"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type memberAPI struct {
 | 
			
		||||
@ -59,53 +59,53 @@ func (ma *memberAPI) delete(c *gin.Context) {
 | 
			
		||||
	var err error
 | 
			
		||||
	switch c.Param("model") {
 | 
			
		||||
	case "server":
 | 
			
		||||
		err = dao.DB.Unscoped().Delete(&model.Server{}, "id = ?", id).Error
 | 
			
		||||
		err = singleton.DB.Unscoped().Delete(&model.Server{}, "id = ?", id).Error
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			// 删除服务器
 | 
			
		||||
			dao.ServerLock.Lock()
 | 
			
		||||
			delete(dao.SecretToID, dao.ServerList[id].Secret)
 | 
			
		||||
			delete(dao.ServerList, id)
 | 
			
		||||
			dao.ServerLock.Unlock()
 | 
			
		||||
			dao.ReSortServer()
 | 
			
		||||
			singleton.ServerLock.Lock()
 | 
			
		||||
			delete(singleton.SecretToID, singleton.ServerList[id].Secret)
 | 
			
		||||
			delete(singleton.ServerList, id)
 | 
			
		||||
			singleton.ServerLock.Unlock()
 | 
			
		||||
			singleton.ReSortServer()
 | 
			
		||||
			// 删除循环流量状态中的此服务器相关的记录
 | 
			
		||||
			dao.AlertsLock.Lock()
 | 
			
		||||
			for i := 0; i < len(dao.Alerts); i++ {
 | 
			
		||||
				if dao.AlertsCycleTransferStatsStore[dao.Alerts[i].ID] != nil {
 | 
			
		||||
					delete(dao.AlertsCycleTransferStatsStore[dao.Alerts[i].ID].ServerName, id)
 | 
			
		||||
					delete(dao.AlertsCycleTransferStatsStore[dao.Alerts[i].ID].Transfer, id)
 | 
			
		||||
					delete(dao.AlertsCycleTransferStatsStore[dao.Alerts[i].ID].NextUpdate, id)
 | 
			
		||||
			singleton.AlertsLock.Lock()
 | 
			
		||||
			for i := 0; i < len(singleton.Alerts); i++ {
 | 
			
		||||
				if singleton.AlertsCycleTransferStatsStore[singleton.Alerts[i].ID] != nil {
 | 
			
		||||
					delete(singleton.AlertsCycleTransferStatsStore[singleton.Alerts[i].ID].ServerName, id)
 | 
			
		||||
					delete(singleton.AlertsCycleTransferStatsStore[singleton.Alerts[i].ID].Transfer, id)
 | 
			
		||||
					delete(singleton.AlertsCycleTransferStatsStore[singleton.Alerts[i].ID].NextUpdate, id)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			dao.AlertsLock.Unlock()
 | 
			
		||||
			singleton.AlertsLock.Unlock()
 | 
			
		||||
			// 删除服务器相关循环流量记录
 | 
			
		||||
			dao.DB.Unscoped().Delete(&model.Transfer{}, "server_id = ?", id)
 | 
			
		||||
			singleton.DB.Unscoped().Delete(&model.Transfer{}, "server_id = ?", id)
 | 
			
		||||
		}
 | 
			
		||||
	case "notification":
 | 
			
		||||
		err = dao.DB.Unscoped().Delete(&model.Notification{}, "id = ?", id).Error
 | 
			
		||||
		err = singleton.DB.Unscoped().Delete(&model.Notification{}, "id = ?", id).Error
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			dao.OnDeleteNotification(id)
 | 
			
		||||
			singleton.OnDeleteNotification(id)
 | 
			
		||||
		}
 | 
			
		||||
	case "monitor":
 | 
			
		||||
		err = dao.DB.Unscoped().Delete(&model.Monitor{}, "id = ?", id).Error
 | 
			
		||||
		err = singleton.DB.Unscoped().Delete(&model.Monitor{}, "id = ?", id).Error
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			dao.ServiceSentinelShared.OnMonitorDelete(id)
 | 
			
		||||
			err = dao.DB.Unscoped().Delete(&model.MonitorHistory{}, "monitor_id = ?", id).Error
 | 
			
		||||
			singleton.ServiceSentinelShared.OnMonitorDelete(id)
 | 
			
		||||
			err = singleton.DB.Unscoped().Delete(&model.MonitorHistory{}, "monitor_id = ?", id).Error
 | 
			
		||||
		}
 | 
			
		||||
	case "cron":
 | 
			
		||||
		err = dao.DB.Unscoped().Delete(&model.Cron{}, "id = ?", id).Error
 | 
			
		||||
		err = singleton.DB.Unscoped().Delete(&model.Cron{}, "id = ?", id).Error
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			dao.CronLock.RLock()
 | 
			
		||||
			defer dao.CronLock.RUnlock()
 | 
			
		||||
			cr := dao.Crons[id]
 | 
			
		||||
			singleton.CronLock.RLock()
 | 
			
		||||
			defer singleton.CronLock.RUnlock()
 | 
			
		||||
			cr := singleton.Crons[id]
 | 
			
		||||
			if cr != nil && cr.CronJobID != 0 {
 | 
			
		||||
				dao.Cron.Remove(cr.CronJobID)
 | 
			
		||||
				singleton.Cron.Remove(cr.CronJobID)
 | 
			
		||||
			}
 | 
			
		||||
			delete(dao.Crons, id)
 | 
			
		||||
			delete(singleton.Crons, id)
 | 
			
		||||
		}
 | 
			
		||||
	case "alert-rule":
 | 
			
		||||
		err = dao.DB.Unscoped().Delete(&model.AlertRule{}, "id = ?", id).Error
 | 
			
		||||
		err = singleton.DB.Unscoped().Delete(&model.AlertRule{}, "id = ?", id).Error
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			dao.OnDeleteAlert(id)
 | 
			
		||||
			singleton.OnDeleteAlert(id)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@ -129,7 +129,7 @@ type searchResult struct {
 | 
			
		||||
func (ma *memberAPI) searchServer(c *gin.Context) {
 | 
			
		||||
	var servers []model.Server
 | 
			
		||||
	likeWord := "%" + c.Query("word") + "%"
 | 
			
		||||
	dao.DB.Select("id,name").Where("id = ? OR name LIKE ? OR tag LIKE ? OR note LIKE ?",
 | 
			
		||||
	singleton.DB.Select("id,name").Where("id = ? OR name LIKE ? OR tag LIKE ? OR note LIKE ?",
 | 
			
		||||
		c.Query("word"), likeWord, likeWord, likeWord).Find(&servers)
 | 
			
		||||
 | 
			
		||||
	var resp []searchResult
 | 
			
		||||
@ -172,10 +172,10 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) {
 | 
			
		||||
		if s.ID == 0 {
 | 
			
		||||
			s.Secret = utils.MD5(fmt.Sprintf("%s%s%d", time.Now(), sf.Name, admin.ID))
 | 
			
		||||
			s.Secret = s.Secret[:18]
 | 
			
		||||
			err = dao.DB.Create(&s).Error
 | 
			
		||||
			err = singleton.DB.Create(&s).Error
 | 
			
		||||
		} else {
 | 
			
		||||
			isEdit = true
 | 
			
		||||
			err = dao.DB.Save(&s).Error
 | 
			
		||||
			err = singleton.DB.Save(&s).Error
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@ -186,26 +186,26 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if isEdit {
 | 
			
		||||
		dao.ServerLock.Lock()
 | 
			
		||||
		s.CopyFromRunningServer(dao.ServerList[s.ID])
 | 
			
		||||
		singleton.ServerLock.Lock()
 | 
			
		||||
		s.CopyFromRunningServer(singleton.ServerList[s.ID])
 | 
			
		||||
		// 如果修改了 Secret
 | 
			
		||||
		if s.Secret != dao.ServerList[s.ID].Secret {
 | 
			
		||||
		if s.Secret != singleton.ServerList[s.ID].Secret {
 | 
			
		||||
			// 删除旧 Secret-ID 绑定关系
 | 
			
		||||
			dao.SecretToID[s.Secret] = s.ID
 | 
			
		||||
			singleton.SecretToID[s.Secret] = s.ID
 | 
			
		||||
			// 设置新的 Secret-ID 绑定关系
 | 
			
		||||
			delete(dao.SecretToID, dao.ServerList[s.ID].Secret)
 | 
			
		||||
			delete(singleton.SecretToID, singleton.ServerList[s.ID].Secret)
 | 
			
		||||
		}
 | 
			
		||||
		dao.ServerList[s.ID] = &s
 | 
			
		||||
		dao.ServerLock.Unlock()
 | 
			
		||||
		singleton.ServerList[s.ID] = &s
 | 
			
		||||
		singleton.ServerLock.Unlock()
 | 
			
		||||
	} else {
 | 
			
		||||
		s.Host = &model.Host{}
 | 
			
		||||
		s.State = &model.HostState{}
 | 
			
		||||
		dao.ServerLock.Lock()
 | 
			
		||||
		dao.SecretToID[s.Secret] = s.ID
 | 
			
		||||
		dao.ServerList[s.ID] = &s
 | 
			
		||||
		dao.ServerLock.Unlock()
 | 
			
		||||
		singleton.ServerLock.Lock()
 | 
			
		||||
		singleton.SecretToID[s.Secret] = s.ID
 | 
			
		||||
		singleton.ServerList[s.ID] = &s
 | 
			
		||||
		singleton.ServerLock.Unlock()
 | 
			
		||||
	}
 | 
			
		||||
	dao.ReSortServer()
 | 
			
		||||
	singleton.ReSortServer()
 | 
			
		||||
	c.JSON(http.StatusOK, model.Response{
 | 
			
		||||
		Code: http.StatusOK,
 | 
			
		||||
	})
 | 
			
		||||
@ -238,13 +238,13 @@ func (ma *memberAPI) addOrEditMonitor(c *gin.Context) {
 | 
			
		||||
	}
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		if m.ID == 0 {
 | 
			
		||||
			err = dao.DB.Create(&m).Error
 | 
			
		||||
			err = singleton.DB.Create(&m).Error
 | 
			
		||||
		} else {
 | 
			
		||||
			err = dao.DB.Save(&m).Error
 | 
			
		||||
			err = singleton.DB.Save(&m).Error
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		err = dao.ServiceSentinelShared.OnMonitorUpdate(m)
 | 
			
		||||
		err = singleton.ServiceSentinelShared.OnMonitorUpdate(m)
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		c.JSON(http.StatusOK, model.Response{
 | 
			
		||||
@ -282,7 +282,7 @@ func (ma *memberAPI) addOrEditCron(c *gin.Context) {
 | 
			
		||||
		cr.Cover = cf.Cover
 | 
			
		||||
		err = json.Unmarshal([]byte(cf.ServersRaw), &cr.Servers)
 | 
			
		||||
	}
 | 
			
		||||
	tx := dao.DB.Begin()
 | 
			
		||||
	tx := singleton.DB.Begin()
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		if cf.ID == 0 {
 | 
			
		||||
			err = tx.Create(&cr).Error
 | 
			
		||||
@ -291,7 +291,7 @@ func (ma *memberAPI) addOrEditCron(c *gin.Context) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		cr.CronJobID, err = dao.Cron.AddFunc(cr.Scheduler, dao.CronTrigger(cr))
 | 
			
		||||
		cr.CronJobID, err = singleton.Cron.AddFunc(cr.Scheduler, singleton.CronTrigger(cr))
 | 
			
		||||
	}
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		err = tx.Commit().Error
 | 
			
		||||
@ -306,15 +306,15 @@ func (ma *memberAPI) addOrEditCron(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dao.CronLock.Lock()
 | 
			
		||||
	defer dao.CronLock.Unlock()
 | 
			
		||||
	crOld := dao.Crons[cr.ID]
 | 
			
		||||
	singleton.CronLock.Lock()
 | 
			
		||||
	defer singleton.CronLock.Unlock()
 | 
			
		||||
	crOld := singleton.Crons[cr.ID]
 | 
			
		||||
	if crOld != nil && crOld.CronJobID != 0 {
 | 
			
		||||
		dao.Cron.Remove(crOld.CronJobID)
 | 
			
		||||
		singleton.Cron.Remove(crOld.CronJobID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	delete(dao.Crons, cr.ID)
 | 
			
		||||
	dao.Crons[cr.ID] = &cr
 | 
			
		||||
	delete(singleton.Crons, cr.ID)
 | 
			
		||||
	singleton.Crons[cr.ID] = &cr
 | 
			
		||||
 | 
			
		||||
	c.JSON(http.StatusOK, model.Response{
 | 
			
		||||
		Code: http.StatusOK,
 | 
			
		||||
@ -323,7 +323,7 @@ func (ma *memberAPI) addOrEditCron(c *gin.Context) {
 | 
			
		||||
 | 
			
		||||
func (ma *memberAPI) manualTrigger(c *gin.Context) {
 | 
			
		||||
	var cr model.Cron
 | 
			
		||||
	if err := dao.DB.First(&cr, "id = ?", c.Param("id")).Error; err != nil {
 | 
			
		||||
	if err := singleton.DB.First(&cr, "id = ?", c.Param("id")).Error; err != nil {
 | 
			
		||||
		c.JSON(http.StatusOK, model.Response{
 | 
			
		||||
			Code:    http.StatusBadRequest,
 | 
			
		||||
			Message: err.Error(),
 | 
			
		||||
@ -331,7 +331,7 @@ func (ma *memberAPI) manualTrigger(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dao.ManualTrigger(cr)
 | 
			
		||||
	singleton.ManualTrigger(cr)
 | 
			
		||||
 | 
			
		||||
	c.JSON(http.StatusOK, model.Response{
 | 
			
		||||
		Code: http.StatusOK,
 | 
			
		||||
@ -351,9 +351,9 @@ func (ma *memberAPI) forceUpdate(c *gin.Context) {
 | 
			
		||||
	var executeResult bytes.Buffer
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < len(forceUpdateServers); i++ {
 | 
			
		||||
		dao.ServerLock.RLock()
 | 
			
		||||
		server := dao.ServerList[forceUpdateServers[i]]
 | 
			
		||||
		dao.ServerLock.RUnlock()
 | 
			
		||||
		singleton.ServerLock.RLock()
 | 
			
		||||
		server := singleton.ServerList[forceUpdateServers[i]]
 | 
			
		||||
		singleton.ServerLock.RUnlock()
 | 
			
		||||
		if server != nil && server.TaskStream != nil {
 | 
			
		||||
			if err := server.TaskStream.Send(&proto.Task{
 | 
			
		||||
				Type: model.TaskTypeUpgrade,
 | 
			
		||||
@ -402,9 +402,9 @@ func (ma *memberAPI) addOrEditNotification(c *gin.Context) {
 | 
			
		||||
	}
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		if n.ID == 0 {
 | 
			
		||||
			err = dao.DB.Create(&n).Error
 | 
			
		||||
			err = singleton.DB.Create(&n).Error
 | 
			
		||||
		} else {
 | 
			
		||||
			err = dao.DB.Save(&n).Error
 | 
			
		||||
			err = singleton.DB.Save(&n).Error
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@ -414,7 +414,7 @@ func (ma *memberAPI) addOrEditNotification(c *gin.Context) {
 | 
			
		||||
		})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	dao.OnRefreshOrAddNotification(n)
 | 
			
		||||
	singleton.OnRefreshOrAddNotification(n)
 | 
			
		||||
	c.JSON(http.StatusOK, model.Response{
 | 
			
		||||
		Code: http.StatusOK,
 | 
			
		||||
	})
 | 
			
		||||
@ -453,9 +453,9 @@ func (ma *memberAPI) addOrEditAlertRule(c *gin.Context) {
 | 
			
		||||
		r.Enable = &enable
 | 
			
		||||
		r.ID = arf.ID
 | 
			
		||||
		if r.ID == 0 {
 | 
			
		||||
			err = dao.DB.Create(&r).Error
 | 
			
		||||
			err = singleton.DB.Create(&r).Error
 | 
			
		||||
		} else {
 | 
			
		||||
			err = dao.DB.Save(&r).Error
 | 
			
		||||
			err = singleton.DB.Save(&r).Error
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@ -465,7 +465,7 @@ func (ma *memberAPI) addOrEditAlertRule(c *gin.Context) {
 | 
			
		||||
		})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	dao.OnRefreshOrAddAlert(r)
 | 
			
		||||
	singleton.OnRefreshOrAddAlert(r)
 | 
			
		||||
	c.JSON(http.StatusOK, model.Response{
 | 
			
		||||
		Code: http.StatusOK,
 | 
			
		||||
	})
 | 
			
		||||
@ -492,7 +492,7 @@ func (ma *memberAPI) logout(c *gin.Context) {
 | 
			
		||||
		})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	dao.DB.Model(admin).UpdateColumns(model.User{
 | 
			
		||||
	singleton.DB.Model(admin).UpdateColumns(model.User{
 | 
			
		||||
		Token:        "",
 | 
			
		||||
		TokenExpired: time.Now(),
 | 
			
		||||
	})
 | 
			
		||||
@ -522,16 +522,16 @@ func (ma *memberAPI) updateSetting(c *gin.Context) {
 | 
			
		||||
		})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	dao.Conf.EnableIPChangeNotification = sf.EnableIPChangeNotification == "on"
 | 
			
		||||
	dao.Conf.Cover = sf.Cover
 | 
			
		||||
	dao.Conf.GRPCHost = sf.GRPCHost
 | 
			
		||||
	dao.Conf.IgnoredIPNotification = sf.IgnoredIPNotification
 | 
			
		||||
	dao.Conf.Site.Brand = sf.Title
 | 
			
		||||
	dao.Conf.Site.Theme = sf.Theme
 | 
			
		||||
	dao.Conf.Site.CustomCode = sf.CustomCode
 | 
			
		||||
	dao.Conf.Site.ViewPassword = sf.ViewPassword
 | 
			
		||||
	dao.Conf.Oauth2.Admin = sf.Admin
 | 
			
		||||
	if err := dao.Conf.Save(); err != nil {
 | 
			
		||||
	singleton.Conf.EnableIPChangeNotification = sf.EnableIPChangeNotification == "on"
 | 
			
		||||
	singleton.Conf.Cover = sf.Cover
 | 
			
		||||
	singleton.Conf.GRPCHost = sf.GRPCHost
 | 
			
		||||
	singleton.Conf.IgnoredIPNotification = sf.IgnoredIPNotification
 | 
			
		||||
	singleton.Conf.Site.Brand = sf.Title
 | 
			
		||||
	singleton.Conf.Site.Theme = sf.Theme
 | 
			
		||||
	singleton.Conf.Site.CustomCode = sf.CustomCode
 | 
			
		||||
	singleton.Conf.Site.ViewPassword = sf.ViewPassword
 | 
			
		||||
	singleton.Conf.Oauth2.Admin = sf.Admin
 | 
			
		||||
	if err := singleton.Conf.Save(); err != nil {
 | 
			
		||||
		c.JSON(http.StatusOK, model.Response{
 | 
			
		||||
			Code:    http.StatusBadRequest,
 | 
			
		||||
			Message: fmt.Sprintf("请求错误:%s", err),
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@ import (
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"github.com/naiba/nezha/model"
 | 
			
		||||
	"github.com/naiba/nezha/pkg/mygin"
 | 
			
		||||
	"github.com/naiba/nezha/service/dao"
 | 
			
		||||
	"github.com/naiba/nezha/service/singleton"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type memberPage struct {
 | 
			
		||||
@ -30,24 +30,24 @@ func (mp *memberPage) serve() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mp *memberPage) server(c *gin.Context) {
 | 
			
		||||
	dao.SortedServerLock.RLock()
 | 
			
		||||
	defer dao.SortedServerLock.RUnlock()
 | 
			
		||||
	singleton.SortedServerLock.RLock()
 | 
			
		||||
	defer singleton.SortedServerLock.RUnlock()
 | 
			
		||||
	c.HTML(http.StatusOK, "dashboard/server", mygin.CommonEnvironment(c, gin.H{
 | 
			
		||||
		"Title":   "服务器管理",
 | 
			
		||||
		"Servers": dao.SortedServerList,
 | 
			
		||||
		"Servers": singleton.SortedServerList,
 | 
			
		||||
	}))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mp *memberPage) monitor(c *gin.Context) {
 | 
			
		||||
	c.HTML(http.StatusOK, "dashboard/monitor", mygin.CommonEnvironment(c, gin.H{
 | 
			
		||||
		"Title":    "服务监控",
 | 
			
		||||
		"Monitors": dao.ServiceSentinelShared.Monitors(),
 | 
			
		||||
		"Monitors": singleton.ServiceSentinelShared.Monitors(),
 | 
			
		||||
	}))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mp *memberPage) cron(c *gin.Context) {
 | 
			
		||||
	var crons []model.Cron
 | 
			
		||||
	dao.DB.Find(&crons)
 | 
			
		||||
	singleton.DB.Find(&crons)
 | 
			
		||||
	c.HTML(http.StatusOK, "dashboard/cron", mygin.CommonEnvironment(c, gin.H{
 | 
			
		||||
		"Title": "计划任务",
 | 
			
		||||
		"Crons": crons,
 | 
			
		||||
@ -56,9 +56,9 @@ func (mp *memberPage) cron(c *gin.Context) {
 | 
			
		||||
 | 
			
		||||
func (mp *memberPage) notification(c *gin.Context) {
 | 
			
		||||
	var nf []model.Notification
 | 
			
		||||
	dao.DB.Find(&nf)
 | 
			
		||||
	singleton.DB.Find(&nf)
 | 
			
		||||
	var ar []model.AlertRule
 | 
			
		||||
	dao.DB.Find(&ar)
 | 
			
		||||
	singleton.DB.Find(&ar)
 | 
			
		||||
	c.HTML(http.StatusOK, "dashboard/notification", mygin.CommonEnvironment(c, gin.H{
 | 
			
		||||
		"Title":         "报警通知",
 | 
			
		||||
		"Notifications": nf,
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,7 @@ import (
 | 
			
		||||
	"github.com/naiba/nezha/model"
 | 
			
		||||
	"github.com/naiba/nezha/pkg/mygin"
 | 
			
		||||
	"github.com/naiba/nezha/pkg/utils"
 | 
			
		||||
	"github.com/naiba/nezha/service/dao"
 | 
			
		||||
	"github.com/naiba/nezha/service/singleton"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type oauth2controller struct {
 | 
			
		||||
@ -28,10 +28,10 @@ func (oa *oauth2controller) serve() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (oa *oauth2controller) getCommonOauth2Config(c *gin.Context) *oauth2.Config {
 | 
			
		||||
	if dao.Conf.Oauth2.Type == model.ConfigTypeGitee {
 | 
			
		||||
	if singleton.Conf.Oauth2.Type == model.ConfigTypeGitee {
 | 
			
		||||
		return &oauth2.Config{
 | 
			
		||||
			ClientID:     dao.Conf.Oauth2.ClientID,
 | 
			
		||||
			ClientSecret: dao.Conf.Oauth2.ClientSecret,
 | 
			
		||||
			ClientID:     singleton.Conf.Oauth2.ClientID,
 | 
			
		||||
			ClientSecret: singleton.Conf.Oauth2.ClientSecret,
 | 
			
		||||
			Scopes:       []string{},
 | 
			
		||||
			Endpoint: oauth2.Endpoint{
 | 
			
		||||
				AuthURL:  "https://gitee.com/oauth/authorize",
 | 
			
		||||
@ -41,8 +41,8 @@ func (oa *oauth2controller) getCommonOauth2Config(c *gin.Context) *oauth2.Config
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		return &oauth2.Config{
 | 
			
		||||
			ClientID:     dao.Conf.Oauth2.ClientID,
 | 
			
		||||
			ClientSecret: dao.Conf.Oauth2.ClientSecret,
 | 
			
		||||
			ClientID:     singleton.Conf.Oauth2.ClientID,
 | 
			
		||||
			ClientSecret: singleton.Conf.Oauth2.ClientSecret,
 | 
			
		||||
			Scopes:       []string{},
 | 
			
		||||
			Endpoint:     GitHubOauth2.Endpoint,
 | 
			
		||||
		}
 | 
			
		||||
@ -59,7 +59,7 @@ func (oa *oauth2controller) getRedirectURL(c *gin.Context) string {
 | 
			
		||||
 | 
			
		||||
func (oa *oauth2controller) login(c *gin.Context) {
 | 
			
		||||
	state := utils.RandStringBytesMaskImprSrcUnsafe(6)
 | 
			
		||||
	dao.Cache.Set(fmt.Sprintf("%s%s", model.CacheKeyOauth2State, c.ClientIP()), state, 0)
 | 
			
		||||
	singleton.Cache.Set(fmt.Sprintf("%s%s", model.CacheKeyOauth2State, c.ClientIP()), state, 0)
 | 
			
		||||
	url := oa.getCommonOauth2Config(c).AuthCodeURL(state, oauth2.AccessTypeOnline)
 | 
			
		||||
	c.Redirect(http.StatusFound, url)
 | 
			
		||||
}
 | 
			
		||||
@ -67,7 +67,7 @@ func (oa *oauth2controller) login(c *gin.Context) {
 | 
			
		||||
func (oa *oauth2controller) callback(c *gin.Context) {
 | 
			
		||||
	var err error
 | 
			
		||||
	// 验证登录跳转时的 State
 | 
			
		||||
	state, ok := dao.Cache.Get(fmt.Sprintf("%s%s", model.CacheKeyOauth2State, c.ClientIP()))
 | 
			
		||||
	state, ok := singleton.Cache.Get(fmt.Sprintf("%s%s", model.CacheKeyOauth2State, c.ClientIP()))
 | 
			
		||||
	if !ok || state.(string) != c.Query("state") {
 | 
			
		||||
		err = errors.New("非法的登录方式")
 | 
			
		||||
	}
 | 
			
		||||
@ -80,7 +80,7 @@ func (oa *oauth2controller) callback(c *gin.Context) {
 | 
			
		||||
	var client *GitHubAPI.Client
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		oc := oauth2Config.Client(ctx, otk)
 | 
			
		||||
		if dao.Conf.Oauth2.Type == model.ConfigTypeGitee {
 | 
			
		||||
		if singleton.Conf.Oauth2.Type == model.ConfigTypeGitee {
 | 
			
		||||
			client, err = GitHubAPI.NewEnterpriseClient("https://gitee.com/api/v5/", "https://gitee.com/api/v5/", oc)
 | 
			
		||||
		} else {
 | 
			
		||||
			client = GitHubAPI.NewClient(oc)
 | 
			
		||||
@ -99,7 +99,7 @@ func (oa *oauth2controller) callback(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	var isAdmin bool
 | 
			
		||||
	for _, admin := range strings.Split(dao.Conf.Oauth2.Admin, ",") {
 | 
			
		||||
	for _, admin := range strings.Split(singleton.Conf.Oauth2.Admin, ",") {
 | 
			
		||||
		if admin != "" && gu.GetLogin() == admin {
 | 
			
		||||
			isAdmin = true
 | 
			
		||||
			break
 | 
			
		||||
@ -115,8 +115,8 @@ func (oa *oauth2controller) callback(c *gin.Context) {
 | 
			
		||||
	}
 | 
			
		||||
	user := model.NewUserFromGitHub(gu)
 | 
			
		||||
	user.IssueNewToken()
 | 
			
		||||
	dao.DB.Save(&user)
 | 
			
		||||
	c.SetCookie(dao.Conf.Site.CookieName, user.Token, 60*60*24, "", "", false, false)
 | 
			
		||||
	singleton.DB.Save(&user)
 | 
			
		||||
	c.SetCookie(singleton.Conf.Site.CookieName, user.Token, 60*60*24, "", "", false, false)
 | 
			
		||||
	c.Status(http.StatusOK)
 | 
			
		||||
	c.Writer.WriteString("<script>window.location.href='/'</script>")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,7 @@ import (
 | 
			
		||||
	"github.com/naiba/nezha/cmd/dashboard/controller"
 | 
			
		||||
	"github.com/naiba/nezha/cmd/dashboard/rpc"
 | 
			
		||||
	"github.com/naiba/nezha/model"
 | 
			
		||||
	"github.com/naiba/nezha/service/dao"
 | 
			
		||||
	"github.com/naiba/nezha/service/singleton"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
@ -26,62 +26,62 @@ func init() {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 初始化 dao 包
 | 
			
		||||
	dao.Conf = &model.Config{}
 | 
			
		||||
	dao.Cron = cron.New(cron.WithSeconds(), cron.WithLocation(shanghai))
 | 
			
		||||
	dao.Crons = make(map[uint64]*model.Cron)
 | 
			
		||||
	dao.ServerList = make(map[uint64]*model.Server)
 | 
			
		||||
	dao.SecretToID = make(map[string]uint64)
 | 
			
		||||
	singleton.Conf = &model.Config{}
 | 
			
		||||
	singleton.Cron = cron.New(cron.WithSeconds(), cron.WithLocation(shanghai))
 | 
			
		||||
	singleton.Crons = make(map[uint64]*model.Cron)
 | 
			
		||||
	singleton.ServerList = make(map[uint64]*model.Server)
 | 
			
		||||
	singleton.SecretToID = make(map[string]uint64)
 | 
			
		||||
 | 
			
		||||
	err = dao.Conf.Read("data/config.yaml")
 | 
			
		||||
	err = singleton.Conf.Read("data/config.yaml")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	dao.DB, err = gorm.Open(sqlite.Open("data/sqlite.db"), &gorm.Config{
 | 
			
		||||
	singleton.DB, err = gorm.Open(sqlite.Open("data/sqlite.db"), &gorm.Config{
 | 
			
		||||
		CreateBatchSize: 200,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	if dao.Conf.Debug {
 | 
			
		||||
		dao.DB = dao.DB.Debug()
 | 
			
		||||
	if singleton.Conf.Debug {
 | 
			
		||||
		singleton.DB = singleton.DB.Debug()
 | 
			
		||||
	}
 | 
			
		||||
	if dao.Conf.GRPCPort == 0 {
 | 
			
		||||
		dao.Conf.GRPCPort = 5555
 | 
			
		||||
	if singleton.Conf.GRPCPort == 0 {
 | 
			
		||||
		singleton.Conf.GRPCPort = 5555
 | 
			
		||||
	}
 | 
			
		||||
	dao.Cache = cache.New(5*time.Minute, 10*time.Minute)
 | 
			
		||||
	singleton.Cache = cache.New(5*time.Minute, 10*time.Minute)
 | 
			
		||||
 | 
			
		||||
	initSystem()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func initSystem() {
 | 
			
		||||
	dao.DB.AutoMigrate(model.Server{}, model.User{},
 | 
			
		||||
	singleton.DB.AutoMigrate(model.Server{}, model.User{},
 | 
			
		||||
		model.Notification{}, model.AlertRule{}, model.Monitor{},
 | 
			
		||||
		model.MonitorHistory{}, model.Cron{}, model.Transfer{})
 | 
			
		||||
 | 
			
		||||
	dao.LoadNotifications()
 | 
			
		||||
	singleton.LoadNotifications()
 | 
			
		||||
	loadServers() //加载服务器列表
 | 
			
		||||
	loadCrons()   //加载计划任务
 | 
			
		||||
 | 
			
		||||
	// 清理 服务请求记录 和 流量记录 的旧数据
 | 
			
		||||
	_, err := dao.Cron.AddFunc("0 30 3 * * *", cleanMonitorHistory)
 | 
			
		||||
	_, err := singleton.Cron.AddFunc("0 30 3 * * *", cleanMonitorHistory)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 流量记录打点
 | 
			
		||||
	_, err = dao.Cron.AddFunc("0 0 * * * *", recordTransferHourlyUsage)
 | 
			
		||||
	_, err = singleton.Cron.AddFunc("0 0 * * * *", recordTransferHourlyUsage)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func recordTransferHourlyUsage() {
 | 
			
		||||
	dao.ServerLock.Lock()
 | 
			
		||||
	defer dao.ServerLock.Unlock()
 | 
			
		||||
	singleton.ServerLock.Lock()
 | 
			
		||||
	defer singleton.ServerLock.Unlock()
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	nowTrimSeconds := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, time.Local)
 | 
			
		||||
	var txs []model.Transfer
 | 
			
		||||
	for id, server := range dao.ServerList {
 | 
			
		||||
	for id, server := range singleton.ServerList {
 | 
			
		||||
		tx := model.Transfer{
 | 
			
		||||
			ServerID: id,
 | 
			
		||||
			In:       server.State.NetInTransfer - uint64(server.PrevHourlyTransferIn),
 | 
			
		||||
@ -98,19 +98,19 @@ func recordTransferHourlyUsage() {
 | 
			
		||||
	if len(txs) == 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	log.Println("NEZHA>> Cron 流量统计入库", len(txs), dao.DB.Create(txs).Error)
 | 
			
		||||
	log.Println("NEZHA>> Cron 流量统计入库", len(txs), singleton.DB.Create(txs).Error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cleanMonitorHistory() {
 | 
			
		||||
	// 清理无效数据
 | 
			
		||||
	dao.DB.Unscoped().Delete(&model.MonitorHistory{}, "created_at < ? OR monitor_id NOT IN (SELECT `id` FROM monitors)", time.Now().AddDate(0, 0, -30))
 | 
			
		||||
	dao.DB.Unscoped().Delete(&model.Transfer{}, "server_id NOT IN (SELECT `id` FROM servers)")
 | 
			
		||||
	singleton.DB.Unscoped().Delete(&model.MonitorHistory{}, "created_at < ? OR monitor_id NOT IN (SELECT `id` FROM monitors)", time.Now().AddDate(0, 0, -30))
 | 
			
		||||
	singleton.DB.Unscoped().Delete(&model.Transfer{}, "server_id NOT IN (SELECT `id` FROM servers)")
 | 
			
		||||
	// 计算可清理流量记录的时长
 | 
			
		||||
	var allServerKeep time.Time
 | 
			
		||||
	specialServerKeep := make(map[uint64]time.Time)
 | 
			
		||||
	var specialServerIDs []uint64
 | 
			
		||||
	var alerts []model.AlertRule
 | 
			
		||||
	dao.DB.Find(&alerts)
 | 
			
		||||
	singleton.DB.Find(&alerts)
 | 
			
		||||
	for i := 0; i < len(alerts); i++ {
 | 
			
		||||
		for j := 0; j < len(alerts[i].Rules); j++ {
 | 
			
		||||
			// 是不是流量记录规则
 | 
			
		||||
@ -136,31 +136,31 @@ func cleanMonitorHistory() {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for id, couldRemove := range specialServerKeep {
 | 
			
		||||
		dao.DB.Unscoped().Delete(&model.Transfer{}, "server_id = ? AND created_at < ?", id, couldRemove)
 | 
			
		||||
		singleton.DB.Unscoped().Delete(&model.Transfer{}, "server_id = ? AND created_at < ?", id, couldRemove)
 | 
			
		||||
	}
 | 
			
		||||
	if allServerKeep.IsZero() {
 | 
			
		||||
		dao.DB.Unscoped().Delete(&model.Transfer{}, "server_id NOT IN (?)", specialServerIDs)
 | 
			
		||||
		singleton.DB.Unscoped().Delete(&model.Transfer{}, "server_id NOT IN (?)", specialServerIDs)
 | 
			
		||||
	} else {
 | 
			
		||||
		dao.DB.Unscoped().Delete(&model.Transfer{}, "server_id NOT IN (?) AND created_at < ?", specialServerIDs, allServerKeep)
 | 
			
		||||
		singleton.DB.Unscoped().Delete(&model.Transfer{}, "server_id NOT IN (?) AND created_at < ?", specialServerIDs, allServerKeep)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func loadServers() {
 | 
			
		||||
	var servers []model.Server
 | 
			
		||||
	dao.DB.Find(&servers)
 | 
			
		||||
	singleton.DB.Find(&servers)
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		innerS := s
 | 
			
		||||
		innerS.Host = &model.Host{}
 | 
			
		||||
		innerS.State = &model.HostState{}
 | 
			
		||||
		dao.ServerList[innerS.ID] = &innerS
 | 
			
		||||
		dao.SecretToID[innerS.Secret] = innerS.ID
 | 
			
		||||
		singleton.ServerList[innerS.ID] = &innerS
 | 
			
		||||
		singleton.SecretToID[innerS.Secret] = innerS.ID
 | 
			
		||||
	}
 | 
			
		||||
	dao.ReSortServer()
 | 
			
		||||
	singleton.ReSortServer()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func loadCrons() {
 | 
			
		||||
	var crons []model.Cron
 | 
			
		||||
	dao.DB.Find(&crons)
 | 
			
		||||
	singleton.DB.Find(&crons)
 | 
			
		||||
	var err error
 | 
			
		||||
	errMsg := new(bytes.Buffer)
 | 
			
		||||
	for i := 0; i < len(crons); i++ {
 | 
			
		||||
@ -171,9 +171,9 @@ func loadCrons() {
 | 
			
		||||
			crIgnoreMap[cr.Servers[j]] = true
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cr.CronJobID, err = dao.Cron.AddFunc(cr.Scheduler, dao.CronTrigger(cr))
 | 
			
		||||
		cr.CronJobID, err = singleton.Cron.AddFunc(cr.Scheduler, singleton.CronTrigger(cr))
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			dao.Crons[cr.ID] = &cr
 | 
			
		||||
			singleton.Crons[cr.ID] = &cr
 | 
			
		||||
		} else {
 | 
			
		||||
			if errMsg.Len() == 0 {
 | 
			
		||||
				errMsg.WriteString("调度失败的计划任务:[")
 | 
			
		||||
@ -183,20 +183,20 @@ func loadCrons() {
 | 
			
		||||
	}
 | 
			
		||||
	if errMsg.Len() > 0 {
 | 
			
		||||
		msg := errMsg.String()
 | 
			
		||||
		dao.SendNotification(msg[:len(msg)-1]+"] 这些任务将无法正常执行,请进入后点重新修改保存。", false)
 | 
			
		||||
		singleton.SendNotification(msg[:len(msg)-1]+"] 这些任务将无法正常执行,请进入后点重新修改保存。", false)
 | 
			
		||||
	}
 | 
			
		||||
	dao.Cron.Start()
 | 
			
		||||
	singleton.Cron.Start()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	cleanMonitorHistory()
 | 
			
		||||
	go rpc.ServeRPC(dao.Conf.GRPCPort)
 | 
			
		||||
	go rpc.ServeRPC(singleton.Conf.GRPCPort)
 | 
			
		||||
	serviceSentinelDispatchBus := make(chan model.Monitor)
 | 
			
		||||
	go rpc.DispatchTask(serviceSentinelDispatchBus)
 | 
			
		||||
	go rpc.DispatchKeepalive()
 | 
			
		||||
	go dao.AlertSentinelStart()
 | 
			
		||||
	dao.NewServiceSentinel(serviceSentinelDispatchBus)
 | 
			
		||||
	srv := controller.ServeWeb(dao.Conf.HTTPPort)
 | 
			
		||||
	go singleton.AlertSentinelStart()
 | 
			
		||||
	singleton.NewServiceSentinel(serviceSentinelDispatchBus)
 | 
			
		||||
	srv := controller.ServeWeb(singleton.Conf.HTTPPort)
 | 
			
		||||
	graceful.Graceful(func() error {
 | 
			
		||||
		return srv.ListenAndServe()
 | 
			
		||||
	}, func(c context.Context) error {
 | 
			
		||||
 | 
			
		||||
@ -8,8 +8,8 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/naiba/nezha/model"
 | 
			
		||||
	pb "github.com/naiba/nezha/proto"
 | 
			
		||||
	"github.com/naiba/nezha/service/dao"
 | 
			
		||||
	rpcService "github.com/naiba/nezha/service/rpc"
 | 
			
		||||
	"github.com/naiba/nezha/service/singleton"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func ServeRPC(port uint) {
 | 
			
		||||
@ -29,45 +29,45 @@ func DispatchTask(serviceSentinelDispatchBus <-chan model.Monitor) {
 | 
			
		||||
	for task := range serviceSentinelDispatchBus {
 | 
			
		||||
		round := 0
 | 
			
		||||
		endIndex := workedServerIndex
 | 
			
		||||
		dao.SortedServerLock.RLock()
 | 
			
		||||
		singleton.SortedServerLock.RLock()
 | 
			
		||||
		// 如果已经轮了一整圈又轮到自己,没有合适机器去请求,跳出循环
 | 
			
		||||
		for round < 1 || workedServerIndex < endIndex {
 | 
			
		||||
			// 如果到了圈尾,再回到圈头,圈数加一,游标重置
 | 
			
		||||
			if workedServerIndex >= len(dao.SortedServerList) {
 | 
			
		||||
			if workedServerIndex >= len(singleton.SortedServerList) {
 | 
			
		||||
				workedServerIndex = 0
 | 
			
		||||
				round++
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			// 如果服务器不在线,跳过这个服务器
 | 
			
		||||
			if dao.SortedServerList[workedServerIndex].TaskStream == nil {
 | 
			
		||||
			if singleton.SortedServerList[workedServerIndex].TaskStream == nil {
 | 
			
		||||
				workedServerIndex++
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			// 如果此任务不可使用此服务器请求,跳过这个服务器(有些 IPv6 only 开了 NAT64 的机器请求 IPv4 总会出问题)
 | 
			
		||||
			if (task.Cover == model.MonitorCoverAll && task.SkipServers[dao.SortedServerList[workedServerIndex].ID]) ||
 | 
			
		||||
				(task.Cover == model.MonitorCoverIgnoreAll && !task.SkipServers[dao.SortedServerList[workedServerIndex].ID]) {
 | 
			
		||||
			if (task.Cover == model.MonitorCoverAll && task.SkipServers[singleton.SortedServerList[workedServerIndex].ID]) ||
 | 
			
		||||
				(task.Cover == model.MonitorCoverIgnoreAll && !task.SkipServers[singleton.SortedServerList[workedServerIndex].ID]) {
 | 
			
		||||
				workedServerIndex++
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			// 找到合适机器执行任务,跳出循环
 | 
			
		||||
			dao.SortedServerList[workedServerIndex].TaskStream.Send(task.PB())
 | 
			
		||||
			singleton.SortedServerList[workedServerIndex].TaskStream.Send(task.PB())
 | 
			
		||||
			workedServerIndex++
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		dao.SortedServerLock.RUnlock()
 | 
			
		||||
		singleton.SortedServerLock.RUnlock()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func DispatchKeepalive() {
 | 
			
		||||
	dao.Cron.AddFunc("@every 60s", func() {
 | 
			
		||||
		dao.SortedServerLock.RLock()
 | 
			
		||||
		defer dao.SortedServerLock.RUnlock()
 | 
			
		||||
		for i := 0; i < len(dao.SortedServerList); i++ {
 | 
			
		||||
			if dao.SortedServerList[i] == nil || dao.SortedServerList[i].TaskStream == nil {
 | 
			
		||||
	singleton.Cron.AddFunc("@every 60s", func() {
 | 
			
		||||
		singleton.SortedServerLock.RLock()
 | 
			
		||||
		defer singleton.SortedServerLock.RUnlock()
 | 
			
		||||
		for i := 0; i < len(singleton.SortedServerList); i++ {
 | 
			
		||||
			if singleton.SortedServerList[i] == nil || singleton.SortedServerList[i].TaskStream == nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			dao.SortedServerList[i].TaskStream.Send(&pb.Task{Type: model.TaskTypeKeepalive})
 | 
			
		||||
			singleton.SortedServerList[i].TaskStream.Send(&pb.Task{Type: model.TaskTypeKeepalive})
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								go.mod
									
									
									
									
									
								
							@ -4,7 +4,7 @@ go 1.13
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	code.cloudfoundry.org/bytefmt v0.0.0-20211005130812-5bb3c17173e5
 | 
			
		||||
	github.com/Erope/goss v0.0.0-20211230093305-df3c03fd1ed4 // indirect
 | 
			
		||||
	github.com/Erope/goss v0.0.0-20211230093305-df3c03fd1ed4
 | 
			
		||||
	github.com/artdarek/go-unzip v1.0.0
 | 
			
		||||
	github.com/blang/semver v3.5.1+incompatible
 | 
			
		||||
	github.com/creack/pty v1.1.17
 | 
			
		||||
@ -25,7 +25,6 @@ require (
 | 
			
		||||
	github.com/ory/graceful v0.1.1
 | 
			
		||||
	github.com/p14yground/go-github-selfupdate v0.0.0-20210520015421-eddf14461293
 | 
			
		||||
	github.com/patrickmn/go-cache v2.1.0+incompatible
 | 
			
		||||
	github.com/pkg/errors v0.9.1 // indirect
 | 
			
		||||
	github.com/robfig/cron/v3 v3.0.1
 | 
			
		||||
	github.com/shirou/gopsutil/v3 v3.21.10
 | 
			
		||||
	github.com/spf13/pflag v1.0.5
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@ import (
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
 | 
			
		||||
	"github.com/naiba/nezha/model"
 | 
			
		||||
	"github.com/naiba/nezha/service/dao"
 | 
			
		||||
	"github.com/naiba/nezha/service/singleton"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type AuthorizeOption struct {
 | 
			
		||||
@ -38,11 +38,11 @@ func Authorize(opt AuthorizeOption) func(*gin.Context) {
 | 
			
		||||
		var isLogin bool
 | 
			
		||||
 | 
			
		||||
		// 用户鉴权
 | 
			
		||||
		token, _ := c.Cookie(dao.Conf.Site.CookieName)
 | 
			
		||||
		token, _ := c.Cookie(singleton.Conf.Site.CookieName)
 | 
			
		||||
		token = strings.TrimSpace(token)
 | 
			
		||||
		if token != "" {
 | 
			
		||||
			var u model.User
 | 
			
		||||
			if err := dao.DB.Where("token = ?", token).First(&u).Error; err == nil {
 | 
			
		||||
			if err := singleton.DB.Where("token = ?", token).First(&u).Error; err == nil {
 | 
			
		||||
				isLogin = u.TokenExpired.After(time.Now())
 | 
			
		||||
			}
 | 
			
		||||
			if isLogin {
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@ import (
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
 | 
			
		||||
	"github.com/naiba/nezha/model"
 | 
			
		||||
	"github.com/naiba/nezha/service/dao"
 | 
			
		||||
	"github.com/naiba/nezha/service/singleton"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var adminPage = map[string]bool{
 | 
			
		||||
@ -20,15 +20,15 @@ var adminPage = map[string]bool{
 | 
			
		||||
 | 
			
		||||
func CommonEnvironment(c *gin.Context, data map[string]interface{}) gin.H {
 | 
			
		||||
	data["MatchedPath"] = c.MustGet("MatchedPath")
 | 
			
		||||
	data["Version"] = dao.Version
 | 
			
		||||
	data["Conf"] = dao.Conf
 | 
			
		||||
	data["Version"] = singleton.Version
 | 
			
		||||
	data["Conf"] = singleton.Conf
 | 
			
		||||
	// 是否是管理页面
 | 
			
		||||
	data["IsAdminPage"] = adminPage[data["MatchedPath"].(string)]
 | 
			
		||||
	// 站点标题
 | 
			
		||||
	if t, has := data["Title"]; !has {
 | 
			
		||||
		data["Title"] = dao.Conf.Site.Brand
 | 
			
		||||
		data["Title"] = singleton.Conf.Site.Brand
 | 
			
		||||
	} else {
 | 
			
		||||
		data["Title"] = fmt.Sprintf("%s - %s", t, dao.Conf.Site.Brand)
 | 
			
		||||
		data["Title"] = fmt.Sprintf("%s - %s", t, singleton.Conf.Site.Brand)
 | 
			
		||||
	}
 | 
			
		||||
	u, ok := c.Get(model.CtxKeyAuthorizedUser)
 | 
			
		||||
	if ok {
 | 
			
		||||
 | 
			
		||||
@ -3,10 +3,11 @@ package rpc
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"github.com/naiba/nezha/service/dao"
 | 
			
		||||
	"google.golang.org/grpc/codes"
 | 
			
		||||
	"google.golang.org/grpc/metadata"
 | 
			
		||||
	"google.golang.org/grpc/status"
 | 
			
		||||
 | 
			
		||||
	"github.com/naiba/nezha/service/singleton"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type AuthHandler struct {
 | 
			
		||||
@ -32,13 +33,13 @@ func (a *AuthHandler) Check(ctx context.Context) (uint64, error) {
 | 
			
		||||
		clientSecret = value[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dao.ServerLock.RLock()
 | 
			
		||||
	defer dao.ServerLock.RUnlock()
 | 
			
		||||
	clientID, hasID := dao.SecretToID[clientSecret]
 | 
			
		||||
	singleton.ServerLock.RLock()
 | 
			
		||||
	defer singleton.ServerLock.RUnlock()
 | 
			
		||||
	clientID, hasID := singleton.SecretToID[clientSecret]
 | 
			
		||||
	if !hasID {
 | 
			
		||||
		return 0, status.Errorf(codes.Unauthenticated, "客户端认证失败")
 | 
			
		||||
	}
 | 
			
		||||
	_, hasServer := dao.ServerList[clientID]
 | 
			
		||||
	_, hasServer := singleton.ServerList[clientID]
 | 
			
		||||
	if !hasServer {
 | 
			
		||||
		return 0, status.Errorf(codes.Unauthenticated, "客户端认证失败")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@ import (
 | 
			
		||||
	"github.com/naiba/nezha/model"
 | 
			
		||||
	"github.com/naiba/nezha/pkg/utils"
 | 
			
		||||
	pb "github.com/naiba/nezha/proto"
 | 
			
		||||
	"github.com/naiba/nezha/service/dao"
 | 
			
		||||
	"github.com/naiba/nezha/service/singleton"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type NezhaHandler struct {
 | 
			
		||||
@ -23,25 +23,25 @@ func (s *NezhaHandler) ReportTask(c context.Context, r *pb.TaskResult) (*pb.Rece
 | 
			
		||||
	}
 | 
			
		||||
	if r.GetType() == model.TaskTypeCommand {
 | 
			
		||||
		// 处理上报的计划任务
 | 
			
		||||
		dao.CronLock.RLock()
 | 
			
		||||
		defer dao.CronLock.RUnlock()
 | 
			
		||||
		cr := dao.Crons[r.GetId()]
 | 
			
		||||
		singleton.CronLock.RLock()
 | 
			
		||||
		defer singleton.CronLock.RUnlock()
 | 
			
		||||
		cr := singleton.Crons[r.GetId()]
 | 
			
		||||
		if cr != nil {
 | 
			
		||||
			dao.ServerLock.RLock()
 | 
			
		||||
			defer dao.ServerLock.RUnlock()
 | 
			
		||||
			singleton.ServerLock.RLock()
 | 
			
		||||
			defer singleton.ServerLock.RUnlock()
 | 
			
		||||
			if cr.PushSuccessful && r.GetSuccessful() {
 | 
			
		||||
				dao.SendNotification(fmt.Sprintf("[任务成功] %s ,服务器:%s,日志:\n%s", cr.Name, dao.ServerList[clientID].Name, r.GetData()), false)
 | 
			
		||||
				singleton.SendNotification(fmt.Sprintf("[任务成功] %s ,服务器:%s,日志:\n%s", cr.Name, singleton.ServerList[clientID].Name, r.GetData()), false)
 | 
			
		||||
			}
 | 
			
		||||
			if !r.GetSuccessful() {
 | 
			
		||||
				dao.SendNotification(fmt.Sprintf("[任务失败] %s ,服务器:%s,日志:\n%s", cr.Name, dao.ServerList[clientID].Name, r.GetData()), false)
 | 
			
		||||
				singleton.SendNotification(fmt.Sprintf("[任务失败] %s ,服务器:%s,日志:\n%s", cr.Name, singleton.ServerList[clientID].Name, r.GetData()), false)
 | 
			
		||||
			}
 | 
			
		||||
			dao.DB.Model(cr).Updates(model.Cron{
 | 
			
		||||
			singleton.DB.Model(cr).Updates(model.Cron{
 | 
			
		||||
				LastExecutedAt: time.Now().Add(time.Second * -1 * time.Duration(r.GetDelay())),
 | 
			
		||||
				LastResult:     r.GetSuccessful(),
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	} else if model.IsServiceSentinelNeeded(r.GetType()) {
 | 
			
		||||
		dao.ServiceSentinelShared.Dispatch(dao.ReportData{
 | 
			
		||||
		singleton.ServiceSentinelShared.Dispatch(singleton.ReportData{
 | 
			
		||||
			Data:     r,
 | 
			
		||||
			Reporter: clientID,
 | 
			
		||||
		})
 | 
			
		||||
@ -56,14 +56,14 @@ func (s *NezhaHandler) RequestTask(h *pb.Host, stream pb.NezhaService_RequestTas
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	closeCh := make(chan error)
 | 
			
		||||
	dao.ServerLock.RLock()
 | 
			
		||||
	singleton.ServerLock.RLock()
 | 
			
		||||
	// 修复不断的请求 task 但是没有 return 导致内存泄漏
 | 
			
		||||
	if dao.ServerList[clientID].TaskClose != nil {
 | 
			
		||||
		close(dao.ServerList[clientID].TaskClose)
 | 
			
		||||
	if singleton.ServerList[clientID].TaskClose != nil {
 | 
			
		||||
		close(singleton.ServerList[clientID].TaskClose)
 | 
			
		||||
	}
 | 
			
		||||
	dao.ServerList[clientID].TaskStream = stream
 | 
			
		||||
	dao.ServerList[clientID].TaskClose = closeCh
 | 
			
		||||
	dao.ServerLock.RUnlock()
 | 
			
		||||
	singleton.ServerList[clientID].TaskStream = stream
 | 
			
		||||
	singleton.ServerList[clientID].TaskClose = closeCh
 | 
			
		||||
	singleton.ServerLock.RUnlock()
 | 
			
		||||
	return <-closeCh
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -74,15 +74,15 @@ func (s *NezhaHandler) ReportSystemState(c context.Context, r *pb.State) (*pb.Re
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	state := model.PB2State(r)
 | 
			
		||||
	dao.ServerLock.RLock()
 | 
			
		||||
	defer dao.ServerLock.RUnlock()
 | 
			
		||||
	dao.ServerList[clientID].LastActive = time.Now()
 | 
			
		||||
	dao.ServerList[clientID].State = &state
 | 
			
		||||
	singleton.ServerLock.RLock()
 | 
			
		||||
	defer singleton.ServerLock.RUnlock()
 | 
			
		||||
	singleton.ServerList[clientID].LastActive = time.Now()
 | 
			
		||||
	singleton.ServerList[clientID].State = &state
 | 
			
		||||
 | 
			
		||||
	// 如果从未记录过,先打点,等到小时时间点时入库
 | 
			
		||||
	if dao.ServerList[clientID].PrevHourlyTransferIn == 0 || dao.ServerList[clientID].PrevHourlyTransferOut == 0 {
 | 
			
		||||
		dao.ServerList[clientID].PrevHourlyTransferIn = int64(state.NetInTransfer)
 | 
			
		||||
		dao.ServerList[clientID].PrevHourlyTransferOut = int64(state.NetOutTransfer)
 | 
			
		||||
	if singleton.ServerList[clientID].PrevHourlyTransferIn == 0 || singleton.ServerList[clientID].PrevHourlyTransferOut == 0 {
 | 
			
		||||
		singleton.ServerList[clientID].PrevHourlyTransferIn = int64(state.NetInTransfer)
 | 
			
		||||
		singleton.ServerList[clientID].PrevHourlyTransferOut = int64(state.NetOutTransfer)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &pb.Receipt{Proced: true}, nil
 | 
			
		||||
@ -95,26 +95,26 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	host := model.PB2Host(r)
 | 
			
		||||
	dao.ServerLock.RLock()
 | 
			
		||||
	defer dao.ServerLock.RUnlock()
 | 
			
		||||
	if dao.Conf.EnableIPChangeNotification &&
 | 
			
		||||
		((dao.Conf.Cover == model.ConfigCoverAll && !dao.Conf.IgnoredIPNotificationServerIDs[clientID]) ||
 | 
			
		||||
			(dao.Conf.Cover == model.ConfigCoverIgnoreAll && dao.Conf.IgnoredIPNotificationServerIDs[clientID])) &&
 | 
			
		||||
		dao.ServerList[clientID].Host != nil &&
 | 
			
		||||
		dao.ServerList[clientID].Host.IP != "" &&
 | 
			
		||||
	singleton.ServerLock.RLock()
 | 
			
		||||
	defer singleton.ServerLock.RUnlock()
 | 
			
		||||
	if singleton.Conf.EnableIPChangeNotification &&
 | 
			
		||||
		((singleton.Conf.Cover == model.ConfigCoverAll && !singleton.Conf.IgnoredIPNotificationServerIDs[clientID]) ||
 | 
			
		||||
			(singleton.Conf.Cover == model.ConfigCoverIgnoreAll && singleton.Conf.IgnoredIPNotificationServerIDs[clientID])) &&
 | 
			
		||||
		singleton.ServerList[clientID].Host != nil &&
 | 
			
		||||
		singleton.ServerList[clientID].Host.IP != "" &&
 | 
			
		||||
		host.IP != "" &&
 | 
			
		||||
		dao.ServerList[clientID].Host.IP != host.IP {
 | 
			
		||||
		dao.SendNotification(fmt.Sprintf(
 | 
			
		||||
		singleton.ServerList[clientID].Host.IP != host.IP {
 | 
			
		||||
		singleton.SendNotification(fmt.Sprintf(
 | 
			
		||||
			"[IP变更] %s ,旧IP:%s,新IP:%s。",
 | 
			
		||||
			dao.ServerList[clientID].Name, utils.IPDesensitize(dao.ServerList[clientID].Host.IP), utils.IPDesensitize(host.IP)), true)
 | 
			
		||||
			singleton.ServerList[clientID].Name, utils.IPDesensitize(singleton.ServerList[clientID].Host.IP), utils.IPDesensitize(host.IP)), true)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 判断是否是机器重启,如果是机器重启要录入最后记录的流量里面
 | 
			
		||||
	if dao.ServerList[clientID].Host.BootTime < host.BootTime {
 | 
			
		||||
		dao.ServerList[clientID].PrevHourlyTransferIn = dao.ServerList[clientID].PrevHourlyTransferIn - int64(dao.ServerList[clientID].State.NetInTransfer)
 | 
			
		||||
		dao.ServerList[clientID].PrevHourlyTransferOut = dao.ServerList[clientID].PrevHourlyTransferOut - int64(dao.ServerList[clientID].State.NetOutTransfer)
 | 
			
		||||
	if singleton.ServerList[clientID].Host.BootTime < host.BootTime {
 | 
			
		||||
		singleton.ServerList[clientID].PrevHourlyTransferIn = singleton.ServerList[clientID].PrevHourlyTransferIn - int64(singleton.ServerList[clientID].State.NetInTransfer)
 | 
			
		||||
		singleton.ServerList[clientID].PrevHourlyTransferOut = singleton.ServerList[clientID].PrevHourlyTransferOut - int64(singleton.ServerList[clientID].State.NetOutTransfer)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dao.ServerList[clientID].Host = &host
 | 
			
		||||
	singleton.ServerList[clientID].Host = &host
 | 
			
		||||
	return &pb.Receipt{Proced: true}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
package dao
 | 
			
		||||
package singleton
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
package dao
 | 
			
		||||
package singleton
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/md5" // #nosec
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
package dao
 | 
			
		||||
package singleton
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
package dao
 | 
			
		||||
package singleton
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
@ -13,7 +13,7 @@ import (
 | 
			
		||||
	pb "github.com/naiba/nezha/proto"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var Version = "v0.12.3" // !!记得修改 README 中的 badge 版本!!
 | 
			
		||||
var Version = "v0.12.6" // !!记得修改 README 中的 badge 版本!!
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	Conf  *model.Config
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user