logo, send email
8
.env
@ -1,6 +1,12 @@
|
|||||||
SERVER=0.0.0.0
|
SERVER=0.0.0.0
|
||||||
PORT=8080
|
PORT=8080
|
||||||
GIN_MODE=debug
|
GIN_MODE=release
|
||||||
|
|
||||||
WG_CONF_DIR=./wireguard
|
WG_CONF_DIR=./wireguard
|
||||||
WG_INTERFACE_NAME=wg0.conf
|
WG_INTERFACE_NAME=wg0.conf
|
||||||
|
|
||||||
|
SMTP_HOST=smtp.gmail.com
|
||||||
|
SMTP_PORT=587
|
||||||
|
SMTP_USERNAME=account@gmail.com
|
||||||
|
SMTP_PASSWORD="*************"
|
||||||
|
SMTP_FROM="Wg Gen Web <account@gmail.com>"
|
||||||
|
30
README.md
@ -1,8 +1,8 @@
|
|||||||
# Wg Gen Web
|
# Wg Gen Web
|
||||||
|
|
||||||
Simple Web based configuration generator for [WireGuard](https://wireguard.com).
|
<h1 align="center"><img height="420" src="./wg-gen-web_cover.png" alt="Simple Web based configuration generator for WireGuard"></h1>
|
||||||
|
|
||||||
---
|
Simple Web based configuration generator for [WireGuard](https://wireguard.com).
|
||||||
|
|
||||||
[![pipeline status](https://gitlab.127-0-0-1.fr/vx3r/wg-gen-web/badges/master/pipeline.svg)](https://gitlab.127-0-0-1.fr/vx3r/wg-gen-web/commits/master)
|
[![pipeline status](https://gitlab.127-0-0-1.fr/vx3r/wg-gen-web/badges/master/pipeline.svg)](https://gitlab.127-0-0-1.fr/vx3r/wg-gen-web/commits/master)
|
||||||
[![Go Report Card](https://goreportcard.com/badge/github.com/vx3r/wg-gen-web)](https://goreportcard.com/report/github.com/vx3r/wg-gen-web)
|
[![Go Report Card](https://goreportcard.com/badge/github.com/vx3r/wg-gen-web)](https://goreportcard.com/report/github.com/vx3r/wg-gen-web)
|
||||||
@ -30,10 +30,12 @@ The goal is to run Wg Gen Web in a container and WireGuard on host system.
|
|||||||
* Generation of `wg0.conf` after any modification
|
* Generation of `wg0.conf` after any modification
|
||||||
* Dockerized
|
* Dockerized
|
||||||
* Pretty cool look
|
* Pretty cool look
|
||||||
![Screenshot](Wg-Gen-Web.png)
|
![Screenshot](wg-gen-web_screenshot.png)
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
The easiest way to run Wg Gen Web is using the container image
|
The easiest way to run Wg Gen Web is using the container image
|
||||||
```
|
```
|
||||||
docker run --rm -it -v /tmp/wireguard:/data -p 8080:8080 -e "WG_CONF_DIR=/data" vx3r/wg-gen-web:latest
|
docker run --rm -it -v /tmp/wireguard:/data -p 8080:8080 -e "WG_CONF_DIR=/data" vx3r/wg-gen-web:latest
|
||||||
@ -51,6 +53,11 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- WG_CONF_DIR=/data
|
- WG_CONF_DIR=/data
|
||||||
- WG_INTERFACE_NAME=wg0.conf
|
- WG_INTERFACE_NAME=wg0.conf
|
||||||
|
- SMTP_HOST=smtp.gmail.com
|
||||||
|
- SMTP_PORT=587
|
||||||
|
- SMTP_USERNAME=account@gmail.com
|
||||||
|
- SMTP_PASSWORD="*************"
|
||||||
|
- SMTP_FROM="Wg Gen Web <account@gmail.com>"
|
||||||
volumes:
|
volumes:
|
||||||
- /etc/wireguard:/data
|
- /etc/wireguard:/data
|
||||||
```
|
```
|
||||||
@ -58,7 +65,18 @@ Please note that mapping ```/etc/wireguard``` to ```/data``` inside the docker,
|
|||||||
If needed, please make sure to backup your files from ```/etc/wireguard```.
|
If needed, please make sure to backup your files from ```/etc/wireguard```.
|
||||||
|
|
||||||
A workaround would be to change the ```WG_INTERFACE_NAME``` to something different, as it will create a new interface (```wg-auto.conf``` for example), note that if you do so, you will have to adapt your daemon accordingly.
|
A workaround would be to change the ```WG_INTERFACE_NAME``` to something different, as it will create a new interface (```wg-auto.conf``` for example), note that if you do so, you will have to adapt your daemon accordingly.
|
||||||
### Automatically apply changes using ```systemd```
|
|
||||||
|
### Directly without docker
|
||||||
|
|
||||||
|
Fill free to download latest artefacts from my GitLab server:
|
||||||
|
* [Backend](https://gitlab.127-0-0-1.fr/vx3r/wg-gen-web/-/jobs/artifacts/master/download?job=build-front)
|
||||||
|
* [Frontend](https://gitlab.127-0-0-1.fr/vx3r/wg-gen-web/-/jobs/artifacts/master/download?job=build-back)
|
||||||
|
|
||||||
|
Put everything in one directory, create `.env` file with all configurations and run the backend.
|
||||||
|
|
||||||
|
## Automatically apply changes to WireGuard
|
||||||
|
|
||||||
|
### Using ```systemd```
|
||||||
Using `systemd.path` monitor for directory changes see [systemd doc](https://www.freedesktop.org/software/systemd/man/systemd.path.html)
|
Using `systemd.path` monitor for directory changes see [systemd doc](https://www.freedesktop.org/software/systemd/man/systemd.path.html)
|
||||||
```
|
```
|
||||||
# /etc/systemd/system/wg-gen-web.path
|
# /etc/systemd/system/wg-gen-web.path
|
||||||
@ -87,7 +105,7 @@ WantedBy=multi-user.target
|
|||||||
```
|
```
|
||||||
Which will restart WireGuard service
|
Which will restart WireGuard service
|
||||||
|
|
||||||
### Automatically apply changes using ```inotifywait```
|
### Using ```inotifywait```
|
||||||
For any other init system, create a daemon running this script
|
For any other init system, create a daemon running this script
|
||||||
```
|
```
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
@ -111,7 +129,7 @@ Feel free to modify this file in order to use your existing keys
|
|||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
* Multi-user support behind [Authelia](https://github.com/authelia/authelia) (suggestions / thoughts are welcome)
|
* Multi-user support behind [Authelia](https://github.com/authelia/authelia) (suggestions / thoughts are welcome)
|
||||||
* Send configs by email to client
|
* ~~Send configs by email to client~~
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
BIN
Wg-Gen-Web.png
Before Width: | Height: | Size: 150 KiB |
16
api/api.go
@ -20,6 +20,7 @@ func ApplyRoutes(r *gin.Engine) {
|
|||||||
client.DELETE("/:id", deleteClient)
|
client.DELETE("/:id", deleteClient)
|
||||||
client.GET("", readClients)
|
client.GET("", readClients)
|
||||||
client.GET("/:id/config", configClient)
|
client.GET("/:id/config", configClient)
|
||||||
|
client.GET("/:id/email", emailClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
server := r.Group("/api/v1.0/server")
|
server := r.Group("/api/v1.0/server")
|
||||||
@ -149,6 +150,21 @@ func configClient(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func emailClient(c *gin.Context) {
|
||||||
|
id := c.Param("id")
|
||||||
|
|
||||||
|
err := repository.EmailClient(id)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to send email to client")
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{})
|
||||||
|
}
|
||||||
|
|
||||||
func readServer(c *gin.Context) {
|
func readServer(c *gin.Context) {
|
||||||
client, err := repository.ReadServer()
|
client, err := repository.ReadServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
2
go.mod
@ -12,4 +12,6 @@ require (
|
|||||||
github.com/sirupsen/logrus v1.4.2
|
github.com/sirupsen/logrus v1.4.2
|
||||||
github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086
|
github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200114203027-fcfc50b29cbb
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200114203027-fcfc50b29cbb
|
||||||
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||||
)
|
)
|
||||||
|
4
go.sum
@ -98,11 +98,15 @@ golang.zx2c4.com/wireguard v0.0.20191012/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49
|
|||||||
golang.zx2c4.com/wireguard v0.0.20200121 h1:vcswa5Q6f+sylDfjqyrVNNrjsFUUbPsgAQTBCAg/Qf8=
|
golang.zx2c4.com/wireguard v0.0.20200121 h1:vcswa5Q6f+sylDfjqyrVNNrjsFUUbPsgAQTBCAg/Qf8=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200114203027-fcfc50b29cbb h1:EZFZIHfDUPApqlA2wgF5LBAXKIKAxNckrehUTPYYAHc=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200114203027-fcfc50b29cbb h1:EZFZIHfDUPApqlA2wgF5LBAXKIKAxNckrehUTPYYAHc=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200114203027-fcfc50b29cbb/go.mod h1:vpFXH8L2bfaEJ/8I7DZ0CVOHsVydo6KeW9Iqh3qMa4g=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200114203027-fcfc50b29cbb/go.mod h1:vpFXH8L2bfaEJ/8I7DZ0CVOHsVydo6KeW9Iqh3qMa4g=
|
||||||
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||||
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||||
gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc=
|
gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc=
|
||||||
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||||
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
|
||||||
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
5
main.go
@ -40,7 +40,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.Getenv("GIN_MODE") == "release" {
|
if os.Getenv("GIN_MODE") == "debug" {
|
||||||
|
// set gin release mode
|
||||||
|
gin.SetMode(gin.DebugMode)
|
||||||
|
} else {
|
||||||
// set gin release mode
|
// set gin release mode
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
// disable console color
|
// disable console color
|
||||||
|
@ -5,13 +5,16 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
uuid "github.com/satori/go.uuid"
|
uuid "github.com/satori/go.uuid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/skip2/go-qrcode"
|
||||||
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model"
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model"
|
||||||
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/util"
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/util"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
"gopkg.in/gomail.v2"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -157,6 +160,82 @@ func DeleteClient(id string) error {
|
|||||||
return generateWgConfig()
|
return generateWgConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendEmail to client
|
||||||
|
func EmailClient(id string) error {
|
||||||
|
client, err := ReadClient(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
configData, err := ReadClientConfig(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// conf as .conf file
|
||||||
|
tmpfileCfg, err := ioutil.TempFile("", "wireguard-vpn-*.conf")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := tmpfileCfg.Write(configData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tmpfileCfg.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.Remove(tmpfileCfg.Name()) // clean up
|
||||||
|
|
||||||
|
// conf as png image
|
||||||
|
png, err := qrcode.Encode(string(configData), qrcode.Medium, 280)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tmpfilePng, err := ioutil.TempFile("", "qrcode-*.png")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := tmpfilePng.Write(png); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tmpfilePng.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.Remove(tmpfilePng.Name()) // clean up
|
||||||
|
|
||||||
|
// get email body
|
||||||
|
emailBody, err := util.DumpEmail(client, filepath.Base(tmpfilePng.Name()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// port to int
|
||||||
|
port, err := strconv.Atoi(os.Getenv("SMTP_PORT"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d := gomail.NewDialer(os.Getenv("SMTP_HOST"), port, os.Getenv("SMTP_USERNAME"), os.Getenv("SMTP_PASSWORD"))
|
||||||
|
s, err := d.Dial()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m := gomail.NewMessage()
|
||||||
|
|
||||||
|
m.SetHeader("From", os.Getenv("SMTP_FROM"))
|
||||||
|
m.SetAddressHeader("To", client.Email, client.Name)
|
||||||
|
m.SetHeader("Subject", "WireGuard VPN Configuration")
|
||||||
|
m.SetBody("text/html", emailBody.String())
|
||||||
|
m.Attach(tmpfileCfg.Name())
|
||||||
|
m.Embed(tmpfilePng.Name())
|
||||||
|
|
||||||
|
err = gomail.Send(s, m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ReadClients all clients
|
// ReadClients all clients
|
||||||
func ReadClients() ([]*model.Client, error) {
|
func ReadClients() ([]*model.Client, error) {
|
||||||
clients := make([]*model.Client, 0)
|
clients := make([]*model.Client, 0)
|
||||||
|
Before Width: | Height: | Size: 4.2 KiB |
BIN
ui/public/favicon.png
Normal file
After Width: | Height: | Size: 261 B |
@ -4,7 +4,7 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
<link rel="icon" href="<%= BASE_URL %>favicon.png">
|
||||||
<title>ui</title>
|
<title>ui</title>
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<v-app id="inspire">
|
<v-app id="inspire">
|
||||||
|
|
||||||
<v-app-bar app>
|
<v-app-bar app>
|
||||||
|
<img class="mr-3" :src="require('./assets/logo.png')" height="50"/>
|
||||||
<v-toolbar-title>Wg Gen Web</v-toolbar-title>
|
<v-toolbar-title>Wg Gen Web</v-toolbar-title>
|
||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 13 KiB |
@ -1 +0,0 @@
|
|||||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.5 100"><defs><style>.cls-1{fill:#1697f6;}.cls-2{fill:#7bc6ff;}.cls-3{fill:#1867c0;}.cls-4{fill:#aeddff;}</style></defs><title>Artboard 46</title><polyline class="cls-1" points="43.75 0 23.31 0 43.75 48.32"/><polygon class="cls-2" points="43.75 62.5 43.75 100 0 14.58 22.92 14.58 43.75 62.5"/><polyline class="cls-3" points="43.75 0 64.19 0 43.75 48.32"/><polygon class="cls-4" points="64.58 14.58 87.5 14.58 43.75 100 43.75 62.5 64.58 14.58"/></svg>
|
|
Before Width: | Height: | Size: 539 B |
@ -13,15 +13,15 @@
|
|||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="server.name"
|
v-model="server.name"
|
||||||
:rules="[
|
:rules="[
|
||||||
v => !!v || 'Name is required',
|
v => !!v || 'Friendly name is required',
|
||||||
]"
|
]"
|
||||||
label="Friendly server name"
|
label="Friendly name"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
type="number"
|
type="number"
|
||||||
v-model="server.persistentKeepalive"
|
v-model="server.persistentKeepalive"
|
||||||
label="Persistent keepalive for clients"
|
label="Persistent keepalive"
|
||||||
:rules="[
|
:rules="[
|
||||||
v => !!v || 'Persistent keepalive is required',
|
v => !!v || 'Persistent keepalive is required',
|
||||||
]"
|
]"
|
||||||
@ -29,9 +29,9 @@
|
|||||||
/>
|
/>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="server.endpoint"
|
v-model="server.endpoint"
|
||||||
label="Endpoint for clients to connect to"
|
label="Public endpoint for clients to connect to"
|
||||||
:rules="[
|
:rules="[
|
||||||
v => !!v || 'Endpoint is required',
|
v => !!v || 'Public endpoint for clients to connect to is required',
|
||||||
]"
|
]"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
@ -47,12 +47,12 @@
|
|||||||
<v-col cols="6">
|
<v-col cols="6">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="server.publicKey"
|
v-model="server.publicKey"
|
||||||
label="Server public key"
|
label="Public key"
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="server.presharedKey"
|
v-model="server.presharedKey"
|
||||||
label="Preshared Key key"
|
label="Preshared key"
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
@ -69,7 +69,7 @@
|
|||||||
:rules="[
|
:rules="[
|
||||||
v => !!v || 'Listen port is required',
|
v => !!v || 'Listen port is required',
|
||||||
]"
|
]"
|
||||||
label="Server listen port"
|
label="Listen port"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
@ -78,10 +78,12 @@
|
|||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer/>
|
<v-spacer/>
|
||||||
<v-btn
|
<v-btn
|
||||||
|
class="ma-2"
|
||||||
color="warning"
|
color="warning"
|
||||||
@click="updateServer"
|
@click="updateServer"
|
||||||
>
|
>
|
||||||
Update server configuration
|
Update server configuration
|
||||||
|
<v-icon right dark>mdi-update</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
@ -100,6 +102,7 @@
|
|||||||
@click.stop="dialogAddClient = true"
|
@click.stop="dialogAddClient = true"
|
||||||
>
|
>
|
||||||
Add new client
|
Add new client
|
||||||
|
<v-icon right dark>mdi-account-multiple-plus-outline</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-row>
|
<v-row>
|
||||||
@ -146,8 +149,8 @@
|
|||||||
text
|
text
|
||||||
:href="getUrlToConfig(client.id, false)"
|
:href="getUrlToConfig(client.id, false)"
|
||||||
>
|
>
|
||||||
Download configuration
|
Download
|
||||||
<v-icon right dark>mdi-cloud-download</v-icon>
|
<v-icon right dark>mdi-cloud-download-outline</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
text
|
text
|
||||||
@ -163,6 +166,13 @@
|
|||||||
Delete
|
Delete
|
||||||
<v-icon right dark>mdi-trash-can-outline</v-icon>
|
<v-icon right dark>mdi-trash-can-outline</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
text
|
||||||
|
@click="sendEmailClient(client.id)"
|
||||||
|
>
|
||||||
|
Send email
|
||||||
|
<v-icon right dark>mdi-email-send-outline</v-icon>
|
||||||
|
</v-btn>
|
||||||
<v-spacer/>
|
<v-spacer/>
|
||||||
<v-tooltip right>
|
<v-tooltip right>
|
||||||
<template v-slot:activator="{ on }">
|
<template v-slot:activator="{ on }">
|
||||||
@ -174,7 +184,7 @@
|
|||||||
v-on:change="updateClient(client)"
|
v-on:change="updateClient(client)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<span>Enable or disable this client</span>
|
<span> {{client.enable ? 'Disable' : 'Enable'}} this client</span>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
|
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
@ -407,6 +417,14 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
sendEmailClient(id) {
|
||||||
|
this.$get(`/client/${id}/email`).then((res) => {
|
||||||
|
this.notify('success', "Email successfully sent");
|
||||||
|
this.getData()
|
||||||
|
}).catch((e) => {
|
||||||
|
this.notify('error', e.response.status + ' ' + e.response.statusText);
|
||||||
|
});
|
||||||
|
},
|
||||||
getUrlToConfig(id, qrcode){
|
getUrlToConfig(id, qrcode){
|
||||||
let base = "/api/v1.0";
|
let base = "/api/v1.0";
|
||||||
if (process.env.NODE_ENV === "development"){
|
if (process.env.NODE_ENV === "development"){
|
||||||
|
204
util/tpl.go
@ -8,6 +8,192 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
emailTpl = `
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||||
|
<head>
|
||||||
|
<!--[if gte mso 9]>
|
||||||
|
<xml>
|
||||||
|
<o:OfficeDocumentSettings>
|
||||||
|
<o:AllowPNG/>
|
||||||
|
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||||
|
</o:OfficeDocumentSettings>
|
||||||
|
</xml>
|
||||||
|
<![endif]-->
|
||||||
|
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="format-detection" content="date=no" />
|
||||||
|
<meta name="format-detection" content="address=no" />
|
||||||
|
<meta name="format-detection" content="telephone=no" />
|
||||||
|
<meta name="x-apple-disable-message-reformatting" />
|
||||||
|
<!--[if !mso]><!-->
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Muli:400,400i,700,700i" rel="stylesheet" />
|
||||||
|
<!--<![endif]-->
|
||||||
|
<title>Email Template</title>
|
||||||
|
<!--[if gte mso 9]>
|
||||||
|
<style type="text/css" media="all">
|
||||||
|
sup { font-size: 100% !important; }
|
||||||
|
</style>
|
||||||
|
<![endif]-->
|
||||||
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||||
|
|
||||||
|
<style type="text/css" media="screen">
|
||||||
|
/* Linked Styles */
|
||||||
|
body { padding:0 !important; margin:0 !important; display:block !important; min-width:100% !important; width:100% !important; background:#001736; -webkit-text-size-adjust:none }
|
||||||
|
a { color:#66c7ff; text-decoration:none }
|
||||||
|
p { padding:0 !important; margin:0 !important }
|
||||||
|
img { -ms-interpolation-mode: bicubic; /* Allow smoother rendering of resized image in Internet Explorer */ }
|
||||||
|
.mcnPreviewText { display: none !important; }
|
||||||
|
|
||||||
|
|
||||||
|
/* Mobile styles */
|
||||||
|
@media only screen and (max-device-width: 480px), only screen and (max-width: 480px) {
|
||||||
|
.mobile-shell { width: 100% !important; min-width: 100% !important; }
|
||||||
|
.bg { background-size: 100% auto !important; -webkit-background-size: 100% auto !important; }
|
||||||
|
|
||||||
|
.text-header,
|
||||||
|
.m-center { text-align: center !important; }
|
||||||
|
|
||||||
|
.center { margin: 0 auto !important; }
|
||||||
|
.container { padding: 20px 10px !important }
|
||||||
|
|
||||||
|
.td { width: 100% !important; min-width: 100% !important; }
|
||||||
|
|
||||||
|
.m-br-15 { height: 15px !important; }
|
||||||
|
.p30-15 { padding: 30px 15px !important; }
|
||||||
|
|
||||||
|
.m-td,
|
||||||
|
.m-hide { display: none !important; width: 0 !important; height: 0 !important; font-size: 0 !important; line-height: 0 !important; min-height: 0 !important; }
|
||||||
|
|
||||||
|
.m-block { display: block !important; }
|
||||||
|
|
||||||
|
.fluid-img img { width: 100% !important; max-width: 100% !important; height: auto !important; }
|
||||||
|
|
||||||
|
.column,
|
||||||
|
.column-top,
|
||||||
|
.column-empty,
|
||||||
|
.column-empty2,
|
||||||
|
.column-dir-top { float: left !important; width: 100% !important; display: block !important; }
|
||||||
|
|
||||||
|
.column-empty { padding-bottom: 10px !important; }
|
||||||
|
.column-empty2 { padding-bottom: 30px !important; }
|
||||||
|
|
||||||
|
.content-spacing { width: 15px !important; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="body" style="padding:0 !important; margin:0 !important; display:block !important; min-width:100% !important; width:100% !important; background:#001736; -webkit-text-size-adjust:none;">
|
||||||
|
<table width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="#001736">
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="top">
|
||||||
|
<table width="650" border="0" cellspacing="0" cellpadding="0" class="mobile-shell">
|
||||||
|
<tr>
|
||||||
|
<td class="td container" style="width:650px; min-width:650px; font-size:0pt; line-height:0pt; margin:0; font-weight:normal; padding:55px 0px;">
|
||||||
|
|
||||||
|
<!-- Article / Image On The Left - Copy On The Right -->
|
||||||
|
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<td style="padding-bottom: 10px;">
|
||||||
|
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<td class="tbrr p30-15" style="padding: 60px 30px; border-radius:26px 26px 0px 0px;" bgcolor="#12325c">
|
||||||
|
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<th class="column-top" width="280" style="font-size:0pt; line-height:0pt; padding:0; margin:0; font-weight:normal; vertical-align:top;">
|
||||||
|
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<td class="fluid-img" style="font-size:0pt; line-height:0pt; text-align:left;"><img src="cid:{{.QrcodePngName}}" width="280" height="210" border="0" alt="" /></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</th>
|
||||||
|
<th class="column-empty2" width="30" style="font-size:0pt; line-height:0pt; padding:0; margin:0; font-weight:normal; vertical-align:top;"></th>
|
||||||
|
<th class="column-top" width="280" style="font-size:0pt; line-height:0pt; padding:0; margin:0; font-weight:normal; vertical-align:top;">
|
||||||
|
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<td class="h4 pb20" style="color:#ffffff; font-family:'Muli', Arial,sans-serif; font-size:20px; line-height:28px; text-align:left; padding-bottom:20px;">Hello</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text pb20" style="color:#ffffff; font-family:Arial,sans-serif; font-size:14px; line-height:26px; text-align:left; padding-bottom:20px;">You probably requested VPN configuration. Here is <strong>{{.Client.Name}}</strong> configuration created <strong>{{.Client.Created.Format "Jan 02, 2006 15:04:05 UTC"}}</strong>. Scan the Qrcode or open attached configuration file in VPN client.</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- END Article / Image On The Left - Copy On The Right -->
|
||||||
|
|
||||||
|
<!-- Two Columns / Articles -->
|
||||||
|
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<td style="padding-bottom: 10px;">
|
||||||
|
<table width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="#0e264b">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<td class="p30-15" style="padding: 50px 30px;">
|
||||||
|
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<td class="h3 pb20" style="color:#ffffff; font-family:'Muli', Arial,sans-serif; font-size:25px; line-height:32px; text-align:left; padding-bottom:20px;">About WireGuard</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text pb20" style="color:#ffffff; font-family:Arial,sans-serif; font-size:14px; line-height:26px; text-align:left; padding-bottom:20px;">WireGuard is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be faster, simpler, leaner, and more useful than IPsec, while avoiding the massive headache. It intends to be considerably more performant than OpenVPN.</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Button -->
|
||||||
|
<tr>
|
||||||
|
<td align="left">
|
||||||
|
<table border="0" cellspacing="0" cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<td class="blue-button text-button" style="background:#66c7ff; color:#c1cddc; font-family:'Muli', Arial,sans-serif; font-size:14px; line-height:18px; padding:12px 30px; text-align:center; border-radius:0px 22px 22px 22px; font-weight:bold;"><a href="https://www.wireguard.com/install" target="_blank" class="link-white" style="color:#ffffff; text-decoration:none;"><span class="link-white" style="color:#ffffff; text-decoration:none;">Download WireGuard VPN Client</span></a></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- END Button -->
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- END Two Columns / Articles -->
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<td class="p30-15 bbrr" style="padding: 50px 30px; border-radius:0px 0px 26px 26px;" bgcolor="#0e264b">
|
||||||
|
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<td class="text-footer1 pb10" style="color:#c1cddc; font-family:'Muli', Arial,sans-serif; font-size:16px; line-height:20px; text-align:center; padding-bottom:10px;">Wg Gen Web - Simple Web based configuration generator for WireGuard</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-footer2" style="color:#8297b3; font-family:'Muli', Arial,sans-serif; font-size:12px; line-height:26px; text-align:center;"><a href="https://github.com/vx3r/wg-gen-web" target="_blank" class="link" style="color:#66c7ff; text-decoration:none;"><span class="link" style="color:#66c7ff; text-decoration:none;">More info on Github</span></a></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- END Footer -->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
clientTpl = `
|
clientTpl = `
|
||||||
[Interface]
|
[Interface]
|
||||||
Address = {{.Client.Address}}
|
Address = {{.Client.Address}}
|
||||||
@ -78,6 +264,24 @@ func DumpServerWg(clients []*model.Client, server *model.Server) (bytes.Buffer,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DumpEmail dump server wg config with go template
|
||||||
|
func DumpEmail(client *model.Client, qrcodePngName string) (bytes.Buffer, error) {
|
||||||
|
var tplBuff bytes.Buffer
|
||||||
|
|
||||||
|
t, err := template.New("email").Parse(emailTpl)
|
||||||
|
if err != nil {
|
||||||
|
return tplBuff, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dump(t, struct {
|
||||||
|
Client *model.Client
|
||||||
|
QrcodePngName string
|
||||||
|
}{
|
||||||
|
Client: client,
|
||||||
|
QrcodePngName: qrcodePngName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func dump(tpl *template.Template, data interface{}) (bytes.Buffer, error) {
|
func dump(tpl *template.Template, data interface{}) (bytes.Buffer, error) {
|
||||||
var tplBuff bytes.Buffer
|
var tplBuff bytes.Buffer
|
||||||
|
|
||||||
|
BIN
wg-gen-web_cover.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
wg-gen-web_screenshot.png
Normal file
After Width: | Height: | Size: 134 KiB |