nezha/cmd/dashboard/controller/member_api.go

1008 lines
27 KiB
Go
Raw Normal View History

2019-12-08 03:59:58 -05:00
package controller
import (
2021-11-04 00:06:20 -04:00
"bytes"
2020-12-19 23:18:27 -05:00
"errors"
2019-12-08 03:59:58 -05:00
"fmt"
"net/http"
2020-03-22 08:55:27 -04:00
"strconv"
2021-04-23 07:09:04 -04:00
"strings"
2019-12-08 03:59:58 -05:00
"time"
"github.com/gin-gonic/gin"
2022-09-16 12:08:27 -04:00
"github.com/jinzhu/copier"
"gorm.io/gorm"
2019-12-08 03:59:58 -05:00
2020-11-10 21:07:45 -05:00
"github.com/naiba/nezha/model"
"github.com/naiba/nezha/pkg/mygin"
"github.com/naiba/nezha/pkg/utils"
2021-11-04 00:06:20 -04:00
"github.com/naiba/nezha/proto"
2023-11-28 10:01:37 -05:00
"github.com/naiba/nezha/resource"
2022-01-08 22:54:14 -05:00
"github.com/naiba/nezha/service/singleton"
2019-12-08 03:59:58 -05:00
)
type memberAPI struct {
r gin.IRouter
}
func (ma *memberAPI) serve() {
mr := ma.r.Group("")
mr.Use(mygin.Authorize(mygin.AuthorizeOption{
2024-02-24 10:21:33 -05:00
MemberOnly: true,
IsPage: false,
Msg: "访问此接口需要登录",
Btn: "点此登录",
Redirect: "/login",
2019-12-08 03:59:58 -05:00
}))
mr.GET("/search-server", ma.searchServer)
mr.GET("/search-tasks", ma.searchTask)
2019-12-20 10:58:09 -05:00
mr.POST("/server", ma.addOrEditServer)
mr.POST("/monitor", ma.addOrEditMonitor)
mr.POST("/cron", ma.addOrEditCron)
mr.GET("/cron/:id/manual", ma.manualTrigger)
2021-11-04 00:06:20 -04:00
mr.POST("/force-update", ma.forceUpdate)
2022-09-16 12:08:27 -04:00
mr.POST("/batch-update-server-group", ma.batchUpdateServerGroup)
mr.POST("/batch-delete-server", ma.batchDeleteServer)
mr.POST("/notification", ma.addOrEditNotification)
mr.POST("/alert-rule", ma.addOrEditAlertRule)
2020-12-09 06:05:40 -05:00
mr.POST("/setting", ma.updateSetting)
mr.DELETE("/:model/:id", ma.delete)
mr.POST("/logout", ma.logout)
2022-05-17 22:10:35 -04:00
mr.GET("/token", ma.getToken)
mr.POST("/token", ma.issueNewToken)
mr.DELETE("/token/:token", ma.deleteToken)
2022-05-16 23:21:27 -04:00
// API
2022-05-17 22:10:35 -04:00
v1 := ma.r.Group("v1")
{
apiv1 := &apiV1{v1}
apiv1.serve()
}
2022-05-16 23:21:27 -04:00
}
2022-05-17 22:10:35 -04:00
type apiResult struct {
Token string `json:"token"`
2022-05-18 08:52:18 -04:00
Note string `json:"note"`
2022-05-16 23:21:27 -04:00
}
2022-05-17 22:10:35 -04:00
// getToken 获取 Token
func (ma *memberAPI) getToken(c *gin.Context) {
u := c.MustGet(model.CtxKeyAuthorizedUser).(*model.User)
2022-05-17 22:28:24 -04:00
singleton.ApiLock.RLock()
2022-05-18 08:52:18 -04:00
defer singleton.ApiLock.RUnlock()
2022-05-17 22:10:35 -04:00
tokenList := singleton.UserIDToApiTokenList[u.ID]
res := make([]*apiResult, len(tokenList))
for i, token := range tokenList {
res[i] = &apiResult{
Token: token,
2022-05-18 08:52:18 -04:00
Note: singleton.ApiTokenList[token].Note,
2022-05-16 23:21:27 -04:00
}
}
2022-05-17 22:10:35 -04:00
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"result": res,
})
}
2022-05-18 08:52:18 -04:00
type TokenForm struct {
Note string
}
2022-05-17 22:10:35 -04:00
// issueNewToken 生成新的 token
func (ma *memberAPI) issueNewToken(c *gin.Context) {
u := c.MustGet(model.CtxKeyAuthorizedUser).(*model.User)
2022-05-18 08:52:18 -04:00
tf := &TokenForm{}
err := c.ShouldBindJSON(tf)
if err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("请求错误:%s", err),
})
return
}
2022-12-16 10:34:14 -05:00
secureToken, err := utils.GenerateRandomString(32)
if err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("请求错误:%s", err),
})
return
}
2022-05-17 22:10:35 -04:00
token := &model.ApiToken{
UserID: u.ID,
2022-12-16 10:34:14 -05:00
Token: secureToken,
2022-05-18 08:52:18 -04:00
Note: tf.Note,
2022-05-16 23:21:27 -04:00
}
2022-05-17 22:10:35 -04:00
singleton.DB.Create(token)
2022-05-17 22:28:24 -04:00
singleton.ApiLock.Lock()
2022-05-17 22:10:35 -04:00
singleton.ApiTokenList[token.Token] = token
singleton.UserIDToApiTokenList[u.ID] = append(singleton.UserIDToApiTokenList[u.ID], token.Token)
2022-05-17 22:28:24 -04:00
singleton.ApiLock.Unlock()
2022-05-17 22:10:35 -04:00
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
Message: "success",
Result: map[string]string{
"token": token.Token,
2022-05-18 08:52:18 -04:00
"note": token.Note,
2022-05-17 22:10:35 -04:00
},
})
}
// 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 不能为空",
})
2022-05-16 23:21:27 -04:00
return
}
2022-05-17 22:28:24 -04:00
singleton.ApiLock.Lock()
defer singleton.ApiLock.Unlock()
2022-05-17 22:10:35 -04:00
if _, ok := singleton.ApiTokenList[token]; !ok {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: "token 不存在",
})
2022-05-16 23:21:27 -04:00
return
}
2022-05-17 22:10:35 -04:00
// 在数据库中删除该Token
singleton.DB.Unscoped().Delete(&model.ApiToken{}, "token = ?", token)
2022-05-17 22:28:24 -04:00
2022-05-17 22:10:35 -04:00
// 在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",
})
2020-03-22 08:55:27 -04:00
}
func (ma *memberAPI) delete(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
if id < 1 {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: "错误的 Server ID",
})
return
}
2019-12-08 10:18:29 -05:00
var err error
switch c.Param("model") {
case "server":
err := singleton.DB.Transaction(func(tx *gorm.DB) error {
err = singleton.DB.Unscoped().Delete(&model.Server{}, "id = ?", id).Error
if err != nil {
return err
}
err = singleton.DB.Unscoped().Delete(&model.MonitorHistory{}, "server_id = ?", id).Error
if err != nil {
return err
}
return nil
})
if err == nil {
2021-11-06 04:00:08 -04:00
// 删除服务器
2022-01-08 22:54:14 -05:00
singleton.ServerLock.Lock()
onServerDelete(id)
2022-01-08 22:54:14 -05:00
singleton.ServerLock.Unlock()
singleton.ReSortServer()
}
case "notification":
2022-01-08 22:54:14 -05:00
err = singleton.DB.Unscoped().Delete(&model.Notification{}, "id = ?", id).Error
2020-12-19 23:18:27 -05:00
if err == nil {
2022-01-08 22:54:14 -05:00
singleton.OnDeleteNotification(id)
2020-12-19 23:18:27 -05:00
}
case "monitor":
2022-01-08 22:54:14 -05:00
err = singleton.DB.Unscoped().Delete(&model.Monitor{}, "id = ?", id).Error
if err == nil {
2022-01-08 22:54:14 -05:00
singleton.ServiceSentinelShared.OnMonitorDelete(id)
err = singleton.DB.Unscoped().Delete(&model.MonitorHistory{}, "monitor_id = ?", id).Error
}
case "cron":
2022-01-08 22:54:14 -05:00
err = singleton.DB.Unscoped().Delete(&model.Cron{}, "id = ?", id).Error
if err == nil {
2022-01-08 22:54:14 -05:00
singleton.CronLock.RLock()
defer singleton.CronLock.RUnlock()
cr := singleton.Crons[id]
if cr != nil && cr.CronJobID != 0 {
2022-01-08 22:54:14 -05:00
singleton.Cron.Remove(cr.CronJobID)
}
2022-01-08 22:54:14 -05:00
delete(singleton.Crons, id)
}
case "alert-rule":
2022-01-08 22:54:14 -05:00
err = singleton.DB.Unscoped().Delete(&model.AlertRule{}, "id = ?", id).Error
2020-12-19 23:18:27 -05:00
if err == nil {
2022-01-08 22:54:14 -05:00
singleton.OnDeleteAlert(id)
2020-12-19 23:18:27 -05:00
}
}
if err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("数据库错误:%s", err),
})
return
}
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
})
}
type searchResult struct {
Name string `json:"name,omitempty"`
Value uint64 `json:"value,omitempty"`
Text string `json:"text,omitempty"`
}
func (ma *memberAPI) searchServer(c *gin.Context) {
var servers []model.Server
likeWord := "%" + c.Query("word") + "%"
2022-01-08 22:54:14 -05:00
singleton.DB.Select("id,name").Where("id = ? OR name LIKE ? OR tag LIKE ? OR note LIKE ?",
c.Query("word"), likeWord, likeWord, likeWord).Find(&servers)
var resp []searchResult
for i := 0; i < len(servers); i++ {
resp = append(resp, searchResult{
Value: servers[i].ID,
Name: servers[i].Name,
Text: servers[i].Name,
})
}
c.JSON(http.StatusOK, map[string]interface{}{
"success": true,
"results": resp,
})
}
func (ma *memberAPI) searchTask(c *gin.Context) {
var tasks []model.Cron
likeWord := "%" + c.Query("word") + "%"
singleton.DB.Select("id,name").Where("id = ? OR name LIKE ?",
c.Query("word"), likeWord).Find(&tasks)
var resp []searchResult
for i := 0; i < len(tasks); i++ {
resp = append(resp, searchResult{
Value: tasks[i].ID,
Name: tasks[i].Name,
Text: tasks[i].Name,
})
}
c.JSON(http.StatusOK, map[string]interface{}{
"success": true,
"results": resp,
})
}
2019-12-08 10:18:29 -05:00
type serverForm struct {
2021-01-08 08:04:50 -05:00
ID uint64
Name string `binding:"required"`
DisplayIndex int
Secret string
2021-01-12 01:09:25 -05:00
Tag string
Note string
HideForGuest string
EnableDDNS string
EnableIPv4 string
EnableIpv6 string
DDNSDomain string
DDNSProfile string
2019-12-08 10:18:29 -05:00
}
2019-12-20 10:58:09 -05:00
func (ma *memberAPI) addOrEditServer(c *gin.Context) {
2019-12-08 10:18:29 -05:00
var sf serverForm
2019-12-09 03:02:49 -05:00
var s model.Server
var isEdit bool
2019-12-08 10:18:29 -05:00
err := c.ShouldBindJSON(&sf)
if err == nil {
s.Name = sf.Name
2020-03-22 08:55:27 -04:00
s.Secret = sf.Secret
2021-01-08 08:04:50 -05:00
s.DisplayIndex = sf.DisplayIndex
2020-03-22 08:55:27 -04:00
s.ID = sf.ID
2021-01-12 01:09:25 -05:00
s.Tag = sf.Tag
s.Note = sf.Note
s.HideForGuest = sf.HideForGuest == "on"
s.EnableDDNS = sf.EnableDDNS == "on"
s.EnableIPv4 = sf.EnableIPv4 == "on"
s.EnableIpv6 = sf.EnableIpv6 == "on"
s.DDNSDomain = sf.DDNSDomain
s.DDNSProfile = sf.DDNSProfile
if s.ID == 0 {
2022-12-16 10:34:14 -05:00
s.Secret, err = utils.GenerateRandomString(18)
if err == nil {
err = singleton.DB.Create(&s).Error
}
2021-01-08 08:04:50 -05:00
} else {
isEdit = true
2022-01-08 22:54:14 -05:00
err = singleton.DB.Save(&s).Error
2021-01-08 08:04:50 -05:00
}
2019-12-08 10:18:29 -05:00
}
if err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("请求错误:%s", err),
})
return
}
if isEdit {
2022-01-08 22:54:14 -05:00
singleton.ServerLock.Lock()
s.CopyFromRunningServer(singleton.ServerList[s.ID])
// 如果修改了 Secret
2022-01-08 22:54:14 -05:00
if s.Secret != singleton.ServerList[s.ID].Secret {
// 删除旧 Secret-ID 绑定关系
2022-01-08 22:54:14 -05:00
singleton.SecretToID[s.Secret] = s.ID
// 设置新的 Secret-ID 绑定关系
2022-01-08 22:54:14 -05:00
delete(singleton.SecretToID, singleton.ServerList[s.ID].Secret)
}
2022-05-16 23:21:27 -04:00
// 如果修改了Tag
oldTag := singleton.ServerList[s.ID].Tag
newTag := s.Tag
if newTag != oldTag {
index := -1
for i := 0; i < len(singleton.ServerTagToIDList[oldTag]); i++ {
if singleton.ServerTagToIDList[oldTag][i] == s.ID {
2022-05-19 21:05:18 -04:00
index = i
2022-05-16 23:21:27 -04:00
break
}
}
if index > -1 {
// 删除旧 Tag-ID 绑定关系
singleton.ServerTagToIDList[oldTag] = append(singleton.ServerTagToIDList[oldTag][:index], singleton.ServerTagToIDList[oldTag][index+1:]...)
if len(singleton.ServerTagToIDList[oldTag]) == 0 {
delete(singleton.ServerTagToIDList, oldTag)
}
2022-05-16 23:21:27 -04:00
}
// 设置新的 Tag-ID 绑定关系
singleton.ServerTagToIDList[newTag] = append(singleton.ServerTagToIDList[newTag], s.ID)
2022-05-16 23:21:27 -04:00
}
2022-01-08 22:54:14 -05:00
singleton.ServerList[s.ID] = &s
singleton.ServerLock.Unlock()
} else {
s.Host = &model.Host{}
s.State = &model.HostState{}
2022-01-08 22:54:14 -05:00
singleton.ServerLock.Lock()
singleton.SecretToID[s.Secret] = s.ID
singleton.ServerList[s.ID] = &s
2022-05-16 23:21:27 -04:00
singleton.ServerTagToIDList[s.Tag] = append(singleton.ServerTagToIDList[s.Tag], s.ID)
2022-01-08 22:54:14 -05:00
singleton.ServerLock.Unlock()
}
2022-01-08 22:54:14 -05:00
singleton.ReSortServer()
2019-12-08 10:18:29 -05:00
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
})
2019-12-08 03:59:58 -05:00
}
type monitorForm struct {
2023-04-15 07:04:38 -04:00
ID uint64
Name string
Target string
Type uint8
Cover uint8
Notify string
NotificationTag string
SkipServersRaw string
Duration uint64
MinLatency float32
MaxLatency float32
LatencyNotify string
EnableTriggerTask string
EnableShowInService string
2023-04-15 07:04:38 -04:00
FailTriggerTasksRaw string
RecoverTriggerTasksRaw string
}
func (ma *memberAPI) addOrEditMonitor(c *gin.Context) {
var mf monitorForm
var m model.Monitor
err := c.ShouldBindJSON(&mf)
if err == nil {
m.Name = mf.Name
2021-04-23 07:09:04 -04:00
m.Target = strings.TrimSpace(mf.Target)
m.Type = mf.Type
m.ID = mf.ID
m.SkipServersRaw = mf.SkipServersRaw
m.Cover = mf.Cover
m.Notify = mf.Notify == "on"
m.NotificationTag = mf.NotificationTag
m.Duration = mf.Duration
2022-09-16 22:30:32 -04:00
m.LatencyNotify = mf.LatencyNotify == "on"
m.MinLatency = mf.MinLatency
m.MaxLatency = mf.MaxLatency
m.EnableShowInService = mf.EnableShowInService == "on"
2023-04-15 07:04:38 -04:00
m.EnableTriggerTask = mf.EnableTriggerTask == "on"
m.RecoverTriggerTasksRaw = mf.RecoverTriggerTasksRaw
m.FailTriggerTasksRaw = mf.FailTriggerTasksRaw
err = m.InitSkipServers()
}
if err == nil {
// 保证NotificationTag不为空
if m.NotificationTag == "" {
m.NotificationTag = "default"
}
2024-06-23 04:15:08 -04:00
err = utils.Json.Unmarshal([]byte(mf.FailTriggerTasksRaw), &m.FailTriggerTasks)
}
if err == nil {
err = utils.Json.Unmarshal([]byte(mf.RecoverTriggerTasksRaw), &m.RecoverTriggerTasks)
}
if err == nil {
if m.ID == 0 {
err = singleton.DB.Create(&m).Error
} else {
err = singleton.DB.Save(&m).Error
}
2024-06-23 04:15:08 -04:00
}
if err == nil {
if m.Cover == 0 {
err = singleton.DB.Unscoped().Delete(&model.MonitorHistory{}, "monitor_id = ? and server_id in (?)", m.ID, strings.Split(m.SkipServersRaw[1:len(m.SkipServersRaw)-1], ",")).Error
} else {
err = singleton.DB.Unscoped().Delete(&model.MonitorHistory{}, "monitor_id = ? and server_id not in (?)", m.ID, strings.Split(m.SkipServersRaw[1:len(m.SkipServersRaw)-1], ",")).Error
}
}
if err == nil {
2022-01-08 22:54:14 -05:00
err = singleton.ServiceSentinelShared.OnMonitorUpdate(m)
}
if err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("请求错误:%s", err),
})
return
}
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
})
}
type cronForm struct {
ID uint64
2022-09-12 18:14:47 -04:00
TaskType uint8 // 0:计划任务 1:触发任务
Name string
Scheduler string
Command string
ServersRaw string
Cover uint8
PushSuccessful string
NotificationTag string
}
func (ma *memberAPI) addOrEditCron(c *gin.Context) {
var cf cronForm
var cr model.Cron
err := c.ShouldBindJSON(&cf)
if err == nil {
2022-09-12 18:14:47 -04:00
cr.TaskType = cf.TaskType
cr.Name = cf.Name
cr.Scheduler = cf.Scheduler
cr.Command = cf.Command
cr.ServersRaw = cf.ServersRaw
cr.PushSuccessful = cf.PushSuccessful == "on"
cr.NotificationTag = cf.NotificationTag
cr.ID = cf.ID
cr.Cover = cf.Cover
err = utils.Json.Unmarshal([]byte(cf.ServersRaw), &cr.Servers)
}
2022-09-12 18:14:47 -04:00
// 计划任务类型不得使用触发服务器执行方式
if cr.TaskType == model.CronTypeCronTask && cr.Cover == model.CronCoverAlertTrigger {
2022-09-12 18:14:47 -04:00
err = errors.New("计划任务类型不得使用触发服务器执行方式")
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("请求错误:%s", err),
})
return
}
2022-01-08 22:54:14 -05:00
tx := singleton.DB.Begin()
if err == nil {
// 保证NotificationTag不为空
if cr.NotificationTag == "" {
cr.NotificationTag = "default"
}
if cf.ID == 0 {
err = tx.Create(&cr).Error
} else {
err = tx.Save(&cr).Error
}
}
2021-07-18 22:44:44 -04:00
if err == nil {
2022-09-12 18:14:47 -04:00
// 对于计划任务类型需要更新CronJob
if cf.TaskType == model.CronTypeCronTask {
cr.CronJobID, err = singleton.Cron.AddFunc(cr.Scheduler, singleton.CronTrigger(cr))
}
}
if err == nil {
err = tx.Commit().Error
} else {
tx.Rollback()
}
2021-07-18 22:37:12 -04:00
if err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
2021-07-18 22:44:44 -04:00
Message: fmt.Sprintf("请求错误:%s", err),
2021-07-18 22:37:12 -04:00
})
return
}
2022-01-08 22:54:14 -05:00
singleton.CronLock.Lock()
defer singleton.CronLock.Unlock()
crOld := singleton.Crons[cr.ID]
if crOld != nil && crOld.CronJobID != 0 {
2022-01-08 22:54:14 -05:00
singleton.Cron.Remove(crOld.CronJobID)
2021-05-27 08:48:12 -04:00
}
2022-01-08 22:54:14 -05:00
delete(singleton.Crons, cr.ID)
singleton.Crons[cr.ID] = &cr
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
})
}
func (ma *memberAPI) manualTrigger(c *gin.Context) {
var cr model.Cron
2022-01-08 22:54:14 -05:00
if err := singleton.DB.First(&cr, "id = ?", c.Param("id")).Error; err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: err.Error(),
})
return
}
2022-01-08 22:54:14 -05:00
singleton.ManualTrigger(cr)
2022-09-16 12:08:27 -04:00
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
})
}
type BatchUpdateServerGroupRequest struct {
Servers []uint64
Group string
}
func (ma *memberAPI) batchUpdateServerGroup(c *gin.Context) {
var req BatchUpdateServerGroupRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: err.Error(),
})
return
}
if err := singleton.DB.Model(&model.Server{}).Where("id in (?)", req.Servers).Update("tag", req.Group).Error; err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: err.Error(),
})
return
}
singleton.ServerLock.Lock()
for i := 0; i < len(req.Servers); i++ {
serverId := req.Servers[i]
var s model.Server
copier.Copy(&s, singleton.ServerList[serverId])
s.Tag = req.Group
// 如果修改了Ta
oldTag := singleton.ServerList[serverId].Tag
newTag := s.Tag
if newTag != oldTag {
2022-09-16 12:08:27 -04:00
index := -1
for i := 0; i < len(singleton.ServerTagToIDList[oldTag]); i++ {
if singleton.ServerTagToIDList[oldTag][i] == s.ID {
2022-09-16 12:08:27 -04:00
index = i
break
}
}
if index > -1 {
// 删除旧 Tag-ID 绑定关系
singleton.ServerTagToIDList[oldTag] = append(singleton.ServerTagToIDList[oldTag][:index], singleton.ServerTagToIDList[oldTag][index+1:]...)
if len(singleton.ServerTagToIDList[oldTag]) == 0 {
delete(singleton.ServerTagToIDList, oldTag)
}
2022-09-16 12:08:27 -04:00
}
// 设置新的 Tag-ID 绑定关系
singleton.ServerTagToIDList[newTag] = append(singleton.ServerTagToIDList[newTag], s.ID)
2022-09-16 12:08:27 -04:00
}
singleton.ServerList[s.ID] = &s
}
singleton.ServerLock.Unlock()
singleton.ReSortServer()
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
})
}
2021-11-04 00:06:20 -04:00
func (ma *memberAPI) forceUpdate(c *gin.Context) {
var forceUpdateServers []uint64
if err := c.ShouldBindJSON(&forceUpdateServers); err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: err.Error(),
})
return
}
var executeResult bytes.Buffer
for i := 0; i < len(forceUpdateServers); i++ {
2022-01-08 22:54:14 -05:00
singleton.ServerLock.RLock()
server := singleton.ServerList[forceUpdateServers[i]]
singleton.ServerLock.RUnlock()
2021-11-04 00:06:20 -04:00
if server != nil && server.TaskStream != nil {
if err := server.TaskStream.Send(&proto.Task{
Type: model.TaskTypeUpgrade,
}); err != nil {
executeResult.WriteString(fmt.Sprintf("%d 下发指令失败 %+v<br/>", forceUpdateServers[i], err))
} else {
executeResult.WriteString(fmt.Sprintf("%d 下发指令成功<br/>", forceUpdateServers[i]))
}
} else {
executeResult.WriteString(fmt.Sprintf("%d 离线<br/>", forceUpdateServers[i]))
}
}
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
Message: executeResult.String(),
})
}
type notificationForm struct {
ID uint64
Name string
Tag string // 分组名
URL string
RequestMethod int
RequestType int
RequestHeader string
RequestBody string
VerifySSL string
SkipCheck string
}
func (ma *memberAPI) addOrEditNotification(c *gin.Context) {
var nf notificationForm
var n model.Notification
err := c.ShouldBindJSON(&nf)
if err == nil {
n.Name = nf.Name
n.Tag = nf.Tag
n.RequestMethod = nf.RequestMethod
n.RequestType = nf.RequestType
n.RequestHeader = nf.RequestHeader
n.RequestBody = nf.RequestBody
n.URL = nf.URL
verifySSL := nf.VerifySSL == "on"
n.VerifySSL = &verifySSL
n.ID = nf.ID
ns := model.NotificationServerBundle{
Notification: &n,
Server: nil,
Loc: singleton.Loc,
}
// 勾选了跳过检查
if nf.SkipCheck != "on" {
err = ns.Send("这是测试消息")
}
2020-12-19 23:18:27 -05:00
}
if err == nil {
// 保证Tag不为空
if n.Tag == "" {
n.Tag = "default"
}
if n.ID == 0 {
2022-01-08 22:54:14 -05:00
err = singleton.DB.Create(&n).Error
} else {
2022-01-08 22:54:14 -05:00
err = singleton.DB.Save(&n).Error
}
}
if err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("请求错误:%s", err),
})
return
}
singleton.OnRefreshOrAddNotification(&n)
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
})
}
type alertRuleForm struct {
ID uint64
Name string
RulesRaw string
FailTriggerTasksRaw string // 失败时触发的任务id
RecoverTriggerTasksRaw string // 恢复时触发的任务id
NotificationTag string
TriggerMode int
Enable string
}
func (ma *memberAPI) addOrEditAlertRule(c *gin.Context) {
var arf alertRuleForm
var r model.AlertRule
err := c.ShouldBindJSON(&arf)
if err == nil {
err = utils.Json.Unmarshal([]byte(arf.RulesRaw), &r.Rules)
2020-12-19 23:18:27 -05:00
}
if err == nil {
if len(r.Rules) == 0 {
err = errors.New("至少定义一条规则")
} else {
for i := 0; i < len(r.Rules); i++ {
2022-02-28 21:19:23 -05:00
if !r.Rules[i].IsTransferDurationRule() {
if r.Rules[i].Duration < 3 {
err = errors.New("错误Duration 至少为 3")
break
}
} else {
if r.Rules[i].CycleInterval < 1 {
err = errors.New("错误: cycle_interval 至少为 1")
break
}
if r.Rules[i].CycleStart == nil {
err = errors.New("错误: cycle_start 未设置")
break
}
if r.Rules[i].CycleStart.After(time.Now()) {
err = errors.New("错误: cycle_start 是个未来值")
break
}
2020-12-19 23:18:27 -05:00
}
}
}
}
if err == nil {
r.Name = arf.Name
r.RulesRaw = arf.RulesRaw
r.FailTriggerTasksRaw = arf.FailTriggerTasksRaw
r.RecoverTriggerTasksRaw = arf.RecoverTriggerTasksRaw
r.NotificationTag = arf.NotificationTag
enable := arf.Enable == "on"
r.TriggerMode = arf.TriggerMode
r.Enable = &enable
r.ID = arf.ID
}
if err == nil {
err = utils.Json.Unmarshal([]byte(arf.FailTriggerTasksRaw), &r.FailTriggerTasks)
}
if err == nil {
err = utils.Json.Unmarshal([]byte(arf.RecoverTriggerTasksRaw), &r.RecoverTriggerTasks)
}
//保证NotificationTag不为空
if err == nil {
if r.NotificationTag == "" {
r.NotificationTag = "default"
}
if r.ID == 0 {
2022-01-08 22:54:14 -05:00
err = singleton.DB.Create(&r).Error
} else {
2022-01-08 22:54:14 -05:00
err = singleton.DB.Save(&r).Error
}
}
if err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("请求错误:%s", err),
})
return
}
2022-01-08 22:54:14 -05:00
singleton.OnRefreshOrAddAlert(r)
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
})
}
2019-12-08 03:59:58 -05:00
type logoutForm struct {
ID uint64
}
func (ma *memberAPI) logout(c *gin.Context) {
2019-12-20 10:58:09 -05:00
admin := c.MustGet(model.CtxKeyAuthorizedUser).(*model.User)
2019-12-08 03:59:58 -05:00
var lf logoutForm
if err := c.ShouldBindJSON(&lf); err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("请求错误:%s", err),
})
return
}
2019-12-20 10:58:09 -05:00
if lf.ID != admin.ID {
2019-12-08 03:59:58 -05:00
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("请求错误:%s", "用户ID不匹配"),
})
return
}
2022-01-08 22:54:14 -05:00
singleton.DB.Model(admin).UpdateColumns(model.User{
2019-12-20 10:58:09 -05:00
Token: "",
TokenExpired: time.Now(),
})
2019-12-08 03:59:58 -05:00
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
})
if oidcLogoutUrl := singleton.Conf.Oauth2.OidcLogoutURL; oidcLogoutUrl != "" {
// 重定向到 OIDC 退出登录地址。不知道为什么,这里的重定向不生效
c.Redirect(http.StatusOK, oidcLogoutUrl)
}
2019-12-08 03:59:58 -05:00
}
2020-12-09 06:05:40 -05:00
type settingForm struct {
Title string
Admin string
2022-04-29 12:48:39 -04:00
Language string
Theme string
2022-06-02 21:45:11 -04:00
DashboardTheme string
CustomCode string
ViewPassword string
IgnoredIPNotification string
IPChangeNotificationTag string // IP变更提醒的通知组
GRPCHost string
Cover uint8
2022-02-19 01:29:06 -05:00
2024-02-27 08:05:39 -05:00
EnableIPChangeNotification string
EnablePlainIPInNotification string
DisableSwitchTemplateInFrontend string
2020-12-09 06:05:40 -05:00
}
func (ma *memberAPI) updateSetting(c *gin.Context) {
var sf settingForm
if err := c.ShouldBind(&sf); err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("请求错误:%s", err),
})
return
}
2022-06-02 21:45:11 -04:00
if _, yes := model.Themes[sf.Theme]; !yes {
2022-06-02 21:45:11 -04:00
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("前台主题不存在:%s", sf.Theme),
2022-06-02 21:45:11 -04:00
})
return
}
if _, yes := model.Themes[sf.DashboardTheme]; !yes {
2022-06-02 21:45:11 -04:00
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("后台主题不存在:%s", sf.DashboardTheme),
})
return
}
2023-11-28 10:01:37 -05:00
if !utils.IsFileExists("resource/template/theme-"+sf.Theme+"/home.html") && !resource.IsTemplateFileExist("template/theme-"+sf.Theme+"/home.html") {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("前台主题文件异常:%s", sf.Theme),
})
return
}
2023-11-28 10:01:37 -05:00
if !utils.IsFileExists("resource/template/dashboard-"+sf.DashboardTheme+"/setting.html") && !resource.IsTemplateFileExist("template/dashboard-"+sf.DashboardTheme+"/setting.html") {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("后台主题文件异常:%s", sf.DashboardTheme),
2022-06-02 21:45:11 -04:00
})
return
}
2022-04-29 12:48:39 -04:00
singleton.Conf.Language = sf.Language
2022-01-08 22:54:14 -05:00
singleton.Conf.EnableIPChangeNotification = sf.EnableIPChangeNotification == "on"
2022-02-19 01:29:06 -05:00
singleton.Conf.EnablePlainIPInNotification = sf.EnablePlainIPInNotification == "on"
2024-02-27 08:05:39 -05:00
singleton.Conf.DisableSwitchTemplateInFrontend = sf.DisableSwitchTemplateInFrontend == "on"
2022-01-08 22:54:14 -05:00
singleton.Conf.Cover = sf.Cover
singleton.Conf.GRPCHost = sf.GRPCHost
singleton.Conf.IgnoredIPNotification = sf.IgnoredIPNotification
singleton.Conf.IPChangeNotificationTag = sf.IPChangeNotificationTag
2022-01-08 22:54:14 -05:00
singleton.Conf.Site.Brand = sf.Title
singleton.Conf.Site.Theme = sf.Theme
2022-06-02 21:45:11 -04:00
singleton.Conf.Site.DashboardTheme = sf.DashboardTheme
2022-01-08 22:54:14 -05:00
singleton.Conf.Site.CustomCode = sf.CustomCode
singleton.Conf.Site.ViewPassword = sf.ViewPassword
singleton.Conf.Oauth2.Admin = sf.Admin
// 保证NotificationTag不为空
if singleton.Conf.IPChangeNotificationTag == "" {
singleton.Conf.IPChangeNotificationTag = "default"
}
2022-01-08 22:54:14 -05:00
if err := singleton.Conf.Save(); err != nil {
2020-12-09 06:05:40 -05:00
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("请求错误:%s", err),
})
return
}
2022-04-29 12:48:39 -04:00
// 更新系统语言
singleton.InitLocalizer()
2020-12-09 06:05:40 -05:00
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
})
}
func (ma *memberAPI) batchDeleteServer(c *gin.Context) {
var servers []uint64
if err := c.ShouldBindJSON(&servers); err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: err.Error(),
})
return
}
if err := singleton.DB.Unscoped().Delete(&model.Server{}, "id in (?)", servers).Error; err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: err.Error(),
})
return
}
singleton.ServerLock.Lock()
for i := 0; i < len(servers); i++ {
id := servers[i]
onServerDelete(id)
}
singleton.ServerLock.Unlock()
singleton.ReSortServer()
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
})
}
func onServerDelete(id uint64) {
tag := singleton.ServerList[id].Tag
delete(singleton.SecretToID, singleton.ServerList[id].Secret)
delete(singleton.ServerList, id)
index := -1
for i := 0; i < len(singleton.ServerTagToIDList[tag]); i++ {
if singleton.ServerTagToIDList[tag][i] == id {
index = i
break
}
}
if index > -1 {
singleton.ServerTagToIDList[tag] = append(singleton.ServerTagToIDList[tag][:index], singleton.ServerTagToIDList[tag][index+1:]...)
if len(singleton.ServerTagToIDList[tag]) == 0 {
delete(singleton.ServerTagToIDList, tag)
}
}
singleton.AlertsLock.Lock()
for i := 0; i < len(singleton.Alerts); i++ {
if singleton.AlertsCycleTransferStatsStore[singleton.Alerts[i].ID] != nil {
delete(singleton.AlertsCycleTransferStatsStore[singleton.Alerts[i].ID].ServerName, id)
delete(singleton.AlertsCycleTransferStatsStore[singleton.Alerts[i].ID].Transfer, id)
delete(singleton.AlertsCycleTransferStatsStore[singleton.Alerts[i].ID].NextUpdate, id)
}
}
singleton.AlertsLock.Unlock()
singleton.DB.Unscoped().Delete(&model.Transfer{}, "server_id = ?", id)
}