custon dashboard template [no ci]

This commit is contained in:
naiba 2022-06-03 09:45:11 +08:00
parent eb0501ad97
commit 2ab7a5fdd8
20 changed files with 95 additions and 37 deletions

View File

@ -489,7 +489,7 @@ func (cp *commonPage) createTerminal(c *gin.Context) {
useSSL: createTerminalReq.Protocol == "https:", useSSL: createTerminalReq.Protocol == "https:",
} }
c.HTML(http.StatusOK, "dashboard/terminal", mygin.CommonEnvironment(c, gin.H{ c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/terminal", mygin.CommonEnvironment(c, gin.H{
"SessionID": id, "SessionID": id,
"ServerName": server.Name, "ServerName": server.Name,
})) }))

View File

@ -46,7 +46,7 @@ func (gp *guestPage) login(c *gin.Context) {
LoginType = "Jihulab" LoginType = "Jihulab"
RegistrationLink = "https://jihulab.com/users/sign_up" RegistrationLink = "https://jihulab.com/users/sign_up"
} }
c.HTML(http.StatusOK, "dashboard/login", mygin.CommonEnvironment(c, gin.H{ c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/login", mygin.CommonEnvironment(c, gin.H{
"Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Login"}), "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Login"}),
"LoginType": LoginType, "LoginType": LoginType,
"RegistrationLink": RegistrationLink, "RegistrationLink": RegistrationLink,

View File

@ -702,6 +702,7 @@ type settingForm struct {
Admin string Admin string
Language string Language string
Theme string Theme string
DashboardTheme string
CustomCode string CustomCode string
ViewPassword string ViewPassword string
IgnoredIPNotification string IgnoredIPNotification string
@ -722,6 +723,23 @@ func (ma *memberAPI) updateSetting(c *gin.Context) {
}) })
return return
} }
if yes, err := utils.IsDirEmpty("resource/template/theme-" + sf.Theme); err != nil || yes {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("前台主题文件异常:%s", err),
})
return
}
if yes, err := utils.IsDirEmpty("resource/template/dashboard-" + sf.DashboardTheme); err != nil || yes {
c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest,
Message: fmt.Sprintf("后台主题文件异常:%s", err),
})
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"
@ -731,6 +749,7 @@ func (ma *memberAPI) updateSetting(c *gin.Context) {
singleton.Conf.IPChangeNotificationTag = sf.IPChangeNotificationTag singleton.Conf.IPChangeNotificationTag = sf.IPChangeNotificationTag
singleton.Conf.Site.Brand = sf.Title singleton.Conf.Site.Brand = sf.Title
singleton.Conf.Site.Theme = sf.Theme singleton.Conf.Site.Theme = sf.Theme
singleton.Conf.Site.DashboardTheme = sf.DashboardTheme
singleton.Conf.Site.CustomCode = sf.CustomCode singleton.Conf.Site.CustomCode = sf.CustomCode
singleton.Conf.Site.ViewPassword = sf.ViewPassword singleton.Conf.Site.ViewPassword = sf.ViewPassword
singleton.Conf.Oauth2.Admin = sf.Admin singleton.Conf.Oauth2.Admin = sf.Admin

View File

@ -34,7 +34,7 @@ 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/api", mygin.CommonEnvironment(c, gin.H{ c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/api", mygin.CommonEnvironment(c, gin.H{
"title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "ApiManagement"}), "title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "ApiManagement"}),
"Tokens": singleton.ApiTokenList, "Tokens": singleton.ApiTokenList,
})) }))
@ -43,14 +43,14 @@ func (mp *memberPage) api(c *gin.Context) {
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/server", mygin.CommonEnvironment(c, gin.H{ c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/server", mygin.CommonEnvironment(c, 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/monitor", mygin.CommonEnvironment(c, gin.H{ c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/monitor", mygin.CommonEnvironment(c, 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(),
})) }))
@ -59,7 +59,7 @@ func (mp *memberPage) monitor(c *gin.Context) {
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/cron", mygin.CommonEnvironment(c, gin.H{ c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/cron", mygin.CommonEnvironment(c, gin.H{
"Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "ScheduledTasks"}), "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "ScheduledTasks"}),
"Crons": crons, "Crons": crons,
})) }))
@ -70,7 +70,7 @@ 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/notification", mygin.CommonEnvironment(c, gin.H{ c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/notification", mygin.CommonEnvironment(c, 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,
@ -78,9 +78,10 @@ func (mp *memberPage) notification(c *gin.Context) {
} }
func (mp *memberPage) setting(c *gin.Context) { func (mp *memberPage) setting(c *gin.Context) {
c.HTML(http.StatusOK, "dashboard/setting", mygin.CommonEnvironment(c, gin.H{ c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/setting", mygin.CommonEnvironment(c, gin.H{
"Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Settings"}), "Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Settings"}),
"Languages": model.Languages, "Languages": model.Languages,
"Themes": model.Themes, "Themes": model.Themes,
"DashboardThemes": model.DashboardThemes,
})) }))
} }

