diff --git a/README.md b/README.md index 31efff0..f53be70 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ | 用户前台 [@hamster1963](https://github.com/hamster1963) | 管理后台 [@uubulb](https://github.com/uubulb) | |---|---| | ![user](.github/user-frontend.20241128.png) | ![admin](.github/admin-frontend.20241128.png) | -| [hamster1963/nezha-dash-react](https://github.com/hamster1963/nezha-dash-react) | [nezhahq/admin-frontend](https://github.com/nezhahq/admin-frontend) | +| [hamster1963/nezha-dash](https://github.com/hamster1963/nezha-dash) | [nezhahq/admin-frontend](https://github.com/nezhahq/admin-frontend) | ## Supported Languages diff --git a/cmd/dashboard/controller/controller.go b/cmd/dashboard/controller/controller.go index 52bc452..d9695c7 100644 --- a/cmd/dashboard/controller/controller.go +++ b/cmd/dashboard/controller/controller.go @@ -249,11 +249,11 @@ func fallbackToFrontend(adminFrontend, userFrontend fs.FS) func(*gin.Context) { } return } - localFilePath := path.Join("user-dist", c.Request.URL.Path) + localFilePath := path.Join(singleton.Conf.UserTemplate, c.Request.URL.Path) if checkLocalFileOrFs(c, userFrontend, localFilePath) { return } - if !checkLocalFileOrFs(c, userFrontend, "user-dist/index.html") { + if !checkLocalFileOrFs(c, userFrontend, singleton.Conf.UserTemplate+"/index.html") { c.JSON(http.StatusOK, newErrorResponse(errors.New("404 Not Found"))) } } diff --git a/cmd/dashboard/controller/setting.go b/cmd/dashboard/controller/setting.go index 6238f25..6b83a60 100644 --- a/cmd/dashboard/controller/setting.go +++ b/cmd/dashboard/controller/setting.go @@ -1,6 +1,8 @@ package controller import ( + "errors" + "github.com/gin-gonic/gin" "github.com/nezhahq/nezha/model" @@ -21,8 +23,9 @@ func listConfig(c *gin.Context) (model.SettingResponse, error) { authorized := isMember // TODO || isViewPasswordVerfied conf := model.SettingResponse{ - Config: *singleton.Conf, - Version: singleton.Version, + Config: *singleton.Conf, + Version: singleton.Version, + UserTemplates: singleton.UserTemplates, } if !authorized { @@ -55,7 +58,16 @@ func updateConfig(c *gin.Context) (any, error) { if err := c.ShouldBindJSON(&sf); err != nil { return nil, err } - + var userTemplateValid bool + for _, v := range singleton.UserTemplates { + if v.Path == sf.UserTemplate { + userTemplateValid = true + break + } + } + if !userTemplateValid { + return nil, errors.New("invalid user template") + } singleton.Conf.Language = sf.Language singleton.Conf.EnableIPChangeNotification = sf.EnableIPChangeNotification singleton.Conf.EnablePlainIPInNotification = sf.EnablePlainIPInNotification @@ -69,6 +81,7 @@ func updateConfig(c *gin.Context) (any, error) { singleton.Conf.CustomCodeDashboard = sf.CustomCodeDashboard singleton.Conf.RealIPHeader = sf.RealIPHeader singleton.Conf.TLS = sf.TLS + singleton.Conf.UserTemplate = sf.UserTemplate if err := singleton.Conf.Save(); err != nil { return nil, newGormError("%v", err) diff --git a/cmd/dashboard/controller/user.go b/cmd/dashboard/controller/user.go index 2edd68b..7c8b9b9 100644 --- a/cmd/dashboard/controller/user.go +++ b/cmd/dashboard/controller/user.go @@ -82,7 +82,7 @@ func updateProfile(c *gin.Context) (any, error) { // @Router /user [get] func listUser(c *gin.Context) ([]model.User, error) { var users []model.User - if err := singleton.DB.Find(&users).Error; err != nil { + if err := singleton.DB.Omit("password").Find(&users).Error; err != nil { return nil, err } return users, nil diff --git a/model/config.go b/model/config.go index fd4c784..ed9b964 100644 --- a/model/config.go +++ b/model/config.go @@ -27,6 +27,7 @@ type Config struct { Language string `mapstructure:"language" json:"language"` // 系统语言,默认 zh_CN SiteName string `mapstructure:"site_name" json:"site_name"` + UserTemplate string `mapstructure:"user_template" json:"user_template,omitempty"` JWTSecretKey string `mapstructure:"jwt_secret_key" json:"jwt_secret_key,omitempty"` AgentSecretKey string `mapstructure:"agent_secret_key" json:"agent_secret_key,omitempty"` ListenPort uint `mapstructure:"listen_port" json:"listen_port,omitempty"` @@ -55,7 +56,7 @@ type Config struct { } // Read 读取配置文件并应用 -func (c *Config) Read(path string) error { +func (c *Config) Read(path string, userTemplates []UserTemplate) error { c.k = koanf.New(".") c.filePath = path @@ -87,6 +88,16 @@ func (c *Config) Read(path string) error { if c.Location == "" { c.Location = "Asia/Shanghai" } + var userTemplateValid bool + for _, v := range userTemplates { + if v.Path == c.UserTemplate { + userTemplateValid = true + break + } + } + if c.UserTemplate == "" || !userTemplateValid { + c.UserTemplate = "user-dist" + } if c.AvgPingCount == 0 { c.AvgPingCount = 2 } diff --git a/model/setting_api.go b/model/setting_api.go index b65c3d2..fb8b51c 100644 --- a/model/setting_api.go +++ b/model/setting_api.go @@ -11,14 +11,24 @@ type SettingForm struct { CustomCode string `json:"custom_code,omitempty" validate:"optional"` CustomCodeDashboard string `json:"custom_code_dashboard,omitempty" validate:"optional"` RealIPHeader string `json:"real_ip_header,omitempty" validate:"optional"` // 真实IP + UserTemplate string `json:"user_template,omitempty" validate:"optional"` TLS bool `json:"tls,omitempty" validate:"optional"` EnableIPChangeNotification bool `json:"enable_ip_change_notification,omitempty" validate:"optional"` EnablePlainIPInNotification bool `json:"enable_plain_ip_in_notification,omitempty" validate:"optional"` } +type UserTemplate struct { + Path string `json:"path,omitempty"` + Name string `json:"name,omitempty"` + GitHub string `json:"github,omitempty"` + Author string `json:"author,omitempty"` + Community bool `json:"community,omitempty"` +} + type SettingResponse struct { Config - Version string `json:"version,omitempty"` + Version string `json:"version,omitempty"` + UserTemplates []UserTemplate `json:"user_templates,omitempty"` } diff --git a/service/singleton/singleton.go b/service/singleton/singleton.go index f9a4d00..f671c5a 100644 --- a/service/singleton/singleton.go +++ b/service/singleton/singleton.go @@ -15,10 +15,24 @@ import ( var Version = "debug" var ( - Conf *model.Config - Cache *cache.Cache - DB *gorm.DB - Loc *time.Location + Conf *model.Config + Cache *cache.Cache + DB *gorm.DB + Loc *time.Location + UserTemplates = []model.UserTemplate{ + { + Path: "user-dist", + Name: "Official", + GitHub: "https://github.com/hamster1963/nezha-dash", + Author: "hamster1963", + }, { + Path: "nazhua-dist", + Name: "Nazhua", + GitHub: "https://github.com/hi2shark/nazhua", + Author: "hi2hi", + Community: true, + }, + } ) func InitTimezoneAndCache() { @@ -44,7 +58,7 @@ func LoadSingleton() { // InitConfigFromPath 从给出的文件路径中加载配置 func InitConfigFromPath(path string) { Conf = &model.Config{} - err := Conf.Read(path) + err := Conf.Read(path, UserTemplates) if err != nil { panic(err) }