From 4fc52b62d21614325d61cc1787dd95bacec9841c Mon Sep 17 00:00:00 2001
From: ByteDream <63594396+ByteDream@users.noreply.github.com>
Date: Thu, 16 Mar 2023 08:40:04 +0100
Subject: [PATCH 1/5] Replace go.rice with native go embedding (#331)

---
 .github/workflows/release.yml | 10 +--------
 .gitignore                    |  1 -
 Dockerfile                    |  7 +------
 README.md                     | 11 +---------
 go.mod                        |  3 +--
 go.sum                        |  9 ---------
 handler/routes.go             |  9 ++++-----
 main.go                       | 38 +++++++++++++++++++++++------------
 router/router.go              | 24 +++++++++++-----------
 util/util.go                  | 18 ++++++++++++++---
 10 files changed, 60 insertions(+), 70 deletions(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 8907d06..feb4c93 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -40,20 +40,12 @@ jobs:
         node-version: '14'
         registry-url: 'https://registry.npmjs.org'
 
-    # prepare assets for go rice
+    # prepare assets
     - name: Prepare assets
       run: |
         chmod +x ./prepare_assets.sh
         ./prepare_assets.sh
 
-    # get go rice tool
-    - name: Get go rice tool
-      run: go get github.com/GeertJohan/go.rice/rice
-
-    # run go rice embed
-    - name: Run go rice embed
-      run: ${HOME}/go/bin/rice embed-go
-
     # build and make the releases
     - name: Build and make the releases
       uses: wangyoucao577/go-release-action@master
diff --git a/.gitignore b/.gitignore
index b8e3cf3..f1e3f44 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,7 +16,6 @@ wireguard-ui
 vendor/
 assets/
 node_modules/
-rice-box.go
 
 # IDEs
 .vscode
diff --git a/Dockerfile b/Dockerfile
index e4d5525..e42603b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -43,10 +43,6 @@ RUN mkdir -p assets/plugins && \
     /build/node_modules/jquery-tags-input/ \
     assets/plugins/
 
-# Get go modules and build tool
-RUN go mod download && \
-    go get github.com/GeertJohan/go.rice/rice
-
 # Add sources
 COPY . /build
 
@@ -54,8 +50,7 @@ COPY . /build
 RUN cp -r /build/custom/ assets/
 
 # Build
-RUN rice embed-go && \
-    CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags="-X main.gitCommit=${COMMIT}" -a -o wg-ui .
+RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags="-X main.gitCommit=${COMMIT}" -a -o wg-ui .
 
 # Release stage
 FROM alpine:3.16
diff --git a/README.md b/README.md
index 7eca337..7fc3207 100644
--- a/README.md
+++ b/README.md
@@ -211,18 +211,9 @@ Prepare the assets directory
 ./prepare_assets.sh
 ```
 
-Then you can embed resources by generating Go source code
-
-```sh
-rice embed-go
-go build -o wireguard-ui
-```
-
-Or, append resources to executable as zip file
-
+Then build your executable
 ```sh
 go build -o wireguard-ui
-rice append --exec wireguard-ui
 ```
 
 ## License
diff --git a/go.mod b/go.mod
index d7c4a16..5842acb 100644
--- a/go.mod
+++ b/go.mod
@@ -1,9 +1,8 @@
 module github.com/ngoduykhanh/wireguard-ui
 
-go 1.14
+go 1.16
 
 require (
-	github.com/GeertJohan/go.rice v1.0.2
 	github.com/glendc/go-external-ip v0.0.0-20170425150139-139229dcdddd
 	github.com/go-playground/universal-translator v0.17.0 // indirect
 	github.com/gorilla/sessions v1.2.0
diff --git a/go.sum b/go.sum
index 814f123..ee957cb 100644
--- a/go.sum
+++ b/go.sum
@@ -1,8 +1,4 @@
-github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
-github.com/GeertJohan/go.rice v1.0.2 h1:PtRw+Tg3oa3HYwiDBZyvOJ8LdIyf6lAovJJtr7YOAYk=
-github.com/GeertJohan/go.rice v1.0.2/go.mod h1:af5vUNlDNkCjOZeSGFgIJxDje9qdjsO6hshx0gTmZt4=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
-github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw=
@@ -12,8 +8,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
 github.com/casbin/casbin/v2 v2.0.0/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/coreos/bbolt v1.3.1-coreos.6.0.20180223184059-4f5275f4ebbf/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
-github.com/daaku/go.zipexe v1.0.0 h1:VSOgZtH418pH9L16hC/JrgSNJbbAL26pj7lmD1+CGdY=
-github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -52,7 +46,6 @@ github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/z
 github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25 h1:EFT6MH3igZK/dIVqgGbTqWVvkZ7wJ5iGN03SVtvvdd8=
 github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25/go.mod h1:sWkGw/wsaHtRsT9zGQ/WyJCotGWG/Anow/9hsAcBWRw=
 github.com/jessevdk/go-flags v0.0.0-20150816100521-1acbbaff2f34/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
-github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
 github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
 github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
@@ -109,7 +102,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/nkovacs/streamquote v1.0.0/go.mod h1:BN+NaZ2CmdKqUuTUXUEm9j95B2TRbpOWpxbJYzzgUsc=
 github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -252,7 +244,6 @@ golang.zx2c4.com/wireguard v0.0.0-20210427022245-097af6e1351b/go.mod h1:a057zjmo
 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c h1:ADNrRDI5NR23/TUCnEmlLZLt4u9DnZ2nwRkPrAcFvto=
 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c/go.mod h1:+1XihzyZUBJcSc5WO9SwNA7v26puQwOEDwanaxfNXPQ=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
 gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
diff --git a/handler/routes.go b/handler/routes.go
index 96be22d..d43d75f 100644
--- a/handler/routes.go
+++ b/handler/routes.go
@@ -5,13 +5,13 @@ import (
 	"encoding/base64"
 	"encoding/json"
 	"fmt"
+	"io/fs"
 	"net/http"
 	"os"
 	"sort"
 	"strings"
 	"time"
 
-	rice "github.com/GeertJohan/go.rice"
 	"github.com/gorilla/sessions"
 	"github.com/labstack/echo-contrib/session"
 	"github.com/labstack/echo/v4"
@@ -948,7 +948,7 @@ func SuggestIPAllocation(db store.IStore) echo.HandlerFunc {
 }
 
 // ApplyServerConfig handler to write config file and restart Wireguard server
-func ApplyServerConfig(db store.IStore, tmplBox *rice.Box) echo.HandlerFunc {
+func ApplyServerConfig(db store.IStore, tmplDir fs.FS) echo.HandlerFunc {
 	return func(c echo.Context) error {
 
 		server, err := db.GetServer()
@@ -976,14 +976,14 @@ func ApplyServerConfig(db store.IStore, tmplBox *rice.Box) echo.HandlerFunc {
 		}
 
 		// Write config file
-		err = util.WriteWireGuardServerConfig(tmplBox, server, clients, users, settings)
+		err = util.WriteWireGuardServerConfig(tmplDir, server, clients, users, settings)
 		if err != nil {
 			log.Error("Cannot apply server config: ", err)
 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{
 				false, fmt.Sprintf("Cannot apply server config: %v", err),
 			})
 		}
-		
+
 		err = util.UpdateHashes(db)
 		if err != nil {
 			log.Error("Cannot update hashes: ", err)
@@ -1016,4 +1016,3 @@ func AboutPage() echo.HandlerFunc {
 		})
 	}
 }
-
diff --git a/main.go b/main.go
index ccd163a..2fb2d03 100644
--- a/main.go
+++ b/main.go
@@ -1,16 +1,17 @@
 package main
 
 import (
+	"embed"
 	"flag"
 	"fmt"
 	"github.com/labstack/echo/v4"
 	"github.com/labstack/gommon/log"
 	"github.com/ngoduykhanh/wireguard-ui/store"
+	"io/fs"
 	"net/http"
 	"os"
 	"time"
 
-	rice "github.com/GeertJohan/go.rice"
 	"github.com/ngoduykhanh/wireguard-ui/emailer"
 	"github.com/ngoduykhanh/wireguard-ui/handler"
 	"github.com/ngoduykhanh/wireguard-ui/router"
@@ -51,6 +52,16 @@ const (
 `
 )
 
+// embed the "templates" directory
+//
+//go:embed templates/*
+var embeddedTemplates embed.FS
+
+// embed the "assets" directory
+//
+//go:embed assets/*
+var embeddedAssets embed.FS
+
 func init() {
 
 	// command-line flags and env variables
@@ -122,17 +133,15 @@ func main() {
 	extraData["gitCommit"] = gitCommit
 	extraData["basePath"] = util.BasePath
 
-	// create rice box for embedded template
-	tmplBox := rice.MustFindBox("templates")
-
-	// rice file server for assets. "assets" is the folder where the files come from.
-	assetHandler := http.FileServer(rice.MustFindBox("assets").HTTPBox())
+	// strip the "templates/" prefix from the embedded directory so files can be read by their direct name (e.g.
+	// "base.html" instead of "templates/base.html")
+	tmplDir, _ := fs.Sub(fs.FS(embeddedTemplates), "templates")
 
 	// create the wireguard config on start, if it doesn't exist
-	initServerConfig(db, tmplBox)
+	initServerConfig(db, tmplDir)
 
 	// register routes
-	app := router.New(tmplBox, extraData, util.SessionSecret)
+	app := router.New(tmplDir, extraData, util.SessionSecret)
 
 	app.GET(util.BasePath, handler.WireGuardClients(db), handler.ValidSession)
 
@@ -170,26 +179,29 @@ func main() {
 	app.POST(util.BasePath+"/wg-server/interfaces", handler.WireGuardServerInterfaces(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin)
 	app.POST(util.BasePath+"/wg-server/keypair", handler.WireGuardServerKeyPair(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin)
 	app.GET(util.BasePath+"/global-settings", handler.GlobalSettings(db), handler.ValidSession, handler.NeedsAdmin)
-
 	app.POST(util.BasePath+"/global-settings", handler.GlobalSettingSubmit(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin)
 	app.GET(util.BasePath+"/status", handler.Status(db), handler.ValidSession)
 	app.GET(util.BasePath+"/api/clients", handler.GetClients(db), handler.ValidSession)
 	app.GET(util.BasePath+"/api/client/:id", handler.GetClient(db), handler.ValidSession)
 	app.GET(util.BasePath+"/api/machine-ips", handler.MachineIPAddresses(), handler.ValidSession)
 	app.GET(util.BasePath+"/api/suggest-client-ips", handler.SuggestIPAllocation(db), handler.ValidSession)
-	app.POST(util.BasePath+"/api/apply-wg-config", handler.ApplyServerConfig(db, tmplBox), handler.ValidSession, handler.ContentTypeJson)
+	app.POST(util.BasePath+"/api/apply-wg-config", handler.ApplyServerConfig(db, tmplDir), handler.ValidSession, handler.ContentTypeJson)
 	app.GET(util.BasePath+"/wake_on_lan_hosts", handler.GetWakeOnLanHosts(db), handler.ValidSession)
 	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)
 
-	// servers other static files
+	// strip the "assets/" prefix from the embedded directory so files can be called directly without the "assets/"
+	// prefix
+	assetsDir, _ := fs.Sub(fs.FS(embeddedAssets), "assets")
+	assetHandler := http.FileServer(http.FS(assetsDir))
+	// serves other static files
 	app.GET(util.BasePath+"/static/*", echo.WrapHandler(http.StripPrefix(util.BasePath+"/static/", assetHandler)))
 
 	app.Logger.Fatal(app.Start(util.BindAddress))
 }
 
-func initServerConfig(db store.IStore, tmplBox *rice.Box) {
+func initServerConfig(db store.IStore, tmplDir fs.FS) {
 	settings, err := db.GetGlobalSettings()
 	if err != nil {
 		log.Fatalf("Cannot get global settings: ", err)
@@ -216,7 +228,7 @@ func initServerConfig(db store.IStore, tmplBox *rice.Box) {
 	}
 
 	// write config file
-	err = util.WriteWireGuardServerConfig(tmplBox, server, clients, users, settings)
+	err = util.WriteWireGuardServerConfig(tmplDir, server, clients, users, settings)
 	if err != nil {
 		log.Fatalf("Cannot create server config: ", err)
 	}
diff --git a/router/router.go b/router/router.go
index cdc6424..c2d1943 100644
--- a/router/router.go
+++ b/router/router.go
@@ -3,11 +3,11 @@ package router
 import (
 	"errors"
 	"io"
+	"io/fs"
 	"reflect"
 	"strings"
 	"text/template"
 
-	rice "github.com/GeertJohan/go.rice"
 	"github.com/gorilla/sessions"
 	"github.com/labstack/echo-contrib/session"
 	"github.com/labstack/echo/v4"
@@ -48,57 +48,57 @@ func (t *TemplateRegistry) Render(w io.Writer, name string, data interface{}, c
 }
 
 // New function
-func New(tmplBox *rice.Box, extraData map[string]string, secret []byte) *echo.Echo {
+func New(tmplDir fs.FS, extraData map[string]string, secret []byte) *echo.Echo {
 	e := echo.New()
 	e.Use(session.Middleware(sessions.NewCookieStore(secret)))
 
 	// read html template file to string
-	tmplBaseString, err := tmplBox.String("base.html")
+	tmplBaseString, err := util.StringFromEmbedFile(tmplDir, "base.html")
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	tmplLoginString, err := tmplBox.String("login.html")
+	tmplLoginString, err := util.StringFromEmbedFile(tmplDir, "login.html")
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	tmplProfileString, err := tmplBox.String("profile.html")
+	tmplProfileString, err := util.StringFromEmbedFile(tmplDir, "profile.html")
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	tmplClientsString, err := tmplBox.String("clients.html")
+	tmplClientsString, err := util.StringFromEmbedFile(tmplDir, "clients.html")
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	tmplServerString, err := tmplBox.String("server.html")
+	tmplServerString, err := util.StringFromEmbedFile(tmplDir, "server.html")
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	tmplGlobalSettingsString, err := tmplBox.String("global_settings.html")
+	tmplGlobalSettingsString, err := util.StringFromEmbedFile(tmplDir, "global_settings.html")
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	tmplUsersSettingsString, err := tmplBox.String("users_settings.html")
+	tmplUsersSettingsString, err := util.StringFromEmbedFile(tmplDir, "users_settings.html")
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	tmplStatusString, err := tmplBox.String("status.html")
+	tmplStatusString, err := util.StringFromEmbedFile(tmplDir, "status.html")
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	tmplWakeOnLanHostsString, err := tmplBox.String("wake_on_lan_hosts.html")
+	tmplWakeOnLanHostsString, err := util.StringFromEmbedFile(tmplDir, "wake_on_lan_hosts.html")
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	aboutPageString, err := tmplBox.String("about.html")
+	aboutPageString, err := util.StringFromEmbedFile(tmplDir, "about.html")
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/util/util.go b/util/util.go
index b62752d..7b93a57 100644
--- a/util/util.go
+++ b/util/util.go
@@ -7,6 +7,7 @@ import (
 	"github.com/ngoduykhanh/wireguard-ui/store"
 	"golang.org/x/mod/sumdb/dirhash"
 	"io"
+	"io/fs"
 	"io/ioutil"
 	"net"
 	"os"
@@ -17,7 +18,6 @@ import (
 	"text/template"
 	"time"
 
-	rice "github.com/GeertJohan/go.rice"
 	externalip "github.com/glendc/go-external-ip"
 	"github.com/labstack/gommon/log"
 	"github.com/ngoduykhanh/wireguard-ui/model"
@@ -382,7 +382,7 @@ func ValidateIPAllocation(serverAddresses []string, ipAllocatedList []string, ip
 }
 
 // WriteWireGuardServerConfig to write Wireguard server config. e.g. wg0.conf
-func WriteWireGuardServerConfig(tmplBox *rice.Box, serverConfig model.Server, clientDataList []model.ClientData, usersList []model.User, globalSettings model.GlobalSetting) error {
+func WriteWireGuardServerConfig(tmplDir fs.FS, serverConfig model.Server, clientDataList []model.ClientData, usersList []model.User, globalSettings model.GlobalSetting) error {
 	var tmplWireguardConf string
 
 	// if set, read wg.conf template from WgConfTemplate
@@ -394,7 +394,7 @@ func WriteWireGuardServerConfig(tmplBox *rice.Box, serverConfig model.Server, cl
 		tmplWireguardConf = string(fileContentBytes)
 	} else {
 		// read default wg.conf template file to string
-		fileContent, err := tmplBox.String("wg.conf")
+		fileContent, err := StringFromEmbedFile(tmplDir, "wg.conf")
 		if err != nil {
 			return err
 		}
@@ -465,6 +465,18 @@ func LookupEnvOrStrings(key string, defaultVal []string) []string {
 	return defaultVal
 }
 
+func StringFromEmbedFile(embed fs.FS, filename string) (string, error) {
+	file, err := embed.Open(filename)
+	if err != nil {
+		return "", err
+	}
+	content, err := io.ReadAll(file)
+	if err != nil {
+		return "", err
+	}
+	return string(content), nil
+}
+
 func ParseLogLevel(lvl string) (log.Lvl, error) {
 	switch strings.ToLower(lvl) {
 	case "debug":

From 00f7f3d280b90fdaa15faf48379d509b1b37e135 Mon Sep 17 00:00:00 2001
From: Khanh Ngo <k@ndk.name>
Date: Thu, 16 Mar 2023 08:58:28 +0100
Subject: [PATCH 2/5] Style fixes

---
 templates/base.html | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/templates/base.html b/templates/base.html
index 6c2e3f3..181049d 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -56,13 +56,15 @@
                         </button>
                     </div>
                 </div>
-                <select name="status-selector" id="status-selector" class="form-control selectpicker show-tick" style="margin-left: 10px">
-                    <option value="All">All</option>
-                    <option value="Enabled">Enabled</option>
-                    <option value="Disabled">Disabled</option>
-                    <option value="Connected">Connected</option>
-                    <option value="Disconnected">Disconnected</option>
-                </select>
+                <div class="form-group form-group-sm">
+                    <select name="status-selector" id="status-selector" class="custom-select form-control-navbar" style="margin-left: 0.5em; height: 90%; font-size: 14px;">
+                        <option value="All">All</option>
+                        <option value="Enabled">Enabled</option>
+                        <option value="Disabled">Disabled</option>
+                        <option value="Connected">Connected</option>
+                        <option value="Disconnected">Disconnected</option>
+                    </select>
+                </div>
             </form>
 
             <!-- Right navbar links -->

From d3c47c53c8460b108ac1a0833f77cc0a3dcce33a Mon Sep 17 00:00:00 2001
From: Khanh Ngo <k@ndk.name>
Date: Thu, 16 Mar 2023 09:09:48 +0100
Subject: [PATCH 3/5] QR code fixes

---
 templates/clients.html | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/templates/clients.html b/templates/clients.html
index 513797c..0fa98f7 100644
--- a/templates/clients.html
+++ b/templates/clients.html
@@ -481,7 +481,9 @@ Wireguard Clients
                 cache: false,
                 method: 'GET',
                 url: '{{.basePath}}/api/client/' + client_id,
-                data: JSON.stringify(data),
+                data: {
+                    qrCodeIncludeFwMark: include_fwmark
+                },
                 dataType: 'json',
                 contentType: "application/json",
                 success: function (resp) {

From 5e0217db049ae0adc8947fa796f9614301603fd6 Mon Sep 17 00:00:00 2001
From: Khanh Ngo <k@ndk.name>
Date: Thu, 16 Mar 2023 16:25:38 +0100
Subject: [PATCH 4/5] Set password type field

---
 templates/users_settings.html | 2 +-
 util/util.go                  | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/templates/users_settings.html b/templates/users_settings.html
index 05d8878..6fb8d69 100644
--- a/templates/users_settings.html
+++ b/templates/users_settings.html
@@ -42,7 +42,7 @@ Users Settings
                     </div>
                     <div class="form-group">
                         <label for="_user_password" class="control-label">Password</label>
-                        <input type="text" class="form-control" id="_user_password" name="_user_password" value=""
+                        <input type="password" class="form-control" id="_user_password" name="_user_password" value=""
                                placeholder="Leave empty to keep the password unchanged">
                     </div>
                     <div class="form-group">
diff --git a/util/util.go b/util/util.go
index 7b93a57..4d4b9b3 100644
--- a/util/util.go
+++ b/util/util.go
@@ -514,11 +514,11 @@ func HashesChanged(db store.IStore) bool {
 	newClient, newServer := GetCurrentHash(db)
 
 	if oldClient != newClient {
-		fmt.Println("Hash for client differs")
+		//fmt.Println("Hash for client differs")
 		return true
 	}
 	if oldServer != newServer {
-		fmt.Println("Hash for server differs")
+		//fmt.Println("Hash for server differs")
 		return true
 	}
 	return false

From ac99317ba3ff35eb169b6acc561ecf6f674f4062 Mon Sep 17 00:00:00 2001
From: Khanh Ngo <k@ndk.name>
Date: Fri, 17 Mar 2023 09:53:57 +0100
Subject: [PATCH 5/5] Update README.md

---
 README.md | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/README.md b/README.md
index 7fc3207..62eef0d 100644
--- a/README.md
+++ b/README.md
@@ -203,6 +203,11 @@ or
 docker compose build --build-arg=COMMIT=$(git rev-parse --short HEAD)
 ```
 
+:information_source: A container image is avaialble on [Docker Hub](https://hub.docker.com/r/ngoduykhanh/wireguard-ui) which you can pull and use 
+```
+docker pull ngoduykhanh/wireguard-ui
+````
+
 ### Build binary file
 
 Prepare the assets directory