Merge pull request #206 from AkkiaS7/feat-api [no ci]
feat: API支持 Co-authored-by: AkkiaS7 <68485070+AkkiaS7@users.noreply.github.com>
This commit is contained in:
		
						commit
						dd79c7f5ab
					
				
							
								
								
									
										67
									
								
								cmd/dashboard/controller/api_v1.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								cmd/dashboard/controller/api_v1.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,67 @@
 | 
				
			|||||||
 | 
					package controller
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
						"github.com/naiba/nezha/pkg/mygin"
 | 
				
			||||||
 | 
						"github.com/naiba/nezha/service/singleton"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type apiV1 struct {
 | 
				
			||||||
 | 
						r gin.IRouter
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (v *apiV1) serve() {
 | 
				
			||||||
 | 
						r := v.r.Group("")
 | 
				
			||||||
 | 
						// API
 | 
				
			||||||
 | 
						r.Use(mygin.Authorize(mygin.AuthorizeOption{
 | 
				
			||||||
 | 
							Member:   true,
 | 
				
			||||||
 | 
							IsPage:   false,
 | 
				
			||||||
 | 
							AllowAPI: true,
 | 
				
			||||||
 | 
							Msg:      "访问此接口需要认证",
 | 
				
			||||||
 | 
							Btn:      "点此登录",
 | 
				
			||||||
 | 
							Redirect: "/login",
 | 
				
			||||||
 | 
						}))
 | 
				
			||||||
 | 
						r.GET("/server/list", v.serverList)
 | 
				
			||||||
 | 
						r.GET("/server/details", v.serverDetails)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// serverList 获取服务器列表 不传入Query参数则获取全部
 | 
				
			||||||
 | 
					// header: Authorization: Token
 | 
				
			||||||
 | 
					// query: tag (服务器分组)
 | 
				
			||||||
 | 
					func (v *apiV1) serverList(c *gin.Context) {
 | 
				
			||||||
 | 
						tag := c.Query("tag")
 | 
				
			||||||
 | 
						if tag != "" {
 | 
				
			||||||
 | 
							c.JSON(200, singleton.ServerAPI.GetListByTag(tag))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						c.JSON(200, singleton.ServerAPI.GetAllList())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// serverDetails 获取服务器信息 不传入Query参数则获取全部
 | 
				
			||||||
 | 
					// header: Authorization: Token
 | 
				
			||||||
 | 
					// query: id (服务器ID,逗号分隔,优先级高于tag查询)
 | 
				
			||||||
 | 
					// query: tag (服务器分组)
 | 
				
			||||||
 | 
					func (v *apiV1) serverDetails(c *gin.Context) {
 | 
				
			||||||
 | 
						var idList []uint64
 | 
				
			||||||
 | 
						idListStr := strings.Split(c.Query("id"), ",")
 | 
				
			||||||
 | 
						if c.Query("id") != "" {
 | 
				
			||||||
 | 
							idList = make([]uint64, len(idListStr))
 | 
				
			||||||
 | 
							for i, v := range idListStr {
 | 
				
			||||||
 | 
								id, _ := strconv.ParseUint(v, 10, 64)
 | 
				
			||||||
 | 
								idList[i] = id
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tag := c.Query("tag")
 | 
				
			||||||
 | 
						if tag != "" {
 | 
				
			||||||
 | 
							c.JSON(200, singleton.ServerAPI.GetStatusByTag(tag))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(idList) != 0 {
 | 
				
			||||||
 | 
							c.JSON(200, singleton.ServerAPI.GetStatusByIDList(idList))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						c.JSON(200, singleton.ServerAPI.GetAllStatus())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -43,6 +43,120 @@ func (ma *memberAPI) serve() {
 | 
				
			|||||||
	mr.POST("/setting", ma.updateSetting)
 | 
						mr.POST("/setting", ma.updateSetting)
 | 
				
			||||||
	mr.DELETE("/:model/:id", ma.delete)
 | 
						mr.DELETE("/:model/:id", ma.delete)
 | 
				
			||||||
	mr.POST("/logout", ma.logout)
 | 
						mr.POST("/logout", ma.logout)
 | 
				
			||||||
 | 
						mr.GET("/token", ma.getToken)
 | 
				
			||||||
 | 
						mr.POST("/token", ma.issueNewToken)
 | 
				
			||||||
 | 
						mr.DELETE("/token/:token", ma.deleteToken)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// API
 | 
				
			||||||
 | 
						v1 := ma.r.Group("v1")
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							apiv1 := &apiV1{v1}
 | 
				
			||||||
 | 
							apiv1.serve()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type apiResult struct {
 | 
				
			||||||
 | 
						Token string `json:"token"`
 | 
				
			||||||
 | 
						Note  string `json:"note"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// getToken 获取 Token
 | 
				
			||||||
 | 
					func (ma *memberAPI) getToken(c *gin.Context) {
 | 
				
			||||||
 | 
						u := c.MustGet(model.CtxKeyAuthorizedUser).(*model.User)
 | 
				
			||||||
 | 
						singleton.ApiLock.RLock()
 | 
				
			||||||
 | 
						defer singleton.ApiLock.RUnlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tokenList := singleton.UserIDToApiTokenList[u.ID]
 | 
				
			||||||
 | 
						res := make([]*apiResult, len(tokenList))
 | 
				
			||||||
 | 
						for i, token := range tokenList {
 | 
				
			||||||
 | 
							res[i] = &apiResult{
 | 
				
			||||||
 | 
								Token: token,
 | 
				
			||||||
 | 
								Note:  singleton.ApiTokenList[token].Note,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
							"code":    0,
 | 
				
			||||||
 | 
							"message": "success",
 | 
				
			||||||
 | 
							"result":  res,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type TokenForm struct {
 | 
				
			||||||
 | 
						Note string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// issueNewToken 生成新的 token
 | 
				
			||||||
 | 
					func (ma *memberAPI) issueNewToken(c *gin.Context) {
 | 
				
			||||||
 | 
						u := c.MustGet(model.CtxKeyAuthorizedUser).(*model.User)
 | 
				
			||||||
 | 
						tf := &TokenForm{}
 | 
				
			||||||
 | 
						err := c.ShouldBindJSON(tf)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.JSON(http.StatusOK, model.Response{
 | 
				
			||||||
 | 
								Code:    http.StatusBadRequest,
 | 
				
			||||||
 | 
								Message: fmt.Sprintf("请求错误:%s", err),
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						token := &model.ApiToken{
 | 
				
			||||||
 | 
							UserID: u.ID,
 | 
				
			||||||
 | 
							Token:  utils.MD5(fmt.Sprintf("%d%d%s", time.Now().UnixNano(), u.ID, u.Login)),
 | 
				
			||||||
 | 
							Note:   tf.Note,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						singleton.DB.Create(token)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						singleton.ApiLock.Lock()
 | 
				
			||||||
 | 
						singleton.ApiTokenList[token.Token] = token
 | 
				
			||||||
 | 
						singleton.UserIDToApiTokenList[u.ID] = append(singleton.UserIDToApiTokenList[u.ID], token.Token)
 | 
				
			||||||
 | 
						singleton.ApiLock.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.JSON(http.StatusOK, model.Response{
 | 
				
			||||||
 | 
							Code:    http.StatusOK,
 | 
				
			||||||
 | 
							Message: "success",
 | 
				
			||||||
 | 
							Result: map[string]string{
 | 
				
			||||||
 | 
								"token": token.Token,
 | 
				
			||||||
 | 
								"note":  token.Note,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// deleteToken 删除 token
 | 
				
			||||||
 | 
					func (ma *memberAPI) deleteToken(c *gin.Context) {
 | 
				
			||||||
 | 
						token := c.Param("token")
 | 
				
			||||||
 | 
						if token == "" {
 | 
				
			||||||
 | 
							c.JSON(http.StatusOK, model.Response{
 | 
				
			||||||
 | 
								Code:    http.StatusBadRequest,
 | 
				
			||||||
 | 
								Message: "token 不能为空",
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						singleton.ApiLock.Lock()
 | 
				
			||||||
 | 
						defer singleton.ApiLock.Unlock()
 | 
				
			||||||
 | 
						if _, ok := singleton.ApiTokenList[token]; !ok {
 | 
				
			||||||
 | 
							c.JSON(http.StatusOK, model.Response{
 | 
				
			||||||
 | 
								Code:    http.StatusBadRequest,
 | 
				
			||||||
 | 
								Message: "token 不存在",
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// 在数据库中删除该Token
 | 
				
			||||||
 | 
						singleton.DB.Unscoped().Delete(&model.ApiToken{}, "token = ?", token)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 在UserIDToApiTokenList中删除该Token
 | 
				
			||||||
 | 
						for i, t := range singleton.UserIDToApiTokenList[singleton.ApiTokenList[token].UserID] {
 | 
				
			||||||
 | 
							if t == token {
 | 
				
			||||||
 | 
								singleton.UserIDToApiTokenList[singleton.ApiTokenList[token].UserID] = append(singleton.UserIDToApiTokenList[singleton.ApiTokenList[token].UserID][:i], singleton.UserIDToApiTokenList[singleton.ApiTokenList[token].UserID][i+1:]...)
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(singleton.UserIDToApiTokenList[singleton.ApiTokenList[token].UserID]) == 0 {
 | 
				
			||||||
 | 
							delete(singleton.UserIDToApiTokenList, singleton.ApiTokenList[token].UserID)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// 在ApiTokenList中删除该Token
 | 
				
			||||||
 | 
						delete(singleton.ApiTokenList, token)
 | 
				
			||||||
 | 
						c.JSON(http.StatusOK, model.Response{
 | 
				
			||||||
 | 
							Code:    http.StatusOK,
 | 
				
			||||||
 | 
							Message: "success",
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ma *memberAPI) delete(c *gin.Context) {
 | 
					func (ma *memberAPI) delete(c *gin.Context) {
 | 
				
			||||||
@ -62,8 +176,21 @@ func (ma *memberAPI) delete(c *gin.Context) {
 | 
				
			|||||||
		if err == nil {
 | 
							if err == nil {
 | 
				
			||||||
			// 删除服务器
 | 
								// 删除服务器
 | 
				
			||||||
			singleton.ServerLock.Lock()
 | 
								singleton.ServerLock.Lock()
 | 
				
			||||||
 | 
								tag := singleton.ServerList[id].Tag
 | 
				
			||||||
			delete(singleton.SecretToID, singleton.ServerList[id].Secret)
 | 
								delete(singleton.SecretToID, singleton.ServerList[id].Secret)
 | 
				
			||||||
			delete(singleton.ServerList, id)
 | 
								delete(singleton.ServerList, id)
 | 
				
			||||||
 | 
								index := 0
 | 
				
			||||||
 | 
								for index < len(singleton.ServerTagToIDList[tag]) {
 | 
				
			||||||
 | 
									if singleton.ServerTagToIDList[tag][index] == id {
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									index++
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// 删除旧 Tag-ID 绑定关系
 | 
				
			||||||
 | 
								singleton.ServerTagToIDList[tag] = append(singleton.ServerTagToIDList[tag][:index], singleton.ServerTagToIDList[tag][index+1:]...)
 | 
				
			||||||
 | 
								if len(singleton.ServerTagToIDList[tag]) == 0 {
 | 
				
			||||||
 | 
									delete(singleton.ServerTagToIDList, tag)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			singleton.ServerLock.Unlock()
 | 
								singleton.ServerLock.Unlock()
 | 
				
			||||||
			singleton.ReSortServer()
 | 
								singleton.ReSortServer()
 | 
				
			||||||
			// 删除循环流量状态中的此服务器相关的记录
 | 
								// 删除循环流量状态中的此服务器相关的记录
 | 
				
			||||||
@ -194,6 +321,23 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) {
 | 
				
			|||||||
			// 设置新的 Secret-ID 绑定关系
 | 
								// 设置新的 Secret-ID 绑定关系
 | 
				
			||||||
			delete(singleton.SecretToID, singleton.ServerList[s.ID].Secret)
 | 
								delete(singleton.SecretToID, singleton.ServerList[s.ID].Secret)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							// 如果修改了Tag
 | 
				
			||||||
 | 
							if s.Tag != singleton.ServerList[s.ID].Tag {
 | 
				
			||||||
 | 
								index := 0
 | 
				
			||||||
 | 
								for index < len(singleton.ServerTagToIDList[s.Tag]) {
 | 
				
			||||||
 | 
									if singleton.ServerTagToIDList[s.Tag][index] == s.ID {
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									index++
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// 删除旧 Tag-ID 绑定关系
 | 
				
			||||||
 | 
								singleton.ServerTagToIDList[singleton.ServerList[s.ID].Tag] = append(singleton.ServerTagToIDList[singleton.ServerList[s.ID].Tag][:index], singleton.ServerTagToIDList[singleton.ServerList[s.ID].Tag][index+1:]...)
 | 
				
			||||||
 | 
								// 设置新的 Tag-ID 绑定关系
 | 
				
			||||||
 | 
								singleton.ServerTagToIDList[s.Tag] = append(singleton.ServerTagToIDList[s.Tag], s.ID)
 | 
				
			||||||
 | 
								if len(singleton.ServerTagToIDList[s.Tag]) == 0 {
 | 
				
			||||||
 | 
									delete(singleton.ServerTagToIDList, s.Tag)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		singleton.ServerList[s.ID] = &s
 | 
							singleton.ServerList[s.ID] = &s
 | 
				
			||||||
		singleton.ServerLock.Unlock()
 | 
							singleton.ServerLock.Unlock()
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
@ -202,6 +346,7 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) {
 | 
				
			|||||||
		singleton.ServerLock.Lock()
 | 
							singleton.ServerLock.Lock()
 | 
				
			||||||
		singleton.SecretToID[s.Secret] = s.ID
 | 
							singleton.SecretToID[s.Secret] = s.ID
 | 
				
			||||||
		singleton.ServerList[s.ID] = &s
 | 
							singleton.ServerList[s.ID] = &s
 | 
				
			||||||
 | 
							singleton.ServerTagToIDList[s.Tag] = append(singleton.ServerTagToIDList[s.Tag], s.ID)
 | 
				
			||||||
		singleton.ServerLock.Unlock()
 | 
							singleton.ServerLock.Unlock()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	singleton.ReSortServer()
 | 
						singleton.ReSortServer()
 | 
				
			||||||
 | 
				
			|||||||
@ -28,6 +28,16 @@ func (mp *memberPage) serve() {
 | 
				
			|||||||
	mr.GET("/cron", mp.cron)
 | 
						mr.GET("/cron", mp.cron)
 | 
				
			||||||
	mr.GET("/notification", mp.notification)
 | 
						mr.GET("/notification", mp.notification)
 | 
				
			||||||
	mr.GET("/setting", mp.setting)
 | 
						mr.GET("/setting", mp.setting)
 | 
				
			||||||
 | 
						mr.GET("/api", mp.api)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (mp *memberPage) api(c *gin.Context) {
 | 
				
			||||||
 | 
						singleton.ApiLock.RLock()
 | 
				
			||||||
 | 
						defer singleton.ApiLock.RUnlock()
 | 
				
			||||||
 | 
						c.HTML(http.StatusOK, "dashboard/api", mygin.CommonEnvironment(c, gin.H{
 | 
				
			||||||
 | 
							"title":  singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "ApiManagement"}),
 | 
				
			||||||
 | 
							"Tokens": singleton.ApiTokenList,
 | 
				
			||||||
 | 
						}))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (mp *memberPage) server(c *gin.Context) {
 | 
					func (mp *memberPage) server(c *gin.Context) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										8
									
								
								model/api_token.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								model/api_token.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					package model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ApiToken struct {
 | 
				
			||||||
 | 
						Common
 | 
				
			||||||
 | 
						UserID uint64 `json:"user_id"`
 | 
				
			||||||
 | 
						Token  string `json:"token"`
 | 
				
			||||||
 | 
						Note   string `json:"note"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -15,6 +15,7 @@ type AuthorizeOption struct {
 | 
				
			|||||||
	Guest    bool
 | 
						Guest    bool
 | 
				
			||||||
	Member   bool
 | 
						Member   bool
 | 
				
			||||||
	IsPage   bool
 | 
						IsPage   bool
 | 
				
			||||||
 | 
						AllowAPI bool
 | 
				
			||||||
	Msg      string
 | 
						Msg      string
 | 
				
			||||||
	Redirect string
 | 
						Redirect string
 | 
				
			||||||
	Btn      string
 | 
						Btn      string
 | 
				
			||||||
@ -34,7 +35,6 @@ func Authorize(opt AuthorizeOption) func(*gin.Context) {
 | 
				
			|||||||
			Link:  opt.Redirect,
 | 
								Link:  opt.Redirect,
 | 
				
			||||||
			Btn:   opt.Btn,
 | 
								Btn:   opt.Btn,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					 | 
				
			||||||
		var isLogin bool
 | 
							var isLogin bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// 用户鉴权
 | 
							// 用户鉴权
 | 
				
			||||||
@ -50,6 +50,23 @@ func Authorize(opt AuthorizeOption) func(*gin.Context) {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// API鉴权
 | 
				
			||||||
 | 
							if opt.AllowAPI {
 | 
				
			||||||
 | 
								apiToken := c.GetHeader("Authorization")
 | 
				
			||||||
 | 
								if apiToken != "" {
 | 
				
			||||||
 | 
									var u model.User
 | 
				
			||||||
 | 
									singleton.ApiLock.RLock()
 | 
				
			||||||
 | 
									if _, ok := singleton.ApiTokenList[apiToken]; ok {
 | 
				
			||||||
 | 
										err := singleton.DB.First(&u).Where("id = ?", singleton.ApiTokenList[apiToken].UserID).Error
 | 
				
			||||||
 | 
										isLogin = err == nil
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									singleton.ApiLock.RUnlock()
 | 
				
			||||||
 | 
									if isLogin {
 | 
				
			||||||
 | 
										c.Set(model.CtxKeyAuthorizedUser, &u)
 | 
				
			||||||
 | 
										c.Set("isAPI", true)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		// 已登录且只能游客访问
 | 
							// 已登录且只能游客访问
 | 
				
			||||||
		if isLogin && opt.Guest {
 | 
							if isLogin && opt.Guest {
 | 
				
			||||||
			ShowErrorPage(c, commonErr, opt.IsPage)
 | 
								ShowErrorPage(c, commonErr, opt.IsPage)
 | 
				
			||||||
 | 
				
			|||||||
@ -17,6 +17,7 @@ var adminPage = map[string]bool{
 | 
				
			|||||||
	"/setting":      true,
 | 
						"/setting":      true,
 | 
				
			||||||
	"/notification": true,
 | 
						"/notification": true,
 | 
				
			||||||
	"/cron":         true,
 | 
						"/cron":         true,
 | 
				
			||||||
 | 
						"/api":          true,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func CommonEnvironment(c *gin.Context, data map[string]interface{}) gin.H {
 | 
					func CommonEnvironment(c *gin.Context, data map[string]interface{}) gin.H {
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,7 @@ import (
 | 
				
			|||||||
	"math/rand"
 | 
						"math/rand"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"regexp"
 | 
						"regexp"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
	"unsafe"
 | 
						"unsafe"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -68,3 +69,27 @@ func IPDesensitize(ipAddr string) string {
 | 
				
			|||||||
	ipAddr = ipv6Desensitize(ipAddr)
 | 
						ipAddr = ipv6Desensitize(ipAddr)
 | 
				
			||||||
	return ipAddr
 | 
						return ipAddr
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SplitIPAddr 传入/分割的v4v6混合地址,返回v4和v6地址与有效地址
 | 
				
			||||||
 | 
					func SplitIPAddr(v4v6Bundle string) (string, string, string) {
 | 
				
			||||||
 | 
						ipList := strings.Split(v4v6Bundle, "/")
 | 
				
			||||||
 | 
						ipv4 := ""
 | 
				
			||||||
 | 
						ipv6 := ""
 | 
				
			||||||
 | 
						validIP := ""
 | 
				
			||||||
 | 
						if len(ipList) > 1 {
 | 
				
			||||||
 | 
							// 双栈
 | 
				
			||||||
 | 
							ipv4 = ipList[0]
 | 
				
			||||||
 | 
							ipv6 = ipList[1]
 | 
				
			||||||
 | 
							validIP = ipv4
 | 
				
			||||||
 | 
						} else if len(ipList) == 1 {
 | 
				
			||||||
 | 
							// 仅ipv4|ipv6
 | 
				
			||||||
 | 
							if strings.Contains(ipList[0], ":") {
 | 
				
			||||||
 | 
								ipv6 = ipList[0]
 | 
				
			||||||
 | 
								validIP = ipv6
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								ipv4 = ipList[0]
 | 
				
			||||||
 | 
								validIP = ipv4
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ipv4, ipv6, validIP
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										15
									
								
								resource/l10n/zh-CN.toml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								resource/l10n/zh-CN.toml
									
									
									
									
										vendored
									
									
								
							@ -469,6 +469,21 @@ other = "服务监控"
 | 
				
			|||||||
[ScheduledTasks]
 | 
					[ScheduledTasks]
 | 
				
			||||||
other = "计划任务"
 | 
					other = "计划任务"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[ApiManagement]
 | 
				
			||||||
 | 
					other="API"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[IssueNewApiToken]
 | 
				
			||||||
 | 
					other="添加Token"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Token]
 | 
				
			||||||
 | 
					other="Token"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[DeleteToken]
 | 
				
			||||||
 | 
					other="删除Token"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[ConfirmToDeleteThisToken]
 | 
				
			||||||
 | 
					other="确认删除Token"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[YouAreNotAuthorized]
 | 
					[YouAreNotAuthorized]
 | 
				
			||||||
other = "此页面需要登录"
 | 
					other = "此页面需要登录"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -202,6 +202,18 @@ function post(path, params, method = 'post') {
 | 
				
			|||||||
  document.body.removeChild(form);
 | 
					  document.body.removeChild(form);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function issueNewApiToken(apiToken) {
 | 
				
			||||||
 | 
					  const modal = $(".api.modal");
 | 
				
			||||||
 | 
					  modal.children(".header").text((apiToken ? LANG.Edit : LANG.Add) + ' ' + "API Token");
 | 
				
			||||||
 | 
					  modal
 | 
				
			||||||
 | 
					      .find(".nezha-primary-btn.button")
 | 
				
			||||||
 | 
					      .html(
 | 
				
			||||||
 | 
					          apiToken ? LANG.Edit + '<i class="edit icon"></i>' : LANG.Add + '<i class="add icon"></i>'
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					  modal.find("textarea[name=Note]").val(apiToken ? apiToken.Note : null);
 | 
				
			||||||
 | 
					  showFormModal(".api.modal", "#apiForm", "/api/token");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function addOrEditServer(server, conf) {
 | 
					function addOrEditServer(server, conf) {
 | 
				
			||||||
  const modal = $(".server.modal");
 | 
					  const modal = $(".server.modal");
 | 
				
			||||||
  modal.children(".header").text((server ? LANG.Edit : LANG.Add) + ' ' + LANG.Server);
 | 
					  modal.children(".header").text((server ? LANG.Edit : LANG.Add) + ' ' + LANG.Server);
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3
									
								
								resource/template/common/menu.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								resource/template/common/menu.html
									
									
									
									
										vendored
									
									
								
							@ -29,6 +29,9 @@
 | 
				
			|||||||
                        <a class="item" href="/">
 | 
					                        <a class="item" href="/">
 | 
				
			||||||
                            <i class="chart area icon"></i>{{tr "BackToHomepage"}}
 | 
					                            <i class="chart area icon"></i>{{tr "BackToHomepage"}}
 | 
				
			||||||
                        </a>
 | 
					                        </a>
 | 
				
			||||||
 | 
					                        <a class="item" href="/api">
 | 
				
			||||||
 | 
					                            <i class="chart key icon"></i>API Token
 | 
				
			||||||
 | 
					                        </a>
 | 
				
			||||||
                        {{else}}
 | 
					                        {{else}}
 | 
				
			||||||
                        <a class="item" href="/server">
 | 
					                        <a class="item" href="/server">
 | 
				
			||||||
                            <i class="terminal icon"></i>{{tr "AdminPanel"}}
 | 
					                            <i class="terminal icon"></i>{{tr "AdminPanel"}}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										19
									
								
								resource/template/component/api.html
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								resource/template/component/api.html
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					{{define "component/api"}}
 | 
				
			||||||
 | 
					<div class="ui tiny api modal transition hidden">
 | 
				
			||||||
 | 
					    <div class="header">{{tr "IssueNewApiToken"}}</div>
 | 
				
			||||||
 | 
					    <div class="content">
 | 
				
			||||||
 | 
					        <form id="apiForm" class="ui form">
 | 
				
			||||||
 | 
					            <input type="hidden" name="id">
 | 
				
			||||||
 | 
					            <div class="field">
 | 
				
			||||||
 | 
					                <label>{{tr "Note"}}</label>
 | 
				
			||||||
 | 
					                <textarea name="Note"></textarea>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </form>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div class=" actions">
 | 
				
			||||||
 | 
					        <div class="ui negative button">{{tr "Cancel"}}</div>
 | 
				
			||||||
 | 
					        <button class="ui positive nezha-primary-btn right labeled icon button">{{tr "Confirm"}}<i class="checkmark icon"></i>
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{{end}}
 | 
				
			||||||
							
								
								
									
										41
									
								
								resource/template/dashboard/api.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								resource/template/dashboard/api.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					{{define "dashboard/api"}}
 | 
				
			||||||
 | 
					{{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 nezha-primary-btn icon button" onclick="issueNewApiToken()"><i class="add icon"></i>
 | 
				
			||||||
 | 
					                    {{tr "IssueNewApiToken"}}
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <table class="ui very basic table">
 | 
				
			||||||
 | 
					            <thead>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <th>{{tr "Token"}}</th>
 | 
				
			||||||
 | 
					                <th>{{tr "Note"}}</th>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					            </thead>
 | 
				
			||||||
 | 
					            <tbody>
 | 
				
			||||||
 | 
					            {{range $token := .Tokens}}
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <td>{{$token.Token}}</td>
 | 
				
			||||||
 | 
					                <td>{{$token.Note}}</td>
 | 
				
			||||||
 | 
					                <td>
 | 
				
			||||||
 | 
					                    <div class="ui mini icon buttons">
 | 
				
			||||||
 | 
					                        <button class="ui button"
 | 
				
			||||||
 | 
					                                onclick="showConfirm('{{tr "DeleteToken"}}','{{tr "ConfirmToDeleteThisToken"}}',deleteRequest,'/api/token/'+{{$token.Token}})">
 | 
				
			||||||
 | 
					                        <i class="trash alternate outline icon"></i>
 | 
				
			||||||
 | 
					                        </button>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					            {{end}}
 | 
				
			||||||
 | 
					            </tbody>
 | 
				
			||||||
 | 
					        </table>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{{template "component/api"}}
 | 
				
			||||||
 | 
					{{template "common/footer" .}}
 | 
				
			||||||
 | 
					{{end}}
 | 
				
			||||||
							
								
								
									
										200
									
								
								service/singleton/api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								service/singleton/api.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,200 @@
 | 
				
			|||||||
 | 
					package singleton
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/naiba/nezha/model"
 | 
				
			||||||
 | 
						"github.com/naiba/nezha/pkg/utils"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						ApiTokenList         = make(map[string]*model.ApiToken)
 | 
				
			||||||
 | 
						UserIDToApiTokenList = make(map[uint64][]string)
 | 
				
			||||||
 | 
						ApiLock              sync.RWMutex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ServerAPI = &ServerAPIService{}
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ServerAPIService struct{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CommonResponse 常规返回结构 包含状态码 和 状态信息
 | 
				
			||||||
 | 
					type CommonResponse struct {
 | 
				
			||||||
 | 
						Code    int    `json:"code"`
 | 
				
			||||||
 | 
						Message string `json:"message"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type CommonServerInfo struct {
 | 
				
			||||||
 | 
						ID      uint64 `json:"id"`
 | 
				
			||||||
 | 
						Name    string `json:"name"`
 | 
				
			||||||
 | 
						Tag     string `json:"tag"`
 | 
				
			||||||
 | 
						IPV4    string `json:"ipv4"`
 | 
				
			||||||
 | 
						IPV6    string `json:"ipv6"`
 | 
				
			||||||
 | 
						ValidIP string `json:"valid_ip"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// StatusResponse 服务器状态子结构 包含服务器信息与状态信息
 | 
				
			||||||
 | 
					type StatusResponse struct {
 | 
				
			||||||
 | 
						CommonServerInfo
 | 
				
			||||||
 | 
						Host   *model.Host      `json:"host"`
 | 
				
			||||||
 | 
						Status *model.HostState `json:"status"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ServerStatusResponse 服务器状态返回结构 包含常规返回结构 和 服务器状态子结构
 | 
				
			||||||
 | 
					type ServerStatusResponse struct {
 | 
				
			||||||
 | 
						CommonResponse
 | 
				
			||||||
 | 
						Result []*StatusResponse `json:"result"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ServerInfoResponse 服务器信息返回结构 包含常规返回结构 和 服务器信息子结构
 | 
				
			||||||
 | 
					type ServerInfoResponse struct {
 | 
				
			||||||
 | 
						CommonResponse
 | 
				
			||||||
 | 
						Result []*CommonServerInfo `json:"result"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func InitAPI() {
 | 
				
			||||||
 | 
						ApiTokenList = make(map[string]*model.ApiToken)
 | 
				
			||||||
 | 
						UserIDToApiTokenList = make(map[uint64][]string)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func LoadAPI() {
 | 
				
			||||||
 | 
						InitAPI()
 | 
				
			||||||
 | 
						var tokenList []*model.ApiToken
 | 
				
			||||||
 | 
						DB.Find(&tokenList)
 | 
				
			||||||
 | 
						for _, token := range tokenList {
 | 
				
			||||||
 | 
							ApiTokenList[token.Token] = token
 | 
				
			||||||
 | 
							UserIDToApiTokenList[token.UserID] = append(UserIDToApiTokenList[token.UserID], token.Token)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetStatusByIDList 获取传入IDList的服务器状态信息
 | 
				
			||||||
 | 
					func (s *ServerAPIService) GetStatusByIDList(idList []uint64) *ServerStatusResponse {
 | 
				
			||||||
 | 
						res := &ServerStatusResponse{}
 | 
				
			||||||
 | 
						res.Result = make([]*StatusResponse, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ServerLock.RLock()
 | 
				
			||||||
 | 
						defer ServerLock.RUnlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, v := range idList {
 | 
				
			||||||
 | 
							server := ServerList[v]
 | 
				
			||||||
 | 
							if server == nil {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ipv4, ipv6, validIP := utils.SplitIPAddr(server.Host.IP)
 | 
				
			||||||
 | 
							info := CommonServerInfo{
 | 
				
			||||||
 | 
								ID:      server.ID,
 | 
				
			||||||
 | 
								Name:    server.Name,
 | 
				
			||||||
 | 
								Tag:     server.Tag,
 | 
				
			||||||
 | 
								IPV4:    ipv4,
 | 
				
			||||||
 | 
								IPV6:    ipv6,
 | 
				
			||||||
 | 
								ValidIP: validIP,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							res.Result = append(res.Result, &StatusResponse{
 | 
				
			||||||
 | 
								CommonServerInfo: info,
 | 
				
			||||||
 | 
								Host:             server.Host,
 | 
				
			||||||
 | 
								Status:           server.State,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						res.CommonResponse = CommonResponse{
 | 
				
			||||||
 | 
							Code:    0,
 | 
				
			||||||
 | 
							Message: "success",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return res
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetStatusByTag 获取传入分组的所有服务器状态信息
 | 
				
			||||||
 | 
					func (s *ServerAPIService) GetStatusByTag(tag string) *ServerStatusResponse {
 | 
				
			||||||
 | 
						return s.GetStatusByIDList(ServerTagToIDList[tag])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetAllStatus 获取所有服务器状态信息
 | 
				
			||||||
 | 
					func (s *ServerAPIService) GetAllStatus() *ServerStatusResponse {
 | 
				
			||||||
 | 
						res := &ServerStatusResponse{}
 | 
				
			||||||
 | 
						res.Result = make([]*StatusResponse, 0)
 | 
				
			||||||
 | 
						ServerLock.RLock()
 | 
				
			||||||
 | 
						defer ServerLock.RUnlock()
 | 
				
			||||||
 | 
						for _, v := range ServerList {
 | 
				
			||||||
 | 
							host := v.Host
 | 
				
			||||||
 | 
							state := v.State
 | 
				
			||||||
 | 
							if host == nil || state == nil {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ipv4, ipv6, validIP := utils.SplitIPAddr(host.IP)
 | 
				
			||||||
 | 
							info := CommonServerInfo{
 | 
				
			||||||
 | 
								ID:      v.ID,
 | 
				
			||||||
 | 
								Name:    v.Name,
 | 
				
			||||||
 | 
								Tag:     v.Tag,
 | 
				
			||||||
 | 
								IPV4:    ipv4,
 | 
				
			||||||
 | 
								IPV6:    ipv6,
 | 
				
			||||||
 | 
								ValidIP: validIP,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							res.Result = append(res.Result, &StatusResponse{
 | 
				
			||||||
 | 
								CommonServerInfo: info,
 | 
				
			||||||
 | 
								Host:             v.Host,
 | 
				
			||||||
 | 
								Status:           v.State,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						res.CommonResponse = CommonResponse{
 | 
				
			||||||
 | 
							Code:    0,
 | 
				
			||||||
 | 
							Message: "success",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return res
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetListByTag 获取传入分组的所有服务器信息
 | 
				
			||||||
 | 
					func (s *ServerAPIService) GetListByTag(tag string) *ServerInfoResponse {
 | 
				
			||||||
 | 
						res := &ServerInfoResponse{}
 | 
				
			||||||
 | 
						res.Result = make([]*CommonServerInfo, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ServerLock.RLock()
 | 
				
			||||||
 | 
						defer ServerLock.RUnlock()
 | 
				
			||||||
 | 
						for _, v := range ServerTagToIDList[tag] {
 | 
				
			||||||
 | 
							host := ServerList[v].Host
 | 
				
			||||||
 | 
							if host == nil {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ipv4, ipv6, validIP := utils.SplitIPAddr(host.IP)
 | 
				
			||||||
 | 
							info := &CommonServerInfo{
 | 
				
			||||||
 | 
								ID:      v,
 | 
				
			||||||
 | 
								Name:    ServerList[v].Name,
 | 
				
			||||||
 | 
								Tag:     ServerList[v].Tag,
 | 
				
			||||||
 | 
								IPV4:    ipv4,
 | 
				
			||||||
 | 
								IPV6:    ipv6,
 | 
				
			||||||
 | 
								ValidIP: validIP,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							res.Result = append(res.Result, info)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						res.CommonResponse = CommonResponse{
 | 
				
			||||||
 | 
							Code:    0,
 | 
				
			||||||
 | 
							Message: "success",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return res
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetAllList 获取所有服务器信息
 | 
				
			||||||
 | 
					func (s *ServerAPIService) GetAllList() *ServerInfoResponse {
 | 
				
			||||||
 | 
						res := &ServerInfoResponse{}
 | 
				
			||||||
 | 
						res.Result = make([]*CommonServerInfo, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ServerLock.RLock()
 | 
				
			||||||
 | 
						defer ServerLock.RUnlock()
 | 
				
			||||||
 | 
						for _, v := range ServerList {
 | 
				
			||||||
 | 
							host := v.Host
 | 
				
			||||||
 | 
							if host == nil {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ipv4, ipv6, validIP := utils.SplitIPAddr(host.IP)
 | 
				
			||||||
 | 
							info := &CommonServerInfo{
 | 
				
			||||||
 | 
								ID:      v.ID,
 | 
				
			||||||
 | 
								Name:    v.Name,
 | 
				
			||||||
 | 
								Tag:     v.Tag,
 | 
				
			||||||
 | 
								IPV4:    ipv4,
 | 
				
			||||||
 | 
								IPV6:    ipv6,
 | 
				
			||||||
 | 
								ValidIP: validIP,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							res.Result = append(res.Result, info)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						res.CommonResponse = CommonResponse{
 | 
				
			||||||
 | 
							Code:    0,
 | 
				
			||||||
 | 
							Message: "success",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return res
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -8,9 +8,10 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	ServerList map[uint64]*model.Server // [ServerID] -> model.Server
 | 
						ServerList        map[uint64]*model.Server // [ServerID] -> model.Server
 | 
				
			||||||
	SecretToID map[string]uint64        // [ServerSecret] -> ServerID
 | 
						SecretToID        map[string]uint64        // [ServerSecret] -> ServerID
 | 
				
			||||||
	ServerLock sync.RWMutex
 | 
						ServerTagToIDList map[string][]uint64      // [ServerTag] -> ServerID
 | 
				
			||||||
 | 
						ServerLock        sync.RWMutex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	SortedServerList []*model.Server // 用于存储服务器列表的 slice,按照服务器 ID 排序
 | 
						SortedServerList []*model.Server // 用于存储服务器列表的 slice,按照服务器 ID 排序
 | 
				
			||||||
	SortedServerLock sync.RWMutex
 | 
						SortedServerLock sync.RWMutex
 | 
				
			||||||
@ -20,6 +21,7 @@ var (
 | 
				
			|||||||
func InitServer() {
 | 
					func InitServer() {
 | 
				
			||||||
	ServerList = make(map[uint64]*model.Server)
 | 
						ServerList = make(map[uint64]*model.Server)
 | 
				
			||||||
	SecretToID = make(map[string]uint64)
 | 
						SecretToID = make(map[string]uint64)
 | 
				
			||||||
 | 
						ServerTagToIDList = make(map[string][]uint64)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//LoadServers 加载服务器列表并根据ID排序
 | 
					//LoadServers 加载服务器列表并根据ID排序
 | 
				
			||||||
@ -33,6 +35,7 @@ func LoadServers() {
 | 
				
			|||||||
		innerS.State = &model.HostState{}
 | 
							innerS.State = &model.HostState{}
 | 
				
			||||||
		ServerList[innerS.ID] = &innerS
 | 
							ServerList[innerS.ID] = &innerS
 | 
				
			||||||
		SecretToID[innerS.Secret] = innerS.ID
 | 
							SecretToID[innerS.Secret] = innerS.ID
 | 
				
			||||||
 | 
							ServerTagToIDList[innerS.Tag] = append(ServerTagToIDList[innerS.Tag], innerS.ID)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ReSortServer()
 | 
						ReSortServer()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -38,6 +38,7 @@ func LoadSingleton() {
 | 
				
			|||||||
	LoadNotifications() // 加载通知服务
 | 
						LoadNotifications() // 加载通知服务
 | 
				
			||||||
	LoadServers()       // 加载服务器列表
 | 
						LoadServers()       // 加载服务器列表
 | 
				
			||||||
	LoadCronTasks()     // 加载定时任务
 | 
						LoadCronTasks()     // 加载定时任务
 | 
				
			||||||
 | 
						LoadAPI()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// InitConfigFromPath 从给出的文件路径中加载配置
 | 
					// InitConfigFromPath 从给出的文件路径中加载配置
 | 
				
			||||||
@ -63,7 +64,7 @@ func InitDBFromPath(path string) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	err = DB.AutoMigrate(model.Server{}, model.User{},
 | 
						err = DB.AutoMigrate(model.Server{}, model.User{},
 | 
				
			||||||
		model.Notification{}, model.AlertRule{}, model.Monitor{},
 | 
							model.Notification{}, model.AlertRule{}, model.Monitor{},
 | 
				
			||||||
		model.MonitorHistory{}, model.Cron{}, model.Transfer{})
 | 
							model.MonitorHistory{}, model.Cron{}, model.Transfer{}, model.ApiToken{})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		panic(err)
 | 
							panic(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user