mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-02-04 04:30:05 +00:00
refactor agent auth & server api
This commit is contained in:
@@ -72,7 +72,7 @@ func (v *apiV1) serverDetails(c *gin.Context) {
|
||||
}
|
||||
tag := c.Query("tag")
|
||||
if tag != "" {
|
||||
c.JSON(200, singleton.ServerAPI.GetStatusByTag(tag))
|
||||
// c.JSON(200, singleton.ServerAPI.GetStatusByTag(tag))
|
||||
return
|
||||
}
|
||||
if len(idList) != 0 {
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/jinzhu/copier"
|
||||
"golang.org/x/sync/singleflight"
|
||||
|
||||
"github.com/naiba/nezha/model"
|
||||
"github.com/naiba/nezha/pkg/utils"
|
||||
@@ -21,8 +20,7 @@ import (
|
||||
)
|
||||
|
||||
type commonPage struct {
|
||||
r *gin.Engine
|
||||
requestGroup singleflight.Group
|
||||
r *gin.Engine
|
||||
}
|
||||
|
||||
func (cp *commonPage) serve() {
|
||||
@@ -37,14 +35,13 @@ func (cp *commonPage) serve() {
|
||||
// TODO: 界面直接跳转使用该接口
|
||||
cr.GET("/network/:id", cp.network)
|
||||
cr.GET("/network", cp.network)
|
||||
cr.GET("/ws", cp.ws)
|
||||
cr.POST("/terminal", cp.createTerminal)
|
||||
cr.GET("/file", cp.createFM)
|
||||
cr.GET("/file/:id", cp.fm)
|
||||
}
|
||||
|
||||
func (p *commonPage) service(c *gin.Context) {
|
||||
res, _, _ := p.requestGroup.Do("servicePage", func() (interface{}, error) {
|
||||
res, _, _ := requestGroup.Do("servicePage", func() (interface{}, error) {
|
||||
singleton.AlertsLock.RLock()
|
||||
defer singleton.AlertsLock.RUnlock()
|
||||
var stats map[uint64]model.ServiceItemResponse
|
||||
@@ -71,7 +68,7 @@ func (p *commonPage) service(c *gin.Context) {
|
||||
func (cp *commonPage) network(c *gin.Context) {
|
||||
var (
|
||||
monitorHistory *model.MonitorHistory
|
||||
servers []*model.Server
|
||||
servers []model.Server
|
||||
serverIdsWithMonitor []uint64
|
||||
monitorInfos = []byte("{}")
|
||||
id uint64
|
||||
@@ -148,7 +145,7 @@ func (cp *commonPage) network(c *gin.Context) {
|
||||
for _, server := range singleton.SortedServerList {
|
||||
for _, id := range serverIdsWithMonitor {
|
||||
if server.ID == id {
|
||||
servers = append(servers, server)
|
||||
servers = append(servers, *server)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,14 +153,14 @@ func (cp *commonPage) network(c *gin.Context) {
|
||||
for _, server := range singleton.SortedServerListForGuest {
|
||||
for _, id := range serverIdsWithMonitor {
|
||||
if server.ID == id {
|
||||
servers = append(servers, server)
|
||||
servers = append(servers, *server)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
serversBytes, _ := utils.Json.Marshal(Data{
|
||||
Now: time.Now().Unix() * 1000,
|
||||
Servers: servers,
|
||||
serversBytes, _ := utils.Json.Marshal(model.StreamServerData{
|
||||
Now: time.Now().Unix() * 1000,
|
||||
// Servers: servers,
|
||||
})
|
||||
|
||||
c.HTML(http.StatusOK, "", gin.H{
|
||||
@@ -176,7 +173,7 @@ func (cp *commonPage) getServerStat(c *gin.Context, withPublicNote bool) ([]byte
|
||||
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
|
||||
var isViewPasswordVerfied bool
|
||||
authorized := isMember || isViewPasswordVerfied
|
||||
v, err, _ := cp.requestGroup.Do(fmt.Sprintf("serverStats::%t", authorized), func() (interface{}, error) {
|
||||
v, err, _ := requestGroup.Do(fmt.Sprintf("serverStats::%t", authorized), func() (interface{}, error) {
|
||||
singleton.SortedServerLock.RLock()
|
||||
defer singleton.SortedServerLock.RUnlock()
|
||||
|
||||
@@ -187,18 +184,18 @@ func (cp *commonPage) getServerStat(c *gin.Context, withPublicNote bool) ([]byte
|
||||
serverList = singleton.SortedServerListForGuest
|
||||
}
|
||||
|
||||
var servers []*model.Server
|
||||
var servers []model.Server
|
||||
for _, server := range serverList {
|
||||
item := *server
|
||||
if !withPublicNote {
|
||||
item.PublicNote = ""
|
||||
}
|
||||
servers = append(servers, &item)
|
||||
servers = append(servers, item)
|
||||
}
|
||||
|
||||
return utils.Json.Marshal(Data{
|
||||
Now: time.Now().Unix() * 1000,
|
||||
Servers: servers,
|
||||
return utils.Json.Marshal(model.StreamServerData{
|
||||
Now: time.Now().Unix() * 1000,
|
||||
// Servers: servers,
|
||||
})
|
||||
})
|
||||
return v.([]byte), err
|
||||
@@ -223,51 +220,6 @@ func (cp *commonPage) home(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 32768,
|
||||
WriteBufferSize: 32768,
|
||||
}
|
||||
|
||||
type Data struct {
|
||||
Now int64 `json:"now,omitempty"`
|
||||
Servers []*model.Server `json:"servers,omitempty"`
|
||||
}
|
||||
|
||||
func (cp *commonPage) ws(c *gin.Context) {
|
||||
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||
// Code: http.StatusInternalServerError,
|
||||
// // Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
|
||||
// // MessageID: "NetworkError",
|
||||
// // }),
|
||||
// Msg: "Websocket协议切换失败",
|
||||
// Link: "/",
|
||||
// Btn: "返回首页",
|
||||
// }, true)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
count := 0
|
||||
for {
|
||||
stat, err := cp.getServerStat(c, false)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if err := conn.WriteMessage(websocket.TextMessage, stat); err != nil {
|
||||
break
|
||||
}
|
||||
count += 1
|
||||
if count%4 == 0 {
|
||||
err = conn.WriteMessage(websocket.PingMessage, []byte{})
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
time.Sleep(time.Second * 2)
|
||||
}
|
||||
}
|
||||
|
||||
func (cp *commonPage) terminal(c *gin.Context) {
|
||||
streamId := c.Param("id")
|
||||
if _, err := rpc.NezhaHandlerSingleton.GetStream(streamId); err != nil {
|
||||
|
||||
@@ -49,13 +49,21 @@ func routers(r *gin.Engine) {
|
||||
if err != nil {
|
||||
log.Fatal("JWT Error:" + err.Error())
|
||||
}
|
||||
if err := authMiddleware.MiddlewareInit(); err != nil {
|
||||
log.Fatal("authMiddleware.MiddlewareInit Error:" + err.Error())
|
||||
}
|
||||
api := r.Group("api/v1")
|
||||
api.Use(handlerMiddleWare(authMiddleware))
|
||||
|
||||
api.POST("/login", authMiddleware.LoginHandler)
|
||||
|
||||
unrequiredAuth := api.Group("", unrquiredAuthMiddleware(authMiddleware))
|
||||
unrequiredAuth.GET("/ws/server", serverStream)
|
||||
unrequiredAuth.GET("/server-group", listServerGroup)
|
||||
|
||||
auth := api.Group("", authMiddleware.MiddlewareFunc())
|
||||
auth.GET("/refresh_token", authMiddleware.RefreshHandler)
|
||||
auth.PATCH("/server/:id", editServer)
|
||||
|
||||
api.DELETE("/batch-delete/server", batchDeleteServer)
|
||||
|
||||
// 通用页面
|
||||
// cp := commonPage{r: r}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
func initParams() *jwt.GinJWTMiddleware {
|
||||
return &jwt.GinJWTMiddleware{
|
||||
Realm: singleton.Conf.SiteName,
|
||||
Key: []byte(singleton.Conf.SecretKey),
|
||||
Key: []byte(singleton.Conf.JWTSecretKey),
|
||||
CookieName: "nz-jwt",
|
||||
Timeout: time.Hour,
|
||||
MaxRefresh: time.Hour,
|
||||
@@ -44,15 +44,6 @@ func initParams() *jwt.GinJWTMiddleware {
|
||||
}
|
||||
}
|
||||
|
||||
func handlerMiddleWare(authMiddleware *jwt.GinJWTMiddleware) gin.HandlerFunc {
|
||||
return func(context *gin.Context) {
|
||||
errInit := authMiddleware.MiddlewareInit()
|
||||
if errInit != nil {
|
||||
log.Fatal("authMiddleware.MiddlewareInit() Error:" + errInit.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func payloadFunc() func(data interface{}) jwt.MapClaims {
|
||||
return func(data interface{}) jwt.MapClaims {
|
||||
if v, ok := data.(string); ok {
|
||||
@@ -81,7 +72,7 @@ func identityHandler() func(c *gin.Context) interface{} {
|
||||
// @Schemes
|
||||
// @Description user login
|
||||
// @Accept json
|
||||
// @param request body model.LoginRequest true "Login Request"
|
||||
// @param loginRequest body model.LoginRequest true "Login Request"
|
||||
// @Produce json
|
||||
// @Success 200 {object} model.CommonResponse[model.LoginResponse]
|
||||
// @Router /login [post]
|
||||
@@ -152,3 +143,40 @@ func refreshResponse(c *gin.Context, code int, token string, expire time.Time) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func unrquiredAuthMiddleware(mw *jwt.GinJWTMiddleware) func(c *gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
claims, err := mw.GetClaimsFromJWT(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch v := claims["exp"].(type) {
|
||||
case nil:
|
||||
return
|
||||
case float64:
|
||||
if int64(v) < mw.TimeFunc().Unix() {
|
||||
return
|
||||
}
|
||||
case json.Number:
|
||||
n, err := v.Int64()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if n < mw.TimeFunc().Unix() {
|
||||
return
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
c.Set("JWT_PAYLOAD", claims)
|
||||
identity := mw.IdentityHandler(c)
|
||||
|
||||
if identity != nil {
|
||||
c.Set(mw.IdentityKey, identity)
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jinzhu/copier"
|
||||
"golang.org/x/net/idna"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/naiba/nezha/model"
|
||||
"github.com/naiba/nezha/pkg/utils"
|
||||
@@ -44,7 +43,6 @@ func (ma *memberAPI) serve() {
|
||||
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("/ddns", ma.addOrEditDDNS)
|
||||
mr.POST("/nat", ma.addOrEditNAT)
|
||||
@@ -188,25 +186,6 @@ func (ma *memberAPI) delete(c *gin.Context) {
|
||||
|
||||
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 {
|
||||
@@ -346,10 +325,8 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) {
|
||||
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.PublicNote = sf.PublicNote
|
||||
s.HideForGuest = sf.HideForGuest == "on"
|
||||
@@ -358,7 +335,7 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) {
|
||||
err = utils.Json.Unmarshal([]byte(sf.DDNSProfilesRaw), &s.DDNSProfiles)
|
||||
if err == nil {
|
||||
if s.ID == 0 {
|
||||
s.Secret, err = utils.GenerateRandomString(18)
|
||||
_, err = utils.GenerateRandomString(18)
|
||||
if err == nil {
|
||||
err = singleton.DB.Create(&s).Error
|
||||
}
|
||||
@@ -378,34 +355,6 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) {
|
||||
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 {
|
||||
@@ -413,9 +362,7 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) {
|
||||
s.State = &model.HostState{}
|
||||
s.TaskCloseLock = new(sync.Mutex)
|
||||
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()
|
||||
@@ -636,28 +583,28 @@ func (ma *memberAPI) batchUpdateServerGroup(c *gin.Context) {
|
||||
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)
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -1067,63 +1014,3 @@ func (ma *memberAPI) updateSetting(c *gin.Context) {
|
||||
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)
|
||||
}
|
||||
|
||||
130
cmd/dashboard/controller/server.go
Normal file
130
cmd/dashboard/controller/server.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/naiba/nezha/model"
|
||||
"github.com/naiba/nezha/pkg/utils"
|
||||
"github.com/naiba/nezha/service/singleton"
|
||||
)
|
||||
|
||||
// Edit server
|
||||
// @Summary Edit server
|
||||
// @Security BearerAuth
|
||||
// @Schemes
|
||||
// @Description Edit server
|
||||
// @Tags auth required
|
||||
// @Produce json
|
||||
// @Success 200 {object} model.CommonResponse[any]
|
||||
// @Router /server/{id} [patch]
|
||||
func editServer(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, model.CommonResponse[interface{}]{
|
||||
Success: false,
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
var sf model.EditServer
|
||||
var s model.Server
|
||||
if err := c.ShouldBindJSON(&sf); err != nil {
|
||||
c.JSON(http.StatusOK, model.CommonResponse[interface{}]{
|
||||
Success: false,
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
s.Name = sf.Name
|
||||
s.DisplayIndex = sf.DisplayIndex
|
||||
s.ID = id
|
||||
s.Note = sf.Note
|
||||
s.PublicNote = sf.PublicNote
|
||||
s.HideForGuest = sf.HideForGuest
|
||||
s.EnableDDNS = sf.EnableDDNS
|
||||
s.DDNSProfiles = sf.DDNSProfiles
|
||||
ddnsProfilesRaw, err := utils.Json.Marshal(s.DDNSProfiles)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, model.CommonResponse[interface{}]{
|
||||
Success: false,
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
s.DDNSProfilesRaw = string(ddnsProfilesRaw)
|
||||
|
||||
if err := singleton.DB.Save(&s).Error; err != nil {
|
||||
c.JSON(http.StatusOK, model.CommonResponse[interface{}]{
|
||||
Success: false,
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
singleton.ServerLock.Lock()
|
||||
s.CopyFromRunningServer(singleton.ServerList[s.ID])
|
||||
singleton.ServerList[s.ID] = &s
|
||||
singleton.ServerLock.Unlock()
|
||||
singleton.ReSortServer()
|
||||
c.JSON(http.StatusOK, model.Response{
|
||||
Code: http.StatusOK,
|
||||
})
|
||||
}
|
||||
|
||||
// Batch delete server
|
||||
// @Summary Batch delete server
|
||||
// @Security BearerAuth
|
||||
// @Schemes
|
||||
// @Description Batch delete server
|
||||
// @Tags auth required
|
||||
// @Accept json
|
||||
// @param request body []uint64 true "id list"
|
||||
// @Produce json
|
||||
// @Success 200 {object} model.CommonResponse[any]
|
||||
// @Router /batch-delete/server [post]
|
||||
func batchDeleteServer(c *gin.Context) {
|
||||
var servers []uint64
|
||||
if err := c.ShouldBindJSON(&servers); err != nil {
|
||||
c.JSON(http.StatusOK, model.CommonResponse[interface{}]{
|
||||
Success: false,
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := singleton.DB.Unscoped().Delete(&model.Server{}, "id in (?)", servers).Error; err != nil {
|
||||
c.JSON(http.StatusOK, model.CommonResponse[interface{}]{
|
||||
Success: false,
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
singleton.ServerLock.Lock()
|
||||
for i := 0; i < len(servers); i++ {
|
||||
id := servers[i]
|
||||
delete(singleton.ServerList, id)
|
||||
|
||||
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)
|
||||
}
|
||||
singleton.ServerLock.Unlock()
|
||||
|
||||
singleton.ReSortServer()
|
||||
|
||||
c.JSON(http.StatusOK, model.CommonResponse[interface{}]{
|
||||
Success: true,
|
||||
})
|
||||
}
|
||||
36
cmd/dashboard/controller/server_group.go
Normal file
36
cmd/dashboard/controller/server_group.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/naiba/nezha/model"
|
||||
"github.com/naiba/nezha/pkg/utils"
|
||||
"github.com/naiba/nezha/service/singleton"
|
||||
)
|
||||
|
||||
// List server group
|
||||
// @Summary List server group
|
||||
// @Schemes
|
||||
// @Description List server group
|
||||
// @Security BearerAuth
|
||||
// @Tags common
|
||||
// @Produce json
|
||||
// @Success 200 {object} model.CommonResponse[[]model.ServerGroup]
|
||||
// @Router /server-group [get]
|
||||
func listServerGroup(c *gin.Context) {
|
||||
authorizedUser, has := c.Get(model.CtxKeyAuthorizedUser)
|
||||
log.Println("bingo test", authorizedUser, has)
|
||||
var sg []model.ServerGroup
|
||||
err := singleton.DB.Find(&sg).Error
|
||||
c.JSON(http.StatusOK, model.CommonResponse[[]model.ServerGroup]{
|
||||
Success: err == nil,
|
||||
Data: sg,
|
||||
Error: utils.IfOrFn[string](err == nil, func() string {
|
||||
return err.Error()
|
||||
}, func() string {
|
||||
return ""
|
||||
}),
|
||||
})
|
||||
}
|
||||
96
cmd/dashboard/controller/ws.go
Normal file
96
cmd/dashboard/controller/ws.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/naiba/nezha/model"
|
||||
"github.com/naiba/nezha/pkg/utils"
|
||||
"github.com/naiba/nezha/service/singleton"
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 32768,
|
||||
WriteBufferSize: 32768,
|
||||
}
|
||||
|
||||
// Websocket server stream
|
||||
// @Summary Websocket server stream
|
||||
// @tags common
|
||||
// @Schemes
|
||||
// @Description Websocket server stream
|
||||
// @security BearerAuth
|
||||
// @Produce json
|
||||
// @Success 200 {object} model.StreamServerData
|
||||
// @Router /ws/server [get]
|
||||
func serverStream(c *gin.Context) {
|
||||
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, model.CommonResponse[interface{}]{
|
||||
Success: false,
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
count := 0
|
||||
for {
|
||||
stat, err := getServerStat(c, count == 0)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if err := conn.WriteMessage(websocket.TextMessage, stat); err != nil {
|
||||
break
|
||||
}
|
||||
count += 1
|
||||
if count%4 == 0 {
|
||||
err = conn.WriteMessage(websocket.PingMessage, []byte{})
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
time.Sleep(time.Second * 2)
|
||||
}
|
||||
}
|
||||
|
||||
var requestGroup singleflight.Group
|
||||
|
||||
func getServerStat(c *gin.Context, withPublicNote bool) ([]byte, error) {
|
||||
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
|
||||
authorized := isMember // TODO || isViewPasswordVerfied
|
||||
v, err, _ := requestGroup.Do(fmt.Sprintf("serverStats::%t", authorized), func() (interface{}, error) {
|
||||
singleton.SortedServerLock.RLock()
|
||||
defer singleton.SortedServerLock.RUnlock()
|
||||
|
||||
var serverList []*model.Server
|
||||
if authorized {
|
||||
serverList = singleton.SortedServerList
|
||||
} else {
|
||||
serverList = singleton.SortedServerListForGuest
|
||||
}
|
||||
|
||||
var servers []model.StreamServer
|
||||
for i := 0; i < len(serverList); i++ {
|
||||
server := serverList[i]
|
||||
servers = append(servers, model.StreamServer{
|
||||
ID: server.ID,
|
||||
Name: server.Name,
|
||||
PublicNote: utils.IfOr(withPublicNote, server.PublicNote, ""),
|
||||
DisplayIndex: server.DisplayIndex,
|
||||
Host: server.Host,
|
||||
State: server.State,
|
||||
LastActive: server.LastActive,
|
||||
})
|
||||
}
|
||||
|
||||
return utils.Json.Marshal(model.StreamServerData{
|
||||
Now: time.Now().Unix() * 1000,
|
||||
Servers: servers,
|
||||
})
|
||||
})
|
||||
return v.([]byte), err
|
||||
}
|
||||
Reference in New Issue
Block a user