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 @@
+
+
+
+
+ Branding Settings
+
+
+
diff --git a/templates/branding_settings.html b/templates/branding_settings.html
new file mode 100644
index 0000000..02443d0
--- /dev/null
+++ b/templates/branding_settings.html
@@ -0,0 +1,202 @@
+{{define "title"}}
+Branding Settings
+{{end}}
+
+{{define "top_css"}}
+{{end}}
+
+{{define "username"}}
+{{ .username }}
+{{end}}
+
+{{define "page_title"}}
+Branding Settings
+{{end}}
+
+{{define "page_content"}}
+
+
+
+
+
+
+
+
+
+
+
+
+ - 1. Favicon
+ - Formats accepted:
.ico
+ - Optional - leave blank or clear to continue using current favicon.
+ - 2. Brand logo
+ - The logo, displayed in the upper left corner.
+ - Formats accepted:
.png
, .jpg
, .jpeg
, .gif
, .ico
+ - Optional - leave blank or clear to continue using current brand logo.
+ - 3. Brand name
+ - The name, displayed in the upper left corner.
+ - Mandatory - must contain at least one symbol.
+
+
+
+
+
+
+
+
+
+
+{{end}}
+
+{{define "bottom_js"}}
+
+
+{{end}}