Merge branch 'master' into master

This commit is contained in:
Daniel Lundin
2019-09-11 21:00:06 +02:00
committed by GitHub
24 changed files with 3808 additions and 93 deletions
+19
View File
@@ -0,0 +1,19 @@
steps:
- name: Build container image
agents:
queue: default
os: linux
plugins:
- EmbarkStudios/k8s#1.1.0:
image: gcr.io/kaniko-project/executor:latest
command:
- --destination=embarkstudios/wireguard-ui
- --context=/build
- --reproducible
- --cache=true
- --cache-repo=kaniko-cache.buildkite.svc.cluster.local/kaniko/cache
- --cache-dir=/cache
mount-secret:
- docker-hub-credentials:/kaniko/.docker
mount-hostpath:
- /mnt/disks/ssd0/cache/kaniko:/cache
+5 -1
View File
@@ -1 +1,5 @@
wireguard-ui
.DS_Store
/wireguard-ui
/bindata.go
ui/public/bundle.*
node_modules
+21
View File
@@ -0,0 +1,21 @@
FROM node:12-alpine AS ui
WORKDIR /ui
COPY ui .
RUN npm install
RUN npm run build
FROM golang:1.12 AS build
WORKDIR /wg
RUN go get github.com/go-bindata/go-bindata/... &&\
go get github.com/elazarl/go-bindata-assetfs/...
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
COPY --from=ui /ui/public ui/public
RUN go-bindata-assetfs -prefix ui/public ui/public &&\
go install .
FROM gcr.io/distroless/base:debug
COPY --from=build /go/bin/wireguard-ui /
ENTRYPOINT [ "/wireguard-ui" ]
+19
View File
@@ -0,0 +1,19 @@
.PHONY: binary container ui
binary: go-binary ui
go-binary:
go-bindata-assetfs -prefix ui/public ui/public
go build .
ui:
cd ui && npm install && npm run build
container:
docker build -t wireguard-ui .
run-dev: go-binary
sudo ./wireguard-ui --log-level=debug --dev-ui-server=http://localhost:5000
run-dev-ui: ui
cd ui && npm run dev
+19 -1
View File
@@ -1,6 +1,7 @@
# Wireguard UI
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FEmbarkStudios%2Fwireguard-ui.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2FEmbarkStudios%2Fwireguard-ui?ref=badge_shield)
[![Build Status](https://badge.buildkite.com/1e96905f9ee8e199808cd7faaf5a8600b2c1820fb30ae7903a.svg)](https://buildkite.com/embark-studios/wireguard-ui)
A basic web UI for managing Wireguard clients.
@@ -11,5 +12,22 @@ A basic web UI for managing Wireguard clients.
* Expiration
## Developing
### Start frontend server
```
npm install --prefix=ui
npm run --prefix=ui dev
```
### Use frontend server when running the server
```
go get -u github.com/go-bindata/go-bindata/...
go get github.com/elazarl/go-bindata-assetfs/...
go-bindata-assetfs -prefix ui/public ui/public
go build .
sudo ./wireguard-ui --log-level=debug --dev-ui-server http://localhost:5000
```
## License
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FEmbarkStudios%2Fwireguard-ui.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FEmbarkStudios%2Fwireguard-ui?ref=badge_large)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FEmbarkStudios%2Fwireguard-ui.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FEmbarkStudios%2Fwireguard-ui?ref=badge_large)
+9 -7
View File
@@ -6,8 +6,8 @@ import (
"net"
"os"
"github.com/mdlayher/wireguardctrl/wgtypes"
log "github.com/sirupsen/logrus"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
type ServerConfig struct {
@@ -19,14 +19,15 @@ type ServerConfig struct {
type UserConfig struct {
Name string
Devices map[string]*DeviceConfig
Clients map[string]*ClientConfig
}
type DeviceConfig struct {
type ClientConfig struct {
Name string
PrivateKey string
PublicKey string
IP net.IP
Notes string
}
func NewServerConfig(cfgPath string) *ServerConfig {
@@ -74,24 +75,25 @@ func (cfg *ServerConfig) GetUserConfig(user string) *UserConfig {
log.WithField("user", user).Info("No such user. Creating one.")
c = &UserConfig{
Name: user,
Devices: make(map[string]*DeviceConfig),
Clients: make(map[string]*ClientConfig),
}
cfg.Users[user] = c
}
return c
}
func NewDeviceConfig(ip net.IP) *DeviceConfig {
func NewClientConfig(ip net.IP) *ClientConfig {
key, err := wgtypes.GeneratePrivateKey()
if err != nil {
log.Fatal(err)
}
cfg := DeviceConfig{
Name: "Unnamed Device",
cfg := ClientConfig{
Name: "Unnamed Client",
PrivateKey: key.String(),
PublicKey: key.PublicKey().String(),
IP: ip,
Notes: "",
}
return &cfg
+8
View File
@@ -5,13 +5,21 @@ go 1.12
require (
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect
github.com/ddollar/forego v0.16.1 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/dln/luxaforce v0.0.0-20180531194703-db2a6b5fc72d // indirect
github.com/elazarl/go-bindata-assetfs v1.0.0
github.com/futurenda/google-auth-id-token-verifier v0.0.0-20170311140316-2a5b89f28b7e // indirect
github.com/google/nftables v0.0.0-20190430150743-07c974e3643d
github.com/julienschmidt/httprouter v1.2.0
github.com/koneu/natend v0.0.0-20150829182554-ec0926ea948d // indirect
github.com/mattn/goreman v0.2.1 // indirect
github.com/mdlayher/wireguardctrl v0.0.0-20190419142446-a4a944b88a6b
github.com/sirupsen/logrus v1.4.1
github.com/toqueteos/webbrowser v1.1.0 // indirect
github.com/vishvananda/netlink v1.0.0
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20190904205523-599d41c32142
google.golang.org/grpc v1.22.0 // indirect
gopkg.in/alecthomas/kingpin.v2 v2.2.6
)
+78
View File
@@ -1,31 +1,72 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/EmbarkStudios/src v0.0.0-20190708074312-9ce83ae72fd4 h1:DemX5XAjzL1lvIuLVAMfX2grfxjZo7CQ9JH7FnB+3VQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
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=
github.com/ddollar/forego v0.16.1 h1:HbAl3XyEdU1lw17PvZzqMzcx7b8BKZshEsdb4+1TI3Y=
github.com/ddollar/forego v0.16.1/go.mod h1:moJFK5OqWdZeLVEYHynQRJqoKImixOuxgQBFCtoe0bU=
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/dln/luxaforce v0.0.0-20180531194703-db2a6b5fc72d h1:0MwrV1xaSUg0I5dzeoRuc6tg0UQACo98/ABwQcOFJg4=
github.com/dln/luxaforce v0.0.0-20180531194703-db2a6b5fc72d/go.mod h1:CahaXJvJI62kC6ikqP63GWz40sAB1igBuQDPlxY3qnk=
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/futurenda/google-auth-id-token-verifier v0.0.0-20170311140316-2a5b89f28b7e h1:qFV0nTBo/TC3ckN/VvyT0B1/VZb94AvSANfU7XR5lcM=
github.com/futurenda/google-auth-id-token-verifier v0.0.0-20170311140316-2a5b89f28b7e/go.mod h1:EX5Jbcw/PxsrlV7D2o77gpzcJevkXl3DQjkF8a0xNMI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/nftables v0.0.0-20190430150743-07c974e3643d h1:TC4HtISCtWOTUVPQmq4CAqe0+8R2fvnkJlQn47ep2hI=
github.com/google/nftables v0.0.0-20190430150743-07c974e3643d/go.mod h1:H/FvZdk1xWxiE2Ad0wX6mq/oqHvEE1LDDujRgYwi3ns=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jsimonetti/rtnetlink v0.0.0-20190503083013-8a08cb3e375e/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/koneu/natend v0.0.0-20150829182554-ec0926ea948d h1:MFX8DxRnKMY/2M3H61iSsVbo/n3h0MWGmWNN1UViOU0=
github.com/koneu/natend v0.0.0-20150829182554-ec0926ea948d/go.mod h1:QHb4k4cr1fQikUahfcRVPcEXiUgFsdIstGqlurL0XL4=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/goreman v0.2.1 h1:6Kdjka0uWKsVM5t4d9UqzzIm2jcPJIP7QWu0ByKvlDE=
github.com/mattn/goreman v0.2.1/go.mod h1:8HCyYaC38XwX0AOu0+fuY02Y5Z7CkITW0oVJavbna4Q=
github.com/mdlayher/genetlink v0.0.0-20190419142426-b806ce69960a h1:tIPUNU99ot2Tko8SorsytkeFC0to6pRJB6koxswpeB8=
github.com/mdlayher/genetlink v0.0.0-20190419142426-b806ce69960a/go.mod h1:J+g63IQCVwjKLOxodrxOylt23f7d5vVB9rgNh+T13Uk=
github.com/mdlayher/genetlink v0.0.0-20190513144241-4cdc5dab577c h1:Z7vEAfVdgfkjIzGSOF6vLt8BGu31+DuCJqXlTI7oj3o=
github.com/mdlayher/genetlink v0.0.0-20190513144241-4cdc5dab577c/go.mod h1:Gxg/DEIMJtqdXDyq47mB98qcpBHmaLrvOAmKKNRE0Tg=
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v0.0.0-20190419142405-71c9566a34ae h1:Ec6B7pKh4YZyKI9VtTeZmN1GGLvAIT/fs2votDcWnMU=
github.com/mdlayher/netlink v0.0.0-20190419142405-71c9566a34ae/go.mod h1:TR9n0u8mie4+iszGTMjP6QRwUZQNynkhDbXTLF6DUi4=
github.com/mdlayher/netlink v0.0.0-20190513144208-ba284d510044/go.mod h1:gOrA34zDL0K3RsACQe54bDYLF/CeFspQ9m5DOycycQ8=
github.com/mdlayher/netlink v0.0.0-20190614145538-d8264f87dbe3 h1:3IPcWjiboJFnnvHeXxT4pYw33BiPJn/DC5BKhcGEbGk=
github.com/mdlayher/netlink v0.0.0-20190614145538-d8264f87dbe3/go.mod h1:ISujvOTprADlNr00kvJIu0d23q57wk2NSV/PT/TEk4E=
github.com/mdlayher/wireguardctrl v0.0.0-20190419142446-a4a944b88a6b h1:3EhP8SsJHU7mbXm4iWtO57HR2LI48KaCuSfDhAywrBg=
github.com/mdlayher/wireguardctrl v0.0.0-20190419142446-a4a944b88a6b/go.mod h1:3oz/rt7iwoq/LQa+QaFheGKzGZvc+vlmaBvtxeh6Jtc=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/toqueteos/webbrowser v1.1.0 h1:Prj1okiysRgHPoe3B1bOIVxcv+UuSt525BDQmR5W0x0=
github.com/toqueteos/webbrowser v1.1.0/go.mod h1:Hqqqmzj8AHn+VlZyVjaRWY20i25hoOZGAABCcg2el4A=
github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCOJgSM=
github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
@@ -33,14 +74,51 @@ github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmF
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480 h1:O5YqonU5IWby+w98jVUG9h7zlCWCcH4RHyPVReBmhzk=
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 h1:Gv7RPwsi3eZ2Fgewe3CBsuOebPwO27PoXzRpJPsvSSM=
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
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-20190419010253-1f3472d942ba h1:h0zCzEL5UW1mERvwTN6AXcc75PpLkY6OcReia6Dq1BM=
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180925112736-b09afc3d579e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20190403152447-81d4e9dc473e/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-20190418153312-f0ce4c0180be h1:mI+jhqkn68ybP0ORJqunXn+fq+Eeb4hHKqLQcFICjAc=
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190830023255-19e00faab6ad h1:cCejgArrk10gX6kFqjWeLwXD7aVMqWoRpyUCaaJSggc=
golang.org/x/sys v0.0.0-20190830023255-19e00faab6ad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.zx2c4.com/wireguard v0.0.20190805 h1:qPzediAzPEswzRxx2GzzPzNqnwqBZUUxEKTnC+7vjng=
golang.zx2c4.com/wireguard v0.0.20190806-0.20190831134842-7937840f9631 h1:FtEzFPuASmrIKZ9yNwMs3tIaZSaw4HGholnL3IWrASc=
golang.zx2c4.com/wireguard v0.0.20190806-0.20190831134842-7937840f9631/go.mod h1:LhfXh5z6bLC2lW2ve6BzYZFwnnsXK3OQjySR0Yh2dO8=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20190904205523-599d41c32142 h1:14yX5ra9TyR9frakvYpAohJ++d21zDP5a3JdLPYbvdc=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20190904205523-599d41c32142/go.mod h1:8wMPWlR886yRavUsaWCpfeZjPMIdIPgVtqDGLUEXsY4=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw=
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+121 -84
View File
@@ -6,35 +6,40 @@ import (
"fmt"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"path"
"strconv"
"strings"
"sync"
"github.com/dgrijalva/jwt-go"
"github.com/elazarl/go-bindata-assetfs"
"github.com/google/nftables"
"github.com/google/nftables/expr"
"github.com/julienschmidt/httprouter"
"github.com/mdlayher/wireguardctrl"
"github.com/mdlayher/wireguardctrl/wgtypes"
log "github.com/sirupsen/logrus"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netns"
"golang.zx2c4.com/wireguard/wgctrl"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"gopkg.in/alecthomas/kingpin.v2"
)
var (
dataDir = kingpin.Flag("data-dir", "Directory used for storage").Default("/var/lib/wireguard-ui").String()
listenAddr = kingpin.Flag("listen-address", "Address to listen to").Default(":8080").String()
natLink = kingpin.Flag("nat-device", "Network interface to masquerade").Default("wlp2s0").String()
clientIPRange = kingpin.Flag("client-ip-range", "Client IP CIDR").Default("172.72.72.1/24").String()
listenAddr = kingpin.Flag("listen-address", "Address to listen to").Default(":8080").String()
natLink = kingpin.Flag("nat-device", "Network interface to masquerade").Default("wlp2s0").String()
clientIPRange = kingpin.Flag("client-ip-range", "Client IP CIDR").Default("172.72.72.1/24").String()
authUserHeader = kingpin.Flag("auth-user-header", "Header containing username").Default("X-Forwarded-User").String()
wgLinkName = kingpin.Flag("wg-device-name", "Wireguard network device name").Default("wg0").String()
wgListenPort = kingpin.Flag("wg-listen-port", "Wireguard UDP port to listen to").Default("51820").Int()
wgEndpoint = kingpin.Flag("wg-endpoint", "Wireguard endpoint address").Default("127.0.0.1:51820").String()
wgAllowedIPs = kingpin.Flag("wg-allowed-ips", "Wireguard client allowed ips").Default("0.0.0.0/0").Strings()
devUIServer = kingpin.Flag("dev-ui-server", "Developer mode: If specified, proxy all static assets to this endpoint").String()
)
type Server struct {
@@ -43,6 +48,7 @@ type Server struct {
Config *ServerConfig
ipAddr net.IP
clientIPRange *net.IPNet
assets http.Handler
}
type WgLink struct {
@@ -82,12 +88,14 @@ func NewServer() *Server {
config := NewServerConfig(cfgPath)
log.Debug("Configuration loaded with public key: ", config.PublicKey)
assets := http.FileServer(&assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: AssetInfo, Prefix: ""})
s := Server{
serverConfigPath: cfgPath,
Config: config,
ipAddr: ipAddr,
clientIPRange: ipNet,
assets: assets,
}
log.Debug("Server initialized: ", *dataDir)
@@ -175,7 +183,7 @@ func (s *Server) allocateIP() net.IP {
allocated := make(map[string]bool)
allocated[s.ipAddr.String()] = true
for _, cfg := range s.Config.Users {
for _, dev := range cfg.Devices {
for _, dev := range cfg.Clients {
allocated[dev.IP.String()] = true
}
}
@@ -214,7 +222,7 @@ func (s *Server) reconfigure() {
func (s *Server) configureWireguard() error {
log.Debugf("Reconfiguring wireguard interface %s", *wgLinkName)
wg, err := wireguardctrl.New()
wg, err := wgctrl.New()
if err != nil {
return err
}
@@ -227,7 +235,7 @@ func (s *Server) configureWireguard() error {
peers := make([]wgtypes.PeerConfig, 0)
for user, cfg := range s.Config.Users {
for id, dev := range cfg.Devices {
for id, dev := range cfg.Clients {
pubKey, err := wgtypes.ParseKey(dev.PublicKey)
if err != nil {
return err
@@ -241,7 +249,7 @@ func (s *Server) configureWireguard() error {
AllowedIPs: allowedIPs,
}
log.WithFields(log.Fields{"user": user, "device": id, "key": dev.PublicKey, "allowedIPs": peer.AllowedIPs}).Debug("Adding wireguard peer")
log.WithFields(log.Fields{"user": user, "client": id, "key": dev.PublicKey, "allowedIPs": peer.AllowedIPs}).Debug("Adding wireguard peer")
peers = append(peers, peer)
}
@@ -270,60 +278,69 @@ func (s *Server) Start() error {
}
router := httprouter.New()
router.GET("/", s.Index)
router.GET("/api/v1/users/:user/devices/:device", s.withAuth(s.GetDevice))
router.PUT("/api/v1/users/:user/devices/:device", s.withAuth(s.EditDevice))
router.DELETE("/api/v1/users/:user/devices/:device", s.withAuth(s.DeleteDevice))
router.GET("/api/v1/users/:user/devices", s.withAuth(s.GetDevices))
router.POST("/api/v1/users/:user/devices", s.withAuth(s.CreateDevice))
router.GET("/api/v1/whoami", s.WhoAmI)
router.GET("/api/v1/users/:user/clients/:client", s.withAuth(s.GetClient))
router.PUT("/api/v1/users/:user/clients/:client", s.withAuth(s.EditClient))
router.DELETE("/api/v1/users/:user/clients/:client", s.withAuth(s.DeleteClient))
router.GET("/api/v1/users/:user/clients", s.withAuth(s.GetClients))
router.POST("/api/v1/users/:user/clients", s.withAuth(s.CreateClient))
if *devUIServer != "" {
log.Debug("Serving static assets proxying from development server: ", *devUIServer)
devProxy := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
url, _ := url.Parse(*devUIServer)
if strings.HasPrefix(r.URL.Path, "/client/") || r.URL.Path == "/about" {
r.URL.Path = "/"
}
proxy := httputil.NewSingleHostReverseProxy(url)
r.URL.Host = url.Host
r.URL.Scheme = url.Scheme
r.Header.Set("X-Forwarded-Host", r.Header.Get("Host"))
r.Host = url.Host
proxy.ServeHTTP(w, r)
})
router.NotFound = devProxy
} else {
log.Debug("Serving static assets embedded in binary")
router.GET("/about", s.Index)
router.GET("/client/:client", s.Index)
router.NotFound = s.assets
}
log.WithField("listenAddr", *listenAddr).Info("Starting server")
return http.ListenAndServe(*listenAddr, router)
return http.ListenAndServe(*listenAddr, s.userFromHeader(router))
}
func userFromJwtToken(r *http.Request) string {
authHeader := r.Header.Get("authorization")
if authHeader == "" {
log.Debug("No Authorization header")
return ""
}
func (s *Server) userFromHeader(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user := r.Header.Get(*authUserHeader)
if user == "" {
log.Debug("Unauthenticated request")
user = "anonymous"
}
if !strings.HasPrefix(authHeader, "Bearer ") {
log.Debug("Incorrect Authorization header: ", authHeader)
return ""
}
cookie := http.Cookie{
Name: "wguser",
Value: user,
Path: "/",
}
http.SetCookie(w, &cookie)
claims := jwt.MapClaims{}
token, err := jwt.ParseWithClaims(authHeader[7:], &claims, func(token *jwt.Token) (interface{}, error) {
return []byte(""), nil
ctx := context.WithValue(r.Context(), "user", user)
handler.ServeHTTP(w, r.WithContext(ctx))
})
if token == nil {
log.Debug("Error parsing JWT token: ", err)
return ""
}
user, ok := claims["email"]
if ok {
return user.(string)
}
user, ok = claims["sub"]
if ok {
return user.(string)
}
return ""
}
func (s *Server) withAuth(handler httprouter.Handle) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
log.Debug("Auth required")
user := userFromJwtToken(r)
if user == "" {
user = "anonymous"
log.Info("Unauthenticated user: ", user)
user := r.Context().Value("user")
if user == nil {
log.Error("Error getting username from request context")
w.WriteHeader(http.StatusInternalServerError)
return
}
if user != ps.ByName("user") {
@@ -332,27 +349,43 @@ func (s *Server) withAuth(handler httprouter.Handle) httprouter.Handle {
return
}
ctx := context.WithValue(r.Context(), "user", user)
handler(w, r.WithContext(ctx), ps)
handler(w, r, ps)
}
}
func (s *Server) Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
log.Debug("Index")
w.Write([]byte("Hello World"))
}
func (s *Server) GetDevices(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
user := r.Context().Value("user")
func (s *Server) WhoAmI(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
user := r.Context().Value("user").(string)
log.Debug(user)
err := json.NewEncoder(w).Encode(s.Config)
err := json.NewEncoder(w).Encode(struct{ User string }{user})
if err != nil {
log.Error(err)
w.WriteHeader(http.StatusInternalServerError)
}
}
func (s *Server) GetDevice(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
func (s *Server) GetClients(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
user := r.Context().Value("user").(string)
log.Debug(user)
clients := map[string]*ClientConfig{}
userConfig := s.Config.Users[user]
if userConfig != nil {
clients = userConfig.Clients
}
err := json.NewEncoder(w).Encode(clients)
if err != nil {
log.Error(err)
w.WriteHeader(http.StatusInternalServerError)
}
}
func (s *Server) Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
log.Debug("Serving single-page app from URL: ", r.URL)
r.URL.Path = "/"
s.assets.ServeHTTP(w, r)
}
func (s *Server) GetClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
user := r.Context().Value("user").(string)
usercfg := s.Config.Users[user]
if usercfg == nil {
@@ -360,8 +393,8 @@ func (s *Server) GetDevice(w http.ResponseWriter, r *http.Request, ps httprouter
return
}
device := usercfg.Devices[ps.ByName("device")]
if device == nil {
client := usercfg.Clients[ps.ByName("client")]
if client == nil {
w.WriteHeader(http.StatusNotFound)
return
}
@@ -381,11 +414,11 @@ DNS = %s
PublicKey = %s
AllowedIPs = %s
Endpoint = %s
`, device.IP.String(), device.PrivateKey, "8.8.8.8", s.Config.PublicKey, allowedIPs, *wgEndpoint)
`, client.IP.String(), client.PrivateKey, "8.8.8.8", s.Config.PublicKey, allowedIPs, *wgEndpoint)
return
}
err := json.NewEncoder(w).Encode(device)
err := json.NewEncoder(w).Encode(client)
if err != nil {
log.Error(err)
w.WriteHeader(http.StatusInternalServerError)
@@ -393,7 +426,7 @@ Endpoint = %s
}
}
func (s *Server) EditDevice(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
func (s *Server) EditClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
user := r.Context().Value("user").(string)
usercfg := s.Config.Users[user]
if usercfg == nil {
@@ -401,13 +434,13 @@ func (s *Server) EditDevice(w http.ResponseWriter, r *http.Request, ps httproute
return
}
device := usercfg.Devices[ps.ByName("device")]
if device == nil {
client := usercfg.Clients[ps.ByName("client")]
if client == nil {
w.WriteHeader(http.StatusNotFound)
return
}
cfg := DeviceConfig{}
cfg := ClientConfig{}
if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
log.Warn("Error parsing request: ", err)
@@ -415,23 +448,27 @@ func (s *Server) EditDevice(w http.ResponseWriter, r *http.Request, ps httproute
return
}
log.Debugf("EditDevice: %#v", cfg)
log.Debugf("EditClient: %#v", cfg)
if cfg.Name != "" {
device.Name = cfg.Name
client.Name = cfg.Name
}
if cfg.Notes != "" {
client.Notes = cfg.Notes
}
s.reconfigure()
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(device); err != nil {
if err := json.NewEncoder(w).Encode(client); err != nil {
log.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}
func (s *Server) DeleteDevice(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
func (s *Server) DeleteClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
user := r.Context().Value("user").(string)
usercfg := s.Config.Users[user]
if usercfg == nil {
@@ -439,32 +476,32 @@ func (s *Server) DeleteDevice(w http.ResponseWriter, r *http.Request, ps httprou
return
}
device := ps.ByName("device")
if usercfg.Devices[device] == nil {
client := ps.ByName("client")
if usercfg.Clients[client] == nil {
w.WriteHeader(http.StatusNotFound)
return
}
delete(usercfg.Devices, device)
delete(usercfg.Clients, client)
s.reconfigure()
log.WithField("user", user).Debug("Deleted device: ", device)
log.WithField("user", user).Debug("Deleted client: ", client)
w.WriteHeader(http.StatusOK)
}
func (s *Server) CreateDevice(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
func (s *Server) CreateClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
s.mutex.Lock()
defer s.mutex.Unlock()
user := r.Context().Value("user").(string)
log.WithField("user", user).Debug("CreateDevice")
log.WithField("user", user).Debug("CreateClient")
c := s.Config.GetUserConfig(user)
log.Debugf("user config: %#v", c)
i := 0
for k := range c.Devices {
for k := range c.Clients {
n, err := strconv.Atoi(k)
if err != nil {
log.Error(err)
@@ -478,12 +515,12 @@ func (s *Server) CreateDevice(w http.ResponseWriter, r *http.Request, ps httprou
i = i + 1
ip := s.allocateIP()
device := NewDeviceConfig(ip)
c.Devices[strconv.Itoa(i)] = device
client := NewClientConfig(ip)
c.Clients[strconv.Itoa(i)] = client
s.reconfigure()
err := json.NewEncoder(w).Encode(device)
err := json.NewEncoder(w).Encode(client)
if err != nil {
log.Error(err)
w.WriteHeader(http.StatusInternalServerError)
+3104
View File
File diff suppressed because it is too large Load Diff
+28
View File
@@ -0,0 +1,28 @@
{
"name": "wireguard-ui",
"version": "1.0.0",
"devDependencies": {
"braces": "^3.0.2",
"npm-run-all": "^4.1.5",
"rollup": "^1.14.3",
"rollup-plugin-commonjs": "^10.0.0",
"rollup-plugin-livereload": "^1.0.0",
"rollup-plugin-node-resolve": "^5.0.1",
"rollup-plugin-svelte": "^5.0.3",
"rollup-plugin-terser": "^5.0.0",
"sirv-cli": "^0.4.3",
"svelte": "^3.5.1",
"svelte-routing": "1.2.0"
},
"scripts": {
"build": "rollup -c",
"autobuild": "rollup -c -w",
"dev": "run-p start:dev autobuild",
"start": "sirv public",
"start:dev": "sirv public --dev"
},
"dependencies": {
"cookie-universal": "^2.0.16",
"ui": "^0.2.4"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+4
View File
@@ -0,0 +1,4 @@
body {
padding-top: 5rem;
}
+23
View File
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf8'>
<meta name='viewport' content='width=device-width'>
<title>Wireguard VPN</title>
<link rel="stylesheet" href="https://unpkg.com/bootstrap-material-design@4.1.1/dist/css/bootstrap-material-design.min.css" integrity="sha384-wXznGJNEXNG1NFsbm0ugrLFMQPWswR3lds2VeinahP8N0zJw9VWSopbjv2x7WCvX" crossorigin="anonymous">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel='icon' type='image/png' href='/favicon.png'>
<link rel='stylesheet' href='/global.css'>
<link rel='stylesheet' href='/bundle.css'>
</head>
<body>
<script src='/bundle.js'></script>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://unpkg.com/popper.js@1.12.6/dist/umd/popper.js" integrity="sha384-fA23ZRQ3G/J53mElWqVJEGJzU0sTs+SvzG8fXVWP+kJQ1lwFAOkcUOysnlKJC33U" crossorigin="anonymous"></script>
<script src="https://unpkg.com/bootstrap-material-design@4.1.1/dist/js/bootstrap-material-design.js" integrity="sha384-CauSuKpEqAFajSpkdjv3z9t8E7RlpJ1UP0lKM/+NdtSarroVKu069AlsRPKkFBz9" crossorigin="anonymous"></script>
<script>$(document).ready(function() { $('body').bootstrapMaterialDesign(); });</script>
</body>
</html>
+47
View File
@@ -0,0 +1,47 @@
import svelte from 'rollup-plugin-svelte';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
const production = !process.env.ROLLUP_WATCH;
export default {
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: 'public/bundle.js'
},
plugins: [
svelte({
// enable run-time checks when not in production
dev: !production,
// we'll extract any component CSS out into
// a separate file — better for performance
css: css => {
css.write('public/bundle.css');
}
}),
// If you have external dependencies installed from
// npm, you'll most likely need these plugins. In
// some cases you'll need additional configuration —
// consult the documentation for details:
// https://github.com/rollup/rollup-plugin-commonjs
resolve(),
commonjs(),
// Watch the `public` directory and refresh the
// browser on changes when not in production
!production && livereload('public'),
// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser()
],
watch: {
clearScreen: false
}
};
+19
View File
@@ -0,0 +1,19 @@
<h1>About</h1>
<p>
Wireguard UI is an <a href="https://embark-studios.com/">Embark Studios</a> Open Source project.
</p>
<p>
For contributions and feedback, please see the
<a href="https://github.com/EmbarkStudios/wireguard-ui">GitHub project</a>.
</p>
<h2>License</h2>
<p>
Wireguard UI is licensed under <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache License, Version 2.0</a>
</p>
<p>Copyright &copy; 2019, Embark Studios AB</p>
+25
View File
@@ -0,0 +1,25 @@
<script>
import { onMount } from 'svelte';
import { Router, Link, Route } from "svelte-routing";
import About from "./About.svelte";
import Clients from "./Clients.svelte";
import EditClient from "./EditClient.svelte";
import Nav from "./Nav.svelte";
import Cookie from "cookie-universal";
const cookies = Cookie();
export let user = cookies.get("wguser", { fromRes: true});
export let url = "";
</script>
<Router url="{url}">
<Nav user="{user}" />
<main role="main" class="container">
<div>
<Route path="client/:clientId" component="{EditClient}" />
<Route path="about" component="{About}" />
<Route path="/"><Clients user="{user}" /></Route>
</div>
</main>
</Router>
+39
View File
@@ -0,0 +1,39 @@
<script>
import { link } from "svelte-routing";
export let client;
export let user;
let clientId = client[0];
let dev = client[1];
var hash = 0;
for (var i = 0; i < dev.PrivateKey.length; i++) {
hash = dev.PrivateKey.charCodeAt(i) + ((hash << 5) - hash);
}
const color = "hsl(" + (hash % 360) + ",50%,95%)";
</script>
<style>
.card {
margin: 1em 0;
}
</style>
<div class="card">
<div class="card-body" style="background-color: {color}">
<a href="/client/{clientId}" use:link replace role="button" class="btn btn-secondary material-icons float-right">edit</a>
<i class="material-icons" aria-hidden="true">devices</i>
<h4 class="card-title">{dev.Name}</h4>
<dl class="row">
<dt class="col-sm-2">IP</dt>
<dd class="col-sm-10">{dev.IP}</dd>
<dt class="col-sm-2">Public Key</dt>
<dd class="col-sm-10">{dev.PublicKey}</dd>
</dl>
<a href="/api/v1/users/{user}/clients/{clientId}?format=config" role="button" class="btn btn-raised btn-primary">Download Config</a>
</div>
</div>
+38
View File
@@ -0,0 +1,38 @@
<script>
import { onMount } from 'svelte';
import Client from './Client.svelte';
export let user;
let clientsUrl = "/api/v1/users/" + user + "/clients";
let clients = [];
async function getClients() {
const res = await fetch(clientsUrl);
clients = Object.entries(await res.json());
console.log("Fetched clients", clients);
}
async function handleNewClick(event) {
const res = await fetch(clientsUrl, {
method: "POST",
}).then(getClients());
let newClient = await res.json();
console.log("New client added", newClient);
}
onMount(getClients);
</script>
<h2>My Clients <small class="text-muted">({user})</small></h2>
<ul class="list-unstyled">
{#each clients as dev}
<li><Client user={user} client={dev}/></li>
{/each}
</ul>
<button on:click={handleNewClick} type="button" class="btn btn-primary bmd-btn-fab float-right">
<i class="material-icons">add</i>
</button>
+114
View File
@@ -0,0 +1,114 @@
<script>
import Cookie from "cookie-universal";
import { onMount } from 'svelte';
import { link, navigate } from "svelte-routing";
export let clientId;
const user = Cookie().get("wguser", { fromRes: true});
const clientUrl = `/api/v1/users/` + user + `/clients/` + clientId;
let client = {};
async function getClient() {
const res = await fetch(clientUrl);
client = await res.json();
console.log("Fetched client", client);
}
async function handleSubmit(event) {
const res = await fetch(clientUrl, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(client),
});
client = await res.json();
navigate("/", { replace: true });
console.log("Saved changes", res);
}
async function handleDeleteClick(event) {
const res = await fetch(clientUrl, {
method: "DELETE",
});
await res;
navigate("/", { replace: true });
console.log("Delete!");
}
onMount(getClient);
</script>
<style>
.container {
margin-bottom: 2em;
}
button {
margin-top: 1em;
}
</style>
<div class="container">
<h2>Client Properties <small class="text-muted"> {client.Name}</h2>
<form on:submit|preventDefault={handleSubmit}>
<div class="form-group">
<label for="name" class="bmd-label-floating">Name</label>
<input type="text" class="form-control" id="name" bind:value={client.Name}>
<span class="bmd-help">Friendly name of client.</span>
</div>
<div class="form-group">
<label for="notes" class="bmd-label-floating">Notes</label>
<textarea class="form-control" id="notes" rows="3" bind:value={client.Notes}/>
<span class="bmd-help">Notes about client.</span>
</div>
<button type="submit" class="btn btn-raised btn-primary">Save Changes</button>
</form>
</div>
<div class="container">
<h3>Additional Properties</h3>
<dl class="row">
<dt class="col-sm-2">IP Address</dt>
<dd class="col-sm-10">{client.IP}</dd>
<dt class="col-sm-2">Private Key</dt>
<dd class="col-sm-10">{client.PrivateKey}</dd>
<dt class="col-sm-2">Public Key</dt>
<dd class="col-sm-10">{client.PublicKey}</dd>
</dl>
</div>
<div class="container">
<h3>Danger Zone</h3>
<button type="button" class="btn btn-sm btn-secondary btn-raised btn-danger" data-toggle="modal" data-target="#deleteClient">
Delete Client Config
</button>
<div class="modal fade" id="deleteClient" tabindex="-1" role="dialog" aria-labelledby="deleteClientLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteClientLabel">Delete {client.Name}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
Are you sure you want to delete this client configuration?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button on:click={handleDeleteClick} type="button" class="btn btn-primary btn-danger" data-dismiss="modal">Delete</button>
</div>
</div>
</div>
</div>
</div>
+30
View File
@@ -0,0 +1,30 @@
<script>
import { Router, Link, Route } from "svelte-routing";
import About from "./About.svelte";
import Clients from "./Clients.svelte";
import NavLink from "./NavLink.svelte";
export let user;
</script>
<header>
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<a class="navbar-brand" href="/">Wireguard VPN</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav mr-auto">
<li class="nav-item"><NavLink to="/">Clients</NavLink></li>
<li class="nav-item"><NavLink to="about">About</NavLink></li>
</ul>
</div>
<div class="text-light">
<small>Logged in as {user}</small>
{#if user != "anonymous"}
<a class="btn btn-outline-light" href="/oauth2/sign_out">Log out</a>
{/if}
</div>
</nav>
</header>
+17
View File
@@ -0,0 +1,17 @@
<script>
import { Link } from "svelte-routing";
export let to = "";
function getProps({ location, href, isPartiallyCurrent, isCurrent }) {
const isActive = href === "/" ? isCurrent : isPartiallyCurrent || isCurrent;
// The object returned here is spread on the anchor element's attributes
if (isActive) {
return { class: "nav-link active" };
}
return { class: "nav-link" };
}
</script>
<Link to="{to}" getProps="{getProps}">
<slot />
</Link>
+7
View File
@@ -0,0 +1,7 @@
import App from './App.svelte';
new App({
target: document.body,
// hydrate: true,
});
+14
View File
@@ -0,0 +1,14 @@
const { createServer } = require("http");
const app = require("./App.js");
createServer((req, res) => {
const { html } = app.render({ url: req.url });
res.write(`
<!DOCTYPE html>
<body>${html}</body>
<script src="/bundle.js"></script>
`);
res.end();
}).listen(5000);