View File

@ -85,7 +85,7 @@ func (oa *oauth2controller) login(c *gin.Context) {
singleton.Cache.Set(fmt.Sprintf("%s%s", model.CacheKeyOauth2State, stateKey), state, cache.DefaultExpiration) singleton.Cache.Set(fmt.Sprintf("%s%s", model.CacheKeyOauth2State, stateKey), state, cache.DefaultExpiration)
url := oa.getCommonOauth2Config(c).AuthCodeURL(state, oauth2.AccessTypeOnline) url := oa.getCommonOauth2Config(c).AuthCodeURL(state, oauth2.AccessTypeOnline)
c.SetCookie(singleton.Conf.Site.CookieName+"-sk", stateKey, 60*5, "", "", false, false) c.SetCookie(singleton.Conf.Site.CookieName+"-sk", stateKey, 60*5, "", "", false, false)
c.HTML(http.StatusOK, "dashboard/redirect", mygin.CommonEnvironment(c, gin.H{ c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/redirect", mygin.CommonEnvironment(c, gin.H{
"URL": url, "URL": url,
})) }))
} }
@ -172,7 +172,7 @@ func (oa *oauth2controller) callback(c *gin.Context) {
user.IssueNewToken() user.IssueNewToken()
singleton.DB.Save(&user) singleton.DB.Save(&user)
c.SetCookie(singleton.Conf.Site.CookieName, user.Token, 60*60*24, "", "", false, false) c.SetCookie(singleton.Conf.Site.CookieName, user.Token, 60*60*24, "", "", false, false)
c.HTML(http.StatusOK, "dashboard/redirect", mygin.CommonEnvironment(c, gin.H{ c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/redirect", mygin.CommonEnvironment(c, gin.H{
"URL": "/", "URL": "/",
})) }))
} }

View File

@ -21,7 +21,12 @@ var Themes = map[string]string{
"daynight": "JackieSung DayNight", "daynight": "JackieSung DayNight",
"mdui": "Neko Mdui", "mdui": "Neko Mdui",
"hotaru": "Hotaru", "hotaru": "Hotaru",
"custom": "Custom(third-party)", "custom": "Custom(local)",
}
var DashboardThemes = map[string]string{
"default": "Default",
"custom": "Custom(local)",
} }
const ( const (
@ -73,6 +78,7 @@ type Config struct {
Brand string // 站点名称 Brand string // 站点名称
CookieName string // 浏览器 Cookie 名称 CookieName string // 浏览器 Cookie 名称
Theme string Theme string
DashboardTheme string
CustomCode string CustomCode string
ViewPassword string // 前台查看密码 ViewPassword string // 前台查看密码
} }
@ -117,6 +123,9 @@ func (c *Config) Read(path string) error {
if c.Site.Theme == "" { if c.Site.Theme == "" {
c.Site.Theme = "default" 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"
} }

View File

@ -6,6 +6,7 @@ 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/service/singleton"
) )
type ErrInfo struct { type ErrInfo struct {
@ -18,7 +19,7 @@ type ErrInfo struct {
func ShowErrorPage(c *gin.Context, i ErrInfo, isPage bool) { func ShowErrorPage(c *gin.Context, i ErrInfo, isPage bool) {
if isPage { if isPage {
c.HTML(i.Code, "dashboard/error", CommonEnvironment(c, gin.H{ c.HTML(i.Code, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/error", CommonEnvironment(c, gin.H{
"Code": i.Code, "Code": i.Code,
"Title": i.Title, "Title": i.Title,
"Msg": i.Msg, "Msg": i.Msg,

View File

@ -3,6 +3,7 @@ package utils
import ( import (
"crypto/md5" // #nosec "crypto/md5" // #nosec
"encoding/hex" "encoding/hex"
"io"
"math/rand" "math/rand"
"os" "os"
"regexp" "regexp"
@ -93,3 +94,17 @@ func SplitIPAddr(v4v6Bundle string) (string, string, string) {
} }
return ipv4, ipv6, validIP return ipv4, ipv6, validIP
} }
func IsDirEmpty(name string) (bool, error) {
f, err := os.Open(name)
if err != nil {
return false, err
}
defer f.Close()
_, err = f.Readdirnames(1)
if err == io.EOF {
return true, nil
}
return false, err
}

View File

@ -470,19 +470,19 @@ other = "服务监控"
other = "计划任务" other = "计划任务"
[ApiManagement] [ApiManagement]
other="API" other = "API"
[IssueNewApiToken] [IssueNewApiToken]
other="添加Token" other = "添加Token"
[Token] [Token]
other="Token" other = "Token"
[DeleteToken] [DeleteToken]
other="删除Token" other = "删除Token"
[ConfirmToDeleteThisToken] [ConfirmToDeleteThisToken]
other="确认删除Token" other = "确认删除Token"
[YouAreNotAuthorized] [YouAreNotAuthorized]
other = "此页面需要登录" other = "此页面需要登录"
@ -531,3 +531,6 @@ other = "IP变更"
[Transleft] [Transleft]
other = "流量剩余" other = "流量剩余"
[DashboardTheme]
other = "管理后台主题"

View File

@ -1,4 +1,4 @@
{{define "dashboard/api"}} {{define "dashboard-default/api"}}
{{template "common/header" .}} {{template "common/header" .}}
{{template "common/menu" .}} {{template "common/menu" .}}
<div class="nb-container"> <div class="nb-container">

View File

@ -1,4 +1,4 @@
{{define "dashboard/cron"}} {{define "dashboard-default/cron"}}
{{template "common/header" .}} {{template "common/header" .}}
{{template "common/menu" .}} {{template "common/menu" .}}
<div class="nb-container"> <div class="nb-container">

View File

@ -1,4 +1,4 @@
{{define "dashboard/error"}} {{define "dashboard-default/error"}}
{{template "common/header" .}} {{template "common/header" .}}
<div class="login nb-container"> <div class="login nb-container">
<div class="ui center aligned grid"> <div class="ui center aligned grid">

View File

@ -1,4 +1,4 @@
{{define "dashboard/login"}} {{define "dashboard-default/login"}}
{{template "common/header" .}} {{template "common/header" .}}
<div class="login nb-container"> <div class="login nb-container">
<div class="ui center aligned grid"> <div class="ui center aligned grid">

View File

@ -1,4 +1,4 @@
{{define "dashboard/monitor"}} {{template "common/header" .}} {{template {{define "dashboard-default/monitor"}} {{template "common/header" .}} {{template
"common/menu" .}} "common/menu" .}}
<div class="nb-container"> <div class="nb-container">
<div class="ui container"> <div class="ui container">

View File

@ -1,4 +1,4 @@
{{define "dashboard/notification"}} {{define "dashboard-default/notification"}}
{{template "common/header" .}} {{template "common/header" .}}
{{template "common/menu" .}} {{template "common/menu" .}}
<div class="nb-container"> <div class="nb-container">

View File

@ -1,4 +1,4 @@
{{define "dashboard/redirect"}} {{define "dashboard-default/redirect"}}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{{.Conf.Language}}"> <html lang="{{.Conf.Language}}">

View File

@ -1,4 +1,4 @@
{{define "dashboard/server"}} {{define "dashboard-default/server"}}
{{template "common/header" .}} {{template "common/header" .}}
{{template "common/menu" .}} {{template "common/menu" .}}
<div class="nb-container"> <div class="nb-container">

View File

@ -1,4 +1,4 @@
{{define "dashboard/setting"}} {{define "dashboard-default/setting"}}
{{template "common/header" .}} {{template "common/header" .}}
{{template "common/menu" .}} {{template "common/menu" .}}
<div class="nb-container"> <div class="nb-container">
@ -20,6 +20,14 @@
{{end}} {{end}}
</select> </select>
</div> </div>
<div class="field">
<label>{{tr "DashboardTheme"}}</label>
<select name="DashboardTheme">
{{range $k,$v := .DashboardThemes}}
<option value="{{$k}}" {{if eq $.Conf.Site.DashboardTheme $k }} selected="selected" {{end}}>{{$v}}
{{end}}
</select>
</div>
<div class="field"> <div class="field">
<label>Language</label> <label>Language</label>
<select name="Language"> <select name="Language">

View File

@ -1,4 +1,4 @@
{{define "dashboard/terminal"}} {{define "dashboard-default/terminal"}}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{{.Conf.Language}}"> <html lang="{{.Conf.Language}}">

View File

@ -6,8 +6,10 @@ services:
restart: always restart: always
volumes: volumes:
- ./data:/dashboard/data - ./data:/dashboard/data
- ./theme-custom/template:/dashboard/resuorce/template/theme-default:ro - ./theme-custom/template:/dashboard/resuorce/template/theme-custom:ro
- ./theme-custom/static:/dashboard/resuorce/static/theme-default:ro - ./theme-custom/static:/dashboard/resuorce/static/theme-custom:ro
- ./dashboard-custom/template:/dashboard/resuorce/template/dashboard-custom:ro
- ./dashboard-custom/static:/dashboard/resuorce/static/dashboard-custom:ro
ports: ports:
- nz_site_port:80 - nz_site_port:80
- nz_grpc_port:nz_grpc_port - nz_grpc_port:nz_grpc_port