nezha/cmd/dashboard/controller/member_api.go
2022-01-09 11:54:14 +08:00

545 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package controller
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
"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/service/singleton"
)
type memberAPI struct {
r gin.IRouter
}
func (ma *memberAPI) serve() {
mr := ma.r.Group("")
mr.Use(mygin.Authorize(mygin.AuthorizeOption{
Member: true,
IsPage: false,
Msg: "访问此接口需要登录",
Btn: "点此登录",
Redirect: "/login",
}))
mr.GET("/search-server", ma.searchServer)
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("/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)
}
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.Unscoped().Delete(&model.Server{}, "id = ?", id).Error
if err == nil {
// 删除服务器
singleton.ServerLock.Lock()
delete(singleton.SecretToID, singleton.ServerList[id].Secret)
delete(singleton.ServerList, id)
singleton.ServerLock.Unlock()
singleton.ReSortServer()
// 删除循环流量状态中的此服务器相关的记录
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)
}
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,
})
}
type serverForm struct {
ID uint64
Name string `binding:"required"`
DisplayIndex int
Secret string
Tag string
Note string
}
func (ma *memberAPI) addOrEditServer(c *gin.Context) {
admin := c.MustGet(model.CtxKeyAuthorizedUser).(*model.User)
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
if s.ID == 0 {
s.Secret = utils.MD5(fmt.Sprintf("%s%s%d", time.Now(), sf.Name, admin.ID))
s.Secret = s.Secret[:18]
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)
}
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.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
SkipServersRaw string
Duration uint64
}
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.Duration = mf.Duration
}
if err == nil {
if m.ID == 0 {
err = singleton.DB.Create(&m).Error
} else {
err = singleton.DB.Save(&m).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
Name string
Scheduler string
Command string
ServersRaw string
Cover uint8
PushSuccessful string
}
func (ma *memberAPI) addOrEditCron(c *gin.Context) {
var cf cronForm
var cr model.Cron
err := c.ShouldBindJSON(&cf)
if err == nil {
cr.Name = cf.Name
cr.Scheduler = cf.Scheduler
cr.Command = cf.Command
cr.ServersRaw = cf.ServersRaw
cr.PushSuccessful = cf.PushSuccessful == "on"
cr.ID = cf.ID
cr.Cover = cf.Cover
err = json.Unmarshal([]byte(cf.ServersRaw), &cr.Servers)
}
tx := singleton.DB.Begin()
if err == nil {
if cf.ID == 0 {
err = tx.Create(&cr).Error
} else {
err = tx.Save(&cr).Error
}
}
if err == nil {
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,
})
}
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<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
URL string
RequestMethod int
RequestType int
RequestHeader string
RequestBody string
VerifySSL 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.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
err = n.Send("这是测试消息")
}
if err == nil {
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
Enable string
}
func (ma *memberAPI) addOrEditAlertRule(c *gin.Context) {
var arf alertRuleForm
var r model.AlertRule
err := c.ShouldBindJSON(&arf)
if err == nil {
err = 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() && r.Rules[i].Duration < 3 {
err = errors.New("错误Duration 至少为 3")
break
}
}
}
}
if err == nil {
r.Name = arf.Name
r.RulesRaw = arf.RulesRaw
enable := arf.Enable == "on"
r.Enable = &enable
r.ID = arf.ID
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
Theme string
CustomCode string
ViewPassword string
EnableIPChangeNotification string
IgnoredIPNotification string
GRPCHost string
Cover uint8
}
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
}
singleton.Conf.EnableIPChangeNotification = sf.EnableIPChangeNotification == "on"
singleton.Conf.Cover = sf.Cover
singleton.Conf.GRPCHost = sf.GRPCHost
singleton.Conf.IgnoredIPNotification = sf.IgnoredIPNotification
singleton.Conf.Site.Brand = sf.Title
singleton.Conf.Site.Theme = sf.Theme
singleton.Conf.Site.CustomCode = sf.CustomCode
singleton.Conf.Site.ViewPassword = sf.ViewPassword
singleton.Conf.Oauth2.Admin = sf.Admin
if err := singleton.Conf.Save(); 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,
})
}