nezha/cmd/dashboard/controller/common_page.go

476 lines
12 KiB
Go
Raw Normal View History

2019-12-08 03:59:58 -05:00
package controller
import (
2024-06-23 04:15:08 -04:00
"fmt"
2019-12-08 03:59:58 -05:00
"net/http"
"strconv"
2019-12-10 04:57:57 -05:00
"time"
2019-12-08 03:59:58 -05:00
"github.com/gin-gonic/gin"
2019-12-10 04:57:57 -05:00
"github.com/gorilla/websocket"
2021-08-17 23:56:54 -04:00
"github.com/hashicorp/go-uuid"
"github.com/jinzhu/copier"
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/utils"
"github.com/naiba/nezha/pkg/websocketx"
2021-08-17 23:56:54 -04:00
"github.com/naiba/nezha/proto"
"github.com/naiba/nezha/service/rpc"
2022-01-08 22:54:14 -05:00
"github.com/naiba/nezha/service/singleton"
2019-12-08 03:59:58 -05:00
)
type commonPage struct {
2024-10-20 11:23:04 -04:00
r *gin.Engine
2019-12-08 03:59:58 -05:00
}
func (cp *commonPage) serve() {
cr := cp.r.Group("")
2024-02-25 21:11:02 -05:00
cr.GET("/terminal/:id", cp.terminal)
// cr.Use(mygin.ValidateViewPassword(mygin.ValidateViewPasswordOption{
// IsPage: true,
// AbortWhenFail: true,
// }))
2019-12-08 03:59:58 -05:00
cr.GET("/", cp.home)
cr.GET("/service", cp.service)
// TODO: 界面直接跳转使用该接口
cr.GET("/network/:id", cp.network)
cr.GET("/network", cp.network)
2021-08-17 23:56:54 -04:00
cr.POST("/terminal", cp.createTerminal)
cr.GET("/file", cp.createFM)
cr.GET("/file/:id", cp.fm)
2019-12-08 03:59:58 -05:00
}
func (p *commonPage) service(c *gin.Context) {
2024-10-20 11:23:04 -04:00
res, _, _ := requestGroup.Do("servicePage", func() (interface{}, error) {
singleton.AlertsLock.RLock()
defer singleton.AlertsLock.RUnlock()
var stats map[uint64]model.ServiceItemResponse
var statsStore map[uint64]model.CycleTransferStats
copier.Copy(&stats, singleton.ServiceSentinelShared.LoadStats())
copier.Copy(&statsStore, singleton.AlertsCycleTransferStatsStore)
for k, service := range stats {
if !service.Monitor.EnableShowInService {
delete(stats, k)
}
}
return []interface {
}{
stats, statsStore,
}, nil
})
c.HTML(http.StatusOK, "", gin.H{
2024-10-19 12:09:16 -04:00
// "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "ServicesStatus"}),
"Services": res.([]interface{})[0],
"CycleTransferStats": res.([]interface{})[1],
})
}
func (cp *commonPage) network(c *gin.Context) {
var (
monitorHistory *model.MonitorHistory
2024-10-20 11:23:04 -04:00
servers []model.Server
serverIdsWithMonitor []uint64
monitorInfos = []byte("{}")
id uint64
)
if len(singleton.SortedServerList) > 0 {
id = singleton.SortedServerList[0].ID
}
if err := singleton.DB.Model(&model.MonitorHistory{}).Select("monitor_id, server_id").
Where("monitor_id != 0 and server_id != 0").Limit(1).First(&monitorHistory).Error; err != nil {
// mygin.ShowErrorPage(c, mygin.ErrInfo{
// Code: http.StatusForbidden,
// Title: "请求失败",
// Msg: "请求参数有误:" + "server monitor history not found",
// Link: "/",
// Btn: "返回重试",
// }, true)
return
} else {
if monitorHistory == nil || monitorHistory.ServerID == 0 {
if len(singleton.SortedServerList) > 0 {
id = singleton.SortedServerList[0].ID
}
} else {
id = monitorHistory.ServerID
}
}
idStr := c.Param("id")
if idStr != "" {
var err error
id, err = strconv.ParseUint(idStr, 10, 64)
if err != nil {
// mygin.ShowErrorPage(c, mygin.ErrInfo{
// Code: http.StatusForbidden,
// Title: "请求失败",
// Msg: "请求参数有误:" + err.Error(),
// Link: "/",
// Btn: "返回重试",
// }, true)
return
}
_, ok := singleton.ServerList[id]
if !ok {
// mygin.ShowErrorPage(c, mygin.ErrInfo{
// Code: http.StatusForbidden,
// Title: "请求失败",
// Msg: "请求参数有误:" + "server id not found",
// Link: "/",
// Btn: "返回重试",
// }, true)
return
}
}
monitorHistories := singleton.MonitorAPI.GetMonitorHistories(map[string]any{"server_id": id})
monitorInfos, _ = utils.Json.Marshal(monitorHistories)
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
2024-10-20 02:05:43 -04:00
var isViewPasswordVerfied bool
if err := singleton.DB.Model(&model.MonitorHistory{}).
Select("distinct(server_id)").
Where("server_id != 0").
Find(&serverIdsWithMonitor).
Error; err != nil {
// mygin.ShowErrorPage(c, mygin.ErrInfo{
// Code: http.StatusForbidden,
// Title: "请求失败",
// Msg: "请求参数有误:" + "no server with monitor histories",
// Link: "/",
// Btn: "返回重试",
// }, true)
return
}
if isMember || isViewPasswordVerfied {
for _, server := range singleton.SortedServerList {
for _, id := range serverIdsWithMonitor {
if server.ID == id {
2024-10-20 11:23:04 -04:00
servers = append(servers, *server)
}
}
}
} else {
for _, server := range singleton.SortedServerListForGuest {
for _, id := range serverIdsWithMonitor {
if server.ID == id {
2024-10-20 11:23:04 -04:00
servers = append(servers, *server)
}
}
}
}
2024-10-20 11:23:04 -04:00
serversBytes, _ := utils.Json.Marshal(model.StreamServerData{
Now: time.Now().Unix() * 1000,
// Servers: servers,
})
c.HTML(http.StatusOK, "", gin.H{
"Servers": string(serversBytes),
"MonitorInfos": string(monitorInfos),
})
}
func (cp *commonPage) getServerStat(c *gin.Context, withPublicNote bool) ([]byte, error) {
2024-06-23 04:15:08 -04:00
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
2024-10-20 02:05:43 -04:00
var isViewPasswordVerfied bool
2024-06-23 04:15:08 -04:00
authorized := isMember || isViewPasswordVerfied
2024-10-20 11:23:04 -04:00
v, err, _ := requestGroup.Do(fmt.Sprintf("serverStats::%t", authorized), func() (interface{}, error) {
2022-03-23 09:07:38 -04:00
singleton.SortedServerLock.RLock()
defer singleton.SortedServerLock.RUnlock()
var serverList []*model.Server
if authorized {
serverList = singleton.SortedServerList
} else {
serverList = singleton.SortedServerListForGuest
}
2024-10-20 11:23:04 -04:00
var servers []model.Server
for _, server := range serverList {
item := *server
if !withPublicNote {
item.PublicNote = ""
2024-06-23 04:15:08 -04:00
}
2024-10-20 11:23:04 -04:00
servers = append(servers, item)
}
2024-10-20 11:23:04 -04:00
return utils.Json.Marshal(model.StreamServerData{
Now: time.Now().Unix() * 1000,
// Servers: servers,
2022-03-23 09:07:38 -04:00
})
})
return v.([]byte), err
}
2019-12-08 03:59:58 -05:00
func (cp *commonPage) home(c *gin.Context) {
stat, err := cp.getServerStat(c, true)
2022-03-23 09:07:38 -04:00
if err != nil {
// mygin.ShowErrorPage(c, mygin.ErrInfo{
// Code: http.StatusInternalServerError,
// // Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
// // MessageID: "SystemError",
// // }),
// Msg: "服务器状态获取失败",
// Link: "/",
// Btn: "返回首页",
// }, true)
2022-03-23 09:07:38 -04:00
return
}
c.HTML(http.StatusOK, "", gin.H{
2024-08-13 12:24:17 -04:00
"Servers": string(stat),
})
2019-12-08 03:59:58 -05:00
}
2019-12-10 04:57:57 -05:00
2021-08-17 23:56:54 -04:00
func (cp *commonPage) terminal(c *gin.Context) {
2024-07-14 07:41:50 -04:00
streamId := c.Param("id")
if _, err := rpc.NezhaHandlerSingleton.GetStream(streamId); err != nil {
// mygin.ShowErrorPage(c, mygin.ErrInfo{
// Code: http.StatusForbidden,
// Title: "无权访问",
// Msg: "终端会话不存在",
// Link: "/",
// Btn: "返回首页",
// }, true)
2021-08-17 23:56:54 -04:00
return
}
2024-07-14 07:41:50 -04:00
defer rpc.NezhaHandlerSingleton.CloseStream(streamId)
2021-08-17 23:56:54 -04:00
wsConn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
2021-08-17 23:56:54 -04:00
if err != nil {
// mygin.ShowErrorPage(c, mygin.ErrInfo{
// Code: http.StatusInternalServerError,
// // Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
// // MessageID: "NetworkError",
// // }),
// Msg: "Websocket协议切换失败",
// Link: "/",
// Btn: "返回首页",
// }, true)
2021-08-17 23:56:54 -04:00
return
}
defer wsConn.Close()
conn := websocketx.NewConn(wsConn)
2021-08-19 22:45:10 -04:00
go func() {
// PING 保活
for {
if err = conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
return
}
time.Sleep(time.Second * 10)
}
}()
2024-07-14 07:41:50 -04:00
if err = rpc.NezhaHandlerSingleton.UserConnected(streamId, conn); err != nil {
return
2021-08-19 22:45:10 -04:00
}
2024-07-14 07:41:50 -04:00
rpc.NezhaHandlerSingleton.StartStream(streamId, time.Second*10)
2021-08-17 23:56:54 -04:00
}
type createTerminalRequest struct {
Host string
Protocol string
ID uint64
}
func (cp *commonPage) createTerminal(c *gin.Context) {
if _, authorized := c.Get(model.CtxKeyAuthorizedUser); !authorized {
// mygin.ShowErrorPage(c, mygin.ErrInfo{
// Code: http.StatusForbidden,
// Title: "无权访问",
// Msg: "用户未登录",
// Link: "/login",
// Btn: "去登录",
// }, true)
2021-08-17 23:56:54 -04:00
return
}
var createTerminalReq createTerminalRequest
if err := c.ShouldBind(&createTerminalReq); err != nil {
// mygin.ShowErrorPage(c, mygin.ErrInfo{
// Code: http.StatusForbidden,
// Title: "请求失败",
// Msg: "请求参数有误:" + err.Error(),
// Link: "/server",
// Btn: "返回重试",
// }, true)
2021-08-17 23:56:54 -04:00
return
}
2024-07-14 07:41:50 -04:00
streamId, err := uuid.GenerateUUID()
2021-08-17 23:56:54 -04:00
if err != nil {
// mygin.ShowErrorPage(c, mygin.ErrInfo{
// Code: http.StatusInternalServerError,
// // Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
// // MessageID: "SystemError",
// // }),
// Msg: "生成会话ID失败",
// Link: "/server",
// Btn: "返回重试",
// }, true)
2021-08-17 23:56:54 -04:00
return
}
2024-07-14 07:41:50 -04:00
rpc.NezhaHandlerSingleton.CreateStream(streamId)
2022-01-08 22:54:14 -05:00
singleton.ServerLock.RLock()
server := singleton.ServerList[createTerminalReq.ID]
singleton.ServerLock.RUnlock()
2021-08-17 23:56:54 -04:00
if server == nil || server.TaskStream == nil {
// mygin.ShowErrorPage(c, mygin.ErrInfo{
// Code: http.StatusForbidden,
// Title: "请求失败",
// Msg: "服务器不存在或处于离线状态",
// Link: "/server",
// Btn: "返回重试",
// }, true)
2021-08-17 23:56:54 -04:00
return
}
terminalData, _ := utils.Json.Marshal(&model.TerminalTask{
2024-07-14 07:41:50 -04:00
StreamID: streamId,
})
if err := server.TaskStream.Send(&proto.Task{
Type: model.TaskTypeTerminalGRPC,
Data: string(terminalData),
}); err != nil {
// mygin.ShowErrorPage(c, mygin.ErrInfo{
// Code: http.StatusForbidden,
// Title: "请求失败",
// Msg: "Agent信令下发失败",
// Link: "/server",
// Btn: "返回重试",
// }, true)
return
2021-08-17 23:56:54 -04:00
}
c.HTML(http.StatusOK, "", gin.H{
2024-07-14 07:41:50 -04:00
"SessionID": streamId,
"ServerName": server.Name,
"ServerID": server.ID,
})
}
func (cp *commonPage) fm(c *gin.Context) {
streamId := c.Param("id")
if _, err := rpc.NezhaHandlerSingleton.GetStream(streamId); err != nil {
// mygin.ShowErrorPage(c, mygin.ErrInfo{
// Code: http.StatusForbidden,
// Title: "无权访问",
// Msg: "FM会话不存在",
// Link: "/",
// Btn: "返回首页",
// }, true)
return
}
defer rpc.NezhaHandlerSingleton.CloseStream(streamId)
wsConn, 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 wsConn.Close()
conn := websocketx.NewConn(wsConn)
go func() {
// PING 保活
for {
if err = conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
return
}
time.Sleep(time.Second * 10)
}
}()
if err = rpc.NezhaHandlerSingleton.UserConnected(streamId, conn); err != nil {
return
}
rpc.NezhaHandlerSingleton.StartStream(streamId, time.Second*10)
}
func (cp *commonPage) createFM(c *gin.Context) {
IdString := c.Query("id")
if _, authorized := c.Get(model.CtxKeyAuthorizedUser); !authorized {
// mygin.ShowErrorPage(c, mygin.ErrInfo{
// Code: http.StatusForbidden,
// Title: "无权访问",
// Msg: "用户未登录",
// Link: "/login",
// Btn: "去登录",
// }, true)
return
}
streamId, err := uuid.GenerateUUID()
if err != nil {
// mygin.ShowErrorPage(c, mygin.ErrInfo{
// Code: http.StatusInternalServerError,
// // Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
// // MessageID: "SystemError",
// // }),
// Msg: "生成会话ID失败",
// Link: "/server",
// Btn: "返回重试",
// }, true)
return
}
rpc.NezhaHandlerSingleton.CreateStream(streamId)
serverId, err := strconv.Atoi(IdString)
if err != nil {
// mygin.ShowErrorPage(c, mygin.ErrInfo{
// Code: http.StatusForbidden,
// Title: "请求失败",
// Msg: "请求参数有误:" + err.Error(),
// Link: "/server",
// Btn: "返回重试",
// }, true)
return
}
singleton.ServerLock.RLock()
server := singleton.ServerList[uint64(serverId)]
singleton.ServerLock.RUnlock()
if server == nil {
// mygin.ShowErrorPage(c, mygin.ErrInfo{
// Code: http.StatusForbidden,
// Title: "请求失败",
// Msg: "服务器不存在或处于离线状态",
// Link: "/server",
// Btn: "返回重试",
// }, true)
return
}
fmData, _ := utils.Json.Marshal(&model.TaskFM{
StreamID: streamId,
})
if err := server.TaskStream.Send(&proto.Task{
Type: model.TaskTypeFM,
Data: string(fmData),
}); err != nil {
// mygin.ShowErrorPage(c, mygin.ErrInfo{
// Code: http.StatusForbidden,
// Title: "请求失败",
// Msg: "Agent信令下发失败",
// Link: "/server",
// Btn: "返回重试",
// }, true)
return
}
c.HTML(http.StatusOK, "dashboard-", gin.H{
"SessionID": streamId,
})
2021-08-17 23:56:54 -04:00
}