From 76928b71d9d90180fa1448d82753dc9f980ecc3f Mon Sep 17 00:00:00 2001 From: Akkia Date: Tue, 17 May 2022 11:21:27 +0800 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20=E6=9C=8D=E5=8A=A1=E5=99=A8?= =?UTF-8?q?=E7=8A=B6=E6=80=81API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/dashboard/controller/member_api.go | 59 ++++++++++++++++++ model/api_token.go | 10 ++++ pkg/mygin/auth.go | 14 ++++- service/singleton/api.go | 83 ++++++++++++++++++++++++++ service/singleton/server.go | 9 ++- service/singleton/singleton.go | 2 +- 6 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 model/api_token.go create mode 100644 service/singleton/api.go diff --git a/cmd/dashboard/controller/member_api.go b/cmd/dashboard/controller/member_api.go index 1b4f2c8763..904ef33ff1 100644 --- a/cmd/dashboard/controller/member_api.go +++ b/cmd/dashboard/controller/member_api.go @@ -43,6 +43,47 @@ func (ma *memberAPI) serve() { mr.POST("/setting", ma.updateSetting) mr.DELETE("/:model/:id", ma.delete) mr.POST("/logout", ma.logout) + + // API + mr.GET("/server/list", ma.serverList) + mr.GET("/server/details", ma.serverDetails) +} + +// serverList 获取服务器列表 +func (ma *memberAPI) serverList(c *gin.Context) { + +} + +// serverDetails 获取服务器信息 +// header: Authorization: Token +// query: idList (服务器ID,逗号分隔,优先级高于tag查询) +// query: tag (服务器分组) +func (ma *memberAPI) serverDetails(c *gin.Context) { + token, _ := c.Cookie("Authorization") + 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") + serverAPI := &singleton.ServerAPI{ + Token: token, + IDList: idList, + Tag: tag, + } + if tag != "" { + c.JSON(200, serverAPI.GetStatusByTag()) + return + } + if len(idList) != 0 { + c.JSON(200, serverAPI.GetStatusByIDList()) + return + } + c.JSON(200, serverAPI.GetAllStatus()) } func (ma *memberAPI) delete(c *gin.Context) { @@ -194,6 +235,23 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) { // 设置新的 Secret-ID 绑定关系 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.ServerLock.Unlock() } else { @@ -202,6 +260,7 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) { singleton.ServerLock.Lock() singleton.SecretToID[s.Secret] = s.ID singleton.ServerList[s.ID] = &s + singleton.ServerTagToIDList[s.Tag] = append(singleton.ServerTagToIDList[s.Tag], s.ID) singleton.ServerLock.Unlock() } singleton.ReSortServer() diff --git a/model/api_token.go b/model/api_token.go new file mode 100644 index 0000000000..b07a571cd7 --- /dev/null +++ b/model/api_token.go @@ -0,0 +1,10 @@ +package model + +import "time" + +type ApiToken struct { + Common + UserId uint64 `json:"user_id"` + Token string `json:"token"` + TokenExpired time.Time `json:"token_expired"` +} diff --git a/pkg/mygin/auth.go b/pkg/mygin/auth.go index 38123f88dc..9dcdfa16b9 100644 --- a/pkg/mygin/auth.go +++ b/pkg/mygin/auth.go @@ -34,7 +34,6 @@ func Authorize(opt AuthorizeOption) func(*gin.Context) { Link: opt.Redirect, Btn: opt.Btn, } - var isLogin bool // 用户鉴权 @@ -50,6 +49,19 @@ func Authorize(opt AuthorizeOption) func(*gin.Context) { } } + // API鉴权 + apiToken := c.GetHeader("Authorization") + if apiToken != "" { + var t model.ApiToken + // TODO: 需要有缓存机制 减少数据库查询次数 + if err := singleton.DB.Where("token = ?", apiToken).First(&t).Error; err == nil { + isLogin = t.TokenExpired.After(time.Now()) + } + if isLogin { + c.Set(model.CtxKeyAuthorizedUser, &t) + } + } + // 已登录且只能游客访问 if isLogin && opt.Guest { ShowErrorPage(c, commonErr, opt.IsPage) diff --git a/service/singleton/api.go b/service/singleton/api.go new file mode 100644 index 0000000000..b7fa1a6a92 --- /dev/null +++ b/service/singleton/api.go @@ -0,0 +1,83 @@ +package singleton + +import "github.com/naiba/nezha/model" + +type ServerAPI struct { + Token string // 传入Token 后期可能会需要用于scope判定 + IDList []uint64 + Tag string +} + +type CommonResponse struct { + Code int `json:"code"` + Message string `json:"message"` +} + +type StatusResponse struct { + Host *model.Host `json:"host"` + Status *model.HostState `json:"status"` +} + +type ServerStatusResponse struct { + CommonResponse + Result []*StatusResponse `json:"result"` +} + +// GetStatusByIDList 获取传入IDList的服务器状态信息 +func (s *ServerAPI) GetStatusByIDList() *ServerStatusResponse { + var res []*StatusResponse + + ServerLock.RLock() + defer ServerLock.RUnlock() + + for _, v := range s.IDList { + server := ServerList[v] + if server == nil { + continue + } + res = append(res, &StatusResponse{ + Host: server.Host, + Status: server.State, + }) + } + + return &ServerStatusResponse{ + CommonResponse: CommonResponse{ + Code: 0, + Message: "success", + }, + Result: res, + } +} + +// GetStatusByTag 获取传入分组的所有服务器状态信息 +func (s *ServerAPI) GetStatusByTag() *ServerStatusResponse { + s.IDList = ServerTagToIDList[s.Tag] + return s.GetStatusByIDList() +} + +// GetAllStatus 获取所有服务器状态信息 +func (s *ServerAPI) GetAllStatus() *ServerStatusResponse { + ServerLock.RLock() + defer ServerLock.RUnlock() + var res []*StatusResponse + for _, v := range ServerList { + host := v.Host + state := v.State + if host == nil || state == nil { + continue + } + res = append(res, &StatusResponse{ + Host: v.Host, + Status: v.State, + }) + } + + return &ServerStatusResponse{ + CommonResponse: CommonResponse{ + Code: 0, + Message: "success", + }, + Result: res, + } +} diff --git a/service/singleton/server.go b/service/singleton/server.go index 9dfcf72cf7..31ad29b85c 100644 --- a/service/singleton/server.go +++ b/service/singleton/server.go @@ -8,9 +8,10 @@ import ( ) var ( - ServerList map[uint64]*model.Server // [ServerID] -> model.Server - SecretToID map[string]uint64 // [ServerSecret] -> ServerID - ServerLock sync.RWMutex + ServerList map[uint64]*model.Server // [ServerID] -> model.Server + SecretToID map[string]uint64 // [ServerSecret] -> ServerID + ServerTagToIDList map[string][]uint64 // [ServerTag] -> ServerID + ServerLock sync.RWMutex SortedServerList []*model.Server // 用于存储服务器列表的 slice,按照服务器 ID 排序 SortedServerLock sync.RWMutex @@ -20,6 +21,7 @@ var ( func InitServer() { ServerList = make(map[uint64]*model.Server) SecretToID = make(map[string]uint64) + ServerTagToIDList = make(map[string][]uint64) } //LoadServers 加载服务器列表并根据ID排序 @@ -33,6 +35,7 @@ func LoadServers() { innerS.State = &model.HostState{} ServerList[innerS.ID] = &innerS SecretToID[innerS.Secret] = innerS.ID + ServerTagToIDList[innerS.Tag] = append(ServerTagToIDList[innerS.Tag], innerS.ID) } ReSortServer() } diff --git a/service/singleton/singleton.go b/service/singleton/singleton.go index 7a3f1e5921..e66e01cebf 100644 --- a/service/singleton/singleton.go +++ b/service/singleton/singleton.go @@ -63,7 +63,7 @@ func InitDBFromPath(path string) { } err = DB.AutoMigrate(model.Server{}, model.User{}, model.Notification{}, model.AlertRule{}, model.Monitor{}, - model.MonitorHistory{}, model.Cron{}, model.Transfer{}) + model.MonitorHistory{}, model.Cron{}, model.Transfer{}, model.ApiToken{}) if err != nil { panic(err) } From f5f71b252e16ffe72bbff2141320a38a7dd923fc Mon Sep 17 00:00:00 2001 From: Akkia Date: Tue, 17 May 2022 20:16:46 +0800 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=E6=9C=8D=E5=8A=A1=E5=99=A8?= =?UTF-8?q?=E4=BF=A1=E6=81=AFAPI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/dashboard/controller/member_api.go | 18 +++- pkg/utils/utils.go | 25 +++++ service/singleton/api.go | 142 +++++++++++++++++++++---- 3 files changed, 160 insertions(+), 25 deletions(-) diff --git a/cmd/dashboard/controller/member_api.go b/cmd/dashboard/controller/member_api.go index 904ef33ff1..334160f00c 100644 --- a/cmd/dashboard/controller/member_api.go +++ b/cmd/dashboard/controller/member_api.go @@ -49,12 +49,24 @@ func (ma *memberAPI) serve() { mr.GET("/server/details", ma.serverDetails) } -// serverList 获取服务器列表 +// serverList 获取服务器列表 不传入Query参数则获取全部 +// header: Authorization: Token +// query: tag (服务器分组) func (ma *memberAPI) serverList(c *gin.Context) { - + token, _ := c.Cookie("Authorization") + tag := c.Query("tag") + serverAPI := &singleton.ServerAPI{ + Token: token, + Tag: tag, + } + if tag != "" { + c.JSON(200, serverAPI.GetListByTag()) + return + } + c.JSON(200, serverAPI.GetAllList()) } -// serverDetails 获取服务器信息 +// serverDetails 获取服务器信息 不传入Query参数则获取全部 // header: Authorization: Token // query: idList (服务器ID,逗号分隔,优先级高于tag查询) // query: tag (服务器分组) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 5989423c98..64adc44688 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -6,6 +6,7 @@ import ( "math/rand" "os" "regexp" + "strings" "time" "unsafe" @@ -68,3 +69,27 @@ func IPDesensitize(ipAddr string) string { ipAddr = ipv6Desensitize(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 +} diff --git a/service/singleton/api.go b/service/singleton/api.go index b7fa1a6a92..6d02c941da 100644 --- a/service/singleton/api.go +++ b/service/singleton/api.go @@ -1,6 +1,9 @@ package singleton -import "github.com/naiba/nezha/model" +import ( + "github.com/naiba/nezha/model" + "github.com/naiba/nezha/pkg/utils" +) type ServerAPI struct { Token string // 传入Token 后期可能会需要用于scope判定 @@ -8,24 +11,44 @@ type ServerAPI struct { Tag string } +// 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"` +} + // GetStatusByIDList 获取传入IDList的服务器状态信息 func (s *ServerAPI) GetStatusByIDList() *ServerStatusResponse { - var res []*StatusResponse + res := &ServerStatusResponse{} + res.Result = make([]*StatusResponse, 0) ServerLock.RLock() defer ServerLock.RUnlock() @@ -35,19 +58,26 @@ func (s *ServerAPI) GetStatusByIDList() *ServerStatusResponse { if server == nil { continue } - res = append(res, &StatusResponse{ - Host: server.Host, - Status: server.State, + 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, }) } - - return &ServerStatusResponse{ - CommonResponse: CommonResponse{ - Code: 0, - Message: "success", - }, - Result: res, + res.CommonResponse = CommonResponse{ + Code: 0, + Message: "success", } + return res } // GetStatusByTag 获取传入分组的所有服务器状态信息 @@ -58,26 +88,94 @@ func (s *ServerAPI) GetStatusByTag() *ServerStatusResponse { // GetAllStatus 获取所有服务器状态信息 func (s *ServerAPI) GetAllStatus() *ServerStatusResponse { + res := &ServerStatusResponse{} + res.Result = make([]*StatusResponse, 0) ServerLock.RLock() defer ServerLock.RUnlock() - var res []*StatusResponse for _, v := range ServerList { host := v.Host state := v.State if host == nil || state == nil { continue } - res = append(res, &StatusResponse{ - Host: v.Host, - Status: v.State, + 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 +} - return &ServerStatusResponse{ - CommonResponse: CommonResponse{ - Code: 0, - Message: "success", - }, - Result: res, +// GetListByTag 获取传入分组的所有服务器信息 +func (s *ServerAPI) GetListByTag() *ServerInfoResponse { + res := &ServerInfoResponse{} + res.Result = make([]*CommonServerInfo, 0) + + ServerLock.RLock() + defer ServerLock.RUnlock() + for _, v := range ServerTagToIDList[s.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 *ServerAPI) 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 } From 990394bf463bef9f60a8b60f3f2ceaac47889058 Mon Sep 17 00:00:00 2001 From: Akkia Date: Wed, 18 May 2022 10:10:35 +0800 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20Token=E7=94=9F=E6=88=90=EF=BD=9C?= =?UTF-8?q?=E5=AD=98=E5=82=A8=EF=BD=9C=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/dashboard/controller/api_v1.go | 78 +++++++++++++++++ cmd/dashboard/controller/member_api.go | 114 ++++++++++++++++--------- model/api_token.go | 7 +- pkg/mygin/auth.go | 23 ++--- service/singleton/api.go | 20 +++++ service/singleton/singleton.go | 1 + 6 files changed, 188 insertions(+), 55 deletions(-) create mode 100644 cmd/dashboard/controller/api_v1.go diff --git a/cmd/dashboard/controller/api_v1.go b/cmd/dashboard/controller/api_v1.go new file mode 100644 index 0000000000..203b5561a3 --- /dev/null +++ b/cmd/dashboard/controller/api_v1.go @@ -0,0 +1,78 @@ +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) { + token, _ := c.Cookie("Authorization") + tag := c.Query("tag") + serverAPI := &singleton.ServerAPI{ + Token: token, + Tag: tag, + } + if tag != "" { + c.JSON(200, serverAPI.GetListByTag()) + return + } + c.JSON(200, serverAPI.GetAllList()) +} + +// serverDetails 获取服务器信息 不传入Query参数则获取全部 +// header: Authorization: Token +// query: idList (服务器ID,逗号分隔,优先级高于tag查询) +// query: tag (服务器分组) +func (v *apiV1) serverDetails(c *gin.Context) { + token, _ := c.Cookie("Authorization") + 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") + serverAPI := &singleton.ServerAPI{ + Token: token, + IDList: idList, + Tag: tag, + } + if tag != "" { + c.JSON(200, serverAPI.GetStatusByTag()) + return + } + if len(idList) != 0 { + c.JSON(200, serverAPI.GetStatusByIDList()) + return + } + c.JSON(200, serverAPI.GetAllStatus()) +} diff --git a/cmd/dashboard/controller/member_api.go b/cmd/dashboard/controller/member_api.go index 334160f00c..9adc40fcb9 100644 --- a/cmd/dashboard/controller/member_api.go +++ b/cmd/dashboard/controller/member_api.go @@ -43,59 +43,93 @@ func (ma *memberAPI) serve() { mr.POST("/setting", ma.updateSetting) mr.DELETE("/:model/:id", ma.delete) mr.POST("/logout", ma.logout) + mr.GET("/token", ma.getToken) + mr.POST("/token", ma.issueNewToken) + mr.DELETE("/token/:token", ma.deleteToken) // API - mr.GET("/server/list", ma.serverList) - mr.GET("/server/details", ma.serverDetails) + v1 := ma.r.Group("v1") + { + apiv1 := &apiV1{v1} + apiv1.serve() + } } -// serverList 获取服务器列表 不传入Query参数则获取全部 -// header: Authorization: Token -// query: tag (服务器分组) -func (ma *memberAPI) serverList(c *gin.Context) { - token, _ := c.Cookie("Authorization") - tag := c.Query("tag") - serverAPI := &singleton.ServerAPI{ - Token: token, - Tag: tag, - } - if tag != "" { - c.JSON(200, serverAPI.GetListByTag()) - return - } - c.JSON(200, serverAPI.GetAllList()) +type apiResult struct { + Token string `json:"token"` } -// serverDetails 获取服务器信息 不传入Query参数则获取全部 -// header: Authorization: Token -// query: idList (服务器ID,逗号分隔,优先级高于tag查询) -// query: tag (服务器分组) -func (ma *memberAPI) serverDetails(c *gin.Context) { - token, _ := c.Cookie("Authorization") - 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 +// getToken 获取 Token +func (ma *memberAPI) getToken(c *gin.Context) { + u := c.MustGet(model.CtxKeyAuthorizedUser).(*model.User) + tokenList := singleton.UserIDToApiTokenList[u.ID] + res := make([]*apiResult, len(tokenList)) + for i, token := range tokenList { + res[i] = &apiResult{ + Token: token, } } - tag := c.Query("tag") - serverAPI := &singleton.ServerAPI{ - Token: token, - IDList: idList, - Tag: tag, + c.JSON(http.StatusOK, gin.H{ + "code": 0, + "message": "success", + "result": res, + }) +} + +// issueNewToken 生成新的 token +func (ma *memberAPI) issueNewToken(c *gin.Context) { + u := c.MustGet(model.CtxKeyAuthorizedUser).(*model.User) + token := &model.ApiToken{ + UserID: u.ID, + Token: utils.MD5(fmt.Sprintf("%d%d%s", time.Now().UnixNano(), u.ID, u.Login)), } - if tag != "" { - c.JSON(200, serverAPI.GetStatusByTag()) + singleton.DB.Create(token) + singleton.ApiTokenList[token.Token] = token + singleton.UserIDToApiTokenList[u.ID] = append(singleton.UserIDToApiTokenList[u.ID], token.Token) + c.JSON(http.StatusOK, model.Response{ + Code: http.StatusOK, + Message: "success", + Result: map[string]string{ + "token": token.Token, + }, + }) +} + +// 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 } - if len(idList) != 0 { - c.JSON(200, serverAPI.GetStatusByIDList()) + if _, ok := singleton.ApiTokenList[token]; !ok { + c.JSON(http.StatusOK, model.Response{ + Code: http.StatusBadRequest, + Message: "token 不存在", + }) return } - c.JSON(200, serverAPI.GetAllStatus()) + // 在数据库中删除该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) { diff --git a/model/api_token.go b/model/api_token.go index b07a571cd7..db10e26d29 100644 --- a/model/api_token.go +++ b/model/api_token.go @@ -1,10 +1,7 @@ package model -import "time" - type ApiToken struct { Common - UserId uint64 `json:"user_id"` - Token string `json:"token"` - TokenExpired time.Time `json:"token_expired"` + UserID uint64 `json:"user_id"` + Token string `json:"token"` } diff --git a/pkg/mygin/auth.go b/pkg/mygin/auth.go index 9dcdfa16b9..dd56d9242c 100644 --- a/pkg/mygin/auth.go +++ b/pkg/mygin/auth.go @@ -15,6 +15,7 @@ type AuthorizeOption struct { Guest bool Member bool IsPage bool + AllowAPI bool Msg string Redirect string Btn string @@ -50,18 +51,20 @@ func Authorize(opt AuthorizeOption) func(*gin.Context) { } // API鉴权 - apiToken := c.GetHeader("Authorization") - if apiToken != "" { - var t model.ApiToken - // TODO: 需要有缓存机制 减少数据库查询次数 - if err := singleton.DB.Where("token = ?", apiToken).First(&t).Error; err == nil { - isLogin = t.TokenExpired.After(time.Now()) - } - if isLogin { - c.Set(model.CtxKeyAuthorizedUser, &t) + if opt.AllowAPI { + apiToken := c.GetHeader("Authorization") + if apiToken != "" { + var u model.User + if _, ok := singleton.ApiTokenList[apiToken]; ok { + err := singleton.DB.First(&u).Where("id = ?", singleton.ApiTokenList[apiToken].UserID).Error + isLogin = err == nil + } + if isLogin { + c.Set(model.CtxKeyAuthorizedUser, &u) + c.Set("isAPI", true) + } } } - // 已登录且只能游客访问 if isLogin && opt.Guest { ShowErrorPage(c, commonErr, opt.IsPage) diff --git a/service/singleton/api.go b/service/singleton/api.go index 6d02c941da..9cfbb4ee91 100644 --- a/service/singleton/api.go +++ b/service/singleton/api.go @@ -5,6 +5,11 @@ import ( "github.com/naiba/nezha/pkg/utils" ) +var ( + ApiTokenList = make(map[string]*model.ApiToken) + UserIDToApiTokenList = make(map[uint64][]string) +) + type ServerAPI struct { Token string // 传入Token 后期可能会需要用于scope判定 IDList []uint64 @@ -45,6 +50,21 @@ type ServerInfoResponse struct { 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 *ServerAPI) GetStatusByIDList() *ServerStatusResponse { res := &ServerStatusResponse{} diff --git a/service/singleton/singleton.go b/service/singleton/singleton.go index f94cd4fa49..d8eec66aa9 100644 --- a/service/singleton/singleton.go +++ b/service/singleton/singleton.go @@ -38,6 +38,7 @@ func LoadSingleton() { LoadNotifications() // 加载通知服务 LoadServers() // 加载服务器列表 LoadCronTasks() // 加载定时任务 + LoadAPI() } // InitConfigFromPath 从给出的文件路径中加载配置 From 73df5fa0debcda1381eec719eb51f4b7b4a34768 Mon Sep 17 00:00:00 2001 From: Akkia Date: Wed, 18 May 2022 10:28:24 +0800 Subject: [PATCH 4/8] =?UTF-8?q?update:=20=E7=BA=BF=E7=A8=8B=E5=AE=89?= =?UTF-8?q?=E5=85=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/dashboard/controller/member_api.go | 9 +++++++++ pkg/mygin/auth.go | 2 ++ service/singleton/api.go | 2 ++ 3 files changed, 13 insertions(+) diff --git a/cmd/dashboard/controller/member_api.go b/cmd/dashboard/controller/member_api.go index 9adc40fcb9..9e737559d6 100644 --- a/cmd/dashboard/controller/member_api.go +++ b/cmd/dashboard/controller/member_api.go @@ -62,7 +62,9 @@ type apiResult struct { // getToken 获取 Token func (ma *memberAPI) getToken(c *gin.Context) { u := c.MustGet(model.CtxKeyAuthorizedUser).(*model.User) + singleton.ApiLock.RLock() tokenList := singleton.UserIDToApiTokenList[u.ID] + singleton.ApiLock.RUnlock() res := make([]*apiResult, len(tokenList)) for i, token := range tokenList { res[i] = &apiResult{ @@ -84,8 +86,12 @@ func (ma *memberAPI) issueNewToken(c *gin.Context) { Token: utils.MD5(fmt.Sprintf("%d%d%s", time.Now().UnixNano(), u.ID, u.Login)), } 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", @@ -105,6 +111,8 @@ func (ma *memberAPI) deleteToken(c *gin.Context) { }) return } + singleton.ApiLock.Lock() + defer singleton.ApiLock.Unlock() if _, ok := singleton.ApiTokenList[token]; !ok { c.JSON(http.StatusOK, model.Response{ Code: http.StatusBadRequest, @@ -114,6 +122,7 @@ func (ma *memberAPI) deleteToken(c *gin.Context) { } // 在数据库中删除该Token singleton.DB.Unscoped().Delete(&model.ApiToken{}, "token = ?", token) + // 在UserIDToApiTokenList中删除该Token for i, t := range singleton.UserIDToApiTokenList[singleton.ApiTokenList[token].UserID] { if t == token { diff --git a/pkg/mygin/auth.go b/pkg/mygin/auth.go index dd56d9242c..0f28974897 100644 --- a/pkg/mygin/auth.go +++ b/pkg/mygin/auth.go @@ -55,10 +55,12 @@ func Authorize(opt AuthorizeOption) func(*gin.Context) { 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) diff --git a/service/singleton/api.go b/service/singleton/api.go index 9cfbb4ee91..f6da4aa552 100644 --- a/service/singleton/api.go +++ b/service/singleton/api.go @@ -3,11 +3,13 @@ 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 ) type ServerAPI struct { From fcd0ccae563cc58ca149feece7e89694c7fd4a02 Mon Sep 17 00:00:00 2001 From: Akkia Date: Wed, 18 May 2022 11:16:14 +0800 Subject: [PATCH 5/8] typo --- cmd/dashboard/controller/api_v1.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/dashboard/controller/api_v1.go b/cmd/dashboard/controller/api_v1.go index 203b5561a3..7c6a937da7 100644 --- a/cmd/dashboard/controller/api_v1.go +++ b/cmd/dashboard/controller/api_v1.go @@ -47,7 +47,7 @@ func (v *apiV1) serverList(c *gin.Context) { // serverDetails 获取服务器信息 不传入Query参数则获取全部 // header: Authorization: Token -// query: idList (服务器ID,逗号分隔,优先级高于tag查询) +// query: id (服务器ID,逗号分隔,优先级高于tag查询) // query: tag (服务器分组) func (v *apiV1) serverDetails(c *gin.Context) { token, _ := c.Cookie("Authorization") From 914d7cbbae315ef968308f01e6233093452f82b2 Mon Sep 17 00:00:00 2001 From: Akkia Date: Wed, 18 May 2022 20:52:18 +0800 Subject: [PATCH 6/8] =?UTF-8?q?add:=20API=20Token=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=9A=84=E5=89=8D=E7=AB=AF=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/dashboard/controller/member_api.go | 20 +++++++++++- cmd/dashboard/controller/member_page.go | 10 ++++++ model/api_token.go | 1 + pkg/mygin/mygin.go | 1 + resource/l10n/en-US.toml | 15 +++++++++ resource/l10n/es-ES.toml | 15 +++++++++ resource/l10n/zh-CN.toml | 15 +++++++++ resource/static/main.js | 12 ++++++++ resource/template/common/menu.html | 1 + resource/template/component/api.html | 19 ++++++++++++ resource/template/dashboard/api.html | 41 +++++++++++++++++++++++++ 11 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 resource/template/component/api.html create mode 100644 resource/template/dashboard/api.html diff --git a/cmd/dashboard/controller/member_api.go b/cmd/dashboard/controller/member_api.go index 9e737559d6..03af65734c 100644 --- a/cmd/dashboard/controller/member_api.go +++ b/cmd/dashboard/controller/member_api.go @@ -57,18 +57,21 @@ func (ma *memberAPI) 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] - singleton.ApiLock.RUnlock() 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{ @@ -78,12 +81,26 @@ func (ma *memberAPI) getToken(c *gin.Context) { }) } +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) @@ -97,6 +114,7 @@ func (ma *memberAPI) issueNewToken(c *gin.Context) { Message: "success", Result: map[string]string{ "token": token.Token, + "note": token.Note, }, }) } diff --git a/cmd/dashboard/controller/member_page.go b/cmd/dashboard/controller/member_page.go index df0cf7d51f..7f188231f4 100644 --- a/cmd/dashboard/controller/member_page.go +++ b/cmd/dashboard/controller/member_page.go @@ -28,6 +28,16 @@ func (mp *memberPage) serve() { mr.GET("/cron", mp.cron) mr.GET("/notification", mp.notification) 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) { diff --git a/model/api_token.go b/model/api_token.go index db10e26d29..1a777c926b 100644 --- a/model/api_token.go +++ b/model/api_token.go @@ -4,4 +4,5 @@ type ApiToken struct { Common UserID uint64 `json:"user_id"` Token string `json:"token"` + Note string `json:"note"` } diff --git a/pkg/mygin/mygin.go b/pkg/mygin/mygin.go index d2afde0608..47593cba86 100644 --- a/pkg/mygin/mygin.go +++ b/pkg/mygin/mygin.go @@ -17,6 +17,7 @@ var adminPage = map[string]bool{ "/setting": true, "/notification": true, "/cron": true, + "/api": true, } func CommonEnvironment(c *gin.Context, data map[string]interface{}) gin.H { diff --git a/resource/l10n/en-US.toml b/resource/l10n/en-US.toml index 0a13711542..f03dd87496 100644 --- a/resource/l10n/en-US.toml +++ b/resource/l10n/en-US.toml @@ -469,6 +469,21 @@ other = "Services" [ScheduledTasks] other = "Scheduled Tasks" +[ApiManagement] +other="API" + +[IssueNewApiToken] +other="Create Token" + +[Token] +other="Token" + +[DeleteToken] +other="Delete Token" + +[ConfirmToDeleteThisToken] +other="Confirm Delete?" + [YouAreNotAuthorized] other = "You are not authorized" diff --git a/resource/l10n/es-ES.toml b/resource/l10n/es-ES.toml index 6966218771..1744b3e0eb 100644 --- a/resource/l10n/es-ES.toml +++ b/resource/l10n/es-ES.toml @@ -469,6 +469,21 @@ other = "Monitorización del servicio" [ScheduledTasks] other = "Tareas programadas" +[ApiManagement] +other="API" + +[IssueNewApiToken] +other="Create Token" + +[Token] +other="Token" + +[DeleteToken] +other="Delete Token" + +[ConfirmToDeleteThisToken] +other="Confirm Delete?" + [YouAreNotAuthorized] other = "Esta página requiere un acceso" diff --git a/resource/l10n/zh-CN.toml b/resource/l10n/zh-CN.toml index fb8fd3f339..c135bd2212 100644 --- a/resource/l10n/zh-CN.toml +++ b/resource/l10n/zh-CN.toml @@ -469,6 +469,21 @@ other = "服务监控" [ScheduledTasks] other = "计划任务" +[ApiManagement] +other="API" + +[IssueNewApiToken] +other="添加Token" + +[Token] +other="Token" + +[DeleteToken] +other="删除Token" + +[ConfirmToDeleteThisToken] +other="确认删除Token" + [YouAreNotAuthorized] other = "此页面需要登录" diff --git a/resource/static/main.js b/resource/static/main.js index 6b151252ea..d7da66ebab 100644 --- a/resource/static/main.js +++ b/resource/static/main.js @@ -202,6 +202,18 @@ function post(path, params, method = 'post') { 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 + '' : LANG.Add + '' + ); + modal.find("textarea[name=Note]").val(apiToken ? apiToken.Note : null); + showFormModal(".api.modal", "#apiForm", "/api/token"); +} + function addOrEditServer(server, conf) { const modal = $(".server.modal"); modal.children(".header").text((server ? LANG.Edit : LANG.Add) + ' ' + LANG.Server); diff --git a/resource/template/common/menu.html b/resource/template/common/menu.html index f909103fd4..a48c7ccdcd 100644 --- a/resource/template/common/menu.html +++ b/resource/template/common/menu.html @@ -9,6 +9,7 @@ {{tr "Services"}} {{tr "Task"}} {{tr "Notification"}} + API {{tr "Settings"}} diff --git a/resource/template/component/api.html b/resource/template/component/api.html new file mode 100644 index 0000000000..02b6c6c601 --- /dev/null +++ b/resource/template/component/api.html @@ -0,0 +1,19 @@ +{{define "component/api"}} + +{{end}} \ No newline at end of file diff --git a/resource/template/dashboard/api.html b/resource/template/dashboard/api.html new file mode 100644 index 0000000000..b9b7795f76 --- /dev/null +++ b/resource/template/dashboard/api.html @@ -0,0 +1,41 @@ +{{define "dashboard/api"}} +{{template "common/header" .}} +{{template "common/menu" .}} +
+
+
+
+ +
+
+ + + + + + + + + {{range $token := .Tokens}} + + + + + + {{end}} + +
{{tr "Token"}}{{tr "Note"}}
{{$token.Token}}{{$token.Note}} +
+ +
+
+
+
+{{template "component/api"}} +{{template "common/footer" .}} +{{end}} \ No newline at end of file From e3419602239e9608c8db564c255b375318e49e18 Mon Sep 17 00:00:00 2001 From: Akkia Date: Wed, 18 May 2022 23:37:39 +0800 Subject: [PATCH 7/8] =?UTF-8?q?update:=20=E4=BB=A3=E7=A0=81=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=EF=BD=9C=E5=89=8D=E7=AB=AF=E5=85=A5=E5=8F=A3=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E8=87=B3=E7=94=A8=E6=88=B7=E5=90=8D=E4=B8=8B=E6=8B=89?= =?UTF-8?q?=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/dashboard/controller/api_v1.go | 19 +++++-------------- cmd/dashboard/controller/member_api.go | 13 +++++++++++++ resource/l10n/en-US.toml | 15 --------------- resource/l10n/es-ES.toml | 15 --------------- resource/template/common/menu.html | 4 +++- service/singleton/api.go | 19 +++++++------------ 6 files changed, 28 insertions(+), 57 deletions(-) diff --git a/cmd/dashboard/controller/api_v1.go b/cmd/dashboard/controller/api_v1.go index 7c6a937da7..335e345810 100644 --- a/cmd/dashboard/controller/api_v1.go +++ b/cmd/dashboard/controller/api_v1.go @@ -32,14 +32,10 @@ func (v *apiV1) serve() { // header: Authorization: Token // query: tag (服务器分组) func (v *apiV1) serverList(c *gin.Context) { - token, _ := c.Cookie("Authorization") tag := c.Query("tag") - serverAPI := &singleton.ServerAPI{ - Token: token, - Tag: tag, - } + serverAPI := &singleton.ServerAPI{} if tag != "" { - c.JSON(200, serverAPI.GetListByTag()) + c.JSON(200, serverAPI.GetListByTag(tag)) return } c.JSON(200, serverAPI.GetAllList()) @@ -50,7 +46,6 @@ func (v *apiV1) serverList(c *gin.Context) { // query: id (服务器ID,逗号分隔,优先级高于tag查询) // query: tag (服务器分组) func (v *apiV1) serverDetails(c *gin.Context) { - token, _ := c.Cookie("Authorization") var idList []uint64 idListStr := strings.Split(c.Query("id"), ",") if c.Query("id") != "" { @@ -61,17 +56,13 @@ func (v *apiV1) serverDetails(c *gin.Context) { } } tag := c.Query("tag") - serverAPI := &singleton.ServerAPI{ - Token: token, - IDList: idList, - Tag: tag, - } + serverAPI := &singleton.ServerAPI{} if tag != "" { - c.JSON(200, serverAPI.GetStatusByTag()) + c.JSON(200, serverAPI.GetStatusByTag(tag)) return } if len(idList) != 0 { - c.JSON(200, serverAPI.GetStatusByIDList()) + c.JSON(200, serverAPI.GetStatusByIDList(idList)) return } c.JSON(200, serverAPI.GetAllStatus()) diff --git a/cmd/dashboard/controller/member_api.go b/cmd/dashboard/controller/member_api.go index 03af65734c..c446e71df9 100644 --- a/cmd/dashboard/controller/member_api.go +++ b/cmd/dashboard/controller/member_api.go @@ -176,8 +176,21 @@ func (ma *memberAPI) delete(c *gin.Context) { if err == nil { // 删除服务器 singleton.ServerLock.Lock() + tag := singleton.ServerList[id].Tag delete(singleton.SecretToID, singleton.ServerList[id].Secret) 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.ReSortServer() // 删除循环流量状态中的此服务器相关的记录 diff --git a/resource/l10n/en-US.toml b/resource/l10n/en-US.toml index f03dd87496..0a13711542 100644 --- a/resource/l10n/en-US.toml +++ b/resource/l10n/en-US.toml @@ -469,21 +469,6 @@ other = "Services" [ScheduledTasks] other = "Scheduled Tasks" -[ApiManagement] -other="API" - -[IssueNewApiToken] -other="Create Token" - -[Token] -other="Token" - -[DeleteToken] -other="Delete Token" - -[ConfirmToDeleteThisToken] -other="Confirm Delete?" - [YouAreNotAuthorized] other = "You are not authorized" diff --git a/resource/l10n/es-ES.toml b/resource/l10n/es-ES.toml index 1744b3e0eb..6966218771 100644 --- a/resource/l10n/es-ES.toml +++ b/resource/l10n/es-ES.toml @@ -469,21 +469,6 @@ other = "Monitorización del servicio" [ScheduledTasks] other = "Tareas programadas" -[ApiManagement] -other="API" - -[IssueNewApiToken] -other="Create Token" - -[Token] -other="Token" - -[DeleteToken] -other="Delete Token" - -[ConfirmToDeleteThisToken] -other="Confirm Delete?" - [YouAreNotAuthorized] other = "Esta página requiere un acceso" diff --git a/resource/template/common/menu.html b/resource/template/common/menu.html index a48c7ccdcd..ba746c4e96 100644 --- a/resource/template/common/menu.html +++ b/resource/template/common/menu.html @@ -9,7 +9,6 @@ {{tr "Services"}} {{tr "Task"}} {{tr "Notification"}} - API {{tr "Settings"}} @@ -30,6 +29,9 @@ {{tr "BackToHomepage"}} + + API Token + {{else}} {{tr "AdminPanel"}} diff --git a/service/singleton/api.go b/service/singleton/api.go index f6da4aa552..1335f31b75 100644 --- a/service/singleton/api.go +++ b/service/singleton/api.go @@ -12,11 +12,7 @@ var ( ApiLock sync.RWMutex ) -type ServerAPI struct { - Token string // 传入Token 后期可能会需要用于scope判定 - IDList []uint64 - Tag string -} +type ServerAPI struct{} // CommonResponse 常规返回结构 包含状态码 和 状态信息 type CommonResponse struct { @@ -68,14 +64,14 @@ func LoadAPI() { } // GetStatusByIDList 获取传入IDList的服务器状态信息 -func (s *ServerAPI) GetStatusByIDList() *ServerStatusResponse { +func (s *ServerAPI) GetStatusByIDList(idList []uint64) *ServerStatusResponse { res := &ServerStatusResponse{} res.Result = make([]*StatusResponse, 0) ServerLock.RLock() defer ServerLock.RUnlock() - for _, v := range s.IDList { + for _, v := range idList { server := ServerList[v] if server == nil { continue @@ -103,9 +99,8 @@ func (s *ServerAPI) GetStatusByIDList() *ServerStatusResponse { } // GetStatusByTag 获取传入分组的所有服务器状态信息 -func (s *ServerAPI) GetStatusByTag() *ServerStatusResponse { - s.IDList = ServerTagToIDList[s.Tag] - return s.GetStatusByIDList() +func (s *ServerAPI) GetStatusByTag(tag string) *ServerStatusResponse { + return s.GetStatusByIDList(ServerTagToIDList[tag]) } // GetAllStatus 获取所有服务器状态信息 @@ -143,13 +138,13 @@ func (s *ServerAPI) GetAllStatus() *ServerStatusResponse { } // GetListByTag 获取传入分组的所有服务器信息 -func (s *ServerAPI) GetListByTag() *ServerInfoResponse { +func (s *ServerAPI) GetListByTag(tag string) *ServerInfoResponse { res := &ServerInfoResponse{} res.Result = make([]*CommonServerInfo, 0) ServerLock.RLock() defer ServerLock.RUnlock() - for _, v := range ServerTagToIDList[s.Tag] { + for _, v := range ServerTagToIDList[tag] { host := ServerList[v].Host if host == nil { continue From 725841cfed02119e0f57a0b0da07ede945b0cd22 Mon Sep 17 00:00:00 2001 From: Akkia Date: Wed, 18 May 2022 23:54:22 +0800 Subject: [PATCH 8/8] =?UTF-8?q?update:=20=E5=8D=95=E4=BE=8B=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/dashboard/controller/api_v1.go | 12 +++++------- service/singleton/api.go | 14 ++++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cmd/dashboard/controller/api_v1.go b/cmd/dashboard/controller/api_v1.go index 335e345810..999fd92f99 100644 --- a/cmd/dashboard/controller/api_v1.go +++ b/cmd/dashboard/controller/api_v1.go @@ -33,12 +33,11 @@ func (v *apiV1) serve() { // query: tag (服务器分组) func (v *apiV1) serverList(c *gin.Context) { tag := c.Query("tag") - serverAPI := &singleton.ServerAPI{} if tag != "" { - c.JSON(200, serverAPI.GetListByTag(tag)) + c.JSON(200, singleton.ServerAPI.GetListByTag(tag)) return } - c.JSON(200, serverAPI.GetAllList()) + c.JSON(200, singleton.ServerAPI.GetAllList()) } // serverDetails 获取服务器信息 不传入Query参数则获取全部 @@ -56,14 +55,13 @@ func (v *apiV1) serverDetails(c *gin.Context) { } } tag := c.Query("tag") - serverAPI := &singleton.ServerAPI{} if tag != "" { - c.JSON(200, serverAPI.GetStatusByTag(tag)) + c.JSON(200, singleton.ServerAPI.GetStatusByTag(tag)) return } if len(idList) != 0 { - c.JSON(200, serverAPI.GetStatusByIDList(idList)) + c.JSON(200, singleton.ServerAPI.GetStatusByIDList(idList)) return } - c.JSON(200, serverAPI.GetAllStatus()) + c.JSON(200, singleton.ServerAPI.GetAllStatus()) } diff --git a/service/singleton/api.go b/service/singleton/api.go index 1335f31b75..060ab02c5f 100644 --- a/service/singleton/api.go +++ b/service/singleton/api.go @@ -10,9 +10,11 @@ var ( ApiTokenList = make(map[string]*model.ApiToken) UserIDToApiTokenList = make(map[uint64][]string) ApiLock sync.RWMutex + + ServerAPI = &ServerAPIService{} ) -type ServerAPI struct{} +type ServerAPIService struct{} // CommonResponse 常规返回结构 包含状态码 和 状态信息 type CommonResponse struct { @@ -64,7 +66,7 @@ func LoadAPI() { } // GetStatusByIDList 获取传入IDList的服务器状态信息 -func (s *ServerAPI) GetStatusByIDList(idList []uint64) *ServerStatusResponse { +func (s *ServerAPIService) GetStatusByIDList(idList []uint64) *ServerStatusResponse { res := &ServerStatusResponse{} res.Result = make([]*StatusResponse, 0) @@ -99,12 +101,12 @@ func (s *ServerAPI) GetStatusByIDList(idList []uint64) *ServerStatusResponse { } // GetStatusByTag 获取传入分组的所有服务器状态信息 -func (s *ServerAPI) GetStatusByTag(tag string) *ServerStatusResponse { +func (s *ServerAPIService) GetStatusByTag(tag string) *ServerStatusResponse { return s.GetStatusByIDList(ServerTagToIDList[tag]) } // GetAllStatus 获取所有服务器状态信息 -func (s *ServerAPI) GetAllStatus() *ServerStatusResponse { +func (s *ServerAPIService) GetAllStatus() *ServerStatusResponse { res := &ServerStatusResponse{} res.Result = make([]*StatusResponse, 0) ServerLock.RLock() @@ -138,7 +140,7 @@ func (s *ServerAPI) GetAllStatus() *ServerStatusResponse { } // GetListByTag 获取传入分组的所有服务器信息 -func (s *ServerAPI) GetListByTag(tag string) *ServerInfoResponse { +func (s *ServerAPIService) GetListByTag(tag string) *ServerInfoResponse { res := &ServerInfoResponse{} res.Result = make([]*CommonServerInfo, 0) @@ -168,7 +170,7 @@ func (s *ServerAPI) GetListByTag(tag string) *ServerInfoResponse { } // GetAllList 获取所有服务器信息 -func (s *ServerAPI) GetAllList() *ServerInfoResponse { +func (s *ServerAPIService) GetAllList() *ServerInfoResponse { res := &ServerInfoResponse{} res.Result = make([]*CommonServerInfo, 0)