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.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
|
||||
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) {
|
||||
@ -62,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()
|
||||
// 删除循环流量状态中的此服务器相关的记录
|
||||
@ -194,6 +321,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 +346,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()
|
||||
|
@ -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) {
|
||||
|
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
|
||||
Member bool
|
||||
IsPage bool
|
||||
AllowAPI bool
|
||||
Msg string
|
||||
Redirect string
|
||||
Btn string
|
||||
@ -34,7 +35,6 @@ func Authorize(opt AuthorizeOption) func(*gin.Context) {
|
||||
Link: opt.Redirect,
|
||||
Btn: opt.Btn,
|
||||
}
|
||||
|
||||
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 {
|
||||
ShowErrorPage(c, commonErr, opt.IsPage)
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
15
resource/l10n/zh-CN.toml
vendored
15
resource/l10n/zh-CN.toml
vendored
@ -469,6 +469,21 @@ other = "服务监控"
|
||||
[ScheduledTasks]
|
||||
other = "计划任务"
|
||||
|
||||
[ApiManagement]
|
||||
other="API"
|
||||
|
||||
[IssueNewApiToken]
|
||||
other="添加Token"
|
||||
|
||||
[Token]
|
||||
other="Token"
|
||||
|
||||
[DeleteToken]
|
||||
other="删除Token"
|
||||
|
||||
[ConfirmToDeleteThisToken]
|
||||
other="确认删除Token"
|
||||
|
||||
[YouAreNotAuthorized]
|
||||
other = "此页面需要登录"
|
||||
|
||||
|
@ -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 + '<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) {
|
||||
const modal = $(".server.modal");
|
||||
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="/">
|
||||
<i class="chart area icon"></i>{{tr "BackToHomepage"}}
|
||||
</a>
|
||||
<a class="item" href="/api">
|
||||
<i class="chart key icon"></i>API Token
|
||||
</a>
|
||||
{{else}}
|
||||
<a class="item" href="/server">
|
||||
<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 (
|
||||
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()
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ func LoadSingleton() {
|
||||
LoadNotifications() // 加载通知服务
|
||||
LoadServers() // 加载服务器列表
|
||||
LoadCronTasks() // 加载定时任务
|
||||
LoadAPI()
|
||||
}
|
||||
|
||||
// InitConfigFromPath 从给出的文件路径中加载配置
|
||||
@ -63,7 +64,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)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user