package controller
import (
"bytes"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/jinzhu/copier"
"gorm.io/gorm"
"github.com/naiba/nezha/model"
"github.com/naiba/nezha/pkg/mygin"
"github.com/naiba/nezha/pkg/utils"
"github.com/naiba/nezha/proto"
"github.com/naiba/nezha/resource"
"github.com/naiba/nezha/service/singleton"
)
type memberAPI struct {
r gin.IRouter
}
func (ma *memberAPI) serve() {
mr := ma.r.Group("")
mr.Use(mygin.Authorize(mygin.AuthorizeOption{
MemberOnly: true,
IsPage: false,
Msg: "访问此接口需要登录",
Btn: "点此登录",
Redirect: "/login",
}))
mr.GET("/search-server", ma.searchServer)
mr.GET("/search-tasks", ma.searchTask)
mr.POST("/server", ma.addOrEditServer)
mr.POST("/monitor", ma.addOrEditMonitor)
mr.POST("/cron", ma.addOrEditCron)
mr.GET("/cron/:id/manual", ma.manualTrigger)
mr.POST("/force-update", ma.forceUpdate)
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)
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
}
secureToken, err := utils.GenerateRandomString(32)
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: secureToken,
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) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
if id < 1 {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: "错误的 Server ID",
})
return
}
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 {
// 删除服务器
singleton.ServerLock.Lock()
onServerDelete(id)
singleton.ServerLock.Unlock()
singleton.ReSortServer()
}
case "notification":
err = singleton.DB.Unscoped().Delete(&model.Notification{}, "id = ?", id).Error
if err == nil {
singleton.OnDeleteNotification(id)
}
case "monitor":
err = singleton.DB.Unscoped().Delete(&model.Monitor{}, "id = ?", id).Error
if err == nil {
singleton.ServiceSentinelShared.OnMonitorDelete(id)
err = singleton.DB.Unscoped().Delete(&model.MonitorHistory{}, "monitor_id = ?", id).Error
}
case "cron":
err = singleton.DB.Unscoped().Delete(&model.Cron{}, "id = ?", id).Error
if err == nil {
singleton.CronLock.RLock()
defer singleton.CronLock.RUnlock()
cr := singleton.Crons[id]
if cr != nil && cr.CronJobID != 0 {
singleton.Cron.Remove(cr.CronJobID)
}
delete(singleton.Crons, id)
}
case "alert-rule":
err = singleton.DB.Unscoped().Delete(&model.AlertRule{}, "id = ?", id).Error
if err == nil {
singleton.OnDeleteAlert(id)
}
}
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") + "%"
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,
})
}
type serverForm struct {
ID uint64
Name string `binding:"required"`
DisplayIndex int
Secret string
Tag string
Note string
HideForGuest string
EnableDDNS string
DDNSDomain string
}
func (ma *memberAPI) addOrEditServer(c *gin.Context) {
var sf serverForm
var s model.Server
var isEdit bool
err := c.ShouldBindJSON(&sf)
if err == nil {
s.Name = sf.Name
s.Secret = sf.Secret
s.DisplayIndex = sf.DisplayIndex
s.ID = sf.ID
s.Tag = sf.Tag
s.Note = sf.Note
s.HideForGuest = sf.HideForGuest == "on"
s.EnableDDNS = sf.EnableDDNS == "on"
s.DDNSDomain = sf.DDNSDomain
if s.ID == 0 {
s.Secret, err = utils.GenerateRandomString(18)
if err == nil {
err = singleton.DB.Create(&s).Error
}
} else {
isEdit = true
err = singleton.DB.Save(&s).Error
}
}
if err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("请求错误:%s", err),
})
return
}
if isEdit {
singleton.ServerLock.Lock()
s.CopyFromRunningServer(singleton.ServerList[s.ID])
// 如果修改了 Secret
if s.Secret != singleton.ServerList[s.ID].Secret {
// 删除旧 Secret-ID 绑定关系
singleton.SecretToID[s.Secret] = s.ID
// 设置新的 Secret-ID 绑定关系
delete(singleton.SecretToID, singleton.ServerList[s.ID].Secret)
}
// 如果修改了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 {
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)
}
}
// 设置新的 Tag-ID 绑定关系
singleton.ServerTagToIDList[newTag] = append(singleton.ServerTagToIDList[newTag], s.ID)
}
singleton.ServerList[s.ID] = &s
singleton.ServerLock.Unlock()
} else {
s.Host = &model.Host{}
s.State = &model.HostState{}
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()
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
})
}
type monitorForm struct {
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
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
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
m.LatencyNotify = mf.LatencyNotify == "on"
m.MinLatency = mf.MinLatency
m.MaxLatency = mf.MaxLatency
m.EnableShowInService = mf.EnableShowInService == "on"
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"
}
if err == nil {
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
}
}
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 {
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
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 {
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)
}
// 计划任务类型不得使用触发服务器执行方式
if cr.TaskType == model.CronTypeCronTask && cr.Cover == model.CronCoverAlertTrigger {
err = errors.New("计划任务类型不得使用触发服务器执行方式")
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("请求错误:%s", err),
})
return
}
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
}
}
if err == nil {
// 对于计划任务类型,需要更新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()
}
if err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("请求错误:%s", err),
})
return
}
singleton.CronLock.Lock()
defer singleton.CronLock.Unlock()
crOld := singleton.Crons[cr.ID]
if crOld != nil && crOld.CronJobID != 0 {
singleton.Cron.Remove(crOld.CronJobID)
}
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
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
}
singleton.ManualTrigger(cr)
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 {
index := -1
for i := 0; i < len(singleton.ServerTagToIDList[oldTag]); i++ {
if singleton.ServerTagToIDList[oldTag][i] == s.ID {
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)
}
}
// 设置新的 Tag-ID 绑定关系
singleton.ServerTagToIDList[newTag] = append(singleton.ServerTagToIDList[newTag], s.ID)
}
singleton.ServerList[s.ID] = &s
}
singleton.ServerLock.Unlock()
singleton.ReSortServer()
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
})
}
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++ {
singleton.ServerLock.RLock()
server := singleton.ServerList[forceUpdateServers[i]]
singleton.ServerLock.RUnlock()
if server != nil && server.TaskStream != nil {
if err := server.TaskStream.Send(&proto.Task{
Type: model.TaskTypeUpgrade,
}); err != nil {
executeResult.WriteString(fmt.Sprintf("%d 下发指令失败 %+v
", forceUpdateServers[i], err))
} else {
executeResult.WriteString(fmt.Sprintf("%d 下发指令成功
", forceUpdateServers[i]))
}
} else {
executeResult.WriteString(fmt.Sprintf("%d 离线
", 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("这是测试消息")
}
}
if err == nil {
// 保证Tag不为空
if n.Tag == "" {
n.Tag = "default"
}
if n.ID == 0 {
err = singleton.DB.Create(&n).Error
} else {
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)
}
if err == nil {
if len(r.Rules) == 0 {
err = errors.New("至少定义一条规则")
} else {
for i := 0; i < len(r.Rules); i++ {
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
}
}
}
}
}
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 {
err = singleton.DB.Create(&r).Error
} else {
err = singleton.DB.Save(&r).Error
}
}
if err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("请求错误:%s", err),
})
return
}
singleton.OnRefreshOrAddAlert(r)
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
})
}
type logoutForm struct {
ID uint64
}
func (ma *memberAPI) logout(c *gin.Context) {
admin := c.MustGet(model.CtxKeyAuthorizedUser).(*model.User)
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
}
if lf.ID != admin.ID {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("请求错误:%s", "用户ID不匹配"),
})
return
}
singleton.DB.Model(admin).UpdateColumns(model.User{
Token: "",
TokenExpired: time.Now(),
})
c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK,
})
}
type settingForm struct {
Title string
Admin string
Language string
Theme string
DashboardTheme string
CustomCode string
ViewPassword string
IgnoredIPNotification string
IPChangeNotificationTag string // IP变更提醒的通知组
GRPCHost string
Cover uint8
EnableIPChangeNotification string
EnablePlainIPInNotification string
DisableSwitchTemplateInFrontend string
}
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
}
if _, yes := model.Themes[sf.Theme]; !yes {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("前台主题不存在:%s", sf.Theme),
})
return
}
if _, yes := model.Themes[sf.DashboardTheme]; !yes {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("后台主题不存在:%s", sf.DashboardTheme),
})
return
}
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
}
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),
})
return
}
singleton.Conf.Language = sf.Language
singleton.Conf.EnableIPChangeNotification = sf.EnableIPChangeNotification == "on"
singleton.Conf.EnablePlainIPInNotification = sf.EnablePlainIPInNotification == "on"
singleton.Conf.DisableSwitchTemplateInFrontend = sf.DisableSwitchTemplateInFrontend == "on"
singleton.Conf.Cover = sf.Cover
singleton.Conf.GRPCHost = sf.GRPCHost
singleton.Conf.IgnoredIPNotification = sf.IgnoredIPNotification
singleton.Conf.IPChangeNotificationTag = sf.IPChangeNotificationTag
singleton.Conf.Site.Brand = sf.Title
singleton.Conf.Site.Theme = sf.Theme
singleton.Conf.Site.DashboardTheme = sf.DashboardTheme
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"
}
if err := singleton.Conf.Save(); err != nil {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("请求错误:%s", err),
})
return
}
// 更新系统语言
singleton.InitLocalizer()
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)
}