Initial commit
This commit is contained in:
		
						commit
						6cb8527c35
					
				|  | @ -0,0 +1,20 @@ | ||||||
|  | # Binaries for programs and plugins | ||||||
|  | *.exe | ||||||
|  | *.exe~ | ||||||
|  | *.dll | ||||||
|  | *.so | ||||||
|  | *.dylib | ||||||
|  | 
 | ||||||
|  | # Test binary, built with `go test -c` | ||||||
|  | *.test | ||||||
|  | 
 | ||||||
|  | # Output of the go coverage tool, specifically when used with LiteIDE | ||||||
|  | *.out | ||||||
|  | 
 | ||||||
|  | # Dependency directories (remove the comment below to include it) | ||||||
|  | vendor/ | ||||||
|  | assets/ | ||||||
|  | db/ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @ -0,0 +1,4 @@ | ||||||
|  | { | ||||||
|  | 	"privateKey": "xyz", | ||||||
|  | 	"pulbicKey": "123" | ||||||
|  | } | ||||||
|  | @ -0,0 +1,15 @@ | ||||||
|  | module github.com/ngoduykhanh/wireguard-ui | ||||||
|  | 
 | ||||||
|  | go 1.14 | ||||||
|  | 
 | ||||||
|  | require ( | ||||||
|  | 	github.com/go-playground/universal-translator v0.17.0 // indirect | ||||||
|  | 	github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25 // indirect | ||||||
|  | 	github.com/labstack/echo/v4 v4.1.16 | ||||||
|  | 	github.com/labstack/gommon v0.3.0 | ||||||
|  | 	github.com/leodido/go-urn v1.2.0 // indirect | ||||||
|  | 	github.com/rs/xid v1.2.1 | ||||||
|  | 	github.com/sdomino/scribble v0.0.0-20191024200645-4116320640ba | ||||||
|  | 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200324154536-ceff61240acf | ||||||
|  | 	gopkg.in/go-playground/validator.v9 v9.31.0 | ||||||
|  | ) | ||||||
|  | @ -0,0 +1,85 @@ | ||||||
|  | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
|  | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= | ||||||
|  | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | ||||||
|  | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= | ||||||
|  | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= | ||||||
|  | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= | ||||||
|  | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= | ||||||
|  | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= | ||||||
|  | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | ||||||
|  | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
|  | 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/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= | ||||||
|  | github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= | ||||||
|  | github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= | ||||||
|  | github.com/labstack/echo/v4 v4.1.16 h1:8swiwjE5Jkai3RPfZoahp8kjVCRNq+y7Q0hPji2Kz0o= | ||||||
|  | github.com/labstack/echo/v4 v4.1.16/go.mod h1:awO+5TzAjvL8XpibdsfXxPgHr+orhtXZJZIQCVjogKI= | ||||||
|  | github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= | ||||||
|  | github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= | ||||||
|  | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= | ||||||
|  | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= | ||||||
|  | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= | ||||||
|  | github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= | ||||||
|  | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= | ||||||
|  | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= | ||||||
|  | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= | ||||||
|  | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= | ||||||
|  | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= | ||||||
|  | github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc= | ||||||
|  | github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= | ||||||
|  | github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= | ||||||
|  | github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= | ||||||
|  | github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc= | ||||||
|  | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||||
|  | github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= | ||||||
|  | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= | ||||||
|  | github.com/sdomino/scribble v0.0.0-20191024200645-4116320640ba h1:8QAc9wFAf2b/9cAXskm0wBylObZ0bTpRcaP7ThjLPVQ= | ||||||
|  | github.com/sdomino/scribble v0.0.0-20191024200645-4116320640ba/go.mod h1:W6zxGUBCXRR5QugSd/nFcFVmwoGnvpjiNY/JwT03Wew= | ||||||
|  | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||||
|  | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||||||
|  | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= | ||||||
|  | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= | ||||||
|  | github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= | ||||||
|  | github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4= | ||||||
|  | github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= | ||||||
|  | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||||
|  | golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||||
|  | golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||||
|  | golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw= | ||||||
|  | golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||||
|  | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||||||
|  | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||||||
|  | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||||
|  | golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||||
|  | golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||||
|  | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||||
|  | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= | ||||||
|  | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||||
|  | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
|  | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
|  | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= | ||||||
|  | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||||
|  | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= | ||||||
|  | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= | ||||||
|  | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||||
|  | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
|  | golang.zx2c4.com/wireguard v0.0.20200121 h1:vcswa5Q6f+sylDfjqyrVNNrjsFUUbPsgAQTBCAg/Qf8= | ||||||
|  | golang.zx2c4.com/wireguard v0.0.20200121/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4= | ||||||
|  | golang.zx2c4.com/wireguard v0.0.20200320 h1:1vE6zVeO7fix9cJX1Z9ZQ+ikPIIx7vIyU0o0tLDD88g= | ||||||
|  | golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200324154536-ceff61240acf h1:rWUZHukj3poXegPQMZOXgxjTGIBe3mLNHNVvL5DsHus= | ||||||
|  | golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200324154536-ceff61240acf/go.mod h1:UdS9frhv65KTfwxME1xE8+rHYoFpbm36gOud1GhBe9c= | ||||||
|  | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
|  | gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+an+ZQdDaD1M= | ||||||
|  | gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= | ||||||
|  | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
|  | @ -0,0 +1,101 @@ | ||||||
|  | package handler | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"net/http" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/labstack/echo/v4" | ||||||
|  | 	"github.com/ngoduykhanh/wireguard-ui/model" | ||||||
|  | 	"github.com/sdomino/scribble" | ||||||
|  | 	"github.com/labstack/gommon/log" | ||||||
|  | 	"github.com/rs/xid" | ||||||
|  | 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Home handler
 | ||||||
|  | func Home() echo.HandlerFunc { | ||||||
|  | 	return func(c echo.Context) error { | ||||||
|  | 		// initialize database directory
 | ||||||
|  | 		dir := "./db" | ||||||
|  | 		db, err := scribble.New(dir, nil) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Error("Cannot initialize the database: ", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		records, err := db.ReadAll("clients") | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Error("Cannot fetch clients from database: ", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		clients := []model.Client{} | ||||||
|  | 		for _, f := range records { | ||||||
|  | 			client := model.Client{} | ||||||
|  | 			if err := json.Unmarshal([]byte(f), &client); err != nil { | ||||||
|  | 				log.Error("Cannot decode client json structure: ", err) | ||||||
|  | 			} | ||||||
|  | 			clients = append(clients, client) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return c.Render(http.StatusOK, "home.html", map[string]interface{}{ | ||||||
|  | 			"name": "Khanh", | ||||||
|  | 			"clients": clients, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewClient handler
 | ||||||
|  | func NewClient() echo.HandlerFunc { | ||||||
|  | 	return func (c echo.Context) error { | ||||||
|  | 		client := new(model.Client) | ||||||
|  | 		c.Bind(client) | ||||||
|  | 
 | ||||||
|  | 		// gen ID
 | ||||||
|  | 		guid := xid.New() | ||||||
|  | 		client.ID = guid.String() | ||||||
|  | 
 | ||||||
|  | 		// gen Wireguard key pairs
 | ||||||
|  | 		key, err := wgtypes.GeneratePrivateKey() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		client.PrivateKey = key.String() | ||||||
|  | 		client.PublicKey = key.PublicKey().String() | ||||||
|  | 		client.CreatedAt = time.Now().UTC() | ||||||
|  | 		client.UpdatedAt = client.CreatedAt | ||||||
|  | 
 | ||||||
|  | 		// write to the database
 | ||||||
|  | 		dir := "./db" | ||||||
|  | 		db, err := scribble.New(dir, nil) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Error("Cannot initialize the database: ", err) | ||||||
|  | 		} | ||||||
|  | 		db.Write("clients", client.ID, client) | ||||||
|  | 		log.Infof("Created wireguard client: %v", client) | ||||||
|  | 
 | ||||||
|  | 		return c.JSON(http.StatusOK, client)	 | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RemoveClient handler
 | ||||||
|  | func RemoveClient() echo.HandlerFunc { | ||||||
|  | 	return func (c echo.Context) error { | ||||||
|  | 		client := new(model.Client) | ||||||
|  | 		c.Bind(client) | ||||||
|  | 
 | ||||||
|  | 		// delete from database
 | ||||||
|  | 		dir := "./db" | ||||||
|  | 		db, err := scribble.New(dir, nil) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Error("Cannot initialize the database: ", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if err := db.Delete("clients", client.ID); err != nil { | ||||||
|  | 			log.Error("Cannot delete wireguard client: ", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		log.Infof("Removed wireguard client: %v", client) | ||||||
|  | 
 | ||||||
|  | 		return c.JSON(http.StatusOK, "Client removed!") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,16 @@ | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"github.com/ngoduykhanh/wireguard-ui/handler" | ||||||
|  | 	"github.com/ngoduykhanh/wireguard-ui/router" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func main() { | ||||||
|  | 	app := router.New() | ||||||
|  | 
 | ||||||
|  | 	app.GET("/", handler.Home()) | ||||||
|  | 	app.POST("/new-client", handler.NewClient()) | ||||||
|  | 	app.POST("/remove-client", handler.RemoveClient()) | ||||||
|  | 
 | ||||||
|  | 	app.Logger.Fatal(app.Start("127.0.0.1:5000")) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,19 @@ | ||||||
|  | package model | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Client model
 | ||||||
|  | type Client struct { | ||||||
|  | 	ID				string		`json:"id"` | ||||||
|  | 	PrivateKey		string		`json:"private_key"` | ||||||
|  | 	PublicKey 		string		`json:"pulbic_key"` | ||||||
|  | 	Name			string		`json:"name"` | ||||||
|  | 	Email			string  	`json:"email"` | ||||||
|  | 	AllocatedIPs	[]string  	`json:"allocated_ips"` | ||||||
|  | 	AllowedIPs		[]string  	`json:"allowed_ips"` | ||||||
|  | 	Enabled			bool 		`json:"enabled"` | ||||||
|  | 	CreatedAt       time.Time 	`json:"created_at"` | ||||||
|  | 	UpdatedAt       time.Time 	`json:"updated_at"` | ||||||
|  | } | ||||||
|  | @ -0,0 +1,49 @@ | ||||||
|  | package router | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"io" | ||||||
|  | 	"text/template" | ||||||
|  | 
 | ||||||
|  | 	"github.com/labstack/echo/v4" | ||||||
|  | 	"github.com/labstack/echo/v4/middleware" | ||||||
|  | 	"github.com/labstack/gommon/log" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // TemplateRegistry is a custom html/template renderer for Echo framework
 | ||||||
|  | type TemplateRegistry struct { | ||||||
|  | 	templates map[string]*template.Template | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Render e.Renderer interface
 | ||||||
|  | func (t *TemplateRegistry) Render(w io.Writer, name string, data interface{}, c echo.Context) error { | ||||||
|  | 	tmpl, ok := t.templates[name] | ||||||
|  | 	if !ok { | ||||||
|  | 		err := errors.New("Template not found -> " + name) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return tmpl.ExecuteTemplate(w, "base.html", data) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // New function
 | ||||||
|  | func New() *echo.Echo { | ||||||
|  | 	e := echo.New() | ||||||
|  | 	templates := make(map[string]*template.Template) | ||||||
|  | 	templates["home.html"] = template.Must(template.ParseFiles("templates/home.html", "templates/base.html")) | ||||||
|  | 
 | ||||||
|  | 	e.Logger.SetLevel(log.DEBUG) | ||||||
|  | 	e.Pre(middleware.RemoveTrailingSlash()) | ||||||
|  | 	e.Use(middleware.Logger()) | ||||||
|  | 	e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ | ||||||
|  | 		AllowOrigins: []string{"*"}, | ||||||
|  | 		AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization}, | ||||||
|  | 		AllowMethods: []string{echo.GET, echo.HEAD, echo.PUT, echo.PATCH, echo.POST, echo.DELETE}, | ||||||
|  | 	})) | ||||||
|  | 	e.Validator = NewValidator() | ||||||
|  | 	e.Static("/static", "assets") | ||||||
|  | 	e.Renderer = &TemplateRegistry{ | ||||||
|  | 		templates: templates, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return e | ||||||
|  | } | ||||||
|  | @ -0,0 +1,20 @@ | ||||||
|  | package router | ||||||
|  | 
 | ||||||
|  | import "gopkg.in/go-playground/validator.v9" | ||||||
|  | 
 | ||||||
|  | // NewValidator func
 | ||||||
|  | func NewValidator() *Validator { | ||||||
|  | 	return &Validator{ | ||||||
|  | 		validator: validator.New(), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Validator struct
 | ||||||
|  | type Validator struct { | ||||||
|  | 	validator *validator.Validate | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Validate func
 | ||||||
|  | func (v *Validator) Validate(i interface{}) error { | ||||||
|  | 	return v.validator.Struct(i) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,299 @@ | ||||||
|  | {{define "base.html"}} | ||||||
|  | <!DOCTYPE html> | ||||||
|  | <html> | ||||||
|  | 
 | ||||||
|  | <head> | ||||||
|  |     <meta charset="utf-8"> | ||||||
|  |     <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||||
|  |     <title>{{template "title" .}}</title> | ||||||
|  |     <!-- Tell the browser to be responsive to screen width --> | ||||||
|  |     <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||||
|  | 
 | ||||||
|  |     <!-- Font Awesome --> | ||||||
|  |     <link rel="stylesheet" href="static/plugins/fontawesome-free/css/all.min.css"> | ||||||
|  |     <!-- iCheck for checkboxes and radio inputs --> | ||||||
|  |     <link rel="stylesheet" href="static/plugins/icheck-bootstrap/icheck-bootstrap.min.css"> | ||||||
|  |     <!-- Select2 --> | ||||||
|  |     <link rel="stylesheet" href="static/plugins/select2/css/select2.min.css"> | ||||||
|  |     <!-- Toastr --> | ||||||
|  |     <link rel="stylesheet" href="static/plugins/toastr/toastr.min.css"> | ||||||
|  |     <!-- Ionicons --> | ||||||
|  |     <link rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css"> | ||||||
|  |     <!-- overlayScrollbars --> | ||||||
|  |     <link rel="stylesheet" href="static/dist/css/adminlte.min.css"> | ||||||
|  |     <!-- Google Font: Source Sans Pro --> | ||||||
|  |     <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700" rel="stylesheet"> | ||||||
|  | </head> | ||||||
|  | 
 | ||||||
|  | <body class="hold-transition sidebar-mini"> | ||||||
|  |     <!-- Site wrapper --> | ||||||
|  |     <div class="wrapper"> | ||||||
|  |         <!-- Navbar --> | ||||||
|  |         <nav class="main-header navbar navbar-expand navbar-white navbar-light"> | ||||||
|  |             <!-- Left navbar links --> | ||||||
|  |             <ul class="navbar-nav"> | ||||||
|  |                 <li class="nav-item"> | ||||||
|  |                     <a class="nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a> | ||||||
|  |                 </li> | ||||||
|  |             </ul> | ||||||
|  | 
 | ||||||
|  |             <!-- SEARCH FORM --> | ||||||
|  |             <form class="form-inline ml-3"> | ||||||
|  |                 <div class="input-group input-group-sm"> | ||||||
|  |                     <input class="form-control form-control-navbar" type="search" placeholder="Search" | ||||||
|  |                         aria-label="Search"> | ||||||
|  |                     <div class="input-group-append"> | ||||||
|  |                         <button class="btn btn-navbar" type="submit"> | ||||||
|  |                             <i class="fas fa-search"></i> | ||||||
|  |                         </button> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |             </form> | ||||||
|  | 
 | ||||||
|  |         <!-- Right navbar links --> | ||||||
|  |         <div class="navbar-nav ml-auto"> | ||||||
|  |             <button type="button" class="btn btn-outline-primary btn-sm" data-toggle="modal" | ||||||
|  |                 data-target="#modal_new_client"><i class="nav-icon fas fa-plus"></i> New | ||||||
|  |                 Client</button> | ||||||
|  |         </nav> | ||||||
|  |         </nav> | ||||||
|  |         <!-- /.navbar --> | ||||||
|  | 
 | ||||||
|  |         <!-- Main Sidebar Container --> | ||||||
|  |         <aside class="main-sidebar sidebar-dark-primary elevation-4"> | ||||||
|  |             <!-- Brand Logo --> | ||||||
|  |             <a href="/" class="brand-link"> | ||||||
|  |                 <!-- <img src="static/dist/img/logo.png" alt="Wireguard UI" | ||||||
|  |                     class="brand-image img-circle elevation-3" style="opacity: .8"> --> | ||||||
|  |                 <span class="brand-text font-weight-light">WIREGUARD UI</span> | ||||||
|  |             </a> | ||||||
|  | 
 | ||||||
|  |             <!-- Sidebar --> | ||||||
|  |             <div class="sidebar"> | ||||||
|  |                 <!-- Sidebar user (optional) --> | ||||||
|  |                 <div class="user-panel mt-3 pb-3 mb-3 d-flex"> | ||||||
|  |                     <div class="image"> | ||||||
|  |                         <img src="static/dist/img/user2-160x160.jpg" class="img-circle elevation-2"> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="info"> | ||||||
|  |                         <a href="#" class="d-block">{{template "username" .}}</a> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  | 
 | ||||||
|  |                 <!-- Sidebar Menu --> | ||||||
|  |                 <nav class="mt-2"> | ||||||
|  |                     <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false"> | ||||||
|  |                         <li class="nav-item"> | ||||||
|  |                             <a href="#" class="nav-link active"> | ||||||
|  |                                 <i class="nav-icon fas fa-th"></i> | ||||||
|  |                                 <p> | ||||||
|  |                                     Dashboard | ||||||
|  |                                 </p> | ||||||
|  |                             </a> | ||||||
|  |                         </li> | ||||||
|  |                         <li class="nav-item"> | ||||||
|  |                             <a href="#" class="nav-link"> | ||||||
|  |                                 <i class="nav-icon fas fa-server"></i> | ||||||
|  |                                 <p> | ||||||
|  |                                     Server Config | ||||||
|  |                                 </p> | ||||||
|  |                             </a> | ||||||
|  |                         </li> | ||||||
|  |                     </ul> | ||||||
|  |                 </nav> | ||||||
|  |                 <!-- /.sidebar-menu --> | ||||||
|  |             </div> | ||||||
|  |             <!-- /.sidebar --> | ||||||
|  |         </aside> | ||||||
|  | 
 | ||||||
|  |         <div class="modal fade" id="modal_new_client"> | ||||||
|  |             <div class="modal-dialog"> | ||||||
|  |                 <div class="modal-content"> | ||||||
|  |                     <div class="modal-header"> | ||||||
|  |                         <h4 class="modal-title">New Wireguard Client</h4> | ||||||
|  |                         <button type="button" class="close" data-dismiss="modal" aria-label="Close"> | ||||||
|  |                             <span aria-hidden="true">×</span> | ||||||
|  |                         </button> | ||||||
|  |                     </div> | ||||||
|  |                     <form name="frm_new_client" id="frm_new_client"> | ||||||
|  |                         <div class="modal-body"> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label for="client_name" class="control-label">Name</label> | ||||||
|  |                                 <input type="text" class="form-control" id="client_name" name="client_name"> | ||||||
|  |                             </div> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label for="client_email" class="control-label">Email</label> | ||||||
|  |                                 <input type="text" class="form-control" id="client_email" name="client_email"> | ||||||
|  |                             </div> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label for="client_allocated_ips" class="control-label">IP Allocation</label> | ||||||
|  |                                 <select id="client_allocated_ips" class=" select2" data-placeholder="Select an IP address" | ||||||
|  |                                     style="width: 100%;"> | ||||||
|  |                                     <option>192.168.1.1</option> | ||||||
|  |                                     <option>192.168.1.2</option> | ||||||
|  |                                     <option>192.168.1.3</option> | ||||||
|  |                                     <option>192.168.1.4</option> | ||||||
|  |                                     <option>192.168.1.5</option> | ||||||
|  |                                     <option>192.168.1.6</option> | ||||||
|  |                                     <option>192.168.1.7</option> | ||||||
|  |                                 </select> | ||||||
|  |                             </div> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label for="client_allowed_ips" class="control-label">Allowed IPs</label> | ||||||
|  |                                 <input type="text" class="form-control" id="client_allowed_ips" value="0.0.0.0/0"> | ||||||
|  |                             </div> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <div class="icheck-primary d-inline"> | ||||||
|  |                                     <input type="checkbox" id="enabled" checked> | ||||||
|  |                                     <label for="enabled"> | ||||||
|  |                                         Enable after creation | ||||||
|  |                                     </label> | ||||||
|  |                                 </div> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="modal-footer justify-content-between"> | ||||||
|  |                             <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> | ||||||
|  |                             <button type="submit" class="btn btn-primary">Submit</button> | ||||||
|  |                         </div> | ||||||
|  |                     </form> | ||||||
|  |                 </div> | ||||||
|  |                 <!-- /.modal-content --> | ||||||
|  |             </div> | ||||||
|  |             <!-- /.modal-dialog --> | ||||||
|  |         </div> | ||||||
|  |         <!-- /.modal --> | ||||||
|  | 
 | ||||||
|  |         <!-- Content Wrapper. Contains page content --> | ||||||
|  |         <div class="content-wrapper"> | ||||||
|  |             <!-- Content Header (Page header) --> | ||||||
|  |             <section class="content-header"> | ||||||
|  |                 <div class="container-fluid"> | ||||||
|  |                     <div class="row mb-2"> | ||||||
|  |                         <div class="col-sm-6"> | ||||||
|  |                             <h1>{{template "page_title" .}}</h1> | ||||||
|  |                         </div> | ||||||
|  |                     </div> | ||||||
|  |                 </div><!-- /.container-fluid --> | ||||||
|  |             </section> | ||||||
|  | 
 | ||||||
|  |             <!-- Main content --> | ||||||
|  |             {{template "page_content" .}} | ||||||
|  |             <!-- /.content --> | ||||||
|  |         </div> | ||||||
|  |         <!-- /.content-wrapper --> | ||||||
|  | 
 | ||||||
|  |         <footer class="main-footer"> | ||||||
|  |             <div class="float-right d-none d-sm-block"> | ||||||
|  |                 <b>Version</b> 0.1 | ||||||
|  |             </div> | ||||||
|  |             <strong>Copyright © 2020 <a href="https://github.com/ngoduykhanh/wireguard-ui">Wireguard UI</a>.</strong> All rights | ||||||
|  |             reserved. | ||||||
|  |         </footer> | ||||||
|  | 
 | ||||||
|  |         <!-- Control Sidebar --> | ||||||
|  |         <aside class="control-sidebar control-sidebar-dark"> | ||||||
|  |             <!-- Control sidebar content goes here --> | ||||||
|  |         </aside> | ||||||
|  |         <!-- /.control-sidebar --> | ||||||
|  |     </div> | ||||||
|  |     <!-- ./wrapper --> | ||||||
|  | 
 | ||||||
|  |     <!-- jQuery --> | ||||||
|  |     <script src="static/plugins/jquery/jquery.min.js"></script> | ||||||
|  |     <!-- Bootstrap 4 --> | ||||||
|  |     <script src="static/plugins/bootstrap/js/bootstrap.bundle.min.js"></script> | ||||||
|  |     <!-- Select2 --> | ||||||
|  |     <script src="static/plugins/select2/js/select2.full.min.js"></script> | ||||||
|  |     <!-- jquery-validation --> | ||||||
|  |     <script src="static/plugins/jquery-validation/jquery.validate.min.js"></script> | ||||||
|  |     <!-- Toastr --> | ||||||
|  |     <script src="static/plugins/toastr/toastr.min.js"></script> | ||||||
|  |     <!-- AdminLTE App --> | ||||||
|  |     <script src="static/dist/js/adminlte.min.js"></script> | ||||||
|  |     <!-- AdminLTE for demo purposes --> | ||||||
|  |     <script src="static/dist/js/demo.js"></script> | ||||||
|  |     <script> | ||||||
|  |         // submitNewClient function for new client form submition | ||||||
|  |         function submitNewClient() { | ||||||
|  |             // TODO: Get allocated_ips and allowed_ips as multiple value in array | ||||||
|  |             var name = $("#client_name").val(); | ||||||
|  |             var email = $("#client_email").val(); | ||||||
|  |             var allocated_ips = $("#client_allocated_ips").select2('val'); | ||||||
|  |             var allowed_ips = $("#client_allowed_ips").val(); | ||||||
|  |             var enabled = false; | ||||||
|  | 
 | ||||||
|  |             if ($("#enabled").is(':checked')){ | ||||||
|  |                 enabled = true; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var data = {"name": name, "email": email, "allocated_ips": [allocated_ips], "allowed_ips": [allowed_ips], | ||||||
|  |             "enabled": enabled}; | ||||||
|  |             console.log(data); | ||||||
|  | 
 | ||||||
|  |             $.ajax({ | ||||||
|  |                 cache: false, | ||||||
|  |                 method: 'POST', | ||||||
|  |                 url: '/new-client', | ||||||
|  |                 dataType: 'json', | ||||||
|  |                 contentType: "application/json", | ||||||
|  |                 data: JSON.stringify(data), | ||||||
|  |                 success: function(data) { | ||||||
|  |                     $('#modal_new_client').modal('hide'); | ||||||
|  |                     toastr.success('Created new client successfully'); | ||||||
|  |                     // TODO: trigger reloading the dashboard | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     </script> | ||||||
|  |     <script> | ||||||
|  |         //Initialize Select2 Elements | ||||||
|  |         $('.select2').select2() | ||||||
|  | 
 | ||||||
|  |         // New client form validation | ||||||
|  |         $(document).ready(function () { | ||||||
|  |             $.validator.setDefaults({ | ||||||
|  |                 submitHandler: function () { | ||||||
|  |                     submitNewClient(); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |             $('#frm_new_client').validate({ | ||||||
|  |                 rules: { | ||||||
|  |                     client_name: { | ||||||
|  |                         required: true, | ||||||
|  |                     }, | ||||||
|  |                     client_email: { | ||||||
|  |                         required: true, | ||||||
|  |                         email: true, | ||||||
|  |                     }, | ||||||
|  |                 }, | ||||||
|  |                 messages: { | ||||||
|  |                     client_name: { | ||||||
|  |                         required: "Please enter a email address" | ||||||
|  |                     }, | ||||||
|  |                     client_email: { | ||||||
|  |                         required: "Please enter a email address", | ||||||
|  |                         email: "Please enter a vaild email address" | ||||||
|  |                     }, | ||||||
|  |                 }, | ||||||
|  |                 errorElement: 'span', | ||||||
|  |                 errorPlacement: function (error, element) { | ||||||
|  |                     error.addClass('invalid-feedback'); | ||||||
|  |                     element.closest('.form-group').append(error); | ||||||
|  |                 }, | ||||||
|  |                 highlight: function (element, errorClass, validClass) { | ||||||
|  |                     $(element).addClass('is-invalid'); | ||||||
|  |                 }, | ||||||
|  |                 unhighlight: function (element, errorClass, validClass) { | ||||||
|  |                     $(element).removeClass('is-invalid'); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     </script> | ||||||
|  | 
 | ||||||
|  |     <!-- START: On page script --> | ||||||
|  |     {{template "bottom_js" .}} | ||||||
|  |     <!-- END: On page script --> | ||||||
|  | </body> | ||||||
|  | 
 | ||||||
|  | </html> | ||||||
|  | {{end}} | ||||||
|  | @ -0,0 +1,113 @@ | ||||||
|  | {{define "title"}} | ||||||
|  | Home | ||||||
|  | {{end}} | ||||||
|  | 
 | ||||||
|  | {{define "username"}} | ||||||
|  | {{index . "name"}} | ||||||
|  | {{end}} | ||||||
|  | 
 | ||||||
|  | {{define "page_title"}} | ||||||
|  | Dashboard | ||||||
|  | {{end}} | ||||||
|  | 
 | ||||||
|  | {{define "page_content"}} | ||||||
|  | <section class="content"> | ||||||
|  |     <div class="container-fluid"> | ||||||
|  |         <h5 class="mt-4 mb-2">Wireguard Clients</h5> | ||||||
|  |         <div class="row"> | ||||||
|  |             {{range .clients}} | ||||||
|  |             <div class="col-sm-6"> | ||||||
|  |                 <div class="info-box"> | ||||||
|  |                     <img | ||||||
|  |                         src="https://wg-gen-web-demo.127-0-0-1.fr/api/v1.0/client/a69b9f3f-556f-4f2a-8020-55bdd4479841/config?qrcode=true" /> | ||||||
|  |                     <div class="info-box-content"> | ||||||
|  |                         <div class="btn-group"> | ||||||
|  |                             <button type="button" class="btn btn-outline-success btn-sm">Download</button> | ||||||
|  |                             <button type="button" class="btn btn-outline-primary btn-sm">Edit</button> | ||||||
|  |                             <button type="button" class="btn btn-outline-warning btn-sm">Disable</button> | ||||||
|  |                             <button type="button" class="btn btn-outline-danger btn-sm" data-toggle="modal" data-target="#modal_remove_client" data-clientid="{{ .ID }}" data-clientname="{{ .Name }}">Remove</button> | ||||||
|  |                         </div> | ||||||
|  |                         <hr> | ||||||
|  |                         <span class="info-box-text"><i class="fas fa-user"></i> {{ .Name }}</span> | ||||||
|  |                         <span class="info-box-text"><i class="fas fa-envelope"></i> {{ .Email }}</span> | ||||||
|  |                         <span class="info-box-text"><i class="fas fa-clock"></i> | ||||||
|  |                             {{ .CreatedAt.Format "2 Jan 2006 15:04" }}</span> | ||||||
|  |                         <span class="info-box-text"><i class="fas fa-history"></i> | ||||||
|  |                             {{ .UpdatedAt.Format "2 Jan 2006 15:04" }}</span> | ||||||
|  |                         <span class="info-box-text"><strong>IP Allocation</strong></span> | ||||||
|  |                         {{range .AllocatedIPs}} | ||||||
|  |                         <small class="badge badge-secondary"></i>{{.}}</small> | ||||||
|  |                         {{end}} | ||||||
|  |                         <span class="info-box-text"><strong>Allowed IPs</strong></span> | ||||||
|  |                         {{range .AllowedIPs}} | ||||||
|  |                         <small class="badge badge-secondary"></i>{{.}}</small> | ||||||
|  |                         {{end}} | ||||||
|  |                     </div> | ||||||
|  |                     <!-- /.info-box-content --> | ||||||
|  |                 </div> | ||||||
|  |                 <!-- /.info-box --> | ||||||
|  |             </div> | ||||||
|  |             <!-- /.col --> | ||||||
|  |             {{end}} | ||||||
|  |         </div> | ||||||
|  |         <!-- /.row --> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | 
 | ||||||
|  | <div class="modal fade" id="modal_remove_client"> | ||||||
|  |     <div class="modal-dialog"> | ||||||
|  |         <div class="modal-content bg-danger"> | ||||||
|  |             <div class="modal-header"> | ||||||
|  |                 <h4 class="modal-title">Remove</h4> | ||||||
|  |                 <button type="button" class="close" data-dismiss="modal" aria-label="Close"> | ||||||
|  |                     <span aria-hidden="true">×</span> | ||||||
|  |                 </button> | ||||||
|  |             </div> | ||||||
|  |             <div class="modal-body"> | ||||||
|  |             </div> | ||||||
|  |             <div class="modal-footer justify-content-between"> | ||||||
|  |                 <button type="button" class="btn btn-outline-light" data-dismiss="modal">Cancel</button> | ||||||
|  |                 <button type="button" class="btn btn-outline-light" id="remove_client_confirm" value="xxx">Apply</button> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <!-- /.modal-content --> | ||||||
|  |     </div> | ||||||
|  |     <!-- /.modal-dialog --> | ||||||
|  | </div> | ||||||
|  | <!-- /.modal --> | ||||||
|  | {{end}} | ||||||
|  | 
 | ||||||
|  | {{define "bottom_js"}} | ||||||
|  |     <script> | ||||||
|  |         // modal_remove_client modal event | ||||||
|  |         $('#modal_remove_client').on('show.bs.modal', function (event) { | ||||||
|  |             var button = $(event.relatedTarget); | ||||||
|  |             var client_id = button.data('clientid'); | ||||||
|  |             var client_name = button.data('clientname'); | ||||||
|  |             var modal = $(this); | ||||||
|  |             modal.find('.modal-body').text("You are about to remove client " + client_name); | ||||||
|  |             modal.find('#remove_client_confirm').val(client_id); | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |         // remove_client_confirm button event | ||||||
|  |         $(document).ready(function () { | ||||||
|  |             $('#remove_client_confirm').click(function () { | ||||||
|  |                 var client_id = $(this).val(); | ||||||
|  |                 var data = {"id": client_id}; | ||||||
|  |                 $.ajax({ | ||||||
|  |                     cache: false, | ||||||
|  |                     method: 'POST', | ||||||
|  |                     url: '/remove-client', | ||||||
|  |                     dataType: 'json', | ||||||
|  |                     contentType: "application/json", | ||||||
|  |                     data: JSON.stringify(data), | ||||||
|  |                     success: function(data) { | ||||||
|  |                         $('#modal_remove_client').modal('hide'); | ||||||
|  |                         toastr.success('Removed client successfully'); | ||||||
|  |                         // TODO: trigger reloading the dashboard | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     </script> | ||||||
|  | {{end}} | ||||||
										
											Binary file not shown.
										
									
								
							
		Loading…
	
		Reference in New Issue