feat: multi user template

This commit is contained in:
naiba 2024-12-06 23:19:28 +08:00
parent 8755b65ac5
commit 9b0697491d
7 changed files with 62 additions and 14 deletions

View File

@ -27,7 +27,7 @@
| 用户前台 [@hamster1963](https://github.com/hamster1963) | 管理后台 [@uubulb](https://github.com/uubulb) | | 用户前台 [@hamster1963](https://github.com/hamster1963) | 管理后台 [@uubulb](https://github.com/uubulb) |
|---|---| |---|---|
| ![user](.github/user-frontend.20241128.png) | ![admin](.github/admin-frontend.20241128.png) | | ![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 ## Supported Languages

View File

@ -249,11 +249,11 @@ func fallbackToFrontend(adminFrontend, userFrontend fs.FS) func(*gin.Context) {
} }
return 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) { if checkLocalFileOrFs(c, userFrontend, localFilePath) {
return 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"))) c.JSON(http.StatusOK, newErrorResponse(errors.New("404 Not Found")))
} }
} }

View File

@ -1,6 +1,8 @@
package controller package controller
import ( import (
"errors"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/nezhahq/nezha/model" "github.com/nezhahq/nezha/model"
@ -21,8 +23,9 @@ func listConfig(c *gin.Context) (model.SettingResponse, error) {
authorized := isMember // TODO || isViewPasswordVerfied authorized := isMember // TODO || isViewPasswordVerfied
conf := model.SettingResponse{ conf := model.SettingResponse{
Config: *singleton.Conf, Config: *singleton.Conf,
Version: singleton.Version, Version: singleton.Version,
UserTemplates: singleton.UserTemplates,
} }
if !authorized { if !authorized {
@ -55,7 +58,16 @@ func updateConfig(c *gin.Context) (any, error) {
if err := c.ShouldBindJSON(&sf); err != nil { if err := c.ShouldBindJSON(&sf); err != nil {
return nil, err 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.Language = sf.Language
singleton.Conf.EnableIPChangeNotification = sf.EnableIPChangeNotification singleton.Conf.EnableIPChangeNotification = sf.EnableIPChangeNotification
singleton.Conf.EnablePlainIPInNotification = sf.EnablePlainIPInNotification singleton.Conf.EnablePlainIPInNotification = sf.EnablePlainIPInNotification
@ -69,6 +81,7 @@ func updateConfig(c *gin.Context) (any, error) {
singleton.Conf.CustomCodeDashboard = sf.CustomCodeDashboard singleton.Conf.CustomCodeDashboard = sf.CustomCodeDashboard
singleton.Conf.RealIPHeader = sf.RealIPHeader singleton.Conf.RealIPHeader = sf.RealIPHeader
singleton.Conf.TLS = sf.TLS singleton.Conf.TLS = sf.TLS
singleton.Conf.UserTemplate = sf.UserTemplate
if err := singleton.Conf.Save(); err != nil { if err := singleton.Conf.Save(); err != nil {
return nil, newGormError("%v", err) return nil, newGormError("%v", err)

View File

@ -82,7 +82,7 @@ func updateProfile(c *gin.Context) (any, error) {
// @Router /user [get] // @Router /user [get]
func listUser(c *gin.Context) ([]model.User, error) { func listUser(c *gin.Context) ([]model.User, error) {
var users []model.User 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 nil, err
} }
return users, nil return users, nil

View File

@ -27,6 +27,7 @@ type Config struct {
Language string `mapstructure:"language" json:"language"` // 系统语言,默认 zh_CN Language string `mapstructure:"language" json:"language"` // 系统语言,默认 zh_CN
SiteName string `mapstructure:"site_name" json:"site_name"` 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"` JWTSecretKey string `mapstructure:"jwt_secret_key" json:"jwt_secret_key,omitempty"`
AgentSecretKey string `mapstructure:"agent_secret_key" json:"agent_secret_key,omitempty"` AgentSecretKey string `mapstructure:"agent_secret_key" json:"agent_secret_key,omitempty"`
ListenPort uint `mapstructure:"listen_port" json:"listen_port,omitempty"` ListenPort uint `mapstructure:"listen_port" json:"listen_port,omitempty"`
@ -55,7 +56,7 @@ type Config struct {
} }
// Read 读取配置文件并应用 // Read 读取配置文件并应用
func (c *Config) Read(path string) error { func (c *Config) Read(path string, userTemplates []UserTemplate) error {
c.k = koanf.New(".") c.k = koanf.New(".")
c.filePath = path c.filePath = path
@ -87,6 +88,16 @@ func (c *Config) Read(path string) error {
if c.Location == "" { if c.Location == "" {
c.Location = "Asia/Shanghai" 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 { if c.AvgPingCount == 0 {
c.AvgPingCount = 2 c.AvgPingCount = 2
} }

View File

@ -11,14 +11,24 @@ type SettingForm struct {
CustomCode string `json:"custom_code,omitempty" validate:"optional"` CustomCode string `json:"custom_code,omitempty" validate:"optional"`
CustomCodeDashboard string `json:"custom_code_dashboard,omitempty" validate:"optional"` CustomCodeDashboard string `json:"custom_code_dashboard,omitempty" validate:"optional"`
RealIPHeader string `json:"real_ip_header,omitempty" validate:"optional"` // 真实IP 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"` TLS bool `json:"tls,omitempty" validate:"optional"`
EnableIPChangeNotification bool `json:"enable_ip_change_notification,omitempty" validate:"optional"` EnableIPChangeNotification bool `json:"enable_ip_change_notification,omitempty" validate:"optional"`
EnablePlainIPInNotification bool `json:"enable_plain_ip_in_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 { type SettingResponse struct {
Config Config
Version string `json:"version,omitempty"` Version string `json:"version,omitempty"`
UserTemplates []UserTemplate `json:"user_templates,omitempty"`
} }

View File

@ -15,10 +15,24 @@ import (
var Version = "debug" var Version = "debug"
var ( var (
Conf *model.Config Conf *model.Config
Cache *cache.Cache Cache *cache.Cache
DB *gorm.DB DB *gorm.DB
Loc *time.Location 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() { func InitTimezoneAndCache() {
@ -44,7 +58,7 @@ func LoadSingleton() {
// InitConfigFromPath 从给出的文件路径中加载配置 // InitConfigFromPath 从给出的文件路径中加载配置
func InitConfigFromPath(path string) { func InitConfigFromPath(path string) {
Conf = &model.Config{} Conf = &model.Config{}
err := Conf.Read(path) err := Conf.Read(path, UserTemplates)
if err != nil { if err != nil {
panic(err) panic(err)
} }