From b7c0990dcb0ea0363a1b01af8caab86c890f7bc8 Mon Sep 17 00:00:00 2001 From: Georgios Komninos Date: Sat, 28 Aug 2021 15:10:30 +0200 Subject: [PATCH] Abstracts database storage & Status page UI (#88) --- Dockerfile | 2 +- README.md | 7 + docker-compose.yaml | 6 +- go.mod | 3 +- go.sum | 68 ++++++- handler/routes.go | 280 +++++++++++++++++---------- main.go | 48 ++--- router/router.go | 6 + util/db.go => store/jsondb/jsondb.go | 185 +++++++----------- store/store.go | 19 ++ templates/base.html | 10 +- templates/status.html | 57 ++++++ util/config.go | 13 ++ util/util.go | 8 + 14 files changed, 456 insertions(+), 256 deletions(-) rename util/db.go => store/jsondb/jsondb.go (52%) create mode 100644 store/store.go create mode 100644 templates/status.html diff --git a/Dockerfile b/Dockerfile index 88fa7e6..1b24728 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build stage -FROM golang:1.14.2-alpine3.11 as builder +FROM golang:1.16.7-alpine3.14 as builder LABEL maintainer="Khanh Ngo %v", client) return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated client successfully"}) @@ -291,7 +287,7 @@ func UpdateClient() echo.HandlerFunc { } // SetClientStatus handler to enable / disable a client -func SetClientStatus() echo.HandlerFunc { +func SetClientStatus(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { data := make(map[string]interface{}) @@ -304,19 +300,17 @@ func SetClientStatus() echo.HandlerFunc { clientID := data["id"].(string) status := data["status"].(bool) - db, err := util.DBConn() + clientdata, err := db.GetClientByID(clientID, false) if err != nil { - log.Error("Cannot initialize database: ", err) - return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot access database"}) + return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, err.Error()}) } - client := model.Client{} - if err := db.Read("clients", clientID, &client); err != nil { - log.Error("Cannot get client from database: ", err) - } + client := *clientdata.Client client.Enabled = status - db.Write("clients", clientID, &client) + if err := db.SaveClient(client); err != nil { + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()}) + } log.Infof("Changed client %s enabled status to %v", client.ID, status) return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Changed client status successfully"}) @@ -324,22 +318,28 @@ func SetClientStatus() echo.HandlerFunc { } // DownloadClient handler -func DownloadClient() echo.HandlerFunc { +func DownloadClient(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { clientID := c.QueryParam("clientid") if clientID == "" { return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Missing clientid parameter"}) } - clientData, err := util.GetClientByID(clientID, false) + clientData, err := db.GetClientByID(clientID, false) if err != nil { log.Errorf("Cannot generate client id %s config file for downloading: %v", clientID, err) return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Client not found"}) } // build config - server, _ := util.GetServer() - globalSettings, _ := util.GetGlobalSettings() + server, err := db.GetServer() + if err != nil { + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()}) + } + globalSettings, err := db.GetGlobalSettings() + if err != nil { + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()}) + } config := util.BuildClientConfig(*clientData.Client, server, globalSettings) // create io reader from string @@ -352,20 +352,15 @@ func DownloadClient() echo.HandlerFunc { } // RemoveClient handler -func RemoveClient() echo.HandlerFunc { +func RemoveClient(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { client := new(model.Client) c.Bind(client) // delete client from database - db, err := util.DBConn() - if err != nil { - log.Error("Cannot initialize database: ", err) - return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot access database"}) - } - if err := db.Delete("clients", client.ID); err != nil { + if err := db.DeleteClient(client.ID); err != nil { log.Error("Cannot delete wireguard client: ", err) return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot delete client from database"}) } @@ -376,10 +371,10 @@ func RemoveClient() echo.HandlerFunc { } // WireGuardServer handler -func WireGuardServer() echo.HandlerFunc { +func WireGuardServer(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { - server, err := util.GetServer() + server, err := db.GetServer() if err != nil { log.Error("Cannot get server config: ", err) } @@ -393,11 +388,11 @@ func WireGuardServer() echo.HandlerFunc { } // WireGuardServerInterfaces handler -func WireGuardServerInterfaces() echo.HandlerFunc { +func WireGuardServerInterfaces(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { - serverInterface := new(model.ServerInterface) - c.Bind(serverInterface) + var serverInterface model.ServerInterface + c.Bind(&serverInterface) // validate the input addresses if util.ValidateServerAddresses(serverInterface.Addresses) == false { @@ -408,13 +403,10 @@ func WireGuardServerInterfaces() echo.HandlerFunc { serverInterface.UpdatedAt = time.Now().UTC() // write config to the database - db, err := util.DBConn() - if err != nil { - log.Error("Cannot initialize database: ", err) - return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot access database"}) - } - db.Write("server", "interfaces", serverInterface) + if err := db.SaveServerInterface(serverInterface); err != nil { + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Interface IP address must be in CIDR format"}) + } log.Infof("Updated wireguard server interfaces settings: %v", serverInterface) return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated interface addresses successfully"}) @@ -422,7 +414,7 @@ func WireGuardServerInterfaces() echo.HandlerFunc { } // WireGuardServerKeyPair handler to generate private and public keys -func WireGuardServerKeyPair() echo.HandlerFunc { +func WireGuardServerKeyPair(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { // gen Wireguard key pair @@ -432,19 +424,14 @@ func WireGuardServerKeyPair() echo.HandlerFunc { return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot generate Wireguard key pair"}) } - serverKeyPair := new(model.ServerKeypair) + var serverKeyPair model.ServerKeypair serverKeyPair.PrivateKey = key.String() serverKeyPair.PublicKey = key.PublicKey().String() serverKeyPair.UpdatedAt = time.Now().UTC() - // write config to the database - db, err := util.DBConn() - if err != nil { - log.Error("Cannot initialize database: ", err) - return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot access database"}) + if err := db.SaveServerKeyPair(serverKeyPair); err != nil { + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot generate Wireguard key pair"}) } - - db.Write("server", "keypair", serverKeyPair) log.Infof("Updated wireguard server interfaces settings: %v", serverKeyPair) return c.JSON(http.StatusOK, serverKeyPair) @@ -452,10 +439,10 @@ func WireGuardServerKeyPair() echo.HandlerFunc { } // GlobalSettings handler -func GlobalSettings() echo.HandlerFunc { +func GlobalSettings(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { - globalSettings, err := util.GetGlobalSettings() + globalSettings, err := db.GetGlobalSettings() if err != nil { log.Error("Cannot get global settings: ", err) } @@ -467,12 +454,99 @@ func GlobalSettings() echo.HandlerFunc { } } -// GlobalSettingSubmit handler to update the global settings -func GlobalSettingSubmit() echo.HandlerFunc { +// Status handler +func Status(db store.IStore) echo.HandlerFunc { + type PeerVM struct { + Name string + Email string + PublicKey string + ReceivedBytes int64 + TransmitBytes int64 + LastHandshakeTime time.Time + LastHandshakeRel time.Duration + Connected bool + } + + type DeviceVM struct { + Name string + Peers []PeerVM + } return func(c echo.Context) error { - globalSettings := new(model.GlobalSetting) - c.Bind(globalSettings) + 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)}, + "error": err.Error(), + "devices": nil, + }) + } + + 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)}, + "error": err.Error(), + "devices": nil, + }) + } + + devicesVm := make([]DeviceVM, 0, len(devices)) + if len(devices) > 0 { + m := make(map[string]*model.Client) + 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)}, + "error": err.Error(), + "devices": nil, + }) + } + for i := range clients { + if clients[i].Client != nil { + m[clients[i].Client.PublicKey] = clients[i].Client + } + } + + conv := map[bool]int{true: 1, false: 0} + for i := range devices { + devVm := DeviceVM{Name: devices[i].Name} + for j := range devices[i].Peers { + pVm := PeerVM{ + PublicKey: devices[i].Peers[j].PublicKey.String(), + ReceivedBytes: devices[i].Peers[j].ReceiveBytes, + TransmitBytes: devices[i].Peers[j].TransmitBytes, + LastHandshakeTime: devices[i].Peers[j].LastHandshakeTime, + LastHandshakeRel: time.Since(devices[i].Peers[j].LastHandshakeTime), + } + pVm.Connected = pVm.LastHandshakeRel.Minutes() < 3. + + if _client, ok := m[pVm.PublicKey]; ok { + pVm.Name = _client.Name + pVm.Email = _client.Email + } + devVm.Peers = append(devVm.Peers, pVm) + } + sort.SliceStable(devVm.Peers, func(i, j int) bool { return devVm.Peers[i].Name < devVm.Peers[j].Name }) + sort.SliceStable(devVm.Peers, func(i, j int) bool { return conv[devVm.Peers[i].Connected] > conv[devVm.Peers[j].Connected] }) + devicesVm = append(devicesVm, devVm) + } + } + + return c.Render(http.StatusOK, "status.html", map[string]interface{}{ + "baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c)}, + "devices": devicesVm, + "error": "", + }) + } +} + +// GlobalSettingSubmit handler to update the global settings +func GlobalSettingSubmit(db store.IStore) echo.HandlerFunc { + return func(c echo.Context) error { + + var globalSettings model.GlobalSetting + c.Bind(&globalSettings) // validate the input dns server list if util.ValidateIPAddressList(globalSettings.DNSServers) == false { @@ -483,13 +557,10 @@ func GlobalSettingSubmit() echo.HandlerFunc { globalSettings.UpdatedAt = time.Now().UTC() // write config to the database - db, err := util.DBConn() - if err != nil { - log.Error("Cannot initialize database: ", err) - return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot access database"}) + if err := db.SaveGlobalSettings(globalSettings); err != nil { + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot generate Wireguard key pair"}) } - db.Write("server", "global_settings", globalSettings) log.Infof("Updated global settings: %v", globalSettings) return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated global settings successfully"}) @@ -521,12 +592,13 @@ func MachineIPAddresses() echo.HandlerFunc { } // SuggestIPAllocation handler to get the list of ip address for client -func SuggestIPAllocation() echo.HandlerFunc { +func SuggestIPAllocation(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { - server, err := util.GetServer() + server, err := db.GetServer() if err != nil { log.Error("Cannot fetch server config from database: ", err) + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, err.Error()}) } // return the list of suggestedIPs @@ -557,22 +629,22 @@ func SuggestIPAllocation() echo.HandlerFunc { } // ApplyServerConfig handler to write config file and restart Wireguard server -func ApplyServerConfig(tmplBox *rice.Box) echo.HandlerFunc { +func ApplyServerConfig(db store.IStore, tmplBox *rice.Box) echo.HandlerFunc { return func(c echo.Context) error { - server, err := util.GetServer() + server, err := db.GetServer() if err != nil { log.Error("Cannot get server config: ", err) return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot get server config"}) } - clients, err := util.GetClients(false) + clients, err := db.GetClients(false) if err != nil { log.Error("Cannot get client config: ", err) return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot get client config"}) } - settings, err := util.GetGlobalSettings() + settings, err := db.GetGlobalSettings() if err != nil { log.Error("Cannot get global settings: ", err) return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot get global settings"}) diff --git a/main.go b/main.go index 2e9e5cd..abd7b19 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ import ( "github.com/ngoduykhanh/wireguard-ui/emailer" "github.com/ngoduykhanh/wireguard-ui/handler" "github.com/ngoduykhanh/wireguard-ui/router" + "github.com/ngoduykhanh/wireguard-ui/store/jsondb" "github.com/ngoduykhanh/wireguard-ui/util" ) @@ -57,14 +58,16 @@ func init() { fmt.Println("Authentication\t:", !util.DisableLogin) fmt.Println("Bind address\t:", util.BindAddress) - // initialize DB - err := util.InitDB() - if err != nil { - fmt.Print("Cannot init database: ", err) - } } func main() { + db, err := jsondb.New("./db") + if err != nil { + panic(err) + } + if err := db.Init(); err != nil { + panic(err) + } // set app extra data extraData := make(map[string]string) extraData["appVersion"] = appVersion @@ -78,32 +81,33 @@ func main() { // register routes app := router.New(tmplBox, extraData, util.SessionSecret) - app.GET("/", handler.WireGuardClients(), handler.ValidSession) + app.GET("/", handler.WireGuardClients(db), handler.ValidSession) if !util.DisableLogin { app.GET("/login", handler.LoginPage()) - app.POST("/login", handler.Login()) + app.POST("/login", handler.Login(db)) } sendmail := emailer.NewSendgridApiMail(util.SendgridApiKey, util.EmailFromName, util.EmailFrom) app.GET("/logout", handler.Logout(), handler.ValidSession) - app.POST("/new-client", handler.NewClient(), handler.ValidSession) - app.POST("/update-client", handler.UpdateClient(), handler.ValidSession) - app.POST("/email-client", handler.EmailClient(sendmail, defaultEmailSubject, defaultEmailContent), handler.ValidSession) - app.POST("/client/set-status", handler.SetClientStatus(), handler.ValidSession) - app.POST("/remove-client", handler.RemoveClient(), handler.ValidSession) - app.GET("/download", handler.DownloadClient(), handler.ValidSession) - app.GET("/wg-server", handler.WireGuardServer(), handler.ValidSession) - app.POST("wg-server/interfaces", handler.WireGuardServerInterfaces(), handler.ValidSession) - app.POST("wg-server/keypair", handler.WireGuardServerKeyPair(), handler.ValidSession) - app.GET("/global-settings", handler.GlobalSettings(), handler.ValidSession) - app.POST("/global-settings", handler.GlobalSettingSubmit(), handler.ValidSession) - app.GET("/api/clients", handler.GetClients(), handler.ValidSession) - app.GET("/api/client/:id", handler.GetClient(), handler.ValidSession) + app.POST("/new-client", handler.NewClient(db), handler.ValidSession) + app.POST("/update-client", handler.UpdateClient(db), handler.ValidSession) + app.POST("/email-client", handler.EmailClient(db, sendmail, defaultEmailSubject, defaultEmailContent), handler.ValidSession) + app.POST("/client/set-status", handler.SetClientStatus(db), handler.ValidSession) + app.POST("/remove-client", handler.RemoveClient(db), handler.ValidSession) + app.GET("/download", handler.DownloadClient(db), handler.ValidSession) + app.GET("/wg-server", handler.WireGuardServer(db), handler.ValidSession) + app.POST("wg-server/interfaces", handler.WireGuardServerInterfaces(db), handler.ValidSession) + app.POST("wg-server/keypair", handler.WireGuardServerKeyPair(db), handler.ValidSession) + app.GET("/global-settings", handler.GlobalSettings(db), handler.ValidSession) + app.POST("/global-settings", handler.GlobalSettingSubmit(db), handler.ValidSession) + app.GET("/status", handler.Status(db), handler.ValidSession) + app.GET("/api/clients", handler.GetClients(db), handler.ValidSession) + app.GET("/api/client/:id", handler.GetClient(db), handler.ValidSession) app.GET("/api/machine-ips", handler.MachineIPAddresses(), handler.ValidSession) - app.GET("/api/suggest-client-ips", handler.SuggestIPAllocation(), handler.ValidSession) - app.GET("/api/apply-wg-config", handler.ApplyServerConfig(tmplBox), handler.ValidSession) + app.GET("/api/suggest-client-ips", handler.SuggestIPAllocation(db), handler.ValidSession) + app.GET("/api/apply-wg-config", handler.ApplyServerConfig(db, tmplBox), handler.ValidSession) // servers other static files app.GET("/static/*", echo.WrapHandler(http.StripPrefix("/static/", assetHandler))) diff --git a/router/router.go b/router/router.go index bb14431..2c91d49 100644 --- a/router/router.go +++ b/router/router.go @@ -74,12 +74,18 @@ func New(tmplBox *rice.Box, extraData map[string]string, secret []byte) *echo.Ec log.Fatal(err) } + tmplStatusString, err := tmplBox.String("status.html") + if err != nil { + log.Fatal(err) + } + // create template list templates := make(map[string]*template.Template) templates["login.html"] = template.Must(template.New("login").Parse(tmplLoginString)) templates["clients.html"] = template.Must(template.New("clients").Parse(tmplBaseString + tmplClientsString)) templates["server.html"] = template.Must(template.New("server").Parse(tmplBaseString + tmplServerString)) templates["global_settings.html"] = template.Must(template.New("global_settings").Parse(tmplBaseString + tmplGlobalSettingsString)) + templates["status.html"] = template.Must(template.New("status").Parse(tmplBaseString + tmplStatusString)) e.Logger.SetLevel(log.DEBUG) e.Pre(middleware.RemoveTrailingSlash()) diff --git a/util/db.go b/store/jsondb/jsondb.go similarity index 52% rename from util/db.go rename to store/jsondb/jsondb.go index 4c9c7fb..763756e 100644 --- a/util/db.go +++ b/store/jsondb/jsondb.go @@ -1,4 +1,4 @@ -package util +package jsondb import ( "encoding/base64" @@ -8,49 +8,40 @@ import ( "path" "time" - "github.com/ngoduykhanh/wireguard-ui/model" "github.com/sdomino/scribble" "github.com/skip2/go-qrcode" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + + "github.com/ngoduykhanh/wireguard-ui/model" + "github.com/ngoduykhanh/wireguard-ui/util" ) -const dbPath = "./db" -const username_env_var = "WGUI_USERNAME" -const password_env_var = "WGUI_PASSWORD" -const defaultUsername = "admin" -const defaultPassword = "admin" -const defaultServerAddress = "10.252.1.0/24" -const defaultServerPort = 51820 -const defaultDNS = "1.1.1.1" -const defaultMTU = 1450 -const defaultPersistentKeepalive = 15 -const defaultConfigFilePath = "/etc/wireguard/wg0.conf" +type JsonDB struct { + conn *scribble.Driver + dbPath string +} -// DBConn to initialize the database connection -func DBConn() (*scribble.Driver, error) { - db, err := scribble.New(dbPath, nil) +// New returns a new pointer JsonDB +func New(dbPath string) (*JsonDB, error) { + conn, err := scribble.New(dbPath, nil) if err != nil { return nil, err } - return db, nil + ans := JsonDB{ + conn: conn, + dbPath: dbPath, + } + return &ans, nil + } -func getCredVar(key, fallback string) string { - if value, ok := os.LookupEnv(key); ok { - return value - } - return fallback -} - -// InitDB to create the default database -func InitDB() error { - var clientPath string = path.Join(dbPath, "clients") - var serverPath string = path.Join(dbPath, "server") +func (o *JsonDB) Init() error { + var clientPath string = path.Join(o.dbPath, "clients") + var serverPath string = path.Join(o.dbPath, "server") var serverInterfacePath string = path.Join(serverPath, "interfaces.json") 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") - // create directories if they do not exist if _, err := os.Stat(clientPath); os.IsNotExist(err) { os.MkdirAll(clientPath, os.ModePerm) @@ -61,24 +52,15 @@ func InitDB() error { // server's interface if _, err := os.Stat(serverInterfacePath); os.IsNotExist(err) { - db, err := DBConn() - if err != nil { - return err - } - serverInterface := new(model.ServerInterface) - serverInterface.Addresses = []string{defaultServerAddress} - serverInterface.ListenPort = defaultServerPort + serverInterface.Addresses = []string{util.DefaultServerAddress} + serverInterface.ListenPort = util.DefaultServerPort serverInterface.UpdatedAt = time.Now().UTC() - db.Write("server", "interfaces", serverInterface) + o.conn.Write("server", "interfaces", serverInterface) } // server's key pair if _, err := os.Stat(serverKeyPairPath); os.IsNotExist(err) { - db, err := DBConn() - if err != nil { - return err - } key, err := wgtypes.GeneratePrivateKey() if err != nil { @@ -88,97 +70,62 @@ func InitDB() error { serverKeyPair.PrivateKey = key.String() serverKeyPair.PublicKey = key.PublicKey().String() serverKeyPair.UpdatedAt = time.Now().UTC() - db.Write("server", "keypair", serverKeyPair) + o.conn.Write("server", "keypair", serverKeyPair) } // global settings if _, err := os.Stat(globalSettingPath); os.IsNotExist(err) { - db, err := DBConn() - if err != nil { - return err - } - publicInterface, err := GetPublicIP() + publicInterface, err := util.GetPublicIP() if err != nil { return err } globalSetting := new(model.GlobalSetting) globalSetting.EndpointAddress = publicInterface.IPAddress - globalSetting.DNSServers = []string{defaultDNS} - globalSetting.MTU = defaultMTU - globalSetting.PersistentKeepalive = defaultPersistentKeepalive - globalSetting.ConfigFilePath = defaultConfigFilePath + globalSetting.DNSServers = []string{util.DefaultDNS} + globalSetting.MTU = util.DefaultMTU + globalSetting.PersistentKeepalive = util.DefaultPersistentKeepalive + globalSetting.ConfigFilePath = util.DefaultConfigFilePath globalSetting.UpdatedAt = time.Now().UTC() - db.Write("server", "global_settings", globalSetting) + o.conn.Write("server", "global_settings", globalSetting) } // user info if _, err := os.Stat(userPath); os.IsNotExist(err) { - db, err := DBConn() - if err != nil { - return err - } - user := new(model.User) - user.Username = getCredVar(username_env_var, defaultUsername) - user.Password = getCredVar(password_env_var, defaultPassword) - db.Write("server", "users", user) + user.Username = util.GetCredVar(util.UsernameEnvVar, util.DefaultUsername) + user.Password = util.GetCredVar(util.PasswordEnvVar, util.DefaultPassword) + o.conn.Write("server", "users", user) } return nil } // GetUser func to query user info from the database -func GetUser() (model.User, error) { +func (o *JsonDB) GetUser() (model.User, error) { user := model.User{} - - db, err := DBConn() - if err != nil { - return user, err - } - - if err := db.Read("server", "users", &user); err != nil { - return user, err - } - - return user, nil + return user, o.conn.Read("server", "users", &user) } // GetGlobalSettings func to query global settings from the database -func GetGlobalSettings() (model.GlobalSetting, error) { +func (o *JsonDB) GetGlobalSettings() (model.GlobalSetting, error) { settings := model.GlobalSetting{} - - db, err := DBConn() - if err != nil { - return settings, err - } - - if err := db.Read("server", "global_settings", &settings); err != nil { - return settings, err - } - - return settings, nil + return settings, o.conn.Read("server", "global_settings", &settings) } // GetServer func to query Server setting from the database -func GetServer() (model.Server, error) { +func (o *JsonDB) GetServer() (model.Server, error) { server := model.Server{} - - db, err := DBConn() - if err != nil { - return server, err - } - // read server interface information serverInterface := model.ServerInterface{} - if err := db.Read("server", "interfaces", &serverInterface); err != nil { + if err := o.conn.Read("server", "interfaces", &serverInterface); err != nil { return server, err } // read server key pair information serverKeyPair := model.ServerKeypair{} - if err := db.Read("server", "keypair", &serverKeyPair); err != nil { + if err := o.conn.Read("server", "keypair", &serverKeyPair); err != nil { return server, err } @@ -188,17 +135,11 @@ func GetServer() (model.Server, error) { return server, nil } -// GetClients to get all clients from the database -func GetClients(hasQRCode bool) ([]model.ClientData, error) { +func (o *JsonDB) GetClients(hasQRCode bool) ([]model.ClientData, error) { var clients []model.ClientData - db, err := DBConn() - if err != nil { - return clients, err - } - // read all client json file in "clients" directory - records, err := db.ReadAll("clients") + records, err := o.conn.ReadAll("clients") if err != nil { return clients, err } @@ -215,10 +156,10 @@ func GetClients(hasQRCode bool) ([]model.ClientData, error) { // generate client qrcode image in base64 if hasQRCode { - server, _ := GetServer() - globalSettings, _ := GetGlobalSettings() + server, _ := o.GetServer() + globalSettings, _ := o.GetGlobalSettings() - png, err := qrcode.Encode(BuildClientConfig(client, server, globalSettings), qrcode.Medium, 256) + png, err := qrcode.Encode(util.BuildClientConfig(client, server, globalSettings), qrcode.Medium, 256) if err == nil { clientData.QRCode = "data:image/png;base64," + base64.StdEncoding.EncodeToString([]byte(png)) } else { @@ -234,27 +175,21 @@ func GetClients(hasQRCode bool) ([]model.ClientData, error) { return clients, nil } -// GetClientByID func to query a client from the database -func GetClientByID(clientID string, hasQRCode bool) (model.ClientData, error) { +func (o *JsonDB) GetClientByID(clientID string, hasQRCode bool) (model.ClientData, error) { client := model.Client{} clientData := model.ClientData{} - db, err := DBConn() - if err != nil { - return clientData, err - } - // read client information - if err := db.Read("clients", clientID, &client); err != nil { + if err := o.conn.Read("clients", clientID, &client); err != nil { return clientData, err } // generate client qrcode image in base64 if hasQRCode { - server, _ := GetServer() - globalSettings, _ := GetGlobalSettings() + server, _ := o.GetServer() + globalSettings, _ := o.GetGlobalSettings() - png, err := qrcode.Encode(BuildClientConfig(client, server, globalSettings), qrcode.Medium, 256) + png, err := qrcode.Encode(util.BuildClientConfig(client, server, globalSettings), qrcode.Medium, 256) if err == nil { clientData.QRCode = "data:image/png;base64," + base64.StdEncoding.EncodeToString([]byte(png)) } else { @@ -266,3 +201,23 @@ func GetClientByID(clientID string, hasQRCode bool) (model.ClientData, error) { return clientData, nil } + +func (o *JsonDB) SaveClient(client model.Client) error { + return o.conn.Write("clients", client.ID, client) +} + +func (o *JsonDB) DeleteClient(clientID string) error { + return o.conn.Delete("clients", clientID) +} + +func (o *JsonDB) SaveServerInterface(serverInterface model.ServerInterface) error { + return o.conn.Write("server", "interfaces", serverInterface) +} + +func (o *JsonDB) SaveServerKeyPair(serverKeyPair model.ServerKeypair) error { + return o.conn.Write("server", "keypair", serverKeyPair) +} + +func (o *JsonDB) SaveGlobalSettings(globalSettings model.GlobalSetting) error { + return o.conn.Write("server", "global_settings", globalSettings) +} diff --git a/store/store.go b/store/store.go new file mode 100644 index 0000000..8750cf2 --- /dev/null +++ b/store/store.go @@ -0,0 +1,19 @@ +package store + +import ( + "github.com/ngoduykhanh/wireguard-ui/model" +) + +type IStore interface { + Init() error + GetUser() (model.User, error) + GetGlobalSettings() (model.GlobalSetting, error) + GetServer() (model.Server, error) + GetClients(hasQRCode bool) ([]model.ClientData, error) + GetClientByID(clientID string, hasQRCode bool) (model.ClientData, error) + SaveClient(client model.Client) error + DeleteClient(clientID string) error + SaveServerInterface(serverInterface model.ServerInterface) error + SaveServerKeyPair(serverKeyPair model.ServerKeypair) error + SaveGlobalSettings(globalSettings model.GlobalSetting) error +} diff --git a/templates/base.html b/templates/base.html index 3ba800c..6b09a32 100644 --- a/templates/base.html +++ b/templates/base.html @@ -120,6 +120,14 @@

+ @@ -446,4 +454,4 @@ -{{end}} \ No newline at end of file +{{end}} diff --git a/templates/status.html b/templates/status.html new file mode 100644 index 0000000..18e883a --- /dev/null +++ b/templates/status.html @@ -0,0 +1,57 @@ +{{define "title"}} +Connected Peers +{{end}} + +{{define "top_css"}} +{{end}} + +{{define "username"}} +{{ .username }} +{{end}} + +{{define "page_title"}} +Connected Peers +{{end}} + +{{define "page_content"}} +
+
+ {{ if .error }} + + {{ end}} + {{ range $dev := .devices }} + + + + + + + + + + + + + + + + {{ range $idx, $peer := $dev.Peers }} + + + + + + + + + + + {{ end }} + +
List of connected peers for device with name {{ $dev.Name }}
#NameEmailPublic KeyReceiveBytesTransmitBytesConnected (Approximation)LastHandshakeTime
{{ $idx }}{{ $peer.Name }}{{ $peer.Email }}{{ $peer.PublicKey }}{{ $peer.ReceivedBytes }}{{ $peer.TransmitBytes }}{{ $peer.Connected }}{{ $peer.LastHandshakeTime }}
+ {{ end }} + + +
+
+{{end}} diff --git a/util/config.go b/util/config.go index 5f86ea9..acdb720 100644 --- a/util/config.go +++ b/util/config.go @@ -11,3 +11,16 @@ var ( EmailContent string SessionSecret []byte ) + +const ( + DefaultUsername = "admin" + DefaultPassword = "admin" + DefaultServerAddress = "10.252.1.0/24" + DefaultServerPort = 51820 + DefaultDNS = "1.1.1.1" + DefaultMTU = 1450 + DefaultPersistentKeepalive = 15 + DefaultConfigFilePath = "/etc/wireguard/wg0.conf" + UsernameEnvVar = "WGUI_USERNAME" + PasswordEnvVar = "WGUI_PASSWORD" +) diff --git a/util/util.go b/util/util.go index 09e5fd9..5d3805e 100644 --- a/util/util.go +++ b/util/util.go @@ -368,3 +368,11 @@ func WriteWireGuardServerConfig(tmplBox *rice.Box, serverConfig model.Server, cl return nil } + +// GetCredVar reads value from environment variable or returns fallback +func GetCredVar(key, fallback string) string { + if value, ok := os.LookupEnv(key); ok { + return value + } + return fallback +}