mirror of
https://github.com/nezhahq/nezha.git
synced 2025-02-08 12:38:13 -05:00
refactor: remove pages, combine grpc http port
This commit is contained in:
parent
4fc0aad7a0
commit
606e10ca0a
@ -7,7 +7,6 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/naiba/nezha/model"
|
"github.com/naiba/nezha/model"
|
||||||
"github.com/naiba/nezha/pkg/mygin"
|
|
||||||
"github.com/naiba/nezha/service/singleton"
|
"github.com/naiba/nezha/service/singleton"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -18,30 +17,30 @@ type apiV1 struct {
|
|||||||
func (v *apiV1) serve() {
|
func (v *apiV1) serve() {
|
||||||
r := v.r.Group("")
|
r := v.r.Group("")
|
||||||
// 强制认证的 API
|
// 强制认证的 API
|
||||||
r.Use(mygin.Authorize(mygin.AuthorizeOption{
|
// r.Use(mygin.Authorize(mygin.AuthorizeOption{
|
||||||
MemberOnly: true,
|
// MemberOnly: true,
|
||||||
AllowAPI: true,
|
// AllowAPI: true,
|
||||||
IsPage: false,
|
// IsPage: false,
|
||||||
Msg: "访问此接口需要认证",
|
// Msg: "访问此接口需要认证",
|
||||||
Btn: "点此登录",
|
// Btn: "点此登录",
|
||||||
Redirect: "/login",
|
// Redirect: "/login",
|
||||||
}))
|
// }))
|
||||||
r.GET("/server/list", v.serverList)
|
r.GET("/server/list", v.serverList)
|
||||||
r.GET("/server/details", v.serverDetails)
|
r.GET("/server/details", v.serverDetails)
|
||||||
// 不强制认证的 API
|
// 不强制认证的 API
|
||||||
mr := v.r.Group("monitor")
|
mr := v.r.Group("monitor")
|
||||||
mr.Use(mygin.Authorize(mygin.AuthorizeOption{
|
// mr.Use(mygin.Authorize(mygin.AuthorizeOption{
|
||||||
MemberOnly: false,
|
// MemberOnly: false,
|
||||||
IsPage: false,
|
// IsPage: false,
|
||||||
AllowAPI: true,
|
// AllowAPI: true,
|
||||||
Msg: "访问此接口需要认证",
|
// Msg: "访问此接口需要认证",
|
||||||
Btn: "点此登录",
|
// Btn: "点此登录",
|
||||||
Redirect: "/login",
|
// Redirect: "/login",
|
||||||
}))
|
// }))
|
||||||
mr.Use(mygin.ValidateViewPassword(mygin.ValidateViewPasswordOption{
|
// mr.Use(mygin.ValidateViewPassword(mygin.ValidateViewPasswordOption{
|
||||||
IsPage: false,
|
// IsPage: false,
|
||||||
AbortWhenFail: true,
|
// AbortWhenFail: true,
|
||||||
}))
|
// }))
|
||||||
mr.GET("/:id", v.monitorHistoriesById)
|
mr.GET("/:id", v.monitorHistoriesById)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,11 +10,9 @@ import (
|
|||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/hashicorp/go-uuid"
|
"github.com/hashicorp/go-uuid"
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
"golang.org/x/sync/singleflight"
|
"golang.org/x/sync/singleflight"
|
||||||
|
|
||||||
"github.com/naiba/nezha/model"
|
"github.com/naiba/nezha/model"
|
||||||
"github.com/naiba/nezha/pkg/mygin"
|
|
||||||
"github.com/naiba/nezha/pkg/utils"
|
"github.com/naiba/nezha/pkg/utils"
|
||||||
"github.com/naiba/nezha/pkg/websocketx"
|
"github.com/naiba/nezha/pkg/websocketx"
|
||||||
"github.com/naiba/nezha/proto"
|
"github.com/naiba/nezha/proto"
|
||||||
@ -29,14 +27,11 @@ type commonPage struct {
|
|||||||
|
|
||||||
func (cp *commonPage) serve() {
|
func (cp *commonPage) serve() {
|
||||||
cr := cp.r.Group("")
|
cr := cp.r.Group("")
|
||||||
cr.Use(mygin.Authorize(mygin.AuthorizeOption{}))
|
|
||||||
cr.Use(mygin.PreferredTheme)
|
|
||||||
cr.POST("/view-password", cp.issueViewPassword)
|
|
||||||
cr.GET("/terminal/:id", cp.terminal)
|
cr.GET("/terminal/:id", cp.terminal)
|
||||||
cr.Use(mygin.ValidateViewPassword(mygin.ValidateViewPasswordOption{
|
// cr.Use(mygin.ValidateViewPassword(mygin.ValidateViewPasswordOption{
|
||||||
IsPage: true,
|
// IsPage: true,
|
||||||
AbortWhenFail: true,
|
// AbortWhenFail: true,
|
||||||
}))
|
// }))
|
||||||
cr.GET("/", cp.home)
|
cr.GET("/", cp.home)
|
||||||
cr.GET("/service", cp.service)
|
cr.GET("/service", cp.service)
|
||||||
// TODO: 界面直接跳转使用该接口
|
// TODO: 界面直接跳转使用该接口
|
||||||
@ -48,44 +43,6 @@ func (cp *commonPage) serve() {
|
|||||||
cr.GET("/file/:id", cp.fm)
|
cr.GET("/file/:id", cp.fm)
|
||||||
}
|
}
|
||||||
|
|
||||||
type viewPasswordForm struct {
|
|
||||||
Password string
|
|
||||||
}
|
|
||||||
|
|
||||||
// PingExample godoc
|
|
||||||
// @Summary ping example
|
|
||||||
// @Schemes
|
|
||||||
// @Description do ping
|
|
||||||
// @Tags example
|
|
||||||
// @Accept json
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {string} Helloworld
|
|
||||||
// @Router /example/helloworld [get]
|
|
||||||
func (p *commonPage) issueViewPassword(c *gin.Context) {
|
|
||||||
var vpf viewPasswordForm
|
|
||||||
err := c.ShouldBind(&vpf)
|
|
||||||
var hash []byte
|
|
||||||
if err == nil && vpf.Password != singleton.Conf.Site.ViewPassword {
|
|
||||||
// err = errors.New(singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "WrongAccessPassword"}))
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
hash, err = bcrypt.GenerateFromPassword([]byte(vpf.Password), bcrypt.DefaultCost)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
|
||||||
Code: http.StatusOK,
|
|
||||||
// Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
|
|
||||||
// MessageID: "AnErrorEccurred",
|
|
||||||
// }),
|
|
||||||
Msg: err.Error(),
|
|
||||||
}, true)
|
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.SetCookie(singleton.Conf.Site.CookieName+"-vp", string(hash), 60*60*24, "", "", false, false)
|
|
||||||
c.Redirect(http.StatusFound, c.Request.Referer())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *commonPage) service(c *gin.Context) {
|
func (p *commonPage) service(c *gin.Context) {
|
||||||
res, _, _ := p.requestGroup.Do("servicePage", func() (interface{}, error) {
|
res, _, _ := p.requestGroup.Do("servicePage", func() (interface{}, error) {
|
||||||
singleton.AlertsLock.RLock()
|
singleton.AlertsLock.RLock()
|
||||||
@ -104,11 +61,11 @@ func (p *commonPage) service(c *gin.Context) {
|
|||||||
stats, statsStore,
|
stats, statsStore,
|
||||||
}, nil
|
}, nil
|
||||||
})
|
})
|
||||||
c.HTML(http.StatusOK, mygin.GetPreferredTheme(c, "/service"), mygin.CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, "", gin.H{
|
||||||
// "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "ServicesStatus"}),
|
// "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "ServicesStatus"}),
|
||||||
"Services": res.([]interface{})[0],
|
"Services": res.([]interface{})[0],
|
||||||
"CycleTransferStats": res.([]interface{})[1],
|
"CycleTransferStats": res.([]interface{})[1],
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *commonPage) network(c *gin.Context) {
|
func (cp *commonPage) network(c *gin.Context) {
|
||||||
@ -124,13 +81,13 @@ func (cp *commonPage) network(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
if err := singleton.DB.Model(&model.MonitorHistory{}).Select("monitor_id, server_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 {
|
Where("monitor_id != 0 and server_id != 0").Limit(1).First(&monitorHistory).Error; err != nil {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusForbidden,
|
// Code: http.StatusForbidden,
|
||||||
Title: "请求失败",
|
// Title: "请求失败",
|
||||||
Msg: "请求参数有误:" + "server monitor history not found",
|
// Msg: "请求参数有误:" + "server monitor history not found",
|
||||||
Link: "/",
|
// Link: "/",
|
||||||
Btn: "返回重试",
|
// Btn: "返回重试",
|
||||||
}, true)
|
// }, true)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
if monitorHistory == nil || monitorHistory.ServerID == 0 {
|
if monitorHistory == nil || monitorHistory.ServerID == 0 {
|
||||||
@ -147,24 +104,24 @@ func (cp *commonPage) network(c *gin.Context) {
|
|||||||
var err error
|
var err error
|
||||||
id, err = strconv.ParseUint(idStr, 10, 64)
|
id, err = strconv.ParseUint(idStr, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusForbidden,
|
// Code: http.StatusForbidden,
|
||||||
Title: "请求失败",
|
// Title: "请求失败",
|
||||||
Msg: "请求参数有误:" + err.Error(),
|
// Msg: "请求参数有误:" + err.Error(),
|
||||||
Link: "/",
|
// Link: "/",
|
||||||
Btn: "返回重试",
|
// Btn: "返回重试",
|
||||||
}, true)
|
// }, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, ok := singleton.ServerList[id]
|
_, ok := singleton.ServerList[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusForbidden,
|
// Code: http.StatusForbidden,
|
||||||
Title: "请求失败",
|
// Title: "请求失败",
|
||||||
Msg: "请求参数有误:" + "server id not found",
|
// Msg: "请求参数有误:" + "server id not found",
|
||||||
Link: "/",
|
// Link: "/",
|
||||||
Btn: "返回重试",
|
// Btn: "返回重试",
|
||||||
}, true)
|
// }, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,13 +135,13 @@ func (cp *commonPage) network(c *gin.Context) {
|
|||||||
Where("server_id != 0").
|
Where("server_id != 0").
|
||||||
Find(&serverIdsWithMonitor).
|
Find(&serverIdsWithMonitor).
|
||||||
Error; err != nil {
|
Error; err != nil {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusForbidden,
|
// Code: http.StatusForbidden,
|
||||||
Title: "请求失败",
|
// Title: "请求失败",
|
||||||
Msg: "请求参数有误:" + "no server with monitor histories",
|
// Msg: "请求参数有误:" + "no server with monitor histories",
|
||||||
Link: "/",
|
// Link: "/",
|
||||||
Btn: "返回重试",
|
// Btn: "返回重试",
|
||||||
}, true)
|
// }, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if isMember || isViewPasswordVerfied {
|
if isMember || isViewPasswordVerfied {
|
||||||
@ -209,11 +166,10 @@ func (cp *commonPage) network(c *gin.Context) {
|
|||||||
Servers: servers,
|
Servers: servers,
|
||||||
})
|
})
|
||||||
|
|
||||||
c.HTML(http.StatusOK, mygin.GetPreferredTheme(c, "/network"), mygin.CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, "", gin.H{
|
||||||
"Servers": string(serversBytes),
|
"Servers": string(serversBytes),
|
||||||
"MonitorInfos": string(monitorInfos),
|
"MonitorInfos": string(monitorInfos),
|
||||||
"MaxTCPPingValue": singleton.Conf.MaxTCPPingValue,
|
})
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *commonPage) getServerStat(c *gin.Context, withPublicNote bool) ([]byte, error) {
|
func (cp *commonPage) getServerStat(c *gin.Context, withPublicNote bool) ([]byte, error) {
|
||||||
@ -251,20 +207,20 @@ func (cp *commonPage) getServerStat(c *gin.Context, withPublicNote bool) ([]byte
|
|||||||
func (cp *commonPage) home(c *gin.Context) {
|
func (cp *commonPage) home(c *gin.Context) {
|
||||||
stat, err := cp.getServerStat(c, true)
|
stat, err := cp.getServerStat(c, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusInternalServerError,
|
// Code: http.StatusInternalServerError,
|
||||||
// Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
|
// // Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
|
||||||
// MessageID: "SystemError",
|
// // MessageID: "SystemError",
|
||||||
// }),
|
// // }),
|
||||||
Msg: "服务器状态获取失败",
|
// Msg: "服务器状态获取失败",
|
||||||
Link: "/",
|
// Link: "/",
|
||||||
Btn: "返回首页",
|
// Btn: "返回首页",
|
||||||
}, true)
|
// }, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.HTML(http.StatusOK, mygin.GetPreferredTheme(c, "/home"), mygin.CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, "", gin.H{
|
||||||
"Servers": string(stat),
|
"Servers": string(stat),
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var upgrader = websocket.Upgrader{
|
var upgrader = websocket.Upgrader{
|
||||||
@ -280,15 +236,15 @@ type Data struct {
|
|||||||
func (cp *commonPage) ws(c *gin.Context) {
|
func (cp *commonPage) ws(c *gin.Context) {
|
||||||
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusInternalServerError,
|
// Code: http.StatusInternalServerError,
|
||||||
// Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
|
// // Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
|
||||||
// MessageID: "NetworkError",
|
// // MessageID: "NetworkError",
|
||||||
// }),
|
// // }),
|
||||||
Msg: "Websocket协议切换失败",
|
// Msg: "Websocket协议切换失败",
|
||||||
Link: "/",
|
// Link: "/",
|
||||||
Btn: "返回首页",
|
// Btn: "返回首页",
|
||||||
}, true)
|
// }, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
@ -315,28 +271,28 @@ func (cp *commonPage) ws(c *gin.Context) {
|
|||||||
func (cp *commonPage) terminal(c *gin.Context) {
|
func (cp *commonPage) terminal(c *gin.Context) {
|
||||||
streamId := c.Param("id")
|
streamId := c.Param("id")
|
||||||
if _, err := rpc.NezhaHandlerSingleton.GetStream(streamId); err != nil {
|
if _, err := rpc.NezhaHandlerSingleton.GetStream(streamId); err != nil {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusForbidden,
|
// Code: http.StatusForbidden,
|
||||||
Title: "无权访问",
|
// Title: "无权访问",
|
||||||
Msg: "终端会话不存在",
|
// Msg: "终端会话不存在",
|
||||||
Link: "/",
|
// Link: "/",
|
||||||
Btn: "返回首页",
|
// Btn: "返回首页",
|
||||||
}, true)
|
// }, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer rpc.NezhaHandlerSingleton.CloseStream(streamId)
|
defer rpc.NezhaHandlerSingleton.CloseStream(streamId)
|
||||||
|
|
||||||
wsConn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
wsConn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusInternalServerError,
|
// Code: http.StatusInternalServerError,
|
||||||
// Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
|
// // Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
|
||||||
// MessageID: "NetworkError",
|
// // MessageID: "NetworkError",
|
||||||
// }),
|
// // }),
|
||||||
Msg: "Websocket协议切换失败",
|
// Msg: "Websocket协议切换失败",
|
||||||
Link: "/",
|
// Link: "/",
|
||||||
Btn: "返回首页",
|
// Btn: "返回首页",
|
||||||
}, true)
|
// }, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer wsConn.Close()
|
defer wsConn.Close()
|
||||||
@ -367,38 +323,38 @@ type createTerminalRequest struct {
|
|||||||
|
|
||||||
func (cp *commonPage) createTerminal(c *gin.Context) {
|
func (cp *commonPage) createTerminal(c *gin.Context) {
|
||||||
if _, authorized := c.Get(model.CtxKeyAuthorizedUser); !authorized {
|
if _, authorized := c.Get(model.CtxKeyAuthorizedUser); !authorized {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusForbidden,
|
// Code: http.StatusForbidden,
|
||||||
Title: "无权访问",
|
// Title: "无权访问",
|
||||||
Msg: "用户未登录",
|
// Msg: "用户未登录",
|
||||||
Link: "/login",
|
// Link: "/login",
|
||||||
Btn: "去登录",
|
// Btn: "去登录",
|
||||||
}, true)
|
// }, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var createTerminalReq createTerminalRequest
|
var createTerminalReq createTerminalRequest
|
||||||
if err := c.ShouldBind(&createTerminalReq); err != nil {
|
if err := c.ShouldBind(&createTerminalReq); err != nil {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusForbidden,
|
// Code: http.StatusForbidden,
|
||||||
Title: "请求失败",
|
// Title: "请求失败",
|
||||||
Msg: "请求参数有误:" + err.Error(),
|
// Msg: "请求参数有误:" + err.Error(),
|
||||||
Link: "/server",
|
// Link: "/server",
|
||||||
Btn: "返回重试",
|
// Btn: "返回重试",
|
||||||
}, true)
|
// }, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
streamId, err := uuid.GenerateUUID()
|
streamId, err := uuid.GenerateUUID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusInternalServerError,
|
// Code: http.StatusInternalServerError,
|
||||||
// Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
|
// // Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
|
||||||
// MessageID: "SystemError",
|
// // MessageID: "SystemError",
|
||||||
// }),
|
// // }),
|
||||||
Msg: "生成会话ID失败",
|
// Msg: "生成会话ID失败",
|
||||||
Link: "/server",
|
// Link: "/server",
|
||||||
Btn: "返回重试",
|
// Btn: "返回重试",
|
||||||
}, true)
|
// }, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,13 +364,13 @@ func (cp *commonPage) createTerminal(c *gin.Context) {
|
|||||||
server := singleton.ServerList[createTerminalReq.ID]
|
server := singleton.ServerList[createTerminalReq.ID]
|
||||||
singleton.ServerLock.RUnlock()
|
singleton.ServerLock.RUnlock()
|
||||||
if server == nil || server.TaskStream == nil {
|
if server == nil || server.TaskStream == nil {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusForbidden,
|
// Code: http.StatusForbidden,
|
||||||
Title: "请求失败",
|
// Title: "请求失败",
|
||||||
Msg: "服务器不存在或处于离线状态",
|
// Msg: "服务器不存在或处于离线状态",
|
||||||
Link: "/server",
|
// Link: "/server",
|
||||||
Btn: "返回重试",
|
// Btn: "返回重试",
|
||||||
}, true)
|
// }, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -425,48 +381,48 @@ func (cp *commonPage) createTerminal(c *gin.Context) {
|
|||||||
Type: model.TaskTypeTerminalGRPC,
|
Type: model.TaskTypeTerminalGRPC,
|
||||||
Data: string(terminalData),
|
Data: string(terminalData),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusForbidden,
|
// Code: http.StatusForbidden,
|
||||||
Title: "请求失败",
|
// Title: "请求失败",
|
||||||
Msg: "Agent信令下发失败",
|
// Msg: "Agent信令下发失败",
|
||||||
Link: "/server",
|
// Link: "/server",
|
||||||
Btn: "返回重试",
|
// Btn: "返回重试",
|
||||||
}, true)
|
// }, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/terminal", mygin.CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, "", gin.H{
|
||||||
"SessionID": streamId,
|
"SessionID": streamId,
|
||||||
"ServerName": server.Name,
|
"ServerName": server.Name,
|
||||||
"ServerID": server.ID,
|
"ServerID": server.ID,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *commonPage) fm(c *gin.Context) {
|
func (cp *commonPage) fm(c *gin.Context) {
|
||||||
streamId := c.Param("id")
|
streamId := c.Param("id")
|
||||||
if _, err := rpc.NezhaHandlerSingleton.GetStream(streamId); err != nil {
|
if _, err := rpc.NezhaHandlerSingleton.GetStream(streamId); err != nil {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusForbidden,
|
// Code: http.StatusForbidden,
|
||||||
Title: "无权访问",
|
// Title: "无权访问",
|
||||||
Msg: "FM会话不存在",
|
// Msg: "FM会话不存在",
|
||||||
Link: "/",
|
// Link: "/",
|
||||||
Btn: "返回首页",
|
// Btn: "返回首页",
|
||||||
}, true)
|
// }, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer rpc.NezhaHandlerSingleton.CloseStream(streamId)
|
defer rpc.NezhaHandlerSingleton.CloseStream(streamId)
|
||||||
|
|
||||||
wsConn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
wsConn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusInternalServerError,
|
// Code: http.StatusInternalServerError,
|
||||||
// Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
|
// // Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
|
||||||
// MessageID: "NetworkError",
|
// // MessageID: "NetworkError",
|
||||||
// }),
|
// // }),
|
||||||
Msg: "Websocket协议切换失败",
|
// Msg: "Websocket协议切换失败",
|
||||||
Link: "/",
|
// Link: "/",
|
||||||
Btn: "返回首页",
|
// Btn: "返回首页",
|
||||||
}, true)
|
// }, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer wsConn.Close()
|
defer wsConn.Close()
|
||||||
@ -492,27 +448,27 @@ func (cp *commonPage) fm(c *gin.Context) {
|
|||||||
func (cp *commonPage) createFM(c *gin.Context) {
|
func (cp *commonPage) createFM(c *gin.Context) {
|
||||||
IdString := c.Query("id")
|
IdString := c.Query("id")
|
||||||
if _, authorized := c.Get(model.CtxKeyAuthorizedUser); !authorized {
|
if _, authorized := c.Get(model.CtxKeyAuthorizedUser); !authorized {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusForbidden,
|
// Code: http.StatusForbidden,
|
||||||
Title: "无权访问",
|
// Title: "无权访问",
|
||||||
Msg: "用户未登录",
|
// Msg: "用户未登录",
|
||||||
Link: "/login",
|
// Link: "/login",
|
||||||
Btn: "去登录",
|
// Btn: "去登录",
|
||||||
}, true)
|
// }, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
streamId, err := uuid.GenerateUUID()
|
streamId, err := uuid.GenerateUUID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusInternalServerError,
|
// Code: http.StatusInternalServerError,
|
||||||
// Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
|
// // Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
|
||||||
// MessageID: "SystemError",
|
// // MessageID: "SystemError",
|
||||||
// }),
|
// // }),
|
||||||
Msg: "生成会话ID失败",
|
// Msg: "生成会话ID失败",
|
||||||
Link: "/server",
|
// Link: "/server",
|
||||||
Btn: "返回重试",
|
// Btn: "返回重试",
|
||||||
}, true)
|
// }, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -520,13 +476,13 @@ func (cp *commonPage) createFM(c *gin.Context) {
|
|||||||
|
|
||||||
serverId, err := strconv.Atoi(IdString)
|
serverId, err := strconv.Atoi(IdString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusForbidden,
|
// Code: http.StatusForbidden,
|
||||||
Title: "请求失败",
|
// Title: "请求失败",
|
||||||
Msg: "请求参数有误:" + err.Error(),
|
// Msg: "请求参数有误:" + err.Error(),
|
||||||
Link: "/server",
|
// Link: "/server",
|
||||||
Btn: "返回重试",
|
// Btn: "返回重试",
|
||||||
}, true)
|
// }, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -534,13 +490,13 @@ func (cp *commonPage) createFM(c *gin.Context) {
|
|||||||
server := singleton.ServerList[uint64(serverId)]
|
server := singleton.ServerList[uint64(serverId)]
|
||||||
singleton.ServerLock.RUnlock()
|
singleton.ServerLock.RUnlock()
|
||||||
if server == nil {
|
if server == nil {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusForbidden,
|
// Code: http.StatusForbidden,
|
||||||
Title: "请求失败",
|
// Title: "请求失败",
|
||||||
Msg: "服务器不存在或处于离线状态",
|
// Msg: "服务器不存在或处于离线状态",
|
||||||
Link: "/server",
|
// Link: "/server",
|
||||||
Btn: "返回重试",
|
// Btn: "返回重试",
|
||||||
}, true)
|
// }, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -551,17 +507,17 @@ func (cp *commonPage) createFM(c *gin.Context) {
|
|||||||
Type: model.TaskTypeFM,
|
Type: model.TaskTypeFM,
|
||||||
Data: string(fmData),
|
Data: string(fmData),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusForbidden,
|
// Code: http.StatusForbidden,
|
||||||
Title: "请求失败",
|
// Title: "请求失败",
|
||||||
Msg: "Agent信令下发失败",
|
// Msg: "Agent信令下发失败",
|
||||||
Link: "/server",
|
// Link: "/server",
|
||||||
Btn: "返回重试",
|
// Btn: "返回重试",
|
||||||
}, true)
|
// }, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/file", mygin.CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, "dashboard-", gin.H{
|
||||||
"SessionID": streamId,
|
"SessionID": streamId,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jwt "github.com/appleboy/gin-jwt/v2"
|
jwt "github.com/appleboy/gin-jwt/v2"
|
||||||
@ -15,7 +16,6 @@ import (
|
|||||||
|
|
||||||
docs "github.com/naiba/nezha/cmd/dashboard/docs"
|
docs "github.com/naiba/nezha/cmd/dashboard/docs"
|
||||||
"github.com/naiba/nezha/model"
|
"github.com/naiba/nezha/model"
|
||||||
"github.com/naiba/nezha/pkg/mygin"
|
|
||||||
"github.com/naiba/nezha/pkg/utils"
|
"github.com/naiba/nezha/pkg/utils"
|
||||||
"github.com/naiba/nezha/proto"
|
"github.com/naiba/nezha/proto"
|
||||||
"github.com/naiba/nezha/service/rpc"
|
"github.com/naiba/nezha/service/rpc"
|
||||||
@ -41,7 +41,7 @@ import (
|
|||||||
|
|
||||||
// @externalDocs.description OpenAPI
|
// @externalDocs.description OpenAPI
|
||||||
// @externalDocs.url https://swagger.io/resources/open-api/
|
// @externalDocs.url https://swagger.io/resources/open-api/
|
||||||
func ServeWeb(port uint) *http.Server {
|
func ServeWeb() *http.Server {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
docs.SwaggerInfo.BasePath = "/api/v1"
|
docs.SwaggerInfo.BasePath = "/api/v1"
|
||||||
@ -50,23 +50,24 @@ func ServeWeb(port uint) *http.Server {
|
|||||||
pprof.Register(r)
|
pprof.Register(r)
|
||||||
}
|
}
|
||||||
r.Use(natGateway)
|
r.Use(natGateway)
|
||||||
|
if singleton.Conf.Debug {
|
||||||
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
|
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
|
||||||
r.Use(mygin.RecordPath)
|
}
|
||||||
|
r.Use(recordPath)
|
||||||
routers(r)
|
routers(r)
|
||||||
page404 := func(c *gin.Context) {
|
page404 := func(c *gin.Context) {
|
||||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
// mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
Code: http.StatusNotFound,
|
// Code: http.StatusNotFound,
|
||||||
Title: "该页面不存在",
|
// Title: "该页面不存在",
|
||||||
Msg: "该页面内容可能已着陆火星",
|
// Msg: "该页面内容可能已着陆火星",
|
||||||
Link: "/",
|
// Link: "/",
|
||||||
Btn: "返回首页",
|
// Btn: "返回首页",
|
||||||
}, true)
|
// }, true)
|
||||||
}
|
}
|
||||||
r.NoRoute(page404)
|
r.NoRoute(page404)
|
||||||
r.NoMethod(page404)
|
r.NoMethod(page404)
|
||||||
|
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: fmt.Sprintf(":%d", port),
|
|
||||||
ReadHeaderTimeout: time.Second * 5,
|
ReadHeaderTimeout: time.Second * 5,
|
||||||
Handler: r,
|
Handler: r,
|
||||||
}
|
}
|
||||||
@ -90,9 +91,6 @@ func routers(r *gin.Engine) {
|
|||||||
// 通用页面
|
// 通用页面
|
||||||
cp := commonPage{r: r}
|
cp := commonPage{r: r}
|
||||||
cp.serve()
|
cp.serve()
|
||||||
// 游客页面
|
|
||||||
gp := guestPage{r}
|
|
||||||
gp.serve()
|
|
||||||
// 会员页面
|
// 会员页面
|
||||||
mp := &memberPage{r}
|
mp := &memberPage{r}
|
||||||
mp.serve()
|
mp.serve()
|
||||||
@ -164,3 +162,11 @@ func natGateway(c *gin.Context) {
|
|||||||
rpc.NezhaHandlerSingleton.StartStream(streamId, time.Second*10)
|
rpc.NezhaHandlerSingleton.StartStream(streamId, time.Second*10)
|
||||||
c.Abort()
|
c.Abort()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func recordPath(c *gin.Context) {
|
||||||
|
url := c.Request.URL.String()
|
||||||
|
for _, p := range c.Params {
|
||||||
|
url = strings.Replace(url, p.Value, ":"+p.Key, 1)
|
||||||
|
}
|
||||||
|
c.Set("MatchedPath", url)
|
||||||
|
}
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
package controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
|
|
||||||
"github.com/naiba/nezha/model"
|
|
||||||
"github.com/naiba/nezha/pkg/mygin"
|
|
||||||
"github.com/naiba/nezha/service/singleton"
|
|
||||||
)
|
|
||||||
|
|
||||||
type guestPage struct {
|
|
||||||
r *gin.Engine
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gp *guestPage) serve() {
|
|
||||||
gr := gp.r.Group("")
|
|
||||||
gr.Use(mygin.Authorize(mygin.AuthorizeOption{
|
|
||||||
GuestOnly: true,
|
|
||||||
IsPage: true,
|
|
||||||
Msg: "您已登录",
|
|
||||||
Btn: "返回首页",
|
|
||||||
Redirect: "/",
|
|
||||||
}))
|
|
||||||
|
|
||||||
gr.GET("/login", gp.login)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gp *guestPage) login(c *gin.Context) {
|
|
||||||
if singleton.Conf.Oauth2.OidcAutoLogin {
|
|
||||||
c.Redirect(http.StatusFound, "/oauth2/login")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
LoginType := "GitHub"
|
|
||||||
RegistrationLink := "https://github.com/join"
|
|
||||||
if singleton.Conf.Oauth2.Type == model.ConfigTypeGitee {
|
|
||||||
LoginType = "Gitee"
|
|
||||||
RegistrationLink = "https://gitee.com/signup"
|
|
||||||
} else if singleton.Conf.Oauth2.Type == model.ConfigTypeGitlab {
|
|
||||||
LoginType = "Gitlab"
|
|
||||||
RegistrationLink = "https://gitlab.com/users/sign_up"
|
|
||||||
} else if singleton.Conf.Oauth2.Type == model.ConfigTypeJihulab {
|
|
||||||
LoginType = "Jihulab"
|
|
||||||
RegistrationLink = "https://jihulab.com/users/sign_up"
|
|
||||||
} else if singleton.Conf.Oauth2.Type == model.ConfigTypeGitea {
|
|
||||||
LoginType = "Gitea"
|
|
||||||
RegistrationLink = fmt.Sprintf("%s/user/sign_up", singleton.Conf.Oauth2.Endpoint)
|
|
||||||
} else if singleton.Conf.Oauth2.Type == model.ConfigTypeCloudflare {
|
|
||||||
LoginType = "Cloudflare"
|
|
||||||
RegistrationLink = "https://dash.cloudflare.com/sign-up/teams"
|
|
||||||
} else if singleton.Conf.Oauth2.Type == model.ConfigTypeOidc {
|
|
||||||
LoginType = singleton.Conf.Oauth2.OidcDisplayName
|
|
||||||
RegistrationLink = singleton.Conf.Oauth2.OidcRegisterURL
|
|
||||||
}
|
|
||||||
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/login", mygin.CommonEnvironment(c, gin.H{
|
|
||||||
// "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Login"}),
|
|
||||||
"LoginType": LoginType,
|
|
||||||
"RegistrationLink": RegistrationLink,
|
|
||||||
}))
|
|
||||||
}
|
|
@ -7,12 +7,14 @@ import (
|
|||||||
jwt "github.com/appleboy/gin-jwt/v2"
|
jwt "github.com/appleboy/gin-jwt/v2"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/naiba/nezha/model"
|
"github.com/naiba/nezha/model"
|
||||||
|
"github.com/naiba/nezha/service/singleton"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initParams() *jwt.GinJWTMiddleware {
|
func initParams() *jwt.GinJWTMiddleware {
|
||||||
return &jwt.GinJWTMiddleware{
|
return &jwt.GinJWTMiddleware{
|
||||||
Realm: "test zone",
|
Realm: singleton.Conf.SiteName,
|
||||||
Key: []byte("secret key"),
|
Key: []byte(singleton.Conf.SecretKey),
|
||||||
Timeout: time.Hour,
|
Timeout: time.Hour,
|
||||||
MaxRefresh: time.Hour,
|
MaxRefresh: time.Hour,
|
||||||
IdentityKey: model.CtxKeyAuthorizedUser,
|
IdentityKey: model.CtxKeyAuthorizedUser,
|
||||||
@ -41,7 +43,7 @@ func payloadFunc() func(data interface{}) jwt.MapClaims {
|
|||||||
return func(data interface{}) jwt.MapClaims {
|
return func(data interface{}) jwt.MapClaims {
|
||||||
if v, ok := data.(*model.User); ok {
|
if v, ok := data.(*model.User); ok {
|
||||||
return jwt.MapClaims{
|
return jwt.MapClaims{
|
||||||
model.CtxKeyAuthorizedUser: v.Username,
|
model.CtxKeyAuthorizedUser: v.ID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return jwt.MapClaims{}
|
return jwt.MapClaims{}
|
||||||
@ -51,36 +53,48 @@ func payloadFunc() func(data interface{}) jwt.MapClaims {
|
|||||||
func identityHandler() func(c *gin.Context) interface{} {
|
func identityHandler() func(c *gin.Context) interface{} {
|
||||||
return func(c *gin.Context) interface{} {
|
return func(c *gin.Context) interface{} {
|
||||||
claims := jwt.ExtractClaims(c)
|
claims := jwt.ExtractClaims(c)
|
||||||
return &model.User{
|
userId := claims[model.CtxKeyAuthorizedUser].(uint64)
|
||||||
Username: claims[model.CtxKeyAuthorizedUser].(string),
|
var user model.User
|
||||||
|
if err := singleton.DB.First(&user, userId).Error; err != nil {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
return &user
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// login test godoc
|
||||||
|
// @Summary ping example
|
||||||
|
// @Schemes
|
||||||
|
// @Description do ping
|
||||||
|
// @Tags example
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {string} Helloworld
|
||||||
|
// @Router /example/login [get]
|
||||||
func authenticator() func(c *gin.Context) (interface{}, error) {
|
func authenticator() func(c *gin.Context) (interface{}, error) {
|
||||||
return func(c *gin.Context) (interface{}, error) {
|
return func(c *gin.Context) (interface{}, error) {
|
||||||
var loginVals model.LoginRequest
|
var loginVals model.LoginRequest
|
||||||
if err := c.ShouldBind(&loginVals); err != nil {
|
if err := c.ShouldBind(&loginVals); err != nil {
|
||||||
return "", jwt.ErrMissingLoginValues
|
return "", jwt.ErrMissingLoginValues
|
||||||
}
|
}
|
||||||
userID := loginVals.Username
|
|
||||||
password := loginVals.Password
|
|
||||||
|
|
||||||
if (userID == "admin" && password == "admin") || (userID == "test" && password == "test") {
|
var user model.User
|
||||||
return &model.User{
|
if err := singleton.DB.Select("id").Where("username = ?", loginVals.Username).First(&user).Error; err != nil {
|
||||||
Username: userID,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
return nil, jwt.ErrFailedAuthentication
|
return nil, jwt.ErrFailedAuthentication
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(loginVals.Password)); err != nil {
|
||||||
|
return nil, jwt.ErrFailedAuthentication
|
||||||
|
}
|
||||||
|
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func authorizator() func(data interface{}, c *gin.Context) bool {
|
func authorizator() func(data interface{}, c *gin.Context) bool {
|
||||||
return func(data interface{}, c *gin.Context) bool {
|
return func(data interface{}, c *gin.Context) bool {
|
||||||
if v, ok := data.(*model.User); ok && v.Username == "admin" {
|
_, ok := data.(*model.User)
|
||||||
return true
|
return ok
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"github.com/naiba/nezha/model"
|
"github.com/naiba/nezha/model"
|
||||||
"github.com/naiba/nezha/pkg/mygin"
|
|
||||||
"github.com/naiba/nezha/pkg/utils"
|
"github.com/naiba/nezha/pkg/utils"
|
||||||
"github.com/naiba/nezha/proto"
|
"github.com/naiba/nezha/proto"
|
||||||
"github.com/naiba/nezha/service/singleton"
|
"github.com/naiba/nezha/service/singleton"
|
||||||
@ -28,13 +27,13 @@ type memberAPI struct {
|
|||||||
|
|
||||||
func (ma *memberAPI) serve() {
|
func (ma *memberAPI) serve() {
|
||||||
mr := ma.r.Group("")
|
mr := ma.r.Group("")
|
||||||
mr.Use(mygin.Authorize(mygin.AuthorizeOption{
|
// mr.Use(mygin.Authorize(mygin.AuthorizeOption{
|
||||||
MemberOnly: true,
|
// MemberOnly: true,
|
||||||
IsPage: false,
|
// IsPage: false,
|
||||||
Msg: "访问此接口需要登录",
|
// Msg: "访问此接口需要登录",
|
||||||
Btn: "点此登录",
|
// Btn: "点此登录",
|
||||||
Redirect: "/login",
|
// Redirect: "/login",
|
||||||
}))
|
// }))
|
||||||
|
|
||||||
mr.GET("/search-server", ma.searchServer)
|
mr.GET("/search-server", ma.searchServer)
|
||||||
mr.GET("/search-tasks", ma.searchTask)
|
mr.GET("/search-tasks", ma.searchTask)
|
||||||
@ -997,30 +996,23 @@ func (ma *memberAPI) logout(c *gin.Context) {
|
|||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
})
|
})
|
||||||
|
|
||||||
if oidcLogoutUrl := singleton.Conf.Oauth2.OidcLogoutURL; oidcLogoutUrl != "" {
|
// if oidcLogoutUrl := singleton.Conf.Oauth2.OidcLogoutURL; oidcLogoutUrl != "" {
|
||||||
// 重定向到 OIDC 退出登录地址。不知道为什么,这里的重定向不生效
|
// // 重定向到 OIDC 退出登录地址。不知道为什么,这里的重定向不生效
|
||||||
c.Redirect(http.StatusOK, oidcLogoutUrl)
|
// c.Redirect(http.StatusOK, oidcLogoutUrl)
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
type settingForm struct {
|
type settingForm struct {
|
||||||
Title string
|
SiteName string
|
||||||
Admin string
|
|
||||||
Language string
|
Language string
|
||||||
Theme string
|
|
||||||
DashboardTheme string
|
|
||||||
CustomCode string
|
|
||||||
CustomCodeDashboard string
|
|
||||||
CustomNameservers string
|
CustomNameservers string
|
||||||
ViewPassword string
|
|
||||||
IgnoredIPNotification string
|
IgnoredIPNotification string
|
||||||
IPChangeNotificationTag string // IP变更提醒的通知组
|
IPChangeNotificationTag string // IP变更提醒的通知组
|
||||||
GRPCHost string
|
InstallHost string
|
||||||
Cover uint8
|
Cover uint8
|
||||||
|
|
||||||
EnableIPChangeNotification string
|
EnableIPChangeNotification string
|
||||||
EnablePlainIPInNotification string
|
EnablePlainIPInNotification string
|
||||||
DisableSwitchTemplateInFrontend string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ma *memberAPI) updateSetting(c *gin.Context) {
|
func (ma *memberAPI) updateSetting(c *gin.Context) {
|
||||||
@ -1033,38 +1025,31 @@ func (ma *memberAPI) updateSetting(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, yes := model.Themes[sf.Theme]; !yes {
|
// if _, yes := model.Themes[sf.Theme]; !yes {
|
||||||
c.JSON(http.StatusOK, model.Response{
|
// c.JSON(http.StatusOK, model.Response{
|
||||||
Code: http.StatusBadRequest,
|
// Code: http.StatusBadRequest,
|
||||||
Message: fmt.Sprintf("前台主题不存在:%s", sf.Theme),
|
// Message: fmt.Sprintf("前台主题不存在:%s", sf.Theme),
|
||||||
})
|
// })
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
if _, yes := model.DashboardThemes[sf.DashboardTheme]; !yes {
|
// if _, yes := model.DashboardThemes[sf.DashboardTheme]; !yes {
|
||||||
c.JSON(http.StatusOK, model.Response{
|
// c.JSON(http.StatusOK, model.Response{
|
||||||
Code: http.StatusBadRequest,
|
// Code: http.StatusBadRequest,
|
||||||
Message: fmt.Sprintf("后台主题不存在:%s", sf.DashboardTheme),
|
// Message: fmt.Sprintf("后台主题不存在:%s", sf.DashboardTheme),
|
||||||
})
|
// })
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
singleton.Conf.Language = sf.Language
|
singleton.Conf.Language = sf.Language
|
||||||
singleton.Conf.EnableIPChangeNotification = sf.EnableIPChangeNotification == "on"
|
singleton.Conf.EnableIPChangeNotification = sf.EnableIPChangeNotification == "on"
|
||||||
singleton.Conf.EnablePlainIPInNotification = sf.EnablePlainIPInNotification == "on"
|
singleton.Conf.EnablePlainIPInNotification = sf.EnablePlainIPInNotification == "on"
|
||||||
singleton.Conf.DisableSwitchTemplateInFrontend = sf.DisableSwitchTemplateInFrontend == "on"
|
|
||||||
singleton.Conf.Cover = sf.Cover
|
singleton.Conf.Cover = sf.Cover
|
||||||
// singleton.Conf.GRPCHost = sf.GRPCHost
|
singleton.Conf.InstallHost = sf.InstallHost
|
||||||
singleton.Conf.IgnoredIPNotification = sf.IgnoredIPNotification
|
singleton.Conf.IgnoredIPNotification = sf.IgnoredIPNotification
|
||||||
singleton.Conf.IPChangeNotificationTag = sf.IPChangeNotificationTag
|
singleton.Conf.IPChangeNotificationTag = sf.IPChangeNotificationTag
|
||||||
singleton.Conf.Site.Brand = sf.Title
|
singleton.Conf.SiteName = sf.SiteName
|
||||||
singleton.Conf.Site.Theme = sf.Theme
|
|
||||||
singleton.Conf.Site.DashboardTheme = sf.DashboardTheme
|
|
||||||
singleton.Conf.Site.CustomCode = sf.CustomCode
|
|
||||||
singleton.Conf.Site.CustomCodeDashboard = sf.CustomCodeDashboard
|
|
||||||
singleton.Conf.DNSServers = sf.CustomNameservers
|
singleton.Conf.DNSServers = sf.CustomNameservers
|
||||||
singleton.Conf.Site.ViewPassword = sf.ViewPassword
|
|
||||||
singleton.Conf.Oauth2.Admin = sf.Admin
|
|
||||||
// 保证NotificationTag不为空
|
// 保证NotificationTag不为空
|
||||||
if singleton.Conf.IPChangeNotificationTag == "" {
|
if singleton.Conf.IPChangeNotificationTag == "" {
|
||||||
singleton.Conf.IPChangeNotificationTag = "default"
|
singleton.Conf.IPChangeNotificationTag = "default"
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/naiba/nezha/model"
|
"github.com/naiba/nezha/model"
|
||||||
"github.com/naiba/nezha/pkg/mygin"
|
|
||||||
"github.com/naiba/nezha/service/singleton"
|
"github.com/naiba/nezha/service/singleton"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,13 +14,13 @@ type memberPage struct {
|
|||||||
|
|
||||||
func (mp *memberPage) serve() {
|
func (mp *memberPage) serve() {
|
||||||
mr := mp.r.Group("")
|
mr := mp.r.Group("")
|
||||||
mr.Use(mygin.Authorize(mygin.AuthorizeOption{
|
// mr.Use(mygin.Authorize(mygin.AuthorizeOption{
|
||||||
MemberOnly: true,
|
// MemberOnly: true,
|
||||||
IsPage: true,
|
// IsPage: true,
|
||||||
// Msg: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "YouAreNotAuthorized"}),
|
// // Msg: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "YouAreNotAuthorized"}),
|
||||||
// Btn: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Login"}),
|
// // Btn: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Login"}),
|
||||||
Redirect: "/login",
|
// Redirect: "/login",
|
||||||
}))
|
// }))
|
||||||
mr.GET("/server", mp.server)
|
mr.GET("/server", mp.server)
|
||||||
mr.GET("/monitor", mp.monitor)
|
mr.GET("/monitor", mp.monitor)
|
||||||
mr.GET("/cron", mp.cron)
|
mr.GET("/cron", mp.cron)
|
||||||
@ -35,35 +34,35 @@ func (mp *memberPage) serve() {
|
|||||||
func (mp *memberPage) api(c *gin.Context) {
|
func (mp *memberPage) api(c *gin.Context) {
|
||||||
singleton.ApiLock.RLock()
|
singleton.ApiLock.RLock()
|
||||||
defer singleton.ApiLock.RUnlock()
|
defer singleton.ApiLock.RUnlock()
|
||||||
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/api", mygin.CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, "dashboard-", gin.H{
|
||||||
// "title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "ApiManagement"}),
|
// "title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "ApiManagement"}),
|
||||||
"Tokens": singleton.ApiTokenList,
|
"Tokens": singleton.ApiTokenList,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mp *memberPage) server(c *gin.Context) {
|
func (mp *memberPage) server(c *gin.Context) {
|
||||||
singleton.SortedServerLock.RLock()
|
singleton.SortedServerLock.RLock()
|
||||||
defer singleton.SortedServerLock.RUnlock()
|
defer singleton.SortedServerLock.RUnlock()
|
||||||
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/server", mygin.CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, "dashboard-", gin.H{
|
||||||
// "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "ServersManagement"}),
|
// "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "ServersManagement"}),
|
||||||
"Servers": singleton.SortedServerList,
|
"Servers": singleton.SortedServerList,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mp *memberPage) monitor(c *gin.Context) {
|
func (mp *memberPage) monitor(c *gin.Context) {
|
||||||
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/monitor", mygin.CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, "dashboard-", gin.H{
|
||||||
// "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "ServicesManagement"}),
|
// "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "ServicesManagement"}),
|
||||||
"Monitors": singleton.ServiceSentinelShared.Monitors(),
|
"Monitors": singleton.ServiceSentinelShared.Monitors(),
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mp *memberPage) cron(c *gin.Context) {
|
func (mp *memberPage) cron(c *gin.Context) {
|
||||||
var crons []model.Cron
|
var crons []model.Cron
|
||||||
singleton.DB.Find(&crons)
|
singleton.DB.Find(&crons)
|
||||||
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/cron", mygin.CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, "dashboard-", gin.H{
|
||||||
// "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "ScheduledTasks"}),
|
// "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "ScheduledTasks"}),
|
||||||
"Crons": crons,
|
"Crons": crons,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mp *memberPage) notification(c *gin.Context) {
|
func (mp *memberPage) notification(c *gin.Context) {
|
||||||
@ -71,37 +70,37 @@ func (mp *memberPage) notification(c *gin.Context) {
|
|||||||
singleton.DB.Find(&nf)
|
singleton.DB.Find(&nf)
|
||||||
var ar []model.AlertRule
|
var ar []model.AlertRule
|
||||||
singleton.DB.Find(&ar)
|
singleton.DB.Find(&ar)
|
||||||
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/notification", mygin.CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, "dashboard-", gin.H{
|
||||||
// "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Notification"}),
|
// "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Notification"}),
|
||||||
"Notifications": nf,
|
"Notifications": nf,
|
||||||
"AlertRules": ar,
|
"AlertRules": ar,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mp *memberPage) ddns(c *gin.Context) {
|
func (mp *memberPage) ddns(c *gin.Context) {
|
||||||
var data []model.DDNSProfile
|
var data []model.DDNSProfile
|
||||||
singleton.DB.Find(&data)
|
singleton.DB.Find(&data)
|
||||||
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/ddns", mygin.CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, "dashboard-", gin.H{
|
||||||
// "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "DDNS"}),
|
// "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "DDNS"}),
|
||||||
"DDNS": data,
|
"DDNS": data,
|
||||||
"ProviderMap": model.ProviderMap,
|
"ProviderMap": model.ProviderMap,
|
||||||
"ProviderList": model.ProviderList,
|
"ProviderList": model.ProviderList,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mp *memberPage) nat(c *gin.Context) {
|
func (mp *memberPage) nat(c *gin.Context) {
|
||||||
var data []model.NAT
|
var data []model.NAT
|
||||||
singleton.DB.Find(&data)
|
singleton.DB.Find(&data)
|
||||||
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/nat", mygin.CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, "dashboard-", gin.H{
|
||||||
// "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "NAT"}),
|
// "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "NAT"}),
|
||||||
"NAT": data,
|
"NAT": data,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mp *memberPage) setting(c *gin.Context) {
|
func (mp *memberPage) setting(c *gin.Context) {
|
||||||
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/setting", mygin.CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, "dashboard-", gin.H{
|
||||||
// "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Settings"}),
|
// "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Settings"}),
|
||||||
"Languages": model.Languages,
|
"Languages": model.Languages,
|
||||||
"DashboardThemes": model.DashboardThemes,
|
"DashboardThemes": model.DashboardThemes,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
@ -4,18 +4,20 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"time"
|
"time"
|
||||||
_ "time/tzdata"
|
_ "time/tzdata"
|
||||||
|
|
||||||
|
"github.com/ory/graceful"
|
||||||
|
"github.com/soheilhy/cmux"
|
||||||
|
flag "github.com/spf13/pflag"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
"github.com/naiba/nezha/cmd/dashboard/controller"
|
"github.com/naiba/nezha/cmd/dashboard/controller"
|
||||||
"github.com/naiba/nezha/cmd/dashboard/rpc"
|
"github.com/naiba/nezha/cmd/dashboard/rpc"
|
||||||
"github.com/naiba/nezha/model"
|
"github.com/naiba/nezha/model"
|
||||||
"github.com/naiba/nezha/proto"
|
"github.com/naiba/nezha/proto"
|
||||||
"github.com/naiba/nezha/service/singleton"
|
"github.com/naiba/nezha/service/singleton"
|
||||||
"github.com/ory/graceful"
|
|
||||||
flag "github.com/spf13/pflag"
|
|
||||||
// gin-swagger middleware
|
|
||||||
// swagger embed files
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DashboardCliParam struct {
|
type DashboardCliParam struct {
|
||||||
@ -43,6 +45,25 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initSystem() {
|
func initSystem() {
|
||||||
|
// 初始化管理员账户
|
||||||
|
var usersCount int64
|
||||||
|
if err := singleton.DB.Model(&model.User{}).Count(&usersCount).Error; err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if usersCount == 0 {
|
||||||
|
hash, err := bcrypt.GenerateFromPassword([]byte("admin"), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
admin := model.User{
|
||||||
|
Username: "admin",
|
||||||
|
Password: string(hash),
|
||||||
|
}
|
||||||
|
if err := singleton.DB.Create(&admin).Error; err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 启动 singleton 包下的所有服务
|
// 启动 singleton 包下的所有服务
|
||||||
singleton.LoadSingleton()
|
singleton.LoadSingleton()
|
||||||
|
|
||||||
@ -63,19 +84,28 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
l, err := net.Listen("tcp", fmt.Sprintf(":%d", singleton.Conf.ListenPort))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := cmux.New(l)
|
||||||
|
grpcL := m.Match(cmux.HTTP2HeaderField("content-type", "application/grpc"))
|
||||||
|
httpL := m.Match(cmux.HTTP1Fast())
|
||||||
|
|
||||||
// TODO 使用 cmux 在同一端口服务 HTTP 和 gRPC
|
// TODO 使用 cmux 在同一端口服务 HTTP 和 gRPC
|
||||||
singleton.CleanMonitorHistory()
|
singleton.CleanMonitorHistory()
|
||||||
go rpc.ServeRPC(singleton.Conf.ListenPort)
|
go rpc.ServeRPC(grpcL)
|
||||||
serviceSentinelDispatchBus := make(chan model.Monitor) // 用于传递服务监控任务信息的channel
|
serviceSentinelDispatchBus := make(chan model.Monitor) // 用于传递服务监控任务信息的channel
|
||||||
go rpc.DispatchTask(serviceSentinelDispatchBus)
|
go rpc.DispatchTask(serviceSentinelDispatchBus)
|
||||||
go rpc.DispatchKeepalive()
|
go rpc.DispatchKeepalive()
|
||||||
go singleton.AlertSentinelStart()
|
go singleton.AlertSentinelStart()
|
||||||
singleton.NewServiceSentinel(serviceSentinelDispatchBus)
|
singleton.NewServiceSentinel(serviceSentinelDispatchBus)
|
||||||
srv := controller.ServeWeb(singleton.Conf.ListenPort)
|
srv := controller.ServeWeb()
|
||||||
|
|
||||||
go dispatchReportInfoTask()
|
go dispatchReportInfoTask()
|
||||||
if err := graceful.Graceful(func() error {
|
if err := graceful.Graceful(func() error {
|
||||||
return srv.ListenAndServe()
|
return srv.Serve(httpL)
|
||||||
}, func(c context.Context) error {
|
}, func(c context.Context) error {
|
||||||
log.Println("NEZHA>> Graceful::START")
|
log.Println("NEZHA>> Graceful::START")
|
||||||
singleton.RecordTransferHourlyUsage()
|
singleton.RecordTransferHourlyUsage()
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package rpc
|
package rpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
@ -12,15 +11,11 @@ import (
|
|||||||
"github.com/naiba/nezha/service/singleton"
|
"github.com/naiba/nezha/service/singleton"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ServeRPC(port uint) {
|
func ServeRPC(l net.Listener) {
|
||||||
server := grpc.NewServer()
|
server := grpc.NewServer()
|
||||||
rpcService.NezhaHandlerSingleton = rpcService.NewNezhaHandler()
|
rpcService.NezhaHandlerSingleton = rpcService.NewNezhaHandler()
|
||||||
pb.RegisterNezhaServiceServer(server, rpcService.NezhaHandlerSingleton)
|
pb.RegisterNezhaServiceServer(server, rpcService.NezhaHandlerSingleton)
|
||||||
listen, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
server.Serve(l)
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
server.Serve(listen)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DispatchTask(serviceSentinelDispatchBus <-chan model.Monitor) {
|
func DispatchTask(serviceSentinelDispatchBus <-chan model.Monitor) {
|
||||||
|
1
go.mod
1
go.mod
@ -20,6 +20,7 @@ require (
|
|||||||
github.com/oschwald/maxminddb-golang v1.13.1
|
github.com/oschwald/maxminddb-golang v1.13.1
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
|
github.com/soheilhy/cmux v0.1.5
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/spf13/viper v1.18.2
|
github.com/spf13/viper v1.18.2
|
||||||
github.com/swaggo/files v1.0.1
|
github.com/swaggo/files v1.0.1
|
||||||
|
7
go.sum
7
go.sum
@ -154,6 +154,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke
|
|||||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||||
|
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
|
||||||
|
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
|
||||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||||
@ -206,6 +208,7 @@ go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTV
|
|||||||
golang.org/x/arch v0.9.0 h1:ub9TgUInamJ8mrZIGlBG6/4TqWeMszd4N8lNorbrr6k=
|
golang.org/x/arch v0.9.0 h1:ub9TgUInamJ8mrZIGlBG6/4TqWeMszd4N8lNorbrr6k=
|
||||||
golang.org/x/arch v0.9.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
golang.org/x/arch v0.9.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||||
@ -215,7 +218,9 @@ golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqR
|
|||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
@ -227,6 +232,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/naiba/nezha/pkg/utils"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
@ -78,39 +79,16 @@ func (c *AgentConfig) Save() error {
|
|||||||
// Config 站点配置
|
// Config 站点配置
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Debug bool // debug模式开关
|
Debug bool // debug模式开关
|
||||||
|
|
||||||
Language string // 系统语言,默认 zh-CN
|
Language string // 系统语言,默认 zh-CN
|
||||||
Site struct {
|
SiteName string
|
||||||
Brand string // 站点名称
|
SecretKey string
|
||||||
CookieName string // 浏览器 Cookie 名称
|
|
||||||
Theme string
|
|
||||||
DashboardTheme string
|
|
||||||
CustomCode string
|
|
||||||
CustomCodeDashboard string
|
|
||||||
ViewPassword string // 前台查看密码
|
|
||||||
}
|
|
||||||
Oauth2 struct {
|
|
||||||
Type string
|
|
||||||
Admin string // 管理员用户名列表
|
|
||||||
AdminGroups string // 管理员用户组列表
|
|
||||||
ClientID string
|
|
||||||
ClientSecret string
|
|
||||||
Endpoint string
|
|
||||||
OidcDisplayName string // for OIDC Display Name
|
|
||||||
OidcIssuer string // for OIDC Issuer
|
|
||||||
OidcLogoutURL string // for OIDC Logout URL
|
|
||||||
OidcRegisterURL string // for OIDC Register URL
|
|
||||||
OidcLoginClaim string // for OIDC Claim
|
|
||||||
OidcGroupClaim string // for OIDC Group Claim
|
|
||||||
OidcScopes string // for OIDC Scopes
|
|
||||||
OidcAutoCreate bool // for OIDC Auto Create
|
|
||||||
OidcAutoLogin bool // for OIDC Auto Login
|
|
||||||
}
|
|
||||||
ListenPort uint
|
ListenPort uint
|
||||||
InstallHost string
|
InstallHost string
|
||||||
TLS bool
|
TLS bool
|
||||||
|
Location string // 时区,默认为 Asia/Shanghai
|
||||||
|
|
||||||
EnablePlainIPInNotification bool // 通知信息IP不打码
|
EnablePlainIPInNotification bool // 通知信息IP不打码
|
||||||
DisableSwitchTemplateInFrontend bool // 前台禁用切换模板功能
|
|
||||||
|
|
||||||
// IP变更提醒
|
// IP变更提醒
|
||||||
EnableIPChangeNotification bool
|
EnableIPChangeNotification bool
|
||||||
@ -118,14 +96,11 @@ type Config struct {
|
|||||||
Cover uint8 // 覆盖范围(0:提醒未被 IgnoredIPNotification 包含的所有服务器; 1:仅提醒被 IgnoredIPNotification 包含的服务器;)
|
Cover uint8 // 覆盖范围(0:提醒未被 IgnoredIPNotification 包含的所有服务器; 1:仅提醒被 IgnoredIPNotification 包含的服务器;)
|
||||||
IgnoredIPNotification string // 特定服务器IP(多个服务器用逗号分隔)
|
IgnoredIPNotification string // 特定服务器IP(多个服务器用逗号分隔)
|
||||||
|
|
||||||
Location string // 时区,默认为 Asia/Shanghai
|
IgnoredIPNotificationServerIDs map[uint64]bool // [ServerID] -> bool(值为true代表当前ServerID在特定服务器列表内)
|
||||||
|
AvgPingCount int
|
||||||
|
DNSServers string
|
||||||
|
|
||||||
v *viper.Viper
|
v *viper.Viper
|
||||||
IgnoredIPNotificationServerIDs map[uint64]bool // [ServerID] -> bool(值为true代表当前ServerID在特定服务器列表内)
|
|
||||||
MaxTCPPingValue int32
|
|
||||||
AvgPingCount int
|
|
||||||
|
|
||||||
DNSServers string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read 读取配置文件并应用
|
// Read 读取配置文件并应用
|
||||||
@ -142,12 +117,6 @@ func (c *Config) Read(path string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Site.Theme == "" {
|
|
||||||
c.Site.Theme = "default"
|
|
||||||
}
|
|
||||||
if c.Site.DashboardTheme == "" {
|
|
||||||
c.Site.DashboardTheme = "default"
|
|
||||||
}
|
|
||||||
if c.Language == "" {
|
if c.Language == "" {
|
||||||
c.Language = "zh-CN"
|
c.Language = "zh-CN"
|
||||||
}
|
}
|
||||||
@ -157,23 +126,17 @@ func (c *Config) Read(path string) error {
|
|||||||
if c.Location == "" {
|
if c.Location == "" {
|
||||||
c.Location = "Asia/Shanghai"
|
c.Location = "Asia/Shanghai"
|
||||||
}
|
}
|
||||||
if c.MaxTCPPingValue == 0 {
|
|
||||||
c.MaxTCPPingValue = 1000
|
|
||||||
}
|
|
||||||
if c.AvgPingCount == 0 {
|
if c.AvgPingCount == 0 {
|
||||||
c.AvgPingCount = 2
|
c.AvgPingCount = 2
|
||||||
}
|
}
|
||||||
if c.Oauth2.OidcScopes == "" {
|
if c.SecretKey == "" {
|
||||||
c.Oauth2.OidcScopes = "openid,profile,email"
|
c.SecretKey, err = utils.GenerateRandomString(1024)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
if c.Oauth2.OidcLoginClaim == "" {
|
if err = c.Save(); err != nil {
|
||||||
c.Oauth2.OidcLoginClaim = "sub"
|
return err
|
||||||
}
|
}
|
||||||
if c.Oauth2.OidcDisplayName == "" {
|
|
||||||
c.Oauth2.OidcDisplayName = "OIDC"
|
|
||||||
}
|
|
||||||
if c.Oauth2.OidcGroupClaim == "" {
|
|
||||||
c.Oauth2.OidcGroupClaim = "groups"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.updateIgnoredIPNotificationID()
|
c.updateIgnoredIPNotificationID()
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
package mygin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
|
|
||||||
"github.com/naiba/nezha/model"
|
|
||||||
"github.com/naiba/nezha/service/singleton"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AuthorizeOption struct {
|
|
||||||
GuestOnly bool
|
|
||||||
MemberOnly bool
|
|
||||||
IsPage bool
|
|
||||||
AllowAPI bool
|
|
||||||
Msg string
|
|
||||||
Redirect string
|
|
||||||
Btn string
|
|
||||||
}
|
|
||||||
|
|
||||||
func Authorize(opt AuthorizeOption) func(*gin.Context) {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
var code = http.StatusForbidden
|
|
||||||
if opt.GuestOnly {
|
|
||||||
code = http.StatusBadRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
commonErr := ErrInfo{
|
|
||||||
Title: "访问受限",
|
|
||||||
Code: code,
|
|
||||||
Msg: opt.Msg,
|
|
||||||
Link: opt.Redirect,
|
|
||||||
Btn: opt.Btn,
|
|
||||||
}
|
|
||||||
var isLogin bool
|
|
||||||
|
|
||||||
// 用户鉴权
|
|
||||||
token, _ := c.Cookie(singleton.Conf.Site.CookieName)
|
|
||||||
token = strings.TrimSpace(token)
|
|
||||||
if token != "" {
|
|
||||||
var u model.User
|
|
||||||
if err := singleton.DB.Where("token = ?", token).First(&u).Error; err == nil {
|
|
||||||
isLogin = true // u.TokenExpired.After(time.Now())
|
|
||||||
}
|
|
||||||
if isLogin {
|
|
||||||
c.Set(model.CtxKeyAuthorizedUser, &u)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// API鉴权
|
|
||||||
if opt.AllowAPI {
|
|
||||||
apiToken := c.GetHeader("Authorization")
|
|
||||||
if apiToken != "" {
|
|
||||||
var u model.User
|
|
||||||
singleton.ApiLock.RLock()
|
|
||||||
if _, ok := singleton.ApiTokenList[apiToken]; ok {
|
|
||||||
err := singleton.DB.First(&u).Where("id = ?", singleton.ApiTokenList[apiToken].UserID).Error
|
|
||||||
isLogin = err == nil
|
|
||||||
}
|
|
||||||
singleton.ApiLock.RUnlock()
|
|
||||||
if isLogin {
|
|
||||||
c.Set(model.CtxKeyAuthorizedUser, &u)
|
|
||||||
c.Set("isAPI", true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 已登录且只能游客访问
|
|
||||||
if isLogin && opt.GuestOnly {
|
|
||||||
ShowErrorPage(c, commonErr, opt.IsPage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 未登录且需要登录
|
|
||||||
if !isLogin && opt.MemberOnly {
|
|
||||||
ShowErrorPage(c, commonErr, opt.IsPage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
package mygin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
|
|
||||||
"github.com/naiba/nezha/model"
|
|
||||||
"github.com/naiba/nezha/service/singleton"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ErrInfo struct {
|
|
||||||
Code int
|
|
||||||
Title string
|
|
||||||
Msg string
|
|
||||||
Link string
|
|
||||||
Btn string
|
|
||||||
}
|
|
||||||
|
|
||||||
func ShowErrorPage(c *gin.Context, i ErrInfo, isPage bool) {
|
|
||||||
if isPage {
|
|
||||||
c.HTML(i.Code, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/error", CommonEnvironment(c, gin.H{
|
|
||||||
"Code": i.Code,
|
|
||||||
"Title": i.Title,
|
|
||||||
"Msg": i.Msg,
|
|
||||||
"Link": i.Link,
|
|
||||||
"Btn": i.Btn,
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
c.JSON(http.StatusOK, model.Response{
|
|
||||||
Code: i.Code,
|
|
||||||
Message: i.Msg,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
c.Abort()
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
package mygin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
|
|
||||||
"github.com/naiba/nezha/model"
|
|
||||||
"github.com/naiba/nezha/service/singleton"
|
|
||||||
)
|
|
||||||
|
|
||||||
var adminPage = map[string]bool{
|
|
||||||
"/server": true,
|
|
||||||
"/monitor": true,
|
|
||||||
"/setting": true,
|
|
||||||
"/notification": true,
|
|
||||||
"/ddns": true,
|
|
||||||
"/nat": true,
|
|
||||||
"/cron": true,
|
|
||||||
"/api": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
func CommonEnvironment(c *gin.Context, data map[string]interface{}) gin.H {
|
|
||||||
data["MatchedPath"] = c.MustGet("MatchedPath")
|
|
||||||
data["Version"] = singleton.Version
|
|
||||||
data["Conf"] = singleton.Conf
|
|
||||||
data["Themes"] = model.Themes
|
|
||||||
data["CustomCode"] = singleton.Conf.Site.CustomCode
|
|
||||||
data["CustomCodeDashboard"] = singleton.Conf.Site.CustomCodeDashboard
|
|
||||||
// 是否是管理页面
|
|
||||||
data["IsAdminPage"] = adminPage[data["MatchedPath"].(string)]
|
|
||||||
// 站点标题
|
|
||||||
if t, has := data["Title"]; !has {
|
|
||||||
data["Title"] = singleton.Conf.Site.Brand
|
|
||||||
} else {
|
|
||||||
data["Title"] = fmt.Sprintf("%s - %s", t, singleton.Conf.Site.Brand)
|
|
||||||
}
|
|
||||||
u, ok := c.Get(model.CtxKeyAuthorizedUser)
|
|
||||||
if ok {
|
|
||||||
data["Admin"] = u
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
func RecordPath(c *gin.Context) {
|
|
||||||
url := c.Request.URL.String()
|
|
||||||
for _, p := range c.Params {
|
|
||||||
url = strings.Replace(url, p.Value, ":"+p.Key, 1)
|
|
||||||
}
|
|
||||||
c.Set("MatchedPath", url)
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
package mygin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/naiba/nezha/model"
|
|
||||||
"github.com/naiba/nezha/pkg/utils"
|
|
||||||
"github.com/naiba/nezha/service/singleton"
|
|
||||||
)
|
|
||||||
|
|
||||||
func PreferredTheme(c *gin.Context) {
|
|
||||||
// 采用前端传入的主题
|
|
||||||
if theme, err := c.Cookie("preferred_theme"); err == nil {
|
|
||||||
if _, has := model.Themes[theme]; has {
|
|
||||||
// 检验自定义主题
|
|
||||||
if theme == "custom" && singleton.Conf.Site.Theme != "custom" && !utils.IsFileExists("resource/template/theme-custom/home.html") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Set(model.CtxKeyPreferredTheme, theme)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetPreferredTheme(c *gin.Context, path string) string {
|
|
||||||
if theme, has := c.Get(model.CtxKeyPreferredTheme); has {
|
|
||||||
return fmt.Sprintf("theme-%s%s", theme, path)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("theme-%s%s", singleton.Conf.Site.Theme, path)
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
package mygin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/naiba/nezha/model"
|
|
||||||
"github.com/naiba/nezha/service/singleton"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ValidateViewPasswordOption struct {
|
|
||||||
IsPage bool
|
|
||||||
AbortWhenFail bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func ValidateViewPassword(opt ValidateViewPasswordOption) gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
if singleton.Conf.Site.ViewPassword == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, authorized := c.Get(model.CtxKeyAuthorizedUser)
|
|
||||||
if authorized {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
viewPassword, err := c.Cookie(singleton.Conf.Site.CookieName + "-vp")
|
|
||||||
if err == nil {
|
|
||||||
err = bcrypt.CompareHashAndPassword([]byte(viewPassword), []byte(singleton.Conf.Site.ViewPassword))
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
c.Set(model.CtxKeyViewPasswordVerified, true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !opt.AbortWhenFail {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if opt.IsPage {
|
|
||||||
c.HTML(http.StatusOK, GetPreferredTheme(c, "/viewpassword"), CommonEnvironment(c, gin.H{
|
|
||||||
// "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "VerifyPassword"}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
} else {
|
|
||||||
c.JSON(http.StatusOK, model.Response{
|
|
||||||
Code: http.StatusForbidden,
|
|
||||||
Message: "访问受限",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
c.Abort()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HybridFS combines embed.FS and os.DirFS.
|
|
||||||
type HybridFS struct {
|
|
||||||
embedFS, dir fs.FS
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHybridFS(embed embed.FS, subDir string, localDir string) (*HybridFS, error) {
|
|
||||||
subFS, err := fs.Sub(embed, subDir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &HybridFS{
|
|
||||||
embedFS: subFS,
|
|
||||||
dir: os.DirFS(localDir),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hfs *HybridFS) Open(name string) (fs.File, error) {
|
|
||||||
// Ensure embed files are not replaced
|
|
||||||
if file, err := hfs.embedFS.Open(name); err == nil {
|
|
||||||
return file, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return hfs.dir.Open(name)
|
|
||||||
}
|
|
@ -156,16 +156,16 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece
|
|||||||
host.IP != "" &&
|
host.IP != "" &&
|
||||||
singleton.ServerList[clientID].Host.IP != host.IP {
|
singleton.ServerList[clientID].Host.IP != host.IP {
|
||||||
|
|
||||||
singleton.SendNotification(singleton.Conf.IPChangeNotificationTag,
|
// singleton.SendNotification(singleton.Conf.IPChangeNotificationTag,
|
||||||
fmt.Sprintf(
|
// fmt.Sprintf(
|
||||||
"[%s] %s, %s => %s",
|
// "[%s] %s, %s => %s",
|
||||||
// singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
|
// singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
|
||||||
// MessageID: "IPChanged",
|
// MessageID: "IPChanged",
|
||||||
// }),
|
// }),
|
||||||
singleton.ServerList[clientID].Name, singleton.IPDesensitize(singleton.ServerList[clientID].Host.IP),
|
// singleton.ServerList[clientID].Name, singleton.IPDesensitize(singleton.ServerList[clientID].Host.IP),
|
||||||
singleton.IPDesensitize(host.IP),
|
// singleton.IPDesensitize(host.IP),
|
||||||
),
|
// ),
|
||||||
nil)
|
// nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -350,9 +350,6 @@ func (ss *ServiceSentinel) worker() {
|
|||||||
ts.count++
|
ts.count++
|
||||||
ts.ping = (ts.ping*float32(ts.count-1) + mh.Delay) / float32(ts.count)
|
ts.ping = (ts.ping*float32(ts.count-1) + mh.Delay) / float32(ts.count)
|
||||||
if ts.count == Conf.AvgPingCount {
|
if ts.count == Conf.AvgPingCount {
|
||||||
if ts.ping > float32(Conf.MaxTCPPingValue) {
|
|
||||||
ts.ping = float32(Conf.MaxTCPPingValue)
|
|
||||||
}
|
|
||||||
ts.count = 0
|
ts.count = 0
|
||||||
if err := DB.Create(&model.MonitorHistory{
|
if err := DB.Create(&model.MonitorHistory{
|
||||||
MonitorID: mh.GetId(),
|
MonitorID: mh.GetId(),
|
||||||
|
Loading…
Reference in New Issue
Block a user