From 12d0dc6288762a3c4e851f2354260fc898f83c66 Mon Sep 17 00:00:00 2001 From: Arminas Date: Sat, 18 Feb 2023 07:31:37 +0200 Subject: [PATCH] Added branding settings page Added branding settings page, where you can change favicon, brand name and brand logo from UI. --- handler/routes.go | 125 +++++++++++++++++-- main.go | 7 +- model/misc.go | 1 + router/router.go | 6 + store/jsondb/jsondb.go | 43 ++++++- store/store.go | 3 + templates/base.html | 12 +- templates/branding_settings.html | 202 +++++++++++++++++++++++++++++++ 8 files changed, 384 insertions(+), 15 deletions(-) create mode 100644 templates/branding_settings.html diff --git a/handler/routes.go b/handler/routes.go index 04f3208..2e004e1 100644 --- a/handler/routes.go +++ b/handler/routes.go @@ -5,8 +5,10 @@ import ( "encoding/base64" "encoding/json" "fmt" + "io" "net/http" "os" + "path" "sort" "strings" "time" @@ -33,11 +35,48 @@ func Health() echo.HandlerFunc { } } -func Favicon() echo.HandlerFunc { +func Favicon(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { if favicon, ok := os.LookupEnv(util.FaviconFilePathEnvVar); ok { return c.File(favicon) + } else { + _, err := os.OpenFile(path.Join(db.GetPath(), "branding")+"/favicon.ico", os.O_RDONLY, 0777) + if err != nil { + return c.Redirect(http.StatusFound, util.BasePath+"/static/custom/img/favicon.ico") + } + return c.File(path.Join(db.GetPath(), "branding") + "/favicon.ico") } + } +} + +func BrandLogo(db store.IStore) echo.HandlerFunc { + return func(c echo.Context) error { + + _, err := os.OpenFile(path.Join(db.GetPath(), "branding")+"/logo.png", os.O_RDONLY, 0777) + if err == nil { + return c.File(path.Join(db.GetPath(), "branding") + "/logo.png") + } + + _, err = os.OpenFile(path.Join(db.GetPath(), "branding")+"/logo.jpg", os.O_RDONLY, 0777) + if err == nil { + return c.File(path.Join(db.GetPath(), "branding") + "/logo.jpg") + } + + _, err = os.OpenFile(path.Join(db.GetPath(), "branding")+"/logo.jpeg", os.O_RDONLY, 0777) + if err == nil { + return c.File(path.Join(db.GetPath(), "branding") + "/logo.jpeg") + } + + _, err = os.OpenFile(path.Join(db.GetPath(), "branding")+"/logo.gif", os.O_RDONLY, 0777) + if err == nil { + return c.File(path.Join(db.GetPath(), "branding") + "/logo.gif") + } + + _, err = os.OpenFile(path.Join(db.GetPath(), "branding")+"/logo.ico", os.O_RDONLY, 0777) + if err == nil { + return c.File(path.Join(db.GetPath(), "branding") + "/logo.ico") + } + return c.Redirect(http.StatusFound, util.BasePath+"/static/custom/img/favicon.ico") } } @@ -120,7 +159,7 @@ func LoadProfile(db store.IStore) echo.HandlerFunc { } return c.Render(http.StatusOK, "profile.html", map[string]interface{}{ - "baseData": model.BaseData{Active: "profile", CurrentUser: currentUser(c)}, + "baseData": model.BaseData{Active: "profile", CurrentUser: currentUser(c), BrandName: db.GetBrandName()}, "userInfo": userInfo, }) } @@ -179,7 +218,7 @@ func WireGuardClients(db store.IStore) echo.HandlerFunc { } return c.Render(http.StatusOK, "clients.html", map[string]interface{}{ - "baseData": model.BaseData{Active: "", CurrentUser: currentUser(c)}, + "baseData": model.BaseData{Active: "", CurrentUser: currentUser(c), BrandName: db.GetBrandName()}, "clientDataList": clientDataList, }) } @@ -532,7 +571,7 @@ func WireGuardServer(db store.IStore) echo.HandlerFunc { } return c.Render(http.StatusOK, "server.html", map[string]interface{}{ - "baseData": model.BaseData{Active: "wg-server", CurrentUser: currentUser(c)}, + "baseData": model.BaseData{Active: "wg-server", CurrentUser: currentUser(c), BrandName: db.GetBrandName()}, "serverInterface": server.Interface, "serverKeyPair": server.KeyPair, }) @@ -600,7 +639,7 @@ func GlobalSettings(db store.IStore) echo.HandlerFunc { } return c.Render(http.StatusOK, "global_settings.html", map[string]interface{}{ - "baseData": model.BaseData{Active: "global-settings", CurrentUser: currentUser(c)}, + "baseData": model.BaseData{Active: "global-settings", CurrentUser: currentUser(c), BrandName: db.GetBrandName()}, "globalSettings": globalSettings, }) } @@ -630,7 +669,7 @@ func Status(db store.IStore) echo.HandlerFunc { wgClient, err := wgctrl.New() if err != nil { return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{ - "baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c)}, + "baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c), BrandName: db.GetBrandName()}, "error": err.Error(), "devices": nil, }) @@ -639,7 +678,7 @@ func Status(db store.IStore) echo.HandlerFunc { devices, err := wgClient.Devices() if err != nil { return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{ - "baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c)}, + "baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c), BrandName: db.GetBrandName()}, "error": err.Error(), "devices": nil, }) @@ -651,7 +690,7 @@ func Status(db store.IStore) echo.HandlerFunc { clients, err := db.GetClients(false) if err != nil { return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{ - "baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c)}, + "baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c), BrandName: db.GetBrandName()}, "error": err.Error(), "devices": nil, }) @@ -697,7 +736,7 @@ func Status(db store.IStore) echo.HandlerFunc { } return c.Render(http.StatusOK, "status.html", map[string]interface{}{ - "baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c)}, + "baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c), BrandName: db.GetBrandName()}, "devices": devicesVm, "error": "", }) @@ -831,11 +870,75 @@ func ApplyServerConfig(db store.IStore, tmplBox *rice.Box) echo.HandlerFunc { } // AboutPage handler -func AboutPage() echo.HandlerFunc { +func AboutPage(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { return c.Render(http.StatusOK, "about.html", map[string]interface{}{ - "baseData": model.BaseData{Active: "about", CurrentUser: currentUser(c)}, + "baseData": model.BaseData{Active: "about", CurrentUser: currentUser(c), BrandName: db.GetBrandName()}, }) } } +// Branding handler +func BrandingSettings(db store.IStore) echo.HandlerFunc { + return func(c echo.Context) error { + return c.Render(http.StatusOK, "branding_settings.html", map[string]interface{}{ + "baseData": model.BaseData{Active: "branding-settings", CurrentUser: currentUser(c), BrandName: db.GetBrandName()}, + }) + } +} + +func UpdateBranding(db store.IStore) echo.HandlerFunc { + + return func(c echo.Context) error { + + c.Request().ParseMultipartForm(32 << 20) + brand_name := c.Request().Form.Get("brand_name") + favicon_exist := true + logo_exist := true + + // check if favicon exist + favicon_file, favicon_handler, err := c.Request().FormFile("favicon") + if err != nil { + favicon_exist = false + } else { + defer favicon_file.Close() + } + + // check if logo exist + logo_file, logo_handler, err := c.Request().FormFile("logo") + if err != nil { + logo_exist = false + } else { + defer logo_file.Close() + } + + if favicon_exist { + f, err := os.OpenFile(path.Join(db.GetPath(), "branding")+"/favicon."+strings.Split(favicon_handler.Filename, ".")[1], os.O_WRONLY|os.O_CREATE, 0777) + if err != nil { + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot open " + path.Join(db.GetPath(), "branding") + "/favicon." + strings.Split(favicon_handler.Filename, ".")[1]}) + } + defer f.Close() + io.Copy(f, favicon_file) + } + + if logo_exist { + os.Remove(path.Join(db.GetPath(), "branding") + "/logo.png") + os.Remove(path.Join(db.GetPath(), "branding") + "/logo.jpg") + os.Remove(path.Join(db.GetPath(), "branding") + "/logo.jpeg") + os.Remove(path.Join(db.GetPath(), "branding") + "/logo.gif") + os.Remove(path.Join(db.GetPath(), "branding") + "/logo.ico") + f, err := os.OpenFile(path.Join(db.GetPath(), "branding")+"/logo."+strings.Split(logo_handler.Filename, ".")[1], os.O_WRONLY|os.O_CREATE, 0777) + if err != nil { + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot open " + path.Join(db.GetPath(), "branding") + "/logo." + strings.Split(logo_handler.Filename, ".")[1]}) + } + defer f.Close() + io.Copy(f, logo_file) + } + + if err := db.SetBrandName(brand_name); err != nil { + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot set brand name"}) + } + + return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated branding settings successfully"}) + } +} diff --git a/main.go b/main.go index cbfa8b7..2a14ccb 100644 --- a/main.go +++ b/main.go @@ -147,9 +147,10 @@ func main() { sendmail = emailer.NewSmtpMail(util.SmtpHostname, util.SmtpPort, util.SmtpUsername, util.SmtpPassword, util.SmtpNoTLSCheck, util.SmtpAuthType, util.EmailFromName, util.EmailFrom, util.SmtpEncryption) } - app.GET(util.BasePath+"/about", handler.AboutPage()) + app.GET(util.BasePath+"/about", handler.AboutPage(db)) app.GET(util.BasePath+"/_health", handler.Health()) - app.GET(util.BasePath+"/favicon", handler.Favicon()) + app.GET(util.BasePath+"/favicon", handler.Favicon(db)) + app.GET(util.BasePath+"/brand-logo", handler.BrandLogo(db)) app.POST(util.BasePath+"/new-client", handler.NewClient(db), handler.ValidSession, handler.ContentTypeJson) app.POST(util.BasePath+"/update-client", handler.UpdateClient(db), handler.ValidSession, handler.ContentTypeJson) app.POST(util.BasePath+"/email-client", handler.EmailClient(db, sendmail, defaultEmailSubject, defaultEmailContent), handler.ValidSession, handler.ContentTypeJson) @@ -171,6 +172,8 @@ func main() { app.POST(util.BasePath+"/wake_on_lan_host", handler.SaveWakeOnLanHost(db), handler.ValidSession, handler.ContentTypeJson) app.DELETE(util.BasePath+"/wake_on_lan_host/:mac_address", handler.DeleteWakeOnHost(db), handler.ValidSession, handler.ContentTypeJson) app.PUT(util.BasePath+"/wake_on_lan_host/:mac_address", handler.WakeOnHost(db), handler.ValidSession, handler.ContentTypeJson) + app.GET(util.BasePath+"/branding-settings", handler.BrandingSettings(db), handler.ValidSession) + app.POST(util.BasePath+"/update-branding", handler.UpdateBranding(db), handler.ValidSession) // servers other static files app.GET(util.BasePath+"/static/*", echo.WrapHandler(http.StripPrefix(util.BasePath+"/static/", assetHandler))) diff --git a/model/misc.go b/model/misc.go index 12d6906..e0fe139 100644 --- a/model/misc.go +++ b/model/misc.go @@ -10,4 +10,5 @@ type Interface struct { type BaseData struct { Active string CurrentUser string + BrandName string } diff --git a/router/router.go b/router/router.go index 9aeaf1b..1ef2ec7 100644 --- a/router/router.go +++ b/router/router.go @@ -98,6 +98,11 @@ func New(tmplBox *rice.Box, extraData map[string]string, secret []byte) *echo.Ec log.Fatal(err) } + brandingSettingsString, err := tmplBox.String("branding_settings.html") + if err != nil { + log.Fatal(err) + } + // create template list funcs := template.FuncMap{ "StringsJoin": strings.Join, @@ -111,6 +116,7 @@ func New(tmplBox *rice.Box, extraData map[string]string, secret []byte) *echo.Ec templates["status.html"] = template.Must(template.New("status").Funcs(funcs).Parse(tmplBaseString + tmplStatusString)) templates["wake_on_lan_hosts.html"] = template.Must(template.New("wake_on_lan_hosts").Funcs(funcs).Parse(tmplBaseString + tmplWakeOnLanHostsString)) templates["about.html"] = template.Must(template.New("about").Funcs(funcs).Parse(tmplBaseString + aboutPageString)) + templates["branding_settings.html"] = template.Must(template.New("branding_settings").Funcs(funcs).Parse(tmplBaseString + brandingSettingsString)) e.Logger.SetLevel(log.DEBUG) e.Pre(middleware.RemoveTrailingSlash()) diff --git a/store/jsondb/jsondb.go b/store/jsondb/jsondb.go index f39a452..624eb48 100644 --- a/store/jsondb/jsondb.go +++ b/store/jsondb/jsondb.go @@ -43,6 +43,9 @@ func (o *JsonDB) Init() error { var serverKeyPairPath string = path.Join(serverPath, "keypair.json") var globalSettingPath string = path.Join(serverPath, "global_settings.json") var userPath string = path.Join(serverPath, "users.json") + var brandingPath string = path.Join(o.dbPath, "branding") + var brandNamePath string = path.Join(brandingPath, "brand_name.json") + // create directories if they do not exist if _, err := os.Stat(clientPath); os.IsNotExist(err) { os.MkdirAll(clientPath, os.ModePerm) @@ -53,6 +56,9 @@ func (o *JsonDB) Init() error { if _, err := os.Stat(wakeOnLanHostsPath); os.IsNotExist(err) { os.MkdirAll(wakeOnLanHostsPath, os.ModePerm) } + if _, err := os.Stat(brandingPath); os.IsNotExist(err) { + os.MkdirAll(brandingPath, os.ModePerm) + } // server's interface if _, err := os.Stat(serverInterfacePath); os.IsNotExist(err) { @@ -117,6 +123,14 @@ func (o *JsonDB) Init() error { } o.conn.Write("server", "users", user) } + // brand name + if _, err := os.Stat(brandNamePath); os.IsNotExist(err) { + type brandName struct { + BrandName string `json:"brand_name"` + } + name := brandName{BrandName: "WIREGUARD UI"} + o.conn.Write("branding", "brand_name", name) + } return nil } @@ -213,7 +227,7 @@ func (o *JsonDB) GetClientByID(clientID string, qrCodeSettings model.QRCodeSetti server, _ := o.GetServer() globalSettings, _ := o.GetGlobalSettings() client := client - if !qrCodeSettings.IncludeDNS{ + if !qrCodeSettings.IncludeDNS { globalSettings.DNSServers = []string{} } if !qrCodeSettings.IncludeMTU { @@ -255,3 +269,30 @@ func (o *JsonDB) SaveServerKeyPair(serverKeyPair model.ServerKeypair) error { func (o *JsonDB) SaveGlobalSettings(globalSettings model.GlobalSetting) error { return o.conn.Write("server", "global_settings", globalSettings) } + +// GetBrandName func to get brand name from the database +func (o *JsonDB) GetBrandName() string { + type brandName struct { + BrandName string `json:"brand_name"` + } + name := brandName{} + o.conn.Read("branding", "brand_name", &name) + + if err := o.conn.Read("branding", "brand_name", &name); err != nil { + return "WIREGUARD UI" + } + + return name.BrandName +} + +func (o *JsonDB) SetBrandName(brandName string) error { + type brandNameStruct struct { + BrandName string `json:"brand_name"` + } + name := brandNameStruct{BrandName: brandName} + return o.conn.Write("branding", "brand_name", name) +} + +func (o *JsonDB) GetPath() string { + return o.dbPath +} diff --git a/store/store.go b/store/store.go index 86d6224..59dd66a 100644 --- a/store/store.go +++ b/store/store.go @@ -22,4 +22,7 @@ type IStore interface { DeleteWakeOnHostLanHost(macAddress string) error SaveWakeOnLanHost(host model.WakeOnLanHost) error DeleteWakeOnHost(host model.WakeOnLanHost) error + GetBrandName() string + SetBrandName(brandName string) error + GetPath() string } diff --git a/templates/base.html b/templates/base.html index 1987ab0..c952697 100644 --- a/templates/base.html +++ b/templates/base.html @@ -78,7 +78,9 @@