From 606e10ca0a5fde5335cd5e14990ac0516e5ec7b3 Mon Sep 17 00:00:00 2001 From: naiba Date: Sun, 20 Oct 2024 11:47:45 +0800 Subject: [PATCH] refactor: remove pages, combine grpc http port --- cmd/dashboard/controller/api_v1.go | 41 ++- cmd/dashboard/controller/common_page.go | 380 +++++++++++------------- cmd/dashboard/controller/controller.go | 36 ++- cmd/dashboard/controller/guest_page.go | 62 ---- cmd/dashboard/controller/jwt.go | 46 ++- cmd/dashboard/controller/member_api.go | 77 ++--- cmd/dashboard/controller/member_page.go | 47 ++- cmd/dashboard/main.go | 44 ++- cmd/dashboard/rpc/rpc.go | 9 +- go.mod | 1 + go.sum | 7 + model/config.go | 73 ++--- pkg/mygin/auth.go | 82 ----- pkg/mygin/error.go | 36 --- pkg/mygin/mygin.go | 52 ---- pkg/mygin/preferred_theme.go | 30 -- pkg/mygin/view_password.go | 50 ---- pkg/utils/hfs.go | 33 -- service/rpc/nezha.go | 20 +- service/singleton/servicesentinel.go | 3 - 20 files changed, 368 insertions(+), 761 deletions(-) delete mode 100644 cmd/dashboard/controller/guest_page.go delete mode 100644 pkg/mygin/auth.go delete mode 100644 pkg/mygin/error.go delete mode 100644 pkg/mygin/mygin.go delete mode 100644 pkg/mygin/preferred_theme.go delete mode 100644 pkg/mygin/view_password.go delete mode 100644 pkg/utils/hfs.go diff --git a/cmd/dashboard/controller/api_v1.go b/cmd/dashboard/controller/api_v1.go index 83e0bd339c..c6c632d3a2 100644 --- a/cmd/dashboard/controller/api_v1.go +++ b/cmd/dashboard/controller/api_v1.go @@ -7,7 +7,6 @@ import ( "github.com/gin-gonic/gin" "github.com/naiba/nezha/model" - "github.com/naiba/nezha/pkg/mygin" "github.com/naiba/nezha/service/singleton" ) @@ -18,30 +17,30 @@ type apiV1 struct { func (v *apiV1) serve() { r := v.r.Group("") // 强制认证的 API - r.Use(mygin.Authorize(mygin.AuthorizeOption{ - MemberOnly: true, - AllowAPI: true, - IsPage: false, - Msg: "访问此接口需要认证", - Btn: "点此登录", - Redirect: "/login", - })) + // r.Use(mygin.Authorize(mygin.AuthorizeOption{ + // MemberOnly: true, + // AllowAPI: true, + // IsPage: false, + // Msg: "访问此接口需要认证", + // Btn: "点此登录", + // Redirect: "/login", + // })) r.GET("/server/list", v.serverList) r.GET("/server/details", v.serverDetails) // 不强制认证的 API mr := v.r.Group("monitor") - mr.Use(mygin.Authorize(mygin.AuthorizeOption{ - MemberOnly: false, - IsPage: false, - AllowAPI: true, - Msg: "访问此接口需要认证", - Btn: "点此登录", - Redirect: "/login", - })) - mr.Use(mygin.ValidateViewPassword(mygin.ValidateViewPasswordOption{ - IsPage: false, - AbortWhenFail: true, - })) + // mr.Use(mygin.Authorize(mygin.AuthorizeOption{ + // MemberOnly: false, + // IsPage: false, + // AllowAPI: true, + // Msg: "访问此接口需要认证", + // Btn: "点此登录", + // Redirect: "/login", + // })) + // mr.Use(mygin.ValidateViewPassword(mygin.ValidateViewPasswordOption{ + // IsPage: false, + // AbortWhenFail: true, + // })) mr.GET("/:id", v.monitorHistoriesById) } diff --git a/cmd/dashboard/controller/common_page.go b/cmd/dashboard/controller/common_page.go index ca6a74223e..f357ff3ca8 100644 --- a/cmd/dashboard/controller/common_page.go +++ b/cmd/dashboard/controller/common_page.go @@ -10,11 +10,9 @@ import ( "github.com/gorilla/websocket" "github.com/hashicorp/go-uuid" "github.com/jinzhu/copier" - "golang.org/x/crypto/bcrypt" "golang.org/x/sync/singleflight" "github.com/naiba/nezha/model" - "github.com/naiba/nezha/pkg/mygin" "github.com/naiba/nezha/pkg/utils" "github.com/naiba/nezha/pkg/websocketx" "github.com/naiba/nezha/proto" @@ -29,14 +27,11 @@ type commonPage struct { func (cp *commonPage) serve() { 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.Use(mygin.ValidateViewPassword(mygin.ValidateViewPasswordOption{ - IsPage: true, - AbortWhenFail: true, - })) + // cr.Use(mygin.ValidateViewPassword(mygin.ValidateViewPasswordOption{ + // IsPage: true, + // AbortWhenFail: true, + // })) cr.GET("/", cp.home) cr.GET("/service", cp.service) // TODO: 界面直接跳转使用该接口 @@ -48,44 +43,6 @@ func (cp *commonPage) serve() { 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) { res, _, _ := p.requestGroup.Do("servicePage", func() (interface{}, error) { singleton.AlertsLock.RLock() @@ -104,11 +61,11 @@ func (p *commonPage) service(c *gin.Context) { stats, statsStore, }, 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"}), "Services": res.([]interface{})[0], "CycleTransferStats": res.([]interface{})[1], - })) + }) } 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"). 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) + // 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 { @@ -147,24 +104,24 @@ func (cp *commonPage) network(c *gin.Context) { 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) + // 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) + // mygin.ShowErrorPage(c, mygin.ErrInfo{ + // Code: http.StatusForbidden, + // Title: "请求失败", + // Msg: "请求参数有误:" + "server id not found", + // Link: "/", + // Btn: "返回重试", + // }, true) return } } @@ -178,13 +135,13 @@ func (cp *commonPage) network(c *gin.Context) { 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) + // mygin.ShowErrorPage(c, mygin.ErrInfo{ + // Code: http.StatusForbidden, + // Title: "请求失败", + // Msg: "请求参数有误:" + "no server with monitor histories", + // Link: "/", + // Btn: "返回重试", + // }, true) return } if isMember || isViewPasswordVerfied { @@ -209,11 +166,10 @@ func (cp *commonPage) network(c *gin.Context) { Servers: servers, }) - c.HTML(http.StatusOK, mygin.GetPreferredTheme(c, "/network"), mygin.CommonEnvironment(c, gin.H{ - "Servers": string(serversBytes), - "MonitorInfos": string(monitorInfos), - "MaxTCPPingValue": singleton.Conf.MaxTCPPingValue, - })) + c.HTML(http.StatusOK, "", gin.H{ + "Servers": string(serversBytes), + "MonitorInfos": string(monitorInfos), + }) } 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) { stat, err := cp.getServerStat(c, true) if err != nil { - mygin.ShowErrorPage(c, mygin.ErrInfo{ - Code: http.StatusInternalServerError, - // Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{ - // MessageID: "SystemError", - // }), - Msg: "服务器状态获取失败", - Link: "/", - Btn: "返回首页", - }, true) + // mygin.ShowErrorPage(c, mygin.ErrInfo{ + // Code: http.StatusInternalServerError, + // // Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{ + // // MessageID: "SystemError", + // // }), + // Msg: "服务器状态获取失败", + // Link: "/", + // Btn: "返回首页", + // }, true) return } - c.HTML(http.StatusOK, mygin.GetPreferredTheme(c, "/home"), mygin.CommonEnvironment(c, gin.H{ + c.HTML(http.StatusOK, "", gin.H{ "Servers": string(stat), - })) + }) } var upgrader = websocket.Upgrader{ @@ -280,15 +236,15 @@ type Data struct { 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) + // 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() @@ -315,28 +271,28 @@ func (cp *commonPage) ws(c *gin.Context) { func (cp *commonPage) terminal(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: "终端会话不存在", - Link: "/", - Btn: "返回首页", - }, true) + // mygin.ShowErrorPage(c, mygin.ErrInfo{ + // Code: http.StatusForbidden, + // Title: "无权访问", + // Msg: "终端会话不存在", + // 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) + // 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() @@ -367,38 +323,38 @@ type createTerminalRequest struct { 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) + // mygin.ShowErrorPage(c, mygin.ErrInfo{ + // Code: http.StatusForbidden, + // Title: "无权访问", + // Msg: "用户未登录", + // Link: "/login", + // Btn: "去登录", + // }, true) 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) + // mygin.ShowErrorPage(c, mygin.ErrInfo{ + // Code: http.StatusForbidden, + // Title: "请求失败", + // Msg: "请求参数有误:" + err.Error(), + // Link: "/server", + // 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) + // mygin.ShowErrorPage(c, mygin.ErrInfo{ + // Code: http.StatusInternalServerError, + // // Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{ + // // MessageID: "SystemError", + // // }), + // Msg: "生成会话ID失败", + // Link: "/server", + // Btn: "返回重试", + // }, true) return } @@ -408,13 +364,13 @@ func (cp *commonPage) createTerminal(c *gin.Context) { server := singleton.ServerList[createTerminalReq.ID] singleton.ServerLock.RUnlock() if server == nil || server.TaskStream == nil { - mygin.ShowErrorPage(c, mygin.ErrInfo{ - Code: http.StatusForbidden, - Title: "请求失败", - Msg: "服务器不存在或处于离线状态", - Link: "/server", - Btn: "返回重试", - }, true) + // mygin.ShowErrorPage(c, mygin.ErrInfo{ + // Code: http.StatusForbidden, + // Title: "请求失败", + // Msg: "服务器不存在或处于离线状态", + // Link: "/server", + // Btn: "返回重试", + // }, true) return } @@ -425,48 +381,48 @@ func (cp *commonPage) createTerminal(c *gin.Context) { Type: model.TaskTypeTerminalGRPC, Data: string(terminalData), }); err != nil { - mygin.ShowErrorPage(c, mygin.ErrInfo{ - Code: http.StatusForbidden, - Title: "请求失败", - Msg: "Agent信令下发失败", - Link: "/server", - Btn: "返回重试", - }, true) + // mygin.ShowErrorPage(c, mygin.ErrInfo{ + // Code: http.StatusForbidden, + // Title: "请求失败", + // Msg: "Agent信令下发失败", + // Link: "/server", + // Btn: "返回重试", + // }, true) return } - c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/terminal", mygin.CommonEnvironment(c, gin.H{ + c.HTML(http.StatusOK, "", gin.H{ "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) + // 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) + // 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() @@ -492,27 +448,27 @@ func (cp *commonPage) fm(c *gin.Context) { 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) + // 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) + // mygin.ShowErrorPage(c, mygin.ErrInfo{ + // Code: http.StatusInternalServerError, + // // Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{ + // // MessageID: "SystemError", + // // }), + // Msg: "生成会话ID失败", + // Link: "/server", + // Btn: "返回重试", + // }, true) return } @@ -520,13 +476,13 @@ func (cp *commonPage) createFM(c *gin.Context) { serverId, err := strconv.Atoi(IdString) if err != nil { - mygin.ShowErrorPage(c, mygin.ErrInfo{ - Code: http.StatusForbidden, - Title: "请求失败", - Msg: "请求参数有误:" + err.Error(), - Link: "/server", - Btn: "返回重试", - }, true) + // mygin.ShowErrorPage(c, mygin.ErrInfo{ + // Code: http.StatusForbidden, + // Title: "请求失败", + // Msg: "请求参数有误:" + err.Error(), + // Link: "/server", + // Btn: "返回重试", + // }, true) return } @@ -534,13 +490,13 @@ func (cp *commonPage) createFM(c *gin.Context) { 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) + // mygin.ShowErrorPage(c, mygin.ErrInfo{ + // Code: http.StatusForbidden, + // Title: "请求失败", + // Msg: "服务器不存在或处于离线状态", + // Link: "/server", + // Btn: "返回重试", + // }, true) return } @@ -551,17 +507,17 @@ func (cp *commonPage) createFM(c *gin.Context) { Type: model.TaskTypeFM, Data: string(fmData), }); err != nil { - mygin.ShowErrorPage(c, mygin.ErrInfo{ - Code: http.StatusForbidden, - Title: "请求失败", - Msg: "Agent信令下发失败", - Link: "/server", - Btn: "返回重试", - }, true) + // mygin.ShowErrorPage(c, mygin.ErrInfo{ + // Code: http.StatusForbidden, + // Title: "请求失败", + // Msg: "Agent信令下发失败", + // Link: "/server", + // Btn: "返回重试", + // }, true) 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, - })) + }) } diff --git a/cmd/dashboard/controller/controller.go b/cmd/dashboard/controller/controller.go index 0dd4a6223b..3ab17d783a 100644 --- a/cmd/dashboard/controller/controller.go +++ b/cmd/dashboard/controller/controller.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "net/http" + "strings" "time" jwt "github.com/appleboy/gin-jwt/v2" @@ -15,7 +16,6 @@ import ( docs "github.com/naiba/nezha/cmd/dashboard/docs" "github.com/naiba/nezha/model" - "github.com/naiba/nezha/pkg/mygin" "github.com/naiba/nezha/pkg/utils" "github.com/naiba/nezha/proto" "github.com/naiba/nezha/service/rpc" @@ -41,7 +41,7 @@ import ( // @externalDocs.description OpenAPI // @externalDocs.url https://swagger.io/resources/open-api/ -func ServeWeb(port uint) *http.Server { +func ServeWeb() *http.Server { gin.SetMode(gin.ReleaseMode) r := gin.Default() docs.SwaggerInfo.BasePath = "/api/v1" @@ -50,23 +50,24 @@ func ServeWeb(port uint) *http.Server { pprof.Register(r) } r.Use(natGateway) - r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) - r.Use(mygin.RecordPath) + if singleton.Conf.Debug { + r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) + } + r.Use(recordPath) routers(r) page404 := func(c *gin.Context) { - mygin.ShowErrorPage(c, mygin.ErrInfo{ - Code: http.StatusNotFound, - Title: "该页面不存在", - Msg: "该页面内容可能已着陆火星", - Link: "/", - Btn: "返回首页", - }, true) + // mygin.ShowErrorPage(c, mygin.ErrInfo{ + // Code: http.StatusNotFound, + // Title: "该页面不存在", + // Msg: "该页面内容可能已着陆火星", + // Link: "/", + // Btn: "返回首页", + // }, true) } r.NoRoute(page404) r.NoMethod(page404) srv := &http.Server{ - Addr: fmt.Sprintf(":%d", port), ReadHeaderTimeout: time.Second * 5, Handler: r, } @@ -90,9 +91,6 @@ func routers(r *gin.Engine) { // 通用页面 cp := commonPage{r: r} cp.serve() - // 游客页面 - gp := guestPage{r} - gp.serve() // 会员页面 mp := &memberPage{r} mp.serve() @@ -164,3 +162,11 @@ func natGateway(c *gin.Context) { rpc.NezhaHandlerSingleton.StartStream(streamId, time.Second*10) 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) +} diff --git a/cmd/dashboard/controller/guest_page.go b/cmd/dashboard/controller/guest_page.go deleted file mode 100644 index 40387c3b5d..0000000000 --- a/cmd/dashboard/controller/guest_page.go +++ /dev/null @@ -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, - })) -} diff --git a/cmd/dashboard/controller/jwt.go b/cmd/dashboard/controller/jwt.go index 8c8fa67764..4eaf9a6aa1 100644 --- a/cmd/dashboard/controller/jwt.go +++ b/cmd/dashboard/controller/jwt.go @@ -7,12 +7,14 @@ import ( jwt "github.com/appleboy/gin-jwt/v2" "github.com/gin-gonic/gin" "github.com/naiba/nezha/model" + "github.com/naiba/nezha/service/singleton" + "golang.org/x/crypto/bcrypt" ) func initParams() *jwt.GinJWTMiddleware { return &jwt.GinJWTMiddleware{ - Realm: "test zone", - Key: []byte("secret key"), + Realm: singleton.Conf.SiteName, + Key: []byte(singleton.Conf.SecretKey), Timeout: time.Hour, MaxRefresh: time.Hour, IdentityKey: model.CtxKeyAuthorizedUser, @@ -41,7 +43,7 @@ func payloadFunc() func(data interface{}) jwt.MapClaims { return func(data interface{}) jwt.MapClaims { if v, ok := data.(*model.User); ok { return jwt.MapClaims{ - model.CtxKeyAuthorizedUser: v.Username, + model.CtxKeyAuthorizedUser: v.ID, } } return jwt.MapClaims{} @@ -51,36 +53,48 @@ func payloadFunc() func(data interface{}) jwt.MapClaims { func identityHandler() func(c *gin.Context) interface{} { return func(c *gin.Context) interface{} { claims := jwt.ExtractClaims(c) - return &model.User{ - Username: claims[model.CtxKeyAuthorizedUser].(string), + userId := claims[model.CtxKeyAuthorizedUser].(uint64) + 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) { return func(c *gin.Context) (interface{}, error) { var loginVals model.LoginRequest if err := c.ShouldBind(&loginVals); err != nil { return "", jwt.ErrMissingLoginValues } - userID := loginVals.Username - password := loginVals.Password - if (userID == "admin" && password == "admin") || (userID == "test" && password == "test") { - return &model.User{ - Username: userID, - }, nil + var user model.User + if err := singleton.DB.Select("id").Where("username = ?", loginVals.Username).First(&user).Error; err != 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 { return func(data interface{}, c *gin.Context) bool { - if v, ok := data.(*model.User); ok && v.Username == "admin" { - return true - } - return false + _, ok := data.(*model.User) + return ok } } diff --git a/cmd/dashboard/controller/member_api.go b/cmd/dashboard/controller/member_api.go index d44a2b4369..739db3465e 100644 --- a/cmd/dashboard/controller/member_api.go +++ b/cmd/dashboard/controller/member_api.go @@ -16,7 +16,6 @@ import ( "gorm.io/gorm" "github.com/naiba/nezha/model" - "github.com/naiba/nezha/pkg/mygin" "github.com/naiba/nezha/pkg/utils" "github.com/naiba/nezha/proto" "github.com/naiba/nezha/service/singleton" @@ -28,13 +27,13 @@ type memberAPI struct { func (ma *memberAPI) serve() { mr := ma.r.Group("") - mr.Use(mygin.Authorize(mygin.AuthorizeOption{ - MemberOnly: true, - IsPage: false, - Msg: "访问此接口需要登录", - Btn: "点此登录", - Redirect: "/login", - })) + // mr.Use(mygin.Authorize(mygin.AuthorizeOption{ + // MemberOnly: true, + // IsPage: false, + // Msg: "访问此接口需要登录", + // Btn: "点此登录", + // Redirect: "/login", + // })) mr.GET("/search-server", ma.searchServer) mr.GET("/search-tasks", ma.searchTask) @@ -997,30 +996,23 @@ func (ma *memberAPI) logout(c *gin.Context) { Code: http.StatusOK, }) - if oidcLogoutUrl := singleton.Conf.Oauth2.OidcLogoutURL; oidcLogoutUrl != "" { - // 重定向到 OIDC 退出登录地址。不知道为什么,这里的重定向不生效 - c.Redirect(http.StatusOK, oidcLogoutUrl) - } + // if oidcLogoutUrl := singleton.Conf.Oauth2.OidcLogoutURL; oidcLogoutUrl != "" { + // // 重定向到 OIDC 退出登录地址。不知道为什么,这里的重定向不生效 + // c.Redirect(http.StatusOK, oidcLogoutUrl) + // } } type settingForm struct { - Title string - Admin string + SiteName string Language string - Theme string - DashboardTheme string - CustomCode string - CustomCodeDashboard string CustomNameservers string - ViewPassword string IgnoredIPNotification string IPChangeNotificationTag string // IP变更提醒的通知组 - GRPCHost string + InstallHost string Cover uint8 - EnableIPChangeNotification string - EnablePlainIPInNotification string - DisableSwitchTemplateInFrontend string + EnableIPChangeNotification string + EnablePlainIPInNotification string } func (ma *memberAPI) updateSetting(c *gin.Context) { @@ -1033,38 +1025,31 @@ func (ma *memberAPI) updateSetting(c *gin.Context) { return } - if _, yes := model.Themes[sf.Theme]; !yes { - c.JSON(http.StatusOK, model.Response{ - Code: http.StatusBadRequest, - Message: fmt.Sprintf("前台主题不存在:%s", sf.Theme), - }) - return - } + // if _, yes := model.Themes[sf.Theme]; !yes { + // c.JSON(http.StatusOK, model.Response{ + // Code: http.StatusBadRequest, + // Message: fmt.Sprintf("前台主题不存在:%s", sf.Theme), + // }) + // return + // } - if _, yes := model.DashboardThemes[sf.DashboardTheme]; !yes { - c.JSON(http.StatusOK, model.Response{ - Code: http.StatusBadRequest, - Message: fmt.Sprintf("后台主题不存在:%s", sf.DashboardTheme), - }) - return - } + // if _, yes := model.DashboardThemes[sf.DashboardTheme]; !yes { + // c.JSON(http.StatusOK, model.Response{ + // Code: http.StatusBadRequest, + // Message: fmt.Sprintf("后台主题不存在:%s", sf.DashboardTheme), + // }) + // return + // } singleton.Conf.Language = sf.Language singleton.Conf.EnableIPChangeNotification = sf.EnableIPChangeNotification == "on" singleton.Conf.EnablePlainIPInNotification = sf.EnablePlainIPInNotification == "on" - singleton.Conf.DisableSwitchTemplateInFrontend = sf.DisableSwitchTemplateInFrontend == "on" singleton.Conf.Cover = sf.Cover - // singleton.Conf.GRPCHost = sf.GRPCHost + singleton.Conf.InstallHost = sf.InstallHost singleton.Conf.IgnoredIPNotification = sf.IgnoredIPNotification singleton.Conf.IPChangeNotificationTag = sf.IPChangeNotificationTag - singleton.Conf.Site.Brand = sf.Title - singleton.Conf.Site.Theme = sf.Theme - singleton.Conf.Site.DashboardTheme = sf.DashboardTheme - singleton.Conf.Site.CustomCode = sf.CustomCode - singleton.Conf.Site.CustomCodeDashboard = sf.CustomCodeDashboard + singleton.Conf.SiteName = sf.SiteName singleton.Conf.DNSServers = sf.CustomNameservers - singleton.Conf.Site.ViewPassword = sf.ViewPassword - singleton.Conf.Oauth2.Admin = sf.Admin // 保证NotificationTag不为空 if singleton.Conf.IPChangeNotificationTag == "" { singleton.Conf.IPChangeNotificationTag = "default" diff --git a/cmd/dashboard/controller/member_page.go b/cmd/dashboard/controller/member_page.go index 2a708db337..c0b4dd736b 100644 --- a/cmd/dashboard/controller/member_page.go +++ b/cmd/dashboard/controller/member_page.go @@ -5,7 +5,6 @@ import ( "github.com/gin-gonic/gin" "github.com/naiba/nezha/model" - "github.com/naiba/nezha/pkg/mygin" "github.com/naiba/nezha/service/singleton" ) @@ -15,13 +14,13 @@ type memberPage struct { func (mp *memberPage) serve() { mr := mp.r.Group("") - mr.Use(mygin.Authorize(mygin.AuthorizeOption{ - MemberOnly: true, - IsPage: true, - // Msg: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "YouAreNotAuthorized"}), - // Btn: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Login"}), - Redirect: "/login", - })) + // mr.Use(mygin.Authorize(mygin.AuthorizeOption{ + // MemberOnly: true, + // IsPage: true, + // // Msg: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "YouAreNotAuthorized"}), + // // Btn: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Login"}), + // Redirect: "/login", + // })) mr.GET("/server", mp.server) mr.GET("/monitor", mp.monitor) mr.GET("/cron", mp.cron) @@ -35,35 +34,35 @@ func (mp *memberPage) serve() { func (mp *memberPage) api(c *gin.Context) { singleton.ApiLock.RLock() 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"}), "Tokens": singleton.ApiTokenList, - })) + }) } func (mp *memberPage) server(c *gin.Context) { singleton.SortedServerLock.RLock() 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"}), "Servers": singleton.SortedServerList, - })) + }) } 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"}), "Monitors": singleton.ServiceSentinelShared.Monitors(), - })) + }) } func (mp *memberPage) cron(c *gin.Context) { var crons []model.Cron 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"}), "Crons": crons, - })) + }) } func (mp *memberPage) notification(c *gin.Context) { @@ -71,37 +70,37 @@ func (mp *memberPage) notification(c *gin.Context) { singleton.DB.Find(&nf) var ar []model.AlertRule 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"}), "Notifications": nf, "AlertRules": ar, - })) + }) } func (mp *memberPage) ddns(c *gin.Context) { var data []model.DDNSProfile 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"}), "DDNS": data, "ProviderMap": model.ProviderMap, "ProviderList": model.ProviderList, - })) + }) } func (mp *memberPage) nat(c *gin.Context) { var data []model.NAT 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"}), "NAT": data, - })) + }) } 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"}), "Languages": model.Languages, "DashboardThemes": model.DashboardThemes, - })) + }) } diff --git a/cmd/dashboard/main.go b/cmd/dashboard/main.go index cd1fb31ba4..07aaac7f0f 100644 --- a/cmd/dashboard/main.go +++ b/cmd/dashboard/main.go @@ -4,18 +4,20 @@ import ( "context" "fmt" "log" + "net" "time" _ "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/rpc" "github.com/naiba/nezha/model" "github.com/naiba/nezha/proto" "github.com/naiba/nezha/service/singleton" - "github.com/ory/graceful" - flag "github.com/spf13/pflag" - // gin-swagger middleware - // swagger embed files ) type DashboardCliParam struct { @@ -43,6 +45,25 @@ func init() { } 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.LoadSingleton() @@ -63,19 +84,28 @@ func main() { 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 singleton.CleanMonitorHistory() - go rpc.ServeRPC(singleton.Conf.ListenPort) + go rpc.ServeRPC(grpcL) serviceSentinelDispatchBus := make(chan model.Monitor) // 用于传递服务监控任务信息的channel go rpc.DispatchTask(serviceSentinelDispatchBus) go rpc.DispatchKeepalive() go singleton.AlertSentinelStart() singleton.NewServiceSentinel(serviceSentinelDispatchBus) - srv := controller.ServeWeb(singleton.Conf.ListenPort) + srv := controller.ServeWeb() go dispatchReportInfoTask() if err := graceful.Graceful(func() error { - return srv.ListenAndServe() + return srv.Serve(httpL) }, func(c context.Context) error { log.Println("NEZHA>> Graceful::START") singleton.RecordTransferHourlyUsage() diff --git a/cmd/dashboard/rpc/rpc.go b/cmd/dashboard/rpc/rpc.go index 5d013d7842..62839fec0d 100644 --- a/cmd/dashboard/rpc/rpc.go +++ b/cmd/dashboard/rpc/rpc.go @@ -1,7 +1,6 @@ package rpc import ( - "fmt" "net" "google.golang.org/grpc" @@ -12,15 +11,11 @@ import ( "github.com/naiba/nezha/service/singleton" ) -func ServeRPC(port uint) { +func ServeRPC(l net.Listener) { server := grpc.NewServer() rpcService.NezhaHandlerSingleton = rpcService.NewNezhaHandler() pb.RegisterNezhaServiceServer(server, rpcService.NezhaHandlerSingleton) - listen, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) - if err != nil { - panic(err) - } - server.Serve(listen) + server.Serve(l) } func DispatchTask(serviceSentinelDispatchBus <-chan model.Monitor) { diff --git a/go.mod b/go.mod index b976af2db6..55696e7c12 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/oschwald/maxminddb-golang v1.13.1 github.com/patrickmn/go-cache v2.1.0+incompatible 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/viper v1.18.2 github.com/swaggo/files v1.0.1 diff --git a/go.sum b/go.sum index bf4da973be..ea8a170dae 100644 --- a/go.sum +++ b/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/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= 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/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= 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/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-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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 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.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= 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-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-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= @@ -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/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-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-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/model/config.go b/model/config.go index a49bd16df8..89aa785137 100644 --- a/model/config.go +++ b/model/config.go @@ -5,6 +5,7 @@ import ( "strconv" "strings" + "github.com/naiba/nezha/pkg/utils" "github.com/spf13/viper" "sigs.k8s.io/yaml" ) @@ -77,40 +78,17 @@ func (c *AgentConfig) Save() error { // Config 站点配置 type Config struct { - Debug bool // debug模式开关 - Language string // 系统语言,默认 zh-CN - Site struct { - Brand 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 - } + Debug bool // debug模式开关 + + Language string // 系统语言,默认 zh-CN + SiteName string + SecretKey string ListenPort uint InstallHost string TLS bool + Location string // 时区,默认为 Asia/Shanghai - EnablePlainIPInNotification bool // 通知信息IP不打码 - DisableSwitchTemplateInFrontend bool // 前台禁用切换模板功能 + EnablePlainIPInNotification bool // 通知信息IP不打码 // IP变更提醒 EnableIPChangeNotification bool @@ -118,14 +96,11 @@ type Config struct { Cover uint8 // 覆盖范围(0:提醒未被 IgnoredIPNotification 包含的所有服务器; 1:仅提醒被 IgnoredIPNotification 包含的服务器;) IgnoredIPNotification string // 特定服务器IP(多个服务器用逗号分隔) - Location string // 时区,默认为 Asia/Shanghai - - v *viper.Viper IgnoredIPNotificationServerIDs map[uint64]bool // [ServerID] -> bool(值为true代表当前ServerID在特定服务器列表内) - MaxTCPPingValue int32 AvgPingCount int + DNSServers string - DNSServers string + v *viper.Viper } // Read 读取配置文件并应用 @@ -142,12 +117,6 @@ func (c *Config) Read(path string) error { return err } - if c.Site.Theme == "" { - c.Site.Theme = "default" - } - if c.Site.DashboardTheme == "" { - c.Site.DashboardTheme = "default" - } if c.Language == "" { c.Language = "zh-CN" } @@ -157,23 +126,17 @@ func (c *Config) Read(path string) error { if c.Location == "" { c.Location = "Asia/Shanghai" } - if c.MaxTCPPingValue == 0 { - c.MaxTCPPingValue = 1000 - } if c.AvgPingCount == 0 { c.AvgPingCount = 2 } - if c.Oauth2.OidcScopes == "" { - c.Oauth2.OidcScopes = "openid,profile,email" - } - if c.Oauth2.OidcLoginClaim == "" { - c.Oauth2.OidcLoginClaim = "sub" - } - if c.Oauth2.OidcDisplayName == "" { - c.Oauth2.OidcDisplayName = "OIDC" - } - if c.Oauth2.OidcGroupClaim == "" { - c.Oauth2.OidcGroupClaim = "groups" + if c.SecretKey == "" { + c.SecretKey, err = utils.GenerateRandomString(1024) + if err != nil { + return err + } + if err = c.Save(); err != nil { + return err + } } c.updateIgnoredIPNotificationID() diff --git a/pkg/mygin/auth.go b/pkg/mygin/auth.go deleted file mode 100644 index 134060c60a..0000000000 --- a/pkg/mygin/auth.go +++ /dev/null @@ -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 - } - } -} diff --git a/pkg/mygin/error.go b/pkg/mygin/error.go deleted file mode 100644 index cabf99dcf2..0000000000 --- a/pkg/mygin/error.go +++ /dev/null @@ -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() -} diff --git a/pkg/mygin/mygin.go b/pkg/mygin/mygin.go deleted file mode 100644 index f8f2830bb1..0000000000 --- a/pkg/mygin/mygin.go +++ /dev/null @@ -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) -} diff --git a/pkg/mygin/preferred_theme.go b/pkg/mygin/preferred_theme.go deleted file mode 100644 index 0c885af11a..0000000000 --- a/pkg/mygin/preferred_theme.go +++ /dev/null @@ -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) -} diff --git a/pkg/mygin/view_password.go b/pkg/mygin/view_password.go deleted file mode 100644 index 28f2ef0a53..0000000000 --- a/pkg/mygin/view_password.go +++ /dev/null @@ -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() - } -} diff --git a/pkg/utils/hfs.go b/pkg/utils/hfs.go deleted file mode 100644 index de62e3194c..0000000000 --- a/pkg/utils/hfs.go +++ /dev/null @@ -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) -} diff --git a/service/rpc/nezha.go b/service/rpc/nezha.go index db4a6d0e01..ed8619ad16 100644 --- a/service/rpc/nezha.go +++ b/service/rpc/nezha.go @@ -156,16 +156,16 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece host.IP != "" && singleton.ServerList[clientID].Host.IP != host.IP { - singleton.SendNotification(singleton.Conf.IPChangeNotificationTag, - fmt.Sprintf( - "[%s] %s, %s => %s", - // singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{ - // MessageID: "IPChanged", - // }), - singleton.ServerList[clientID].Name, singleton.IPDesensitize(singleton.ServerList[clientID].Host.IP), - singleton.IPDesensitize(host.IP), - ), - nil) + // singleton.SendNotification(singleton.Conf.IPChangeNotificationTag, + // fmt.Sprintf( + // "[%s] %s, %s => %s", + // singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{ + // MessageID: "IPChanged", + // }), + // singleton.ServerList[clientID].Name, singleton.IPDesensitize(singleton.ServerList[clientID].Host.IP), + // singleton.IPDesensitize(host.IP), + // ), + // nil) } /** diff --git a/service/singleton/servicesentinel.go b/service/singleton/servicesentinel.go index 2980ad2ada..0934c12217 100644 --- a/service/singleton/servicesentinel.go +++ b/service/singleton/servicesentinel.go @@ -350,9 +350,6 @@ func (ss *ServiceSentinel) worker() { ts.count++ ts.ping = (ts.ping*float32(ts.count-1) + mh.Delay) / float32(ts.count) if ts.count == Conf.AvgPingCount { - if ts.ping > float32(Conf.MaxTCPPingValue) { - ts.ping = float32(Conf.MaxTCPPingValue) - } ts.count = 0 if err := DB.Create(&model.MonitorHistory{ MonitorID: mh.GetId(),