first commit
This commit is contained in:
		
							
								
								
									
										12
									
								
								.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.env
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					SERVER=0.0.0.0
 | 
				
			||||||
 | 
					PORT=8080
 | 
				
			||||||
 | 
					GIN_MODE=release
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WG_CONF_DIR=./wireguard
 | 
				
			||||||
 | 
					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>"
 | 
				
			||||||
							
								
								
									
										25
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					ARG COMMIT="N/A"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FROM golang:alpine AS build-back
 | 
				
			||||||
 | 
					WORKDIR /app
 | 
				
			||||||
 | 
					ARG COMMIT
 | 
				
			||||||
 | 
					COPY . .
 | 
				
			||||||
 | 
					RUN go build -ldflags="-X 'gitlab.127-0-0-1.fr/vx3r/wg-gen-web/util.Version=${COMMIT}'" -o wg-gen-web-linux
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FROM node:10-alpine AS build-front
 | 
				
			||||||
 | 
					WORKDIR /app
 | 
				
			||||||
 | 
					COPY ui/package*.json ./
 | 
				
			||||||
 | 
					RUN npm install
 | 
				
			||||||
 | 
					COPY ui/ ./
 | 
				
			||||||
 | 
					RUN npm run build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FROM alpine
 | 
				
			||||||
 | 
					WORKDIR /app
 | 
				
			||||||
 | 
					COPY --from=build-back /app/wg-gen-web-linux .
 | 
				
			||||||
 | 
					COPY --from=build-front /app/dist ./ui/dist
 | 
				
			||||||
 | 
					COPY .env .
 | 
				
			||||||
 | 
					RUN chmod +x ./wg-gen-web-linux
 | 
				
			||||||
 | 
					RUN apk add --no-cache ca-certificates
 | 
				
			||||||
 | 
					EXPOSE 8080
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CMD ["/app/wg-gen-web-linux"]
 | 
				
			||||||
							
								
								
									
										13
									
								
								LICENSE-WTFPL
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								LICENSE-WTFPL
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					           DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
 | 
				
			||||||
 | 
					                   Version 2, December 2004
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Copyright (C) 2013 Stephen Mathieson <me@stephenmathieson.com>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Everyone is permitted to copy and distribute verbatim or modified
 | 
				
			||||||
 | 
					copies of this license document, and changing it is allowed as long
 | 
				
			||||||
 | 
					as the name is changed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					           DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
 | 
				
			||||||
 | 
					  TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 0. You just DO WHAT THE FUCK YOU WANT TO.
 | 
				
			||||||
							
								
								
									
										241
									
								
								LISEZMOI.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								LISEZMOI.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,241 @@
 | 
				
			|||||||
 | 
					# Wg Gen Web
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<h1><img src="./web-ui.png" ></h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Générateur de configuration simple basé sur le Web pour [WireGuard](https://wireguard.com).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Why another one ?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Toutes les implémentations de WireGuard UI essaient de gérer le service en appliquant des configurations et en créant des règles de réseau.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Cette implémentation ne fait que générer de la configuration et c'est à vous de créer des règles réseau et d'appliquer la configuration à WireGuard.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Par exemple en surveillant le répertoire généré avec [inotifywait](https://github.com/inotify-tools/inotify-tools/wiki) ou avec systemd.path. 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Le but est d'exécuter Wg-Gen-Web dans un espace et WireGuard sur le système hôte.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Caractéristiques
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 * Libre-service et basé sur le web
 | 
				
			||||||
 | 
					 * QR-Code pour une configuration pratique du client mobile
 | 
				
			||||||
 | 
					 * Support optionnel multi-utilisateurs derrière un proxy d'authentification
 | 
				
			||||||
 | 
					 * Prise en charge de l'authentification simple
 | 
				
			||||||
 | 
					 * Zéro dépendance externe - juste un binaire unique utilisant le module noyau wireguard
 | 
				
			||||||
 | 
					 * Déploiement de binaires et de conteneurs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Vous devez avoir WireGuard installé sur la machine qui exécute `wg-ui`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Wg ui
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Installation de Go (Debian)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Installer la dernière version de Go à partir de (<https://golang.org/dl/>)  
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					cd ~
 | 
				
			||||||
 | 
					wget https://go.dev/dl/go1.21.5.linux-amd64.tar.gz
 | 
				
			||||||
 | 
					sudo tar -C /usr/local -xzf go1.21.5.linux-amd64.tar.gz
 | 
				
			||||||
 | 
					echo "export PATH=$PATH:/usr/local/go/bin"  >> ~/.bashrc
 | 
				
			||||||
 | 
					source ~/.bashrc
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Installer la version LTS de nodejs pour le frontend
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					# debian
 | 
				
			||||||
 | 
					curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo bash -
 | 
				
			||||||
 | 
					sudo apt update
 | 
				
			||||||
 | 
					sudo apt install nodejs
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Cloner wg-webui-fr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					git clone https://gitea.rnmkcy.eu/yann/wg-webui-fr
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Construire wg-webui-fr 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Exécuter les commandes suivantes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					cd ~/wg-webui-fr/
 | 
				
			||||||
 | 
					go mod tidy
 | 
				
			||||||
 | 
					go build -o wg-ui main.go
 | 
				
			||||||
 | 
					cd ui
 | 
				
			||||||
 | 
					export NODE_OPTIONS=--openssl-legacy-provider
 | 
				
			||||||
 | 
					export VUE_APP_API_BASE_URL=http://localhost:8080/api/v1.0
 | 
				
			||||||
 | 
					npm install
 | 
				
			||||||
 | 
					npm run build
 | 
				
			||||||
 | 
					sudo mkdir -p /opt/appwg/ui
 | 
				
			||||||
 | 
					sudo cp ../wg-ui /opt/appwg
 | 
				
			||||||
 | 
					sudo cp -r dist /opt/appwg/ui/
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Environnement
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Créer un fichier environnement `/opt/appwg/.env`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					SERVER=127.0.0.1
 | 
				
			||||||
 | 
					PORT=8090
 | 
				
			||||||
 | 
					GIN_MODE=debug
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WG_CONF_DIR=/opt/appwg/wireguard
 | 
				
			||||||
 | 
					WG_INTERFACE_NAME=wg0.conf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SMTP_HOST="mx.exemple.com"
 | 
				
			||||||
 | 
					SMTP_PORT=587
 | 
				
			||||||
 | 
					SMTP_USERNAME="Utilisateur@exemple.com"
 | 
				
			||||||
 | 
					SMTP_PASSWORD="Mot de passe"
 | 
				
			||||||
 | 
					SMTP_FROM="Utilisateur@exemple.com"
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ATTENTION!!! il faut au minimun un fichier `wg0.conf` dans `/opt/appwg/wireguard`   
 | 
				
			||||||
 | 
					Avec une installation de l'application wireguard, `WG_CONF_DIR=/etc/wireguard` 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Utilisation systemd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Créer le service `/etc/systemd/system/wgweb.service`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					[Unit]
 | 
				
			||||||
 | 
					Description=Wireguard web
 | 
				
			||||||
 | 
					After=network.target
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Service]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Type=simple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Restart=on-failure
 | 
				
			||||||
 | 
					RestartSec=10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WorkingDirectory=/opt/appwg
 | 
				
			||||||
 | 
					ExecStart=/opt/appwg/wg-ui
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Install]
 | 
				
			||||||
 | 
					WantedBy=multi-user.target
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Recharger 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					sudo systemctl daemon-reload
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Activer et lancer le service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					sudo systemctl enable wgweb.service --now
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Wireguard ui est accessible sur localhost:8090  
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Pour un accès via ssh  
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					ssh -L 9500:localhost:8090 utilisateur@adresse_serveur
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Accès navigateur
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Ensuite ouvrir un navigateur localement sur localhost:9500
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Activer le service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sudo systemctl enable wgweb.service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Mise è jour wg-webui-fr 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Exécuter les commandes suivantes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					echo "Arrêt wgweb.service"
 | 
				
			||||||
 | 
					sudo systemctl stop wgweb.service
 | 
				
			||||||
 | 
					echo "Sauvegarde"
 | 
				
			||||||
 | 
					sudo cp /opt/appwg/.env _.env
 | 
				
			||||||
 | 
					echo "Supprimer appwg"
 | 
				
			||||||
 | 
					sudo rm -r /opt/appwg
 | 
				
			||||||
 | 
					echo "Construire wg-ui main.go"
 | 
				
			||||||
 | 
					cd $HOME/wg-webui-fr/
 | 
				
			||||||
 | 
					go mod tidy
 | 
				
			||||||
 | 
					go build -o wg-ui main.go
 | 
				
			||||||
 | 
					cd ui
 | 
				
			||||||
 | 
					export NODE_OPTIONS=--openssl-legacy-provider
 | 
				
			||||||
 | 
					export VUE_APP_API_BASE_URL=http://localhost:8080/api/v1.0
 | 
				
			||||||
 | 
					npm install
 | 
				
			||||||
 | 
					npm run build
 | 
				
			||||||
 | 
					sudo mkdir -p /opt/appwg/ui
 | 
				
			||||||
 | 
					sudo cp ../wg-ui /opt/appwg
 | 
				
			||||||
 | 
					sudo cp -r dist /opt/appwg/ui/
 | 
				
			||||||
 | 
					echo "Restaurer environnement"
 | 
				
			||||||
 | 
					sudo cp $HOME/_.env /opt/appwg/.env
 | 
				
			||||||
 | 
					echo "Démarrer le service"
 | 
				
			||||||
 | 
					sudo systemctl start wgweb.service
 | 
				
			||||||
 | 
					echo "FIN reconstruction"
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Wireguard ui est accessible sur localhost:8090  
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Appliquer automatiquement les changements à WireGuard
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Utilisation de systemd (DEFAUT) 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Utilisation de `systemd.path` pour surveiller les changements de répertoire voir [systemd doc](https://www.freedesktop.org/software/systemd/man/systemd.path.html)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					# /etc/systemd/system/wg-gen-web.path
 | 
				
			||||||
 | 
					[Unit]
 | 
				
			||||||
 | 
					Description=Watch /etc/wireguard for changes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Path]
 | 
				
			||||||
 | 
					PathModified=/etc/wireguard
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Install]
 | 
				
			||||||
 | 
					WantedBy=multi-user.target
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Ce `.path` activera le fichier unit avec le même nom
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					# /etc/systemd/system/wg-gen-web.service
 | 
				
			||||||
 | 
					[Unit]
 | 
				
			||||||
 | 
					Description=Restart WireGuard
 | 
				
			||||||
 | 
					After=network.target
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Service]
 | 
				
			||||||
 | 
					Type=oneshot
 | 
				
			||||||
 | 
					ExecStart=/usr/bin/systemctl restart wg-quick@wg0.service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Install]
 | 
				
			||||||
 | 
					WantedBy=multi-user.target
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Ce qui va redémarrer le service WireGuard
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Activer et lancer 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					sudo systemctl enable wg-gen-web.path --now
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Utiliser inotifywait 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Pour tout autre système d'initialisation, créez un daemon exécutant ce script
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					#!/bin/sh
 | 
				
			||||||
 | 
					while inotifywait -e modify -e create /etc/wireguard; do
 | 
				
			||||||
 | 
					  wg-quick down wg0
 | 
				
			||||||
 | 
					  wg-quick up wg0
 | 
				
			||||||
 | 
					done
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Comment utiliser avec une configuration WireGuard existante
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Après le premier lancement, Wg Gen Web créera le fichier `server.json` dans le répertoire défini le paramètre `WG_INTERFACE_NAME` du fichier environnement.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Modifier le fichier existant **server.json** pour être identique au paramétrage de wireguard **wg0.conf**
 | 
				
			||||||
							
								
								
									
										245
									
								
								api/api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								api/api.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,245 @@
 | 
				
			|||||||
 | 
					package api
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
						log "github.com/sirupsen/logrus"
 | 
				
			||||||
 | 
						"github.com/skip2/go-qrcode"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/core"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/template"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/util"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApplyRoutes applies router to gin Router
 | 
				
			||||||
 | 
					func ApplyRoutes(r *gin.Engine) {
 | 
				
			||||||
 | 
						client := r.Group("/api/v1.0/client")
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							client.POST("", createClient)
 | 
				
			||||||
 | 
							client.GET("/:id", readClient)
 | 
				
			||||||
 | 
							client.PATCH("/:id", updateClient)
 | 
				
			||||||
 | 
							client.DELETE("/:id", deleteClient)
 | 
				
			||||||
 | 
							client.GET("", readClients)
 | 
				
			||||||
 | 
							client.GET("/:id/config", configClient)
 | 
				
			||||||
 | 
							client.GET("/:id/email", emailClient)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						server := r.Group("/api/v1.0/server")
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							server.GET("", readServer)
 | 
				
			||||||
 | 
							server.PATCH("", updateServer)
 | 
				
			||||||
 | 
							server.GET("/config", configServer)
 | 
				
			||||||
 | 
							server.GET("/version", version)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func createClient(c *gin.Context) {
 | 
				
			||||||
 | 
						var data model.Client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := c.ShouldBindJSON(&data); err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Error("failed to bind")
 | 
				
			||||||
 | 
							c.AbortWithStatus(http.StatusUnprocessableEntity)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := core.CreateClient(&data)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Error("failed to create client")
 | 
				
			||||||
 | 
							c.AbortWithStatus(http.StatusInternalServerError)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.JSON(http.StatusOK, client)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func readClient(c *gin.Context) {
 | 
				
			||||||
 | 
						id := c.Param("id")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := core.ReadClient(id)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Error("failed to read client")
 | 
				
			||||||
 | 
							c.AbortWithStatus(http.StatusInternalServerError)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.JSON(http.StatusOK, client)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func updateClient(c *gin.Context) {
 | 
				
			||||||
 | 
						var data model.Client
 | 
				
			||||||
 | 
						id := c.Param("id")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := c.ShouldBindJSON(&data); err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Error("failed to bind")
 | 
				
			||||||
 | 
							c.AbortWithStatus(http.StatusUnprocessableEntity)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := core.UpdateClient(id, &data)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Error("failed to update client")
 | 
				
			||||||
 | 
							c.AbortWithStatus(http.StatusInternalServerError)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.JSON(http.StatusOK, client)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func deleteClient(c *gin.Context) {
 | 
				
			||||||
 | 
						id := c.Param("id")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := core.DeleteClient(id)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Error("failed to remove client")
 | 
				
			||||||
 | 
							c.AbortWithStatus(http.StatusInternalServerError)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.JSON(http.StatusOK, gin.H{})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func readClients(c *gin.Context) {
 | 
				
			||||||
 | 
						clients, err := core.ReadClients()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Error("failed to list clients")
 | 
				
			||||||
 | 
							c.AbortWithStatus(http.StatusInternalServerError)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.JSON(http.StatusOK, clients)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func configClient(c *gin.Context) {
 | 
				
			||||||
 | 
						configData, err := core.ReadClientConfig(c.Param("id"))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Error("failed to read client config")
 | 
				
			||||||
 | 
							c.AbortWithStatus(http.StatusInternalServerError)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						formatQr := c.DefaultQuery("qrcode", "false")
 | 
				
			||||||
 | 
						if formatQr == "false" {
 | 
				
			||||||
 | 
							// return config as txt file
 | 
				
			||||||
 | 
							c.Header("Content-Disposition", "attachment; filename=wg0.conf")
 | 
				
			||||||
 | 
							c.Data(http.StatusOK, "application/config", configData)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// return config as png qrcode
 | 
				
			||||||
 | 
						png, err := qrcode.Encode(string(configData), qrcode.Medium, 250)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Error("failed to create qrcode")
 | 
				
			||||||
 | 
							c.AbortWithStatus(http.StatusInternalServerError)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						c.Data(http.StatusOK, "image/png", png)
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func emailClient(c *gin.Context) {
 | 
				
			||||||
 | 
						id := c.Param("id")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := core.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) {
 | 
				
			||||||
 | 
						client, err := core.ReadServer()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Error("failed to read client")
 | 
				
			||||||
 | 
							c.AbortWithStatus(http.StatusInternalServerError)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.JSON(http.StatusOK, client)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func updateServer(c *gin.Context) {
 | 
				
			||||||
 | 
						var data model.Server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := c.ShouldBindJSON(&data); err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Error("failed to bind")
 | 
				
			||||||
 | 
							c.AbortWithStatus(http.StatusUnprocessableEntity)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := core.UpdateServer(&data)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Error("failed to update client")
 | 
				
			||||||
 | 
							c.AbortWithStatus(http.StatusInternalServerError)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.JSON(http.StatusOK, client)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func configServer(c *gin.Context) {
 | 
				
			||||||
 | 
						clients, err := core.ReadClients()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Error("failed to read clients")
 | 
				
			||||||
 | 
							c.AbortWithStatus(http.StatusUnprocessableEntity)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						server, err := core.ReadServer()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Error("failed to read server")
 | 
				
			||||||
 | 
							c.AbortWithStatus(http.StatusUnprocessableEntity)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						configData, err := template.DumpServerWg(clients, server)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Error("failed to dump wg config")
 | 
				
			||||||
 | 
							c.AbortWithStatus(http.StatusUnprocessableEntity)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// return config as txt file
 | 
				
			||||||
 | 
						c.Header("Content-Disposition", "attachment; filename=wg0.conf")
 | 
				
			||||||
 | 
						c.Data(http.StatusOK, "application/config", configData)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func version(c *gin.Context) {
 | 
				
			||||||
 | 
						c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
							"version": util.Version,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										13
									
								
								appwg/.env
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										13
									
								
								appwg/.env
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					SERVER=127.0.0.1
 | 
				
			||||||
 | 
					PORT=8100
 | 
				
			||||||
 | 
					GIN_MODE=release
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WG_CONF_DIR=/etc/wireguard
 | 
				
			||||||
 | 
					WG_INTERFACE_NAME=wg0.conf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SMTP_HOST=xoyaz.xyz
 | 
				
			||||||
 | 
					SMTP_PORT=587
 | 
				
			||||||
 | 
					SMTP_USERNAME="ian@xoyaz.xyz"
 | 
				
			||||||
 | 
					SMTP_PASSWORD="HaveuseOrmeauxEncageSaisine"
 | 
				
			||||||
 | 
					SMTP_FROM="ian@xoyaz.xyz"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										1
									
								
								appwg/ui/dist/css/Clients.56a11097.css
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/css/Clients.56a11097.css
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								appwg/ui/dist/css/Clients~Server.f87dfffe.css
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/css/Clients~Server.f87dfffe.css
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										5
									
								
								appwg/ui/dist/css/chunk-vendors.0ec741de.css
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										5
									
								
								appwg/ui/dist/css/chunk-vendors.0ec741de.css
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								appwg/ui/dist/favicon.png
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								appwg/ui/dist/favicon.png
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 34 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								appwg/ui/dist/img/logo.8f75d612.png
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								appwg/ui/dist/img/logo.8f75d612.png
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 14 KiB  | 
							
								
								
									
										1
									
								
								appwg/ui/dist/index.html
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/index.html
									
									
									
									
										vendored
									
									
										Executable file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.png"><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://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"><link href="/css/Clients.56a11097.css" rel="prefetch"><link href="/css/Clients~Server.f87dfffe.css" rel="prefetch"><link href="/js/Clients.adbe44c3.js" rel="prefetch"><link href="/js/Clients~Server.12421a31.js" rel="prefetch"><link href="/js/Index.711255c3.js" rel="prefetch"><link href="/js/Server.e0cb69bc.js" rel="prefetch"><link href="/css/chunk-vendors.0ec741de.css" rel="preload" as="style"><link href="/js/app.12cb1049.js" rel="preload" as="script"><link href="/js/chunk-vendors.40c1d4b4.js" rel="preload" as="script"><link href="/css/chunk-vendors.0ec741de.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but ui doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/js/chunk-vendors.40c1d4b4.js"></script><script src="/js/app.12cb1049.js"></script></body></html>
 | 
				
			||||||
							
								
								
									
										2
									
								
								appwg/ui/dist/js/Clients.1916468e.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										2
									
								
								appwg/ui/dist/js/Clients.1916468e.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								appwg/ui/dist/js/Clients.1916468e.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/js/Clients.1916468e.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								appwg/ui/dist/js/Clients.64ae6101.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										2
									
								
								appwg/ui/dist/js/Clients.64ae6101.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								appwg/ui/dist/js/Clients.64ae6101.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/js/Clients.64ae6101.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								appwg/ui/dist/js/Clients.adbe44c3.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								appwg/ui/dist/js/Clients.adbe44c3.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								appwg/ui/dist/js/Clients.adbe44c3.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/js/Clients.adbe44c3.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								appwg/ui/dist/js/Clients.c3e03423.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										2
									
								
								appwg/ui/dist/js/Clients.c3e03423.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								appwg/ui/dist/js/Clients.c3e03423.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/js/Clients.c3e03423.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								appwg/ui/dist/js/Clients.d1e169a5.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										2
									
								
								appwg/ui/dist/js/Clients.d1e169a5.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								appwg/ui/dist/js/Clients.d1e169a5.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/js/Clients.d1e169a5.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								appwg/ui/dist/js/Clients~Server.12421a31.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								appwg/ui/dist/js/Clients~Server.12421a31.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								appwg/ui/dist/js/Clients~Server.12421a31.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/js/Clients~Server.12421a31.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								appwg/ui/dist/js/Clients~Server.f3845244.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										2
									
								
								appwg/ui/dist/js/Clients~Server.f3845244.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								appwg/ui/dist/js/Clients~Server.f3845244.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/js/Clients~Server.f3845244.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								appwg/ui/dist/js/Index.39a233d4.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										2
									
								
								appwg/ui/dist/js/Index.39a233d4.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["Index"],{d504:function(e,n,t){"use strict";t.r(n);var r=function(){var e=this,n=e._self._c;return n("div")},c=[],l={created(){this.$router.replace({name:"clients"})}},s=l,u=t("2877"),a=Object(u["a"])(s,r,c,!1,null,null,null);n["default"]=a.exports}}]);
 | 
				
			||||||
 | 
					//# sourceMappingURL=Index.39a233d4.js.map
 | 
				
			||||||
							
								
								
									
										1
									
								
								appwg/ui/dist/js/Index.39a233d4.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/js/Index.39a233d4.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					{"version":3,"sources":["webpack:///./src/views/Index.vue?e07c","webpack:///src/views/Index.vue","webpack:///./src/views/Index.vue?062b","webpack:///./src/views/Index.vue"],"names":["render","_vm","this","_c","_self","staticRenderFns","$router","replace","name","component"],"mappings":"8GAAA,IAAIA,EAAS,WAAkB,IAAIC,EAAIC,KAAKC,EAAGF,EAAIG,MAAMD,GAAG,OAAOA,EAAG,QAElEE,EAAkB,GCEL,GACb,UACEH,KAAKI,QAAQC,QAAQ,CAAEC,KAAM,cCN0J,I,YCOzLC,EAAY,eACd,EACAT,EACAK,GACA,EACA,KACA,KACA,MAIa,aAAAI,E","file":"js/Index.39a233d4.js","sourcesContent":["var render = function render(){var _vm=this,_c=_vm._self._c;return _c(\"div\")\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","<template>\n</template>\n\n<script>\n  export default {\n    created () {\n      this.$router.replace({ name: 'clients' })\n    }\n  }\n</script>\n","import mod from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Index.vue?vue&type=script&lang=js\"; export default mod; export * from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Index.vue?vue&type=script&lang=js\"","import { render, staticRenderFns } from \"./Index.vue?vue&type=template&id=2e4a022b\"\nimport script from \"./Index.vue?vue&type=script&lang=js\"\nexport * from \"./Index.vue?vue&type=script&lang=js\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n  script,\n  render,\n  staticRenderFns,\n  false,\n  null,\n  null,\n  null\n  \n)\n\nexport default component.exports"],"sourceRoot":""}
 | 
				
			||||||
							
								
								
									
										2
									
								
								appwg/ui/dist/js/Index.711255c3.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								appwg/ui/dist/js/Index.711255c3.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["Index"],{d504:function(e,n,t){"use strict";t.r(n);var r=function(){var e=this,n=e._self._c;return n("div")},c=[],l={created(){this.$router.replace({name:"clients"})}},s=l,u=t("2877"),a=Object(u["a"])(s,r,c,!1,null,null,null);n["default"]=a.exports}}]);
 | 
				
			||||||
 | 
					//# sourceMappingURL=Index.711255c3.js.map
 | 
				
			||||||
							
								
								
									
										1
									
								
								appwg/ui/dist/js/Index.711255c3.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/js/Index.711255c3.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					{"version":3,"sources":["webpack:///./src/views/Index.vue?2335","webpack:///src/views/Index.vue","webpack:///./src/views/Index.vue?062b","webpack:///./src/views/Index.vue"],"names":["render","_vm","this","_c","_self","staticRenderFns","$router","replace","name","component"],"mappings":"8GAAA,IAAIA,EAAS,WAAkB,IAAIC,EAAIC,KAAKC,EAAGF,EAAIG,MAAMD,GAAG,OAAOA,EAAG,QAElEE,EAAkB,GCEL,GACb,UACEH,KAAKI,QAAQC,QAAQ,CAAEC,KAAM,cCN0J,I,YCOzLC,EAAY,eACd,EACAT,EACAK,GACA,EACA,KACA,KACA,MAIa,aAAAI,E","file":"js/Index.711255c3.js","sourcesContent":["var render = function render(){var _vm=this,_c=_vm._self._c;return _c(\"div\")\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","<template>\n</template>\n\n<script>\n  export default {\n    created () {\n      this.$router.replace({ name: 'clients' })\n    }\n  }\n</script>\n","import mod from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Index.vue?vue&type=script&lang=js\"; export default mod; export * from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Index.vue?vue&type=script&lang=js\"","import { render, staticRenderFns } from \"./Index.vue?vue&type=template&id=2e4a022b\"\nimport script from \"./Index.vue?vue&type=script&lang=js\"\nexport * from \"./Index.vue?vue&type=script&lang=js\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n  script,\n  render,\n  staticRenderFns,\n  false,\n  null,\n  null,\n  null\n  \n)\n\nexport default component.exports"],"sourceRoot":""}
 | 
				
			||||||
							
								
								
									
										2
									
								
								appwg/ui/dist/js/Index.72bdca15.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										2
									
								
								appwg/ui/dist/js/Index.72bdca15.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["Index"],{d504:function(e,n,t){"use strict";t.r(n);var r=function(){var e=this,n=e._self._c;return n("div")},c=[],l={created(){this.$router.replace({name:"clients"})}},s=l,u=t("2877"),a=Object(u["a"])(s,r,c,!1,null,null,null);n["default"]=a.exports}}]);
 | 
				
			||||||
 | 
					//# sourceMappingURL=Index.72bdca15.js.map
 | 
				
			||||||
							
								
								
									
										1
									
								
								appwg/ui/dist/js/Index.72bdca15.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/js/Index.72bdca15.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					{"version":3,"sources":["webpack:///./src/views/Index.vue?363c","webpack:///src/views/Index.vue","webpack:///./src/views/Index.vue?062b","webpack:///./src/views/Index.vue"],"names":["render","_vm","this","_c","_self","staticRenderFns","$router","replace","name","component"],"mappings":"8GAAA,IAAIA,EAAS,WAAkB,IAAIC,EAAIC,KAAKC,EAAGF,EAAIG,MAAMD,GAAG,OAAOA,EAAG,QAElEE,EAAkB,GCEL,GACb,UACEH,KAAKI,QAAQC,QAAQ,CAAEC,KAAM,cCN0J,I,YCOzLC,EAAY,eACd,EACAT,EACAK,GACA,EACA,KACA,KACA,MAIa,aAAAI,E","file":"js/Index.72bdca15.js","sourcesContent":["var render = function render(){var _vm=this,_c=_vm._self._c;return _c(\"div\")\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","<template>\n</template>\n\n<script>\n  export default {\n    created () {\n      this.$router.replace({ name: 'clients' })\n    }\n  }\n</script>\n","import mod from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Index.vue?vue&type=script&lang=js\"; export default mod; export * from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--1-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Index.vue?vue&type=script&lang=js\"","import { render, staticRenderFns } from \"./Index.vue?vue&type=template&id=2e4a022b\"\nimport script from \"./Index.vue?vue&type=script&lang=js\"\nexport * from \"./Index.vue?vue&type=script&lang=js\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n  script,\n  render,\n  staticRenderFns,\n  false,\n  null,\n  null,\n  null\n  \n)\n\nexport default component.exports"],"sourceRoot":""}
 | 
				
			||||||
							
								
								
									
										2
									
								
								appwg/ui/dist/js/Server.2c31711e.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										2
									
								
								appwg/ui/dist/js/Server.2c31711e.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								appwg/ui/dist/js/Server.2c31711e.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/js/Server.2c31711e.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								appwg/ui/dist/js/Server.33db6d96.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										2
									
								
								appwg/ui/dist/js/Server.33db6d96.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								appwg/ui/dist/js/Server.33db6d96.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/js/Server.33db6d96.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								appwg/ui/dist/js/Server.e0cb69bc.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								appwg/ui/dist/js/Server.e0cb69bc.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								appwg/ui/dist/js/Server.e0cb69bc.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/js/Server.e0cb69bc.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								appwg/ui/dist/js/app.12cb1049.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								appwg/ui/dist/js/app.12cb1049.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								appwg/ui/dist/js/app.12cb1049.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/js/app.12cb1049.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								appwg/ui/dist/js/app.2e28011b.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										2
									
								
								appwg/ui/dist/js/app.2e28011b.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								appwg/ui/dist/js/app.2e28011b.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/js/app.2e28011b.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								appwg/ui/dist/js/app.50b90708.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										2
									
								
								appwg/ui/dist/js/app.50b90708.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								appwg/ui/dist/js/app.50b90708.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/js/app.50b90708.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								appwg/ui/dist/js/app.a3a42c5a.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										2
									
								
								appwg/ui/dist/js/app.a3a42c5a.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								appwg/ui/dist/js/app.a3a42c5a.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/js/app.a3a42c5a.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								appwg/ui/dist/js/app.d8243852.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										2
									
								
								appwg/ui/dist/js/app.d8243852.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								appwg/ui/dist/js/app.d8243852.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/js/app.d8243852.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										291
									
								
								appwg/ui/dist/js/chunk-vendors.40c1d4b4.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								appwg/ui/dist/js/chunk-vendors.40c1d4b4.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								appwg/ui/dist/js/chunk-vendors.40c1d4b4.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/js/chunk-vendors.40c1d4b4.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										287
									
								
								appwg/ui/dist/js/chunk-vendors.b9edb8dc.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										287
									
								
								appwg/ui/dist/js/chunk-vendors.b9edb8dc.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								appwg/ui/dist/js/chunk-vendors.b9edb8dc.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										1
									
								
								appwg/ui/dist/js/chunk-vendors.b9edb8dc.js.map
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								appwg/wg-ui
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								appwg/wg-ui
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										280
									
								
								core/client.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										280
									
								
								core/client.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,280 @@
 | 
				
			|||||||
 | 
					package core
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						uuid "github.com/satori/go.uuid"
 | 
				
			||||||
 | 
						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/storage"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/template"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/util"
 | 
				
			||||||
 | 
						"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 | 
				
			||||||
 | 
						"gopkg.in/gomail.v2"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"sort"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CreateClient client with all necessary data
 | 
				
			||||||
 | 
					func CreateClient(client *model.Client) (*model.Client, error) {
 | 
				
			||||||
 | 
						// check if client is valid
 | 
				
			||||||
 | 
						errs := client.IsValid()
 | 
				
			||||||
 | 
						if len(errs) != 0 {
 | 
				
			||||||
 | 
							for _, err := range errs {
 | 
				
			||||||
 | 
								log.WithFields(log.Fields{
 | 
				
			||||||
 | 
									"err": err,
 | 
				
			||||||
 | 
								}).Error("client validation error")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return nil, errors.New("failed to validate client")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						u := uuid.NewV4()
 | 
				
			||||||
 | 
						client.Id = u.String()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						key, err := wgtypes.GeneratePrivateKey()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						client.PrivateKey = key.String()
 | 
				
			||||||
 | 
						client.PublicKey = key.PublicKey().String()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						presharedKey, err := wgtypes.GenerateKey()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						client.PresharedKey = presharedKey.String()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						reserverIps, err := GetAllReservedIps()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ips := make([]string, 0)
 | 
				
			||||||
 | 
						for _, network := range client.Address {
 | 
				
			||||||
 | 
							ip, err := util.GetAvailableIp(network, reserverIps)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if util.IsIPv6(ip) {
 | 
				
			||||||
 | 
								ip = ip + "/128"
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								ip = ip + "/32"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ips = append(ips, ip)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						client.Address = ips
 | 
				
			||||||
 | 
						client.Created = time.Now().UTC()
 | 
				
			||||||
 | 
						client.Updated = client.Created
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = storage.Serialize(client.Id, client)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						v, err := storage.Deserialize(client.Id)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						client = v.(*model.Client)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// data modified, dump new config
 | 
				
			||||||
 | 
						return client, UpdateServerConfigWg()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReadClient client by id
 | 
				
			||||||
 | 
					func ReadClient(id string) (*model.Client, error) {
 | 
				
			||||||
 | 
						v, err := storage.Deserialize(id)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						client := v.(*model.Client)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return client, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdateClient preserve keys
 | 
				
			||||||
 | 
					func UpdateClient(Id string, client *model.Client) (*model.Client, error) {
 | 
				
			||||||
 | 
						v, err := storage.Deserialize(Id)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						current := v.(*model.Client)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if current.Id != client.Id {
 | 
				
			||||||
 | 
							return nil, errors.New("records Id mismatch")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// check if client is valid
 | 
				
			||||||
 | 
						errs := client.IsValid()
 | 
				
			||||||
 | 
						if len(errs) != 0 {
 | 
				
			||||||
 | 
							for _, err := range errs {
 | 
				
			||||||
 | 
								log.WithFields(log.Fields{
 | 
				
			||||||
 | 
									"err": err,
 | 
				
			||||||
 | 
								}).Error("client validation error")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return nil, errors.New("failed to validate client")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// keep keys
 | 
				
			||||||
 | 
						client.PrivateKey = current.PrivateKey
 | 
				
			||||||
 | 
						client.PublicKey = current.PublicKey
 | 
				
			||||||
 | 
						client.Updated = time.Now().UTC()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = storage.Serialize(client.Id, client)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						v, err = storage.Deserialize(Id)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						client = v.(*model.Client)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// data modified, dump new config
 | 
				
			||||||
 | 
						return client, UpdateServerConfigWg()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeleteClient from disk
 | 
				
			||||||
 | 
					func DeleteClient(id string) error {
 | 
				
			||||||
 | 
						path := filepath.Join(os.Getenv("WG_CONF_DIR"), id)
 | 
				
			||||||
 | 
						err := os.Remove(path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// data modified, dump new config
 | 
				
			||||||
 | 
						return UpdateServerConfigWg()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReadClients all clients
 | 
				
			||||||
 | 
					func ReadClients() ([]*model.Client, error) {
 | 
				
			||||||
 | 
						clients := make([]*model.Client, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						files, err := ioutil.ReadDir(filepath.Join(os.Getenv("WG_CONF_DIR")))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, f := range files {
 | 
				
			||||||
 | 
							// clients file name is an uuid
 | 
				
			||||||
 | 
							_, err := uuid.FromString(f.Name())
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								c, err := storage.Deserialize(f.Name())
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.WithFields(log.Fields{
 | 
				
			||||||
 | 
										"err":  err,
 | 
				
			||||||
 | 
										"path": f.Name(),
 | 
				
			||||||
 | 
									}).Error("failed to deserialize client")
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									clients = append(clients, c.(*model.Client))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sort.Slice(clients, func(i, j int) bool {
 | 
				
			||||||
 | 
							return clients[i].Created.After(clients[j].Created)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return clients, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReadClientConfig in wg format
 | 
				
			||||||
 | 
					func ReadClientConfig(id string) ([]byte, error) {
 | 
				
			||||||
 | 
						client, err := ReadClient(id)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						server, err := ReadServer()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						configDataWg, err := template.DumpClientWg(client, server)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return configDataWg, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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 := template.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", string(emailBody))
 | 
				
			||||||
 | 
						m.Attach(tmpfileCfg.Name())
 | 
				
			||||||
 | 
						m.Embed(tmpfilePng.Name())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = gomail.Send(s, m)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										309
									
								
								core/migrate.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										309
									
								
								core/migrate.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,309 @@
 | 
				
			|||||||
 | 
					package core
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						uuid "github.com/satori/go.uuid"
 | 
				
			||||||
 | 
						log "github.com/sirupsen/logrus"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/storage"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/util"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Migrate all changes, current struct fields change
 | 
				
			||||||
 | 
					func MigrateInitialStructChange() error {
 | 
				
			||||||
 | 
						clients, err := readClients()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s, err := deserialize("server.json")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, client := range clients {
 | 
				
			||||||
 | 
							switch v := client["allowedIPs"].(type) {
 | 
				
			||||||
 | 
							case []interface{}:
 | 
				
			||||||
 | 
								log.Infof("client %s has been already migrated", client["id"])
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								log.Infof("unexpected type %T, mus be migrated", v)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							c := &model.Client{}
 | 
				
			||||||
 | 
							c.Id = client["id"].(string)
 | 
				
			||||||
 | 
							c.Name = client["name"].(string)
 | 
				
			||||||
 | 
							c.Email = client["email"].(string)
 | 
				
			||||||
 | 
							c.Enable = client["enable"].(bool)
 | 
				
			||||||
 | 
							c.AllowedIPs = make([]string, 0)
 | 
				
			||||||
 | 
							for _, address := range strings.Split(client["allowedIPs"].(string), ",") {
 | 
				
			||||||
 | 
								if util.IsValidCidr(strings.TrimSpace(address)) {
 | 
				
			||||||
 | 
									c.AllowedIPs = append(c.AllowedIPs, strings.TrimSpace(address))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							c.Address = make([]string, 0)
 | 
				
			||||||
 | 
							for _, address := range strings.Split(client["address"].(string), ",") {
 | 
				
			||||||
 | 
								if util.IsValidCidr(strings.TrimSpace(address)) {
 | 
				
			||||||
 | 
									c.Address = append(c.Address, strings.TrimSpace(address))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							c.PrivateKey = client["privateKey"].(string)
 | 
				
			||||||
 | 
							c.PublicKey = client["publicKey"].(string)
 | 
				
			||||||
 | 
							created, err := time.Parse(time.RFC3339, client["created"].(string))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.WithFields(log.Fields{
 | 
				
			||||||
 | 
									"err": err,
 | 
				
			||||||
 | 
								}).Errorf("failed to parse time")
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							c.Created = created
 | 
				
			||||||
 | 
							updated, err := time.Parse(time.RFC3339, client["updated"].(string))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.WithFields(log.Fields{
 | 
				
			||||||
 | 
									"err": err,
 | 
				
			||||||
 | 
								}).Errorf("failed to parse time")
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							c.Updated = updated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err = storage.Serialize(c.Id, c)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.WithFields(log.Fields{
 | 
				
			||||||
 | 
									"err": err,
 | 
				
			||||||
 | 
								}).Errorf("failed to Serialize client")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch v := s["address"].(type) {
 | 
				
			||||||
 | 
						case []interface{}:
 | 
				
			||||||
 | 
							log.Info("server has been already migrated")
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							log.Infof("unexpected type %T, mus be migrated", v)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						server := &model.Server{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						server.Address = make([]string, 0)
 | 
				
			||||||
 | 
						for _, address := range strings.Split(s["address"].(string), ",") {
 | 
				
			||||||
 | 
							if util.IsValidCidr(strings.TrimSpace(address)) {
 | 
				
			||||||
 | 
								server.Address = append(server.Address, strings.TrimSpace(address))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						server.ListenPort = int(s["listenPort"].(float64))
 | 
				
			||||||
 | 
						server.PrivateKey = s["privateKey"].(string)
 | 
				
			||||||
 | 
						server.PublicKey = s["publicKey"].(string)
 | 
				
			||||||
 | 
						//server.PresharedKey = s["presharedKey"].(string)
 | 
				
			||||||
 | 
						server.Endpoint = s["endpoint"].(string)
 | 
				
			||||||
 | 
						server.PersistentKeepalive = int(s["persistentKeepalive"].(float64))
 | 
				
			||||||
 | 
						server.Dns = make([]string, 0)
 | 
				
			||||||
 | 
						for _, address := range strings.Split(s["dns"].(string), ",") {
 | 
				
			||||||
 | 
							if util.IsValidIp(strings.TrimSpace(address)) {
 | 
				
			||||||
 | 
								server.Dns = append(server.Dns, strings.TrimSpace(address))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if val, ok := s["preUp"]; ok {
 | 
				
			||||||
 | 
							server.PreUp = val.(string)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if val, ok := s["postUp"]; ok {
 | 
				
			||||||
 | 
							server.PostUp = val.(string)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if val, ok := s["preDown"]; ok {
 | 
				
			||||||
 | 
							server.PreDown = val.(string)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if val, ok := s["postDown"]; ok {
 | 
				
			||||||
 | 
							server.PostDown = val.(string)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						created, err := time.Parse(time.RFC3339, s["created"].(string))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Errorf("failed to parse time")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						server.Created = created
 | 
				
			||||||
 | 
						updated, err := time.Parse(time.RFC3339, s["updated"].(string))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Errorf("failed to parse time")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						server.Updated = updated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = storage.Serialize("server.json", server)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Errorf("failed to Serialize server")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Migrate presharedKey issue #23
 | 
				
			||||||
 | 
					func MigratePresharedKey() error {
 | 
				
			||||||
 | 
						clients, err := readClients()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s, err := deserialize("server.json")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, client := range clients {
 | 
				
			||||||
 | 
							if _, ok := client["presharedKey"]; ok {
 | 
				
			||||||
 | 
								log.Infof("client %s has been already migrated for preshared key", client["id"])
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							c := &model.Client{}
 | 
				
			||||||
 | 
							c.Id = client["id"].(string)
 | 
				
			||||||
 | 
							c.Name = client["name"].(string)
 | 
				
			||||||
 | 
							c.Email = client["email"].(string)
 | 
				
			||||||
 | 
							c.Enable = client["enable"].(bool)
 | 
				
			||||||
 | 
							if val, ok := client["ignorePersistentKeepalive"]; ok {
 | 
				
			||||||
 | 
								c.IgnorePersistentKeepalive = val.(bool)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								c.IgnorePersistentKeepalive = false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							c.PresharedKey = s["presharedKey"].(string)
 | 
				
			||||||
 | 
							c.AllowedIPs = make([]string, 0)
 | 
				
			||||||
 | 
							for _, address := range client["allowedIPs"].([]interface{}) {
 | 
				
			||||||
 | 
								c.AllowedIPs = append(c.AllowedIPs, address.(string))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							c.Address = make([]string, 0)
 | 
				
			||||||
 | 
							for _, address := range client["address"].([]interface{}) {
 | 
				
			||||||
 | 
								c.Address = append(c.Address, address.(string))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							c.PrivateKey = client["privateKey"].(string)
 | 
				
			||||||
 | 
							c.PublicKey = client["publicKey"].(string)
 | 
				
			||||||
 | 
							created, err := time.Parse(time.RFC3339, client["created"].(string))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.WithFields(log.Fields{
 | 
				
			||||||
 | 
									"err": err,
 | 
				
			||||||
 | 
								}).Errorf("failed to parse time")
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							c.Created = created
 | 
				
			||||||
 | 
							updated, err := time.Parse(time.RFC3339, client["updated"].(string))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.WithFields(log.Fields{
 | 
				
			||||||
 | 
									"err": err,
 | 
				
			||||||
 | 
								}).Errorf("failed to parse time")
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							c.Updated = updated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err = storage.Serialize(c.Id, c)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.WithFields(log.Fields{
 | 
				
			||||||
 | 
									"err": err,
 | 
				
			||||||
 | 
								}).Errorf("failed to Serialize client")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, ok := s["presharedKey"]; ok {
 | 
				
			||||||
 | 
							server := &model.Server{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							server.Address = make([]string, 0)
 | 
				
			||||||
 | 
							server.Address = make([]string, 0)
 | 
				
			||||||
 | 
							for _, address := range s["address"].([]interface{}) {
 | 
				
			||||||
 | 
								server.Address = append(server.Address, address.(string))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							server.ListenPort = int(s["listenPort"].(float64))
 | 
				
			||||||
 | 
							server.PrivateKey = s["privateKey"].(string)
 | 
				
			||||||
 | 
							server.PublicKey = s["publicKey"].(string)
 | 
				
			||||||
 | 
							server.Endpoint = s["endpoint"].(string)
 | 
				
			||||||
 | 
							server.PersistentKeepalive = int(s["persistentKeepalive"].(float64))
 | 
				
			||||||
 | 
							server.Dns = make([]string, 0)
 | 
				
			||||||
 | 
							for _, address := range s["dns"].([]interface{}) {
 | 
				
			||||||
 | 
								server.Dns = append(server.Dns, address.(string))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if val, ok := s["preUp"]; ok {
 | 
				
			||||||
 | 
								server.PreUp = val.(string)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if val, ok := s["postUp"]; ok {
 | 
				
			||||||
 | 
								server.PostUp = val.(string)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if val, ok := s["preDown"]; ok {
 | 
				
			||||||
 | 
								server.PreDown = val.(string)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if val, ok := s["postDown"]; ok {
 | 
				
			||||||
 | 
								server.PostDown = val.(string)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							created, err := time.Parse(time.RFC3339, s["created"].(string))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.WithFields(log.Fields{
 | 
				
			||||||
 | 
									"err": err,
 | 
				
			||||||
 | 
								}).Errorf("failed to parse time")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							server.Created = created
 | 
				
			||||||
 | 
							updated, err := time.Parse(time.RFC3339, s["updated"].(string))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.WithFields(log.Fields{
 | 
				
			||||||
 | 
									"err": err,
 | 
				
			||||||
 | 
								}).Errorf("failed to parse time")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							server.Updated = updated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err = storage.Serialize("server.json", server)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.WithFields(log.Fields{
 | 
				
			||||||
 | 
									"err": err,
 | 
				
			||||||
 | 
								}).Errorf("failed to Serialize server")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func readClients() ([]map[string]interface{}, error) {
 | 
				
			||||||
 | 
						clients := make([]map[string]interface{}, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						files, err := ioutil.ReadDir(filepath.Join(os.Getenv("WG_CONF_DIR")))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, f := range files {
 | 
				
			||||||
 | 
							// clients file name is an uuid
 | 
				
			||||||
 | 
							_, err := uuid.FromString(f.Name())
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								c, err := deserialize(f.Name())
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.WithFields(log.Fields{
 | 
				
			||||||
 | 
										"err":  err,
 | 
				
			||||||
 | 
										"path": f.Name(),
 | 
				
			||||||
 | 
									}).Error("failed to deserialize client")
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									clients = append(clients, c)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return clients, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func deserialize(id string) (map[string]interface{}, error) {
 | 
				
			||||||
 | 
						path := filepath.Join(os.Getenv("WG_CONF_DIR"), id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data, err := util.ReadFile(path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var d map[string]interface{}
 | 
				
			||||||
 | 
						err = json.Unmarshal(data, &d)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return d, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										166
									
								
								core/server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								core/server.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,166 @@
 | 
				
			|||||||
 | 
					package core
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						log "github.com/sirupsen/logrus"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/storage"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/template"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/util"
 | 
				
			||||||
 | 
						"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReadServer object, create default one
 | 
				
			||||||
 | 
					func ReadServer() (*model.Server, error) {
 | 
				
			||||||
 | 
						if !util.FileExists(filepath.Join(os.Getenv("WG_CONF_DIR"), "server.json")) {
 | 
				
			||||||
 | 
							server := &model.Server{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							key, err := wgtypes.GeneratePrivateKey()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							server.PrivateKey = key.String()
 | 
				
			||||||
 | 
							server.PublicKey = key.PublicKey().String()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							server.Endpoint = "wireguard.example.com:123"
 | 
				
			||||||
 | 
							server.ListenPort = 51820
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							server.Address = make([]string, 0)
 | 
				
			||||||
 | 
							server.Address = append(server.Address, "fd9f:6666::10:6:6:1/64")
 | 
				
			||||||
 | 
							server.Address = append(server.Address, "10.6.6.1/24")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							server.Dns = make([]string, 0)
 | 
				
			||||||
 | 
							server.Dns = append(server.Dns, "fd9f::10:0:0:2")
 | 
				
			||||||
 | 
							server.Dns = append(server.Dns, "10.0.0.2")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							server.PersistentKeepalive = 16
 | 
				
			||||||
 | 
							server.Mtu = 0
 | 
				
			||||||
 | 
							server.PreUp = "echo WireGuard PreUp"
 | 
				
			||||||
 | 
							server.PostUp = "echo WireGuard PostUp"
 | 
				
			||||||
 | 
							server.PreDown = "echo WireGuard PreDown"
 | 
				
			||||||
 | 
							server.PostDown = "echo WireGuard PostDown"
 | 
				
			||||||
 | 
							server.Created = time.Now().UTC()
 | 
				
			||||||
 | 
							server.Updated = server.Created
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err = storage.Serialize("server.json", server)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// server.json was missing, dump wg config after creation
 | 
				
			||||||
 | 
							err = UpdateServerConfigWg()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c, err := storage.Deserialize("server.json")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return c.(*model.Server), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdateServer keep private values from existing one
 | 
				
			||||||
 | 
					func UpdateServer(server *model.Server) (*model.Server, error) {
 | 
				
			||||||
 | 
						current, err := storage.Deserialize("server.json")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// check if server is valid
 | 
				
			||||||
 | 
						errs := server.IsValid()
 | 
				
			||||||
 | 
						if len(errs) != 0 {
 | 
				
			||||||
 | 
							for _, err := range errs {
 | 
				
			||||||
 | 
								log.WithFields(log.Fields{
 | 
				
			||||||
 | 
									"err": err,
 | 
				
			||||||
 | 
								}).Error("server validation error")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return nil, errors.New("failed to validate server")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						server.PrivateKey = current.(*model.Server).PrivateKey
 | 
				
			||||||
 | 
						server.PublicKey = current.(*model.Server).PublicKey
 | 
				
			||||||
 | 
						//server.PresharedKey = current.(*model.Server).PresharedKey
 | 
				
			||||||
 | 
						server.Updated = time.Now().UTC()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = storage.Serialize("server.json", server)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						v, err := storage.Deserialize("server.json")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						server = v.(*model.Server)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return server, UpdateServerConfigWg()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdateServerConfigWg in wg format
 | 
				
			||||||
 | 
					func UpdateServerConfigWg() error {
 | 
				
			||||||
 | 
						clients, err := ReadClients()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						server, err := ReadServer()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = template.DumpServerWg(clients, server)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetAllReservedIps the list of all reserved IPs, client and server
 | 
				
			||||||
 | 
					func GetAllReservedIps() ([]string, error) {
 | 
				
			||||||
 | 
						clients, err := ReadClients()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						server, err := ReadServer()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						reserverIps := make([]string, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, client := range clients {
 | 
				
			||||||
 | 
							for _, cidr := range client.Address {
 | 
				
			||||||
 | 
								ip, err := util.GetIpFromCidr(cidr)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.WithFields(log.Fields{
 | 
				
			||||||
 | 
										"err":  err,
 | 
				
			||||||
 | 
										"cidr": cidr,
 | 
				
			||||||
 | 
									}).Error("failed to ip from cidr")
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									reserverIps = append(reserverIps, ip)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, cidr := range server.Address {
 | 
				
			||||||
 | 
							ip, err := util.GetIpFromCidr(cidr)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.WithFields(log.Fields{
 | 
				
			||||||
 | 
									"err":  err,
 | 
				
			||||||
 | 
									"cidr": err,
 | 
				
			||||||
 | 
								}).Error("failed to ip from cidr")
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								reserverIps = append(reserverIps, ip)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return reserverIps, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										17
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					module gitlab.127-0-0-1.fr/vx3r/wg-gen-web
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go 1.14
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e
 | 
				
			||||||
 | 
						github.com/gin-contrib/cors v1.3.1
 | 
				
			||||||
 | 
						github.com/gin-contrib/static v0.0.0-20191128031702-f81c604d8ac2
 | 
				
			||||||
 | 
						github.com/gin-gonic/gin v1.6.2
 | 
				
			||||||
 | 
						github.com/joho/godotenv v1.3.0
 | 
				
			||||||
 | 
						github.com/satori/go.uuid v1.2.0
 | 
				
			||||||
 | 
						github.com/sirupsen/logrus v1.5.0
 | 
				
			||||||
 | 
						github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086
 | 
				
			||||||
 | 
						golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200324154536-ceff61240acf
 | 
				
			||||||
 | 
						gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
 | 
				
			||||||
 | 
						gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										118
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"github.com/danielkov/gin-helmet"
 | 
				
			||||||
 | 
						"github.com/gin-contrib/cors"
 | 
				
			||||||
 | 
						"github.com/gin-contrib/static"
 | 
				
			||||||
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
						"github.com/joho/godotenv"
 | 
				
			||||||
 | 
						log "github.com/sirupsen/logrus"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/api"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/core"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/util"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						log.SetFormatter(&log.TextFormatter{})
 | 
				
			||||||
 | 
						log.SetOutput(os.Stderr)
 | 
				
			||||||
 | 
						log.SetLevel(log.DebugLevel)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						log.Infof("Starting Wg Gen Web version: %s", util.Version)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// load .env environment variables
 | 
				
			||||||
 | 
						err := godotenv.Load()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Fatal("failed to load .env file")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// check directories or create it
 | 
				
			||||||
 | 
						if !util.DirectoryExists(filepath.Join(os.Getenv("WG_CONF_DIR"))) {
 | 
				
			||||||
 | 
							err = os.Mkdir(filepath.Join(os.Getenv("WG_CONF_DIR")), 0755)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.WithFields(log.Fields{
 | 
				
			||||||
 | 
									"err": err,
 | 
				
			||||||
 | 
									"dir": filepath.Join(os.Getenv("WG_CONF_DIR")),
 | 
				
			||||||
 | 
								}).Fatal("failed to create directory")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// check if server.json exists otherwise create it with default values
 | 
				
			||||||
 | 
						if !util.FileExists(filepath.Join(os.Getenv("WG_CONF_DIR"), "server.json")) {
 | 
				
			||||||
 | 
							_, err = core.ReadServer()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.WithFields(log.Fields{
 | 
				
			||||||
 | 
									"err": err,
 | 
				
			||||||
 | 
								}).Fatal("server.json doesnt not exists and can not read it")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if os.Getenv("GIN_MODE") == "debug" {
 | 
				
			||||||
 | 
							// set gin release debug
 | 
				
			||||||
 | 
							gin.SetMode(gin.DebugMode)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// set gin release mode
 | 
				
			||||||
 | 
							gin.SetMode(gin.ReleaseMode)
 | 
				
			||||||
 | 
							// disable console color
 | 
				
			||||||
 | 
							gin.DisableConsoleColor()
 | 
				
			||||||
 | 
							// log level info
 | 
				
			||||||
 | 
							log.SetLevel(log.InfoLevel)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// migrate
 | 
				
			||||||
 | 
						err = core.MigrateInitialStructChange()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Fatal("failed to migrate initial struct changes")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = core.MigratePresharedKey()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Fatal("failed to migrate preshared key struct changes")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// dump wg config file
 | 
				
			||||||
 | 
						err = core.UpdateServerConfigWg()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Fatal("failed to dump wg config file")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// creates a gin router with default middleware: logger and recovery (crash-free) middleware
 | 
				
			||||||
 | 
						app := gin.Default()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// cors middleware
 | 
				
			||||||
 | 
						config := cors.DefaultConfig()
 | 
				
			||||||
 | 
						config.AllowAllOrigins = true
 | 
				
			||||||
 | 
						app.Use(cors.New(config))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// protection middleware
 | 
				
			||||||
 | 
						app.Use(helmet.Default())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// no route redirect to frontend app
 | 
				
			||||||
 | 
						app.NoRoute(func(c *gin.Context) {
 | 
				
			||||||
 | 
							c.Redirect(301, "/index.html")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// serve static files
 | 
				
			||||||
 | 
						app.Use(static.Serve("/", static.LocalFile("./ui/dist", false)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// apply api router
 | 
				
			||||||
 | 
						api.ApplyRoutes(app)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = app.Run(fmt.Sprintf("%s:%s", os.Getenv("SERVER"), os.Getenv("PORT")))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.WithFields(log.Fields{
 | 
				
			||||||
 | 
								"err": err,
 | 
				
			||||||
 | 
							}).Fatal("failed to start server")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										64
									
								
								model/client.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								model/client.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
				
			|||||||
 | 
					package model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/util"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Client structure
 | 
				
			||||||
 | 
					type Client struct {
 | 
				
			||||||
 | 
						Id                        string    `json:"id"`
 | 
				
			||||||
 | 
						Name                      string    `json:"name"`
 | 
				
			||||||
 | 
						Email                     string    `json:"email"`
 | 
				
			||||||
 | 
						Enable                    bool      `json:"enable"`
 | 
				
			||||||
 | 
						IgnorePersistentKeepalive bool      `json:"ignorePersistentKeepalive"`
 | 
				
			||||||
 | 
						PresharedKey              string    `json:"presharedKey"`
 | 
				
			||||||
 | 
						AllowedIPs                []string  `json:"allowedIPs"`
 | 
				
			||||||
 | 
						Address                   []string  `json:"address"`
 | 
				
			||||||
 | 
						PrivateKey                string    `json:"privateKey"`
 | 
				
			||||||
 | 
						PublicKey                 string    `json:"publicKey"`
 | 
				
			||||||
 | 
						Created                   time.Time `json:"created"`
 | 
				
			||||||
 | 
						Updated                   time.Time `json:"updated"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a Client) IsValid() []error {
 | 
				
			||||||
 | 
						errs := make([]error, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// check if the name empty
 | 
				
			||||||
 | 
						if a.Name == "" {
 | 
				
			||||||
 | 
							errs = append(errs, fmt.Errorf("name est requis"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// check the name field is between 3 to 40 chars
 | 
				
			||||||
 | 
						if len(a.Name) < 2 || len(a.Name) > 40 {
 | 
				
			||||||
 | 
							errs = append(errs, fmt.Errorf("name field must be between 2-40 chars"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// email is not required, but if provided must match regex
 | 
				
			||||||
 | 
						if a.Email != "" {
 | 
				
			||||||
 | 
							if !util.RegexpEmail.MatchString(a.Email) {
 | 
				
			||||||
 | 
								errs = append(errs, fmt.Errorf("email %s is invalid", a.Email))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// check if the allowedIPs empty
 | 
				
			||||||
 | 
						if len(a.AllowedIPs) == 0 {
 | 
				
			||||||
 | 
							errs = append(errs, fmt.Errorf("allowedIPs field est requis"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// check if the allowedIPs are valid
 | 
				
			||||||
 | 
						for _, allowedIP := range a.AllowedIPs {
 | 
				
			||||||
 | 
							if !util.IsValidCidr(allowedIP) {
 | 
				
			||||||
 | 
								errs = append(errs, fmt.Errorf("allowedIP %s is invalid", allowedIP))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// check if the address empty
 | 
				
			||||||
 | 
						if len(a.Address) == 0 {
 | 
				
			||||||
 | 
							errs = append(errs, fmt.Errorf("address field est requis"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// check if the address are valid
 | 
				
			||||||
 | 
						for _, address := range a.Address {
 | 
				
			||||||
 | 
							if !util.IsValidCidr(address) {
 | 
				
			||||||
 | 
								errs = append(errs, fmt.Errorf("address %s is invalid", address))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return errs
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										64
									
								
								model/server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								model/server.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
				
			|||||||
 | 
					package model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/util"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Server structure
 | 
				
			||||||
 | 
					type Server struct {
 | 
				
			||||||
 | 
						Address             []string  `json:"address"`
 | 
				
			||||||
 | 
						ListenPort          int       `json:"listenPort"`
 | 
				
			||||||
 | 
						Mtu                 int       `json:"mtu"`
 | 
				
			||||||
 | 
						PrivateKey          string    `json:"privateKey"`
 | 
				
			||||||
 | 
						PublicKey           string    `json:"publicKey"`
 | 
				
			||||||
 | 
						Endpoint            string    `json:"endpoint"`
 | 
				
			||||||
 | 
						PersistentKeepalive int       `json:"persistentKeepalive"`
 | 
				
			||||||
 | 
						Dns                 []string  `json:"dns"`
 | 
				
			||||||
 | 
						PreUp               string    `json:"preUp"`
 | 
				
			||||||
 | 
						PostUp              string    `json:"postUp"`
 | 
				
			||||||
 | 
						PreDown             string    `json:"preDown"`
 | 
				
			||||||
 | 
						PostDown            string    `json:"postDown"`
 | 
				
			||||||
 | 
						Created             time.Time `json:"created"`
 | 
				
			||||||
 | 
						Updated             time.Time `json:"updated"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a Server) IsValid() []error {
 | 
				
			||||||
 | 
						errs := make([]error, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// check if the address empty
 | 
				
			||||||
 | 
						if len(a.Address) == 0 {
 | 
				
			||||||
 | 
							errs = append(errs, fmt.Errorf("address est requis"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// check if the address are valid
 | 
				
			||||||
 | 
						for _, address := range a.Address {
 | 
				
			||||||
 | 
							if !util.IsValidCidr(address) {
 | 
				
			||||||
 | 
								errs = append(errs, fmt.Errorf("address %s is invalid", address))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// check if the listenPort is valid
 | 
				
			||||||
 | 
						if a.ListenPort < 0 || a.ListenPort > 65535 {
 | 
				
			||||||
 | 
							errs = append(errs, fmt.Errorf("listenPort %s is invalid", a.ListenPort))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// check if the endpoint empty
 | 
				
			||||||
 | 
						if a.Endpoint == "" {
 | 
				
			||||||
 | 
							errs = append(errs, fmt.Errorf("endpoint est requis"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// check if the persistentKeepalive is valid
 | 
				
			||||||
 | 
						if a.PersistentKeepalive < 0 {
 | 
				
			||||||
 | 
							errs = append(errs, fmt.Errorf("persistentKeepalive %d is invalid", a.PersistentKeepalive))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// check if the mtu is valid
 | 
				
			||||||
 | 
						if a.Mtu < 0 {
 | 
				
			||||||
 | 
							errs = append(errs, fmt.Errorf("MTU %d is invalid", a.PersistentKeepalive))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// check if the address are valid
 | 
				
			||||||
 | 
						for _, dns := range a.Dns {
 | 
				
			||||||
 | 
							if !util.IsValidIp(dns) {
 | 
				
			||||||
 | 
								errs = append(errs, fmt.Errorf("dns %s is invalid", dns))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return errs
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										47
									
								
								storage/file.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								storage/file.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					package storage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/util"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Serialize write interface to disk
 | 
				
			||||||
 | 
					func Serialize(id string, c interface{}) error {
 | 
				
			||||||
 | 
						b, err := json.MarshalIndent(c, "", "  ")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return util.WriteFile(filepath.Join(os.Getenv("WG_CONF_DIR"), id), b)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Deserialize read interface from disk
 | 
				
			||||||
 | 
					func Deserialize(id string) (interface{}, error) {
 | 
				
			||||||
 | 
						path := filepath.Join(os.Getenv("WG_CONF_DIR"), id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data, err := util.ReadFile(path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if id == "server.json" {
 | 
				
			||||||
 | 
							var s *model.Server
 | 
				
			||||||
 | 
							err = json.Unmarshal(data, &s)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return s, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// if not the server, must be client
 | 
				
			||||||
 | 
						var c *model.Client
 | 
				
			||||||
 | 
						err = json.Unmarshal(data, &c)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return c, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										311
									
								
								template/template.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										311
									
								
								template/template.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,311 @@
 | 
				
			|||||||
 | 
					package template
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model"
 | 
				
			||||||
 | 
						"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/util"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"text/template"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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;">Vous avez probablement demandé une configuration VPN. Voici la configuration <strong>{{.Client.Name}}</strong> créée le <strong>{{.Client.Created.Format "02/01/2006 15:4"}}</strong>. Scannez le Qrcode ou ouvrez le fichier de configuration joint dans le client VPN.</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;">A propos de 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 est un VPN extrêmement simple, rapide et moderne qui utilise un chiffrement de pointe. Il vise à être plus rapide, plus simple, plus léger et plus utile tout en évitant le casse-tête. Il est beaucoup plus performant comparé à 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;">Télécharger 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-webui-fr - Générateur de configuration simple basé sur le Web pour 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://gitea.xoyize.xyz/yako/wg-webui-fr" target="_blank" class="link" style="color:#66c7ff; text-decoration:none;"><span class="link" style="color:#66c7ff; text-decoration:none;">Informations sur Gitea</span></a></td>
 | 
				
			||||||
 | 
					                                        </tr>
 | 
				
			||||||
 | 
					                                    </table>
 | 
				
			||||||
 | 
					                                </td>
 | 
				
			||||||
 | 
					                            </tr>
 | 
				
			||||||
 | 
					                        </table>
 | 
				
			||||||
 | 
					                        <!-- END Footer -->
 | 
				
			||||||
 | 
					                    </td>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					            </table>
 | 
				
			||||||
 | 
					        </td>
 | 
				
			||||||
 | 
					    </tr>
 | 
				
			||||||
 | 
					</table>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						clientTpl = `[Interface]
 | 
				
			||||||
 | 
					Address = {{ StringsJoin .Client.Address ", " }}
 | 
				
			||||||
 | 
					PrivateKey = {{ .Client.PrivateKey }}
 | 
				
			||||||
 | 
					{{ if ne (len .Server.Dns) 0 -}}
 | 
				
			||||||
 | 
					DNS = {{ StringsJoin .Server.Dns ", " }}
 | 
				
			||||||
 | 
					{{- end }}
 | 
				
			||||||
 | 
					{{ if ne .Server.Mtu 0 -}}
 | 
				
			||||||
 | 
					MTU = {{.Server.Mtu}}
 | 
				
			||||||
 | 
					{{- end}}
 | 
				
			||||||
 | 
					[Peer]
 | 
				
			||||||
 | 
					PublicKey = {{ .Server.PublicKey }}
 | 
				
			||||||
 | 
					PresharedKey = {{ .Client.PresharedKey }}
 | 
				
			||||||
 | 
					AllowedIPs = {{ StringsJoin .Client.AllowedIPs ", " }}
 | 
				
			||||||
 | 
					Endpoint = {{ .Server.Endpoint }}
 | 
				
			||||||
 | 
					{{ if and (ne .Server.PersistentKeepalive 0) (not .Client.IgnorePersistentKeepalive) -}}
 | 
				
			||||||
 | 
					PersistentKeepalive = {{.Server.PersistentKeepalive}}
 | 
				
			||||||
 | 
					{{- end}}
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						wgTpl = `# Updated: {{ .Server.Updated }} / Created: {{ .Server.Created }}
 | 
				
			||||||
 | 
					[Interface]
 | 
				
			||||||
 | 
					{{- range .Server.Address }}
 | 
				
			||||||
 | 
					Address = {{ . }}
 | 
				
			||||||
 | 
					{{- end }}
 | 
				
			||||||
 | 
					ListenPort = {{ .Server.ListenPort }}
 | 
				
			||||||
 | 
					PrivateKey = {{ .Server.PrivateKey }}
 | 
				
			||||||
 | 
					{{ if ne .Server.Mtu 0 -}}
 | 
				
			||||||
 | 
					MTU = {{.Server.Mtu}}
 | 
				
			||||||
 | 
					{{- end}}
 | 
				
			||||||
 | 
					PreUp = {{ .Server.PreUp }}
 | 
				
			||||||
 | 
					PostUp = {{ .Server.PostUp }}
 | 
				
			||||||
 | 
					PreDown = {{ .Server.PreDown }}
 | 
				
			||||||
 | 
					PostDown = {{ .Server.PostDown }}
 | 
				
			||||||
 | 
					{{- range .Clients }}
 | 
				
			||||||
 | 
					{{ if .Enable -}}
 | 
				
			||||||
 | 
					# {{.Name}} / {{.Email}} / Updated: {{.Updated}} / Created: {{.Created}}
 | 
				
			||||||
 | 
					[Peer]
 | 
				
			||||||
 | 
					PublicKey = {{ .PublicKey }}
 | 
				
			||||||
 | 
					PresharedKey = {{ .PresharedKey }}
 | 
				
			||||||
 | 
					AllowedIPs = {{ StringsJoin .Address ", " }}
 | 
				
			||||||
 | 
					{{- end }}
 | 
				
			||||||
 | 
					{{ end }}`
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DumpClientWg dump client wg config with go template
 | 
				
			||||||
 | 
					func DumpClientWg(client *model.Client, server *model.Server) ([]byte, error) {
 | 
				
			||||||
 | 
						t, err := template.New("client").Funcs(template.FuncMap{"StringsJoin": strings.Join}).Parse(clientTpl)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return dump(t, struct {
 | 
				
			||||||
 | 
							Client *model.Client
 | 
				
			||||||
 | 
							Server *model.Server
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							Client: client,
 | 
				
			||||||
 | 
							Server: server,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DumpServerWg dump server wg config with go template, write it to file and return bytes
 | 
				
			||||||
 | 
					func DumpServerWg(clients []*model.Client, server *model.Server) ([]byte, error) {
 | 
				
			||||||
 | 
						t, err := template.New("server").Funcs(template.FuncMap{"StringsJoin": strings.Join}).Parse(wgTpl)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						configDataWg, err := dump(t, struct {
 | 
				
			||||||
 | 
							Clients []*model.Client
 | 
				
			||||||
 | 
							Server  *model.Server
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							Clients: clients,
 | 
				
			||||||
 | 
							Server:  server,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = util.WriteFile(filepath.Join(os.Getenv("WG_CONF_DIR"), os.Getenv("WG_INTERFACE_NAME")), configDataWg)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return configDataWg, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DumpEmail dump server wg config with go template
 | 
				
			||||||
 | 
					func DumpEmail(client *model.Client, qrcodePngName string) ([]byte, error) {
 | 
				
			||||||
 | 
						t, err := template.New("email").Parse(emailTpl)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return dump(t, struct {
 | 
				
			||||||
 | 
							Client        *model.Client
 | 
				
			||||||
 | 
							QrcodePngName string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							Client:        client,
 | 
				
			||||||
 | 
							QrcodePngName: qrcodePngName,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func dump(tpl *template.Template, data interface{}) ([]byte, error) {
 | 
				
			||||||
 | 
						var tplBuff bytes.Buffer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := tpl.Execute(&tplBuff, data)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return tplBuff.Bytes(), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										2
									
								
								ui/.browserslistrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								ui/.browserslistrc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					> 1%
 | 
				
			||||||
 | 
					last 2 versions
 | 
				
			||||||
							
								
								
									
										19
									
								
								ui/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								ui/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					# ui
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Project setup
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					npm install
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Compiles and hot-reloads for development
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					npm run serve
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Compiles and minifies for production
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					npm run build
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Customize configuration
 | 
				
			||||||
 | 
					See [Configuration Reference](https://cli.vuejs.org/config/).
 | 
				
			||||||
							
								
								
									
										27
									
								
								ui/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								ui/package.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "name": "ui",
 | 
				
			||||||
 | 
					  "version": "0.1.0",
 | 
				
			||||||
 | 
					  "private": true,
 | 
				
			||||||
 | 
					  "scripts": {
 | 
				
			||||||
 | 
					    "serve": "vue-cli-service serve",
 | 
				
			||||||
 | 
					    "build": "vue-cli-service build"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "axios": "^0.19.2",
 | 
				
			||||||
 | 
					    "is-cidr": "^3.1.0",
 | 
				
			||||||
 | 
					    "moment": "^2.24.0",
 | 
				
			||||||
 | 
					    "vue": "^2.6.10",
 | 
				
			||||||
 | 
					    "vue-moment": "^4.1.0",
 | 
				
			||||||
 | 
					    "vue-router": "^3.1.6",
 | 
				
			||||||
 | 
					    "vuetify": "^2.2.18"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
					    "@vue/cli-plugin-router": "^4.2.3",
 | 
				
			||||||
 | 
					    "@vue/cli-service": "^4.2.3",
 | 
				
			||||||
 | 
					    "sass": "^1.26.3",
 | 
				
			||||||
 | 
					    "sass-loader": "^8.0.0",
 | 
				
			||||||
 | 
					    "vue-cli-plugin-vuetify": "^2.0.5",
 | 
				
			||||||
 | 
					    "vue-template-compiler": "^2.6.10",
 | 
				
			||||||
 | 
					    "vuetify-loader": "^1.3.0"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								ui/public/favicon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								ui/public/favicon.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 34 KiB  | 
							
								
								
									
										19
									
								
								ui/public/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								ui/public/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					  <head>
 | 
				
			||||||
 | 
					    <meta charset="utf-8">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width,initial-scale=1.0">
 | 
				
			||||||
 | 
					    <link rel="icon" href="<%= BASE_URL %>favicon.png">
 | 
				
			||||||
 | 
					    <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://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
 | 
				
			||||||
 | 
					  </head>
 | 
				
			||||||
 | 
					  <body>
 | 
				
			||||||
 | 
					    <noscript>
 | 
				
			||||||
 | 
					      <strong>We're sorry but ui doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
 | 
				
			||||||
 | 
					    </noscript>
 | 
				
			||||||
 | 
					    <div id="app"></div>
 | 
				
			||||||
 | 
					    <!-- built files will be auto injected -->
 | 
				
			||||||
 | 
					  </body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										97
									
								
								ui/src/App.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								ui/src/App.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <v-app id="inspire">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <v-app-bar app>
 | 
				
			||||||
 | 
					      <img class="mr-3" :src="require('./assets/logo.png')" height="50" alt="Wg Gen Web"/>
 | 
				
			||||||
 | 
					      <v-toolbar-title to="/">Configuration Wireguard VPN</v-toolbar-title>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <v-spacer />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <v-toolbar-items>
 | 
				
			||||||
 | 
					        <v-btn to="/clients">
 | 
				
			||||||
 | 
					          Clients
 | 
				
			||||||
 | 
					          <v-icon right dark>mdi-account-network-outline</v-icon>
 | 
				
			||||||
 | 
					        </v-btn>
 | 
				
			||||||
 | 
					        <v-btn to="/server">
 | 
				
			||||||
 | 
					          Serveur
 | 
				
			||||||
 | 
					          <v-icon right dark>mdi-vpn</v-icon>
 | 
				
			||||||
 | 
					        </v-btn>
 | 
				
			||||||
 | 
					      </v-toolbar-items>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    </v-app-bar>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <v-content>
 | 
				
			||||||
 | 
					      <v-container>
 | 
				
			||||||
 | 
					        <router-view />
 | 
				
			||||||
 | 
					      </v-container>
 | 
				
			||||||
 | 
					      <Notification v-bind:notification="notification"/>
 | 
				
			||||||
 | 
					    </v-content>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <v-footer app>
 | 
				
			||||||
 | 
					      <v-row justify="start" no-gutters>
 | 
				
			||||||
 | 
					        <v-col cols="12" lg="6" md="12" sm="12">
 | 
				
			||||||
 | 
					          <div :align="$vuetify.breakpoint.smAndDown ? 'center' : 'left'">
 | 
				
			||||||
 | 
					            <small>{{ new Date().getFullYear() }} </small>
 | 
				
			||||||
 | 
					            <small><a class="pr-1 pl-1" href="http://www.wtfpl.net/" target="_blank">Licence WTFPL</a></small>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </v-col>
 | 
				
			||||||
 | 
					      </v-row>
 | 
				
			||||||
 | 
					      <v-row justify="end" no-gutters>
 | 
				
			||||||
 | 
					        <v-col cols="12" lg="6" md="12" sm="12">
 | 
				
			||||||
 | 
					          <div :align="$vuetify.breakpoint.smAndDown ? 'center' : 'right'">
 | 
				
			||||||
 | 
					            <small>Sources </small>
 | 
				
			||||||
 | 
					            <a href="https://gitea.rnmkcy.eu/yann/wg-webui-fr">Gitea</a>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </v-col>
 | 
				
			||||||
 | 
					      </v-row>
 | 
				
			||||||
 | 
					    </v-footer>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  </v-app>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					  import {ApiService} from "./services/ApiService";
 | 
				
			||||||
 | 
					  import Notification from './components/Notification'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export default {
 | 
				
			||||||
 | 
					    name: 'App',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    components: {
 | 
				
			||||||
 | 
					      Notification
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    data: () => ({
 | 
				
			||||||
 | 
					      api: null,
 | 
				
			||||||
 | 
					      version:'N/A',
 | 
				
			||||||
 | 
					      notification: {
 | 
				
			||||||
 | 
					        show: false,
 | 
				
			||||||
 | 
					        color: '',
 | 
				
			||||||
 | 
					        text: '',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mounted() {
 | 
				
			||||||
 | 
					      this.api = new ApiService();
 | 
				
			||||||
 | 
					      this.getVersion()
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    created () {
 | 
				
			||||||
 | 
					      this.$vuetify.theme.dark = true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    methods: {
 | 
				
			||||||
 | 
					      getVersion() {
 | 
				
			||||||
 | 
					        this.api.get('/server/version').then((res) => {
 | 
				
			||||||
 | 
					          this.version = res.version;
 | 
				
			||||||
 | 
					        }).catch((e) => {
 | 
				
			||||||
 | 
					          this.notify('error', e.response.status + ' ' + e.response.statusText);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      notify(color, msg) {
 | 
				
			||||||
 | 
					        this.notification.show = true;
 | 
				
			||||||
 | 
					        this.notification.color = color;
 | 
				
			||||||
 | 
					        this.notification.text = msg;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								ui/src/assets/logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								ui/src/assets/logo.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 14 KiB  | 
							
								
								
									
										476
									
								
								ui/src/components/Clients.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										476
									
								
								ui/src/components/Clients.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,476 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <v-container>
 | 
				
			||||||
 | 
					        <v-row>
 | 
				
			||||||
 | 
					            <v-col cols="12">
 | 
				
			||||||
 | 
					                <v-card dark>
 | 
				
			||||||
 | 
					                    <v-list-item>
 | 
				
			||||||
 | 
					                        <v-list-item-content>
 | 
				
			||||||
 | 
					                            <v-list-item-title class="headline">Clients</v-list-item-title>
 | 
				
			||||||
 | 
					                        </v-list-item-content>
 | 
				
			||||||
 | 
					                        <v-btn
 | 
				
			||||||
 | 
					                                color="success"
 | 
				
			||||||
 | 
					                                @click="startAddClient"
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                            Ajout nouveau client
 | 
				
			||||||
 | 
					                            <v-icon right dark>mdi-account-multiple-plus-outline</v-icon>
 | 
				
			||||||
 | 
					                        </v-btn>
 | 
				
			||||||
 | 
					                    </v-list-item>
 | 
				
			||||||
 | 
					                    <v-row>
 | 
				
			||||||
 | 
					                        <v-col
 | 
				
			||||||
 | 
					                                v-for="(client, i) in clients"
 | 
				
			||||||
 | 
					                                :key="i"
 | 
				
			||||||
 | 
					                                sm12 lg6
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                            <v-card
 | 
				
			||||||
 | 
					                                    :color="client.enable ? '#1F7087' : 'warning'"
 | 
				
			||||||
 | 
					                                    class="mx-auto"
 | 
				
			||||||
 | 
					                                    raised
 | 
				
			||||||
 | 
					                                    shaped
 | 
				
			||||||
 | 
					                            >
 | 
				
			||||||
 | 
					                                <v-list-item>
 | 
				
			||||||
 | 
					                                    <v-list-item-content>
 | 
				
			||||||
 | 
					                                        <v-list-item-title class="headline">{{ client.name }}</v-list-item-title>
 | 
				
			||||||
 | 
					                                        <v-list-item-subtitle>{{ client.email }}</v-list-item-subtitle>
 | 
				
			||||||
 | 
					                                        <v-list-item-subtitle>Créé: {{ client.created | formatDate }}</v-list-item-subtitle>
 | 
				
			||||||
 | 
					                                        <v-list-item-subtitle>Modifié: {{ client.updated | formatDate }}</v-list-item-subtitle>
 | 
				
			||||||
 | 
					                                    </v-list-item-content>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    <v-list-item-avatar
 | 
				
			||||||
 | 
					                                            tile
 | 
				
			||||||
 | 
					                                            size="150"
 | 
				
			||||||
 | 
					                                    >
 | 
				
			||||||
 | 
					                                        <v-img :src="`${apiBaseUrl}/client/${client.id}/config?qrcode=true`"/>
 | 
				
			||||||
 | 
					                                    </v-list-item-avatar>
 | 
				
			||||||
 | 
					                                </v-list-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                <v-card-text class="text--primary">
 | 
				
			||||||
 | 
					                                    <v-chip
 | 
				
			||||||
 | 
					                                            v-for="(ip, i) in client.address"
 | 
				
			||||||
 | 
					                                            :key="i"
 | 
				
			||||||
 | 
					                                            color="indigo"
 | 
				
			||||||
 | 
					                                            text-color="white"
 | 
				
			||||||
 | 
					                                    >
 | 
				
			||||||
 | 
					                                        <v-icon left>mdi-ip-network</v-icon>
 | 
				
			||||||
 | 
					                                        {{ ip }}
 | 
				
			||||||
 | 
					                                    </v-chip>
 | 
				
			||||||
 | 
					                                </v-card-text>
 | 
				
			||||||
 | 
					                                <v-card-actions>
 | 
				
			||||||
 | 
					                                  <v-tooltip bottom>
 | 
				
			||||||
 | 
					                                    <template v-slot:activator="{ on }">
 | 
				
			||||||
 | 
					                                      <v-btn
 | 
				
			||||||
 | 
					                                              text
 | 
				
			||||||
 | 
					                                              :href="`${apiBaseUrl}/client/${client.id}/config?qrcode=false`"
 | 
				
			||||||
 | 
					                                              v-on="on"
 | 
				
			||||||
 | 
					                                      >
 | 
				
			||||||
 | 
					                                          <span class="d-none d-lg-flex">Télécharger</span>
 | 
				
			||||||
 | 
					                                          <v-icon right dark>mdi-cloud-download-outline</v-icon>
 | 
				
			||||||
 | 
					                                      </v-btn>
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                    <span>Télécharger</span>
 | 
				
			||||||
 | 
					                                  </v-tooltip>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                  <v-tooltip bottom>
 | 
				
			||||||
 | 
					                                    <template v-slot:activator="{ on }">
 | 
				
			||||||
 | 
					                                      <v-btn
 | 
				
			||||||
 | 
					                                            text
 | 
				
			||||||
 | 
					                                            @click.stop="startUpdateClient(client)"
 | 
				
			||||||
 | 
					                                            v-on="on"
 | 
				
			||||||
 | 
					                                      >
 | 
				
			||||||
 | 
					                                        <span class="d-none d-lg-flex">Editer</span>
 | 
				
			||||||
 | 
					                                        <v-icon right dark>mdi-square-edit-outline</v-icon>
 | 
				
			||||||
 | 
					                                    </v-btn>
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                    <span>Editer</span>
 | 
				
			||||||
 | 
					                                  </v-tooltip>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                  <v-tooltip bottom>
 | 
				
			||||||
 | 
					                                    <template v-slot:activator="{ on }">
 | 
				
			||||||
 | 
					                                      <v-btn
 | 
				
			||||||
 | 
					                                              text
 | 
				
			||||||
 | 
					                                              @click="sendEmailClient(client.id)"
 | 
				
			||||||
 | 
					                                              v-on="on"
 | 
				
			||||||
 | 
					                                      >
 | 
				
			||||||
 | 
					                                          <span class="d-none d-lg-flex">Envoyer message</span>
 | 
				
			||||||
 | 
					                                          <v-icon right dark>mdi-email-outline</v-icon>
 | 
				
			||||||
 | 
					                                      </v-btn>
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                    <span>Envoyer message</span>
 | 
				
			||||||
 | 
					                                  </v-tooltip>
 | 
				
			||||||
 | 
					                                  
 | 
				
			||||||
 | 
					                                  <v-tooltip bottom>
 | 
				
			||||||
 | 
					                                    <template v-slot:activator="{ on }">
 | 
				
			||||||
 | 
					                                      <v-btn
 | 
				
			||||||
 | 
					                                              text
 | 
				
			||||||
 | 
					                                              @click="deleteClient(client)"
 | 
				
			||||||
 | 
					                                              v-on="on"
 | 
				
			||||||
 | 
					                                      >
 | 
				
			||||||
 | 
					                                          <span class="d-none d-lg-flex">Supprimer</span>
 | 
				
			||||||
 | 
					                                          <v-icon right dark>mdi-trash-can-outline</v-icon>
 | 
				
			||||||
 | 
					                                      </v-btn>
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                    <span>Supprimer</span>
 | 
				
			||||||
 | 
					                                  </v-tooltip>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    <v-spacer/>
 | 
				
			||||||
 | 
					                                    <v-tooltip right>
 | 
				
			||||||
 | 
					                                        <template v-slot:activator="{ on }">
 | 
				
			||||||
 | 
					                                            <v-switch
 | 
				
			||||||
 | 
					                                                    dark
 | 
				
			||||||
 | 
					                                                    v-on="on"
 | 
				
			||||||
 | 
					                                                    color="success"
 | 
				
			||||||
 | 
					                                                    v-model="client.enable"
 | 
				
			||||||
 | 
					                                                    v-on:change="updateClient(client)"
 | 
				
			||||||
 | 
					                                            />
 | 
				
			||||||
 | 
					                                        </template>
 | 
				
			||||||
 | 
					                                        <span> {{client.enable ? 'Désactiver' : 'Activer'}} ce client</span>
 | 
				
			||||||
 | 
					                                    </v-tooltip>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                </v-card-actions>
 | 
				
			||||||
 | 
					                            </v-card>
 | 
				
			||||||
 | 
					                        </v-col>
 | 
				
			||||||
 | 
					                    </v-row>
 | 
				
			||||||
 | 
					                </v-card>
 | 
				
			||||||
 | 
					            </v-col>
 | 
				
			||||||
 | 
					        </v-row>
 | 
				
			||||||
 | 
					        <v-dialog
 | 
				
			||||||
 | 
					                v-if="client"
 | 
				
			||||||
 | 
					                v-model="dialogAddClient"
 | 
				
			||||||
 | 
					                max-width="550"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <v-card>
 | 
				
			||||||
 | 
					                <v-card-title class="headline">Ajout nouveau client</v-card-title>
 | 
				
			||||||
 | 
					                <v-card-text>
 | 
				
			||||||
 | 
					                    <v-row>
 | 
				
			||||||
 | 
					                        <v-col
 | 
				
			||||||
 | 
					                                cols="12"
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                            <v-form
 | 
				
			||||||
 | 
					                                    ref="form"
 | 
				
			||||||
 | 
					                                    v-model="valid"
 | 
				
			||||||
 | 
					                            >
 | 
				
			||||||
 | 
					                                <v-text-field
 | 
				
			||||||
 | 
					                                        v-model="client.name"
 | 
				
			||||||
 | 
					                                        label="Nom convivial"
 | 
				
			||||||
 | 
					                                        :rules="[ v => !!v || 'Nom client est requis', ]"
 | 
				
			||||||
 | 
					                                        required
 | 
				
			||||||
 | 
					                                />
 | 
				
			||||||
 | 
					                                <v-text-field
 | 
				
			||||||
 | 
					                                        v-model="client.email"
 | 
				
			||||||
 | 
					                                        label="Client messagerie"
 | 
				
			||||||
 | 
					                                        :rules="[ v => (/.+@.+\..+/.test(v) || v === '') || 'Adresse non valide',]"
 | 
				
			||||||
 | 
					                                />
 | 
				
			||||||
 | 
					                                <v-select
 | 
				
			||||||
 | 
					                                        v-model="client.address"
 | 
				
			||||||
 | 
					                                        :items="server.address"
 | 
				
			||||||
 | 
					                                        label="Adresse IP client choisie parmi les réseaux suivants"
 | 
				
			||||||
 | 
					                                        :rules="[ v => !!v || 'Réseau requis', ]"
 | 
				
			||||||
 | 
					                                        multiple
 | 
				
			||||||
 | 
					                                        chips
 | 
				
			||||||
 | 
					                                        persistent-hint
 | 
				
			||||||
 | 
					                                        required
 | 
				
			||||||
 | 
					                                />
 | 
				
			||||||
 | 
					                                <v-combobox
 | 
				
			||||||
 | 
					                                        v-model="client.allowedIPs"
 | 
				
			||||||
 | 
					                                        chips
 | 
				
			||||||
 | 
					                                        hint="Saisir le CIDR IPv4 ou IPv6 et appuyez sur touche Entrée"
 | 
				
			||||||
 | 
					                                        label="IP autorisés"
 | 
				
			||||||
 | 
					                                        multiple
 | 
				
			||||||
 | 
					                                        dark
 | 
				
			||||||
 | 
					                                >
 | 
				
			||||||
 | 
					                                    <template v-slot:selection="{ attrs, item, select, selected }">
 | 
				
			||||||
 | 
					                                        <v-chip
 | 
				
			||||||
 | 
					                                                v-bind="attrs"
 | 
				
			||||||
 | 
					                                                :input-value="selected"
 | 
				
			||||||
 | 
					                                                close
 | 
				
			||||||
 | 
					                                                @click="select"
 | 
				
			||||||
 | 
					                                                @click:close="client.allowedIPs.splice(client.allowedIPs.indexOf(item), 1)"
 | 
				
			||||||
 | 
					                                        >
 | 
				
			||||||
 | 
					                                            <strong>{{ item }}</strong> 
 | 
				
			||||||
 | 
					                                        </v-chip>
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </v-combobox>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                <v-switch
 | 
				
			||||||
 | 
					                                        v-model="client.enable"
 | 
				
			||||||
 | 
					                                        color="red"
 | 
				
			||||||
 | 
					                                        inset
 | 
				
			||||||
 | 
					                                        :label="client.enable ? 'Activer client après création': 'Désactiver client après création'"
 | 
				
			||||||
 | 
					                                />
 | 
				
			||||||
 | 
					                                <v-switch
 | 
				
			||||||
 | 
					                                        v-model="client.ignorePersistentKeepalive"
 | 
				
			||||||
 | 
					                                        color="red"
 | 
				
			||||||
 | 
					                                        inset
 | 
				
			||||||
 | 
					                                        :label="'Ignorer le keepalive persistant global: ' + (client.ignorePersistentKeepalive ? 'Oui': 'NON')"
 | 
				
			||||||
 | 
					                                />
 | 
				
			||||||
 | 
					                            </v-form>
 | 
				
			||||||
 | 
					                        </v-col>
 | 
				
			||||||
 | 
					                    </v-row>
 | 
				
			||||||
 | 
					                </v-card-text>
 | 
				
			||||||
 | 
					                <v-card-actions>
 | 
				
			||||||
 | 
					                    <v-spacer/>
 | 
				
			||||||
 | 
					                    <v-btn
 | 
				
			||||||
 | 
					                            :disabled="!valid"
 | 
				
			||||||
 | 
					                            color="success"
 | 
				
			||||||
 | 
					                            @click="addClient(client)"
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                        Valider
 | 
				
			||||||
 | 
					                        <v-icon right dark>mdi-check-outline</v-icon>
 | 
				
			||||||
 | 
					                    </v-btn>
 | 
				
			||||||
 | 
					                    <v-btn
 | 
				
			||||||
 | 
					                            color="primary"
 | 
				
			||||||
 | 
					                            @click="dialogAddClient = false"
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                        Abandon
 | 
				
			||||||
 | 
					                        <v-icon right dark>mdi-close-circle-outline</v-icon>
 | 
				
			||||||
 | 
					                    </v-btn>
 | 
				
			||||||
 | 
					                </v-card-actions>
 | 
				
			||||||
 | 
					            </v-card>
 | 
				
			||||||
 | 
					        </v-dialog>
 | 
				
			||||||
 | 
					        <v-dialog
 | 
				
			||||||
 | 
					                v-if="client"
 | 
				
			||||||
 | 
					                v-model="dialogEditClient"
 | 
				
			||||||
 | 
					                max-width="550"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <v-card>
 | 
				
			||||||
 | 
					                <v-card-title class="headline">Edition client</v-card-title>
 | 
				
			||||||
 | 
					                <v-card-text>
 | 
				
			||||||
 | 
					                    <v-row>
 | 
				
			||||||
 | 
					                        <v-col
 | 
				
			||||||
 | 
					                                cols="12"
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                            <v-form
 | 
				
			||||||
 | 
					                                    ref="form"
 | 
				
			||||||
 | 
					                                    v-model="valid"
 | 
				
			||||||
 | 
					                            >
 | 
				
			||||||
 | 
					                                <v-text-field
 | 
				
			||||||
 | 
					                                        v-model="client.name"
 | 
				
			||||||
 | 
					                                        label="Nom convivial"
 | 
				
			||||||
 | 
					                                        :rules="[ v => !!v || 'Nom client est requis',]"
 | 
				
			||||||
 | 
					                                        required
 | 
				
			||||||
 | 
					                                />
 | 
				
			||||||
 | 
					                                <v-text-field
 | 
				
			||||||
 | 
					                                        v-model="client.email"
 | 
				
			||||||
 | 
					                                        label="Messagerie"
 | 
				
			||||||
 | 
					                                        :rules="[ v => (/.+@.+\..+/.test(v) || v === '') || 'Adresse non valide',]"
 | 
				
			||||||
 | 
					                                        required
 | 
				
			||||||
 | 
					                                />
 | 
				
			||||||
 | 
					                                <v-combobox
 | 
				
			||||||
 | 
					                                        v-model="client.address"
 | 
				
			||||||
 | 
					                                        chips
 | 
				
			||||||
 | 
					                                        hint="Saisir le CIDR IPv4 ou IPv6 et appuyez sur touche Entrée"
 | 
				
			||||||
 | 
					                                        label="Adresses"
 | 
				
			||||||
 | 
					                                        multiple
 | 
				
			||||||
 | 
					                                        dark
 | 
				
			||||||
 | 
					                                >
 | 
				
			||||||
 | 
					                                    <template v-slot:selection="{ attrs, item, select, selected }">
 | 
				
			||||||
 | 
					                                        <v-chip
 | 
				
			||||||
 | 
					                                                v-bind="attrs"
 | 
				
			||||||
 | 
					                                                :input-value="selected"
 | 
				
			||||||
 | 
					                                                close
 | 
				
			||||||
 | 
					                                                @click="select"
 | 
				
			||||||
 | 
					                                                @click:close="client.address.splice(client.address.indexOf(item), 1)"
 | 
				
			||||||
 | 
					                                        >
 | 
				
			||||||
 | 
					                                            <strong>{{ item }}</strong> 
 | 
				
			||||||
 | 
					                                        </v-chip>
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </v-combobox>
 | 
				
			||||||
 | 
					                                <v-combobox
 | 
				
			||||||
 | 
					                                        v-model="client.allowedIPs"
 | 
				
			||||||
 | 
					                                        chips
 | 
				
			||||||
 | 
					                                        hint="Saisir le CIDR IPv4 ou IPv6 et appuyez sur touche Entrée"
 | 
				
			||||||
 | 
					                                        label="IP autorisés"
 | 
				
			||||||
 | 
					                                        multiple
 | 
				
			||||||
 | 
					                                        dark
 | 
				
			||||||
 | 
					                                >
 | 
				
			||||||
 | 
					                                    <template v-slot:selection="{ attrs, item, select, selected }">
 | 
				
			||||||
 | 
					                                        <v-chip
 | 
				
			||||||
 | 
					                                                v-bind="attrs"
 | 
				
			||||||
 | 
					                                                :input-value="selected"
 | 
				
			||||||
 | 
					                                                close
 | 
				
			||||||
 | 
					                                                @click="select"
 | 
				
			||||||
 | 
					                                                @click:close="client.allowedIPs.splice(client.allowedIPs.indexOf(item), 1)"
 | 
				
			||||||
 | 
					                                        >
 | 
				
			||||||
 | 
					                                            <strong>{{ item }}</strong> 
 | 
				
			||||||
 | 
					                                        </v-chip>
 | 
				
			||||||
 | 
					                                    </template>
 | 
				
			||||||
 | 
					                                </v-combobox>
 | 
				
			||||||
 | 
					                                <v-switch
 | 
				
			||||||
 | 
					                                        v-model="client.ignorePersistentKeepalive"
 | 
				
			||||||
 | 
					                                        color="red"
 | 
				
			||||||
 | 
					                                        inset
 | 
				
			||||||
 | 
					                                        :label="'Ignorer le keepalive persistant global: ' + (client.ignorePersistentKeepalive ? 'Oui': 'NON')"
 | 
				
			||||||
 | 
					                                />
 | 
				
			||||||
 | 
					                            </v-form>
 | 
				
			||||||
 | 
					                        </v-col>
 | 
				
			||||||
 | 
					                    </v-row>
 | 
				
			||||||
 | 
					                </v-card-text>
 | 
				
			||||||
 | 
					                <v-card-actions>
 | 
				
			||||||
 | 
					                    <v-spacer/>
 | 
				
			||||||
 | 
					                    <v-btn
 | 
				
			||||||
 | 
					                            :disabled="!valid"
 | 
				
			||||||
 | 
					                            color="success"
 | 
				
			||||||
 | 
					                            @click="updateClient(client)"
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                        Valider
 | 
				
			||||||
 | 
					                        <v-icon right dark>mdi-check-outline</v-icon>
 | 
				
			||||||
 | 
					                    </v-btn>
 | 
				
			||||||
 | 
					                    <v-btn
 | 
				
			||||||
 | 
					                            color="primary"
 | 
				
			||||||
 | 
					                            @click="dialogEditClient = false"
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                        Abandon
 | 
				
			||||||
 | 
					                        <v-icon right dark>mdi-close-circle-outline</v-icon>
 | 
				
			||||||
 | 
					                    </v-btn>
 | 
				
			||||||
 | 
					                </v-card-actions>
 | 
				
			||||||
 | 
					            </v-card>
 | 
				
			||||||
 | 
					        </v-dialog>
 | 
				
			||||||
 | 
					        <Notification v-bind:notification="notification"/>
 | 
				
			||||||
 | 
					    </v-container>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					  import {ApiService, API_BASE_URL} from '../services/ApiService'
 | 
				
			||||||
 | 
					  import Notification from '../components/Notification'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export default {
 | 
				
			||||||
 | 
					    name: 'Clients',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    components: {
 | 
				
			||||||
 | 
					      Notification
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    data: () => ({
 | 
				
			||||||
 | 
					      api: null,
 | 
				
			||||||
 | 
					      apiBaseUrl: API_BASE_URL,
 | 
				
			||||||
 | 
					      clients: [],
 | 
				
			||||||
 | 
					      notification: {
 | 
				
			||||||
 | 
					        show: false,
 | 
				
			||||||
 | 
					        color: '',
 | 
				
			||||||
 | 
					        text: '',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      dialogAddClient: false,
 | 
				
			||||||
 | 
					      dialogEditClient: false,
 | 
				
			||||||
 | 
					      client: null,
 | 
				
			||||||
 | 
					      server: null,
 | 
				
			||||||
 | 
					      valid: false,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mounted () {
 | 
				
			||||||
 | 
					      this.api = new ApiService();
 | 
				
			||||||
 | 
					      this.getClients();
 | 
				
			||||||
 | 
					      this.getServer()
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    methods: {
 | 
				
			||||||
 | 
					      getClients() {
 | 
				
			||||||
 | 
					        this.api.get('/client').then((res) => {
 | 
				
			||||||
 | 
					          this.clients = res
 | 
				
			||||||
 | 
					        }).catch((e) => {
 | 
				
			||||||
 | 
					          this.notify('error', e.response.status + ' ' + e.response.statusText);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      getServer() {
 | 
				
			||||||
 | 
					        this.api.get('/server').then((res) => {
 | 
				
			||||||
 | 
					          this.server = res;
 | 
				
			||||||
 | 
					        }).catch((e) => {
 | 
				
			||||||
 | 
					          this.notify('error', e.response.status + ' ' + e.response.statusText);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      startAddClient() {
 | 
				
			||||||
 | 
					        this.dialogAddClient = true;
 | 
				
			||||||
 | 
					        this.client = {
 | 
				
			||||||
 | 
					          name: "",
 | 
				
			||||||
 | 
					          email: "",
 | 
				
			||||||
 | 
					          enable: true,
 | 
				
			||||||
 | 
					          allowedIPs: ["0.0.0.0/0", "::/0"],
 | 
				
			||||||
 | 
					          address: this.server.address,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      addClient(client) {
 | 
				
			||||||
 | 
					        if (client.allowedIPs.length < 1) {
 | 
				
			||||||
 | 
					          this.notify('error', 'Veuillez fournir au moins une adresse CIDR valide pour les adresses IP autorisées du client.');
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (let i = 0; i < client.allowedIPs.length; i++){
 | 
				
			||||||
 | 
					          if (this.$isCidr(client.allowedIPs[i]) === 0) {
 | 
				
			||||||
 | 
					            this.notify('error', 'Un CIDR invalide a été détecté, veuillez corriger avant de soumettre');
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.dialogAddClient = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.api.post('/client', client).then((res) => {
 | 
				
			||||||
 | 
					          this.notify('success', `Client ${res.name} ajouté avec succès`);
 | 
				
			||||||
 | 
					          this.getClients()
 | 
				
			||||||
 | 
					        }).catch((e) => {
 | 
				
			||||||
 | 
					          this.notify('error', e.response.status + ' ' + e.response.statusText);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      deleteClient(client) {
 | 
				
			||||||
 | 
					        if(confirm(`Voulez-vous vraiment supprimer ${client.name} ?`)){
 | 
				
			||||||
 | 
					          this.api.delete(`/client/${client.id}`).then((res) => {
 | 
				
			||||||
 | 
					            this.notify('success', "Client supprimé avec succès");
 | 
				
			||||||
 | 
					            this.getClients()
 | 
				
			||||||
 | 
					          }).catch((e) => {
 | 
				
			||||||
 | 
					            this.notify('error', e.response.status + ' ' + e.response.statusText);
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      sendEmailClient(id) {
 | 
				
			||||||
 | 
					        this.api.get(`/client/${id}/email`).then((res) => {
 | 
				
			||||||
 | 
					          this.notify('success', "Courriel envoyé avec succès");
 | 
				
			||||||
 | 
					          this.getClients()
 | 
				
			||||||
 | 
					        }).catch((e) => {
 | 
				
			||||||
 | 
					          this.notify('error', e.response.status + ' ' + e.response.statusText);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      startUpdateClient(client) {
 | 
				
			||||||
 | 
					        this.client = client;
 | 
				
			||||||
 | 
					        this.dialogEditClient = true;
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      updateClient(client) {
 | 
				
			||||||
 | 
					        // check allowed IPs
 | 
				
			||||||
 | 
					        if (client.allowedIPs.length < 1) {
 | 
				
			||||||
 | 
					          this.notify('error', 'Veuillez fournir au moins une adresse CIDR valide pour les adresses IP autorisées du client');
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (let i = 0; i < client.allowedIPs.length; i++){
 | 
				
			||||||
 | 
					          if (this.$isCidr(client.allowedIPs[i]) === 0) {
 | 
				
			||||||
 | 
					            this.notify('error', 'Un CIDR invalide a été détecté, veuillez corriger avant de soumettre');
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // check address
 | 
				
			||||||
 | 
					        if (client.address.length < 1) {
 | 
				
			||||||
 | 
					          this.notify('error', 'Veuillez fournir au moins une adresse CIDR valide pour le client');
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (let i = 0; i < client.address.length; i++){
 | 
				
			||||||
 | 
					          if (this.$isCidr(client.address[i]) === 0) {
 | 
				
			||||||
 | 
					            this.notify('error', 'Un CIDR invalide a été détecté, veuillez corriger avant de soumettre');
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // all good, submit
 | 
				
			||||||
 | 
					        this.dialogEditClient = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.api.patch(`/client/${client.id}`, client).then((res) => {
 | 
				
			||||||
 | 
					          this.notify('success', `Client ${res.name} mise à jour réussie`);
 | 
				
			||||||
 | 
					          this.getClients()
 | 
				
			||||||
 | 
					        }).catch((e) => {
 | 
				
			||||||
 | 
					          this.notify('error', e.response.status + ' ' + e.response.statusText);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      notify(color, msg) {
 | 
				
			||||||
 | 
					        this.notification.show = true;
 | 
				
			||||||
 | 
					        this.notification.color = color;
 | 
				
			||||||
 | 
					        this.notification.text = msg;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
							
								
								
									
										25
									
								
								ui/src/components/Notification.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								ui/src/components/Notification.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <v-snackbar
 | 
				
			||||||
 | 
					            v-model="notification.show"
 | 
				
			||||||
 | 
					            :right="true"
 | 
				
			||||||
 | 
					            :top="true"
 | 
				
			||||||
 | 
					            :color="notification.color"
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					        {{ notification.text }}
 | 
				
			||||||
 | 
					        <v-btn
 | 
				
			||||||
 | 
					                dark
 | 
				
			||||||
 | 
					                text
 | 
				
			||||||
 | 
					                @click="notification.show = false"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            Fermer
 | 
				
			||||||
 | 
					        </v-btn>
 | 
				
			||||||
 | 
					    </v-snackbar>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					  export default {
 | 
				
			||||||
 | 
					    name: 'Notification',
 | 
				
			||||||
 | 
					    props: ['notification'],
 | 
				
			||||||
 | 
					    data: () => ({
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
							
								
								
									
										235
									
								
								ui/src/components/Server.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								ui/src/components/Server.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,235 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <v-container v-if="server">
 | 
				
			||||||
 | 
					        <v-row>
 | 
				
			||||||
 | 
					            <v-col cols="12">
 | 
				
			||||||
 | 
					                <v-card dark>
 | 
				
			||||||
 | 
					                    <v-list-item>
 | 
				
			||||||
 | 
					                        <v-list-item-content>
 | 
				
			||||||
 | 
					                            <v-list-item-title class="headline">Interface configuration serveur</v-list-item-title>
 | 
				
			||||||
 | 
					                        </v-list-item-content>
 | 
				
			||||||
 | 
					                    </v-list-item>
 | 
				
			||||||
 | 
					                    <div class="d-flex flex-no-wrap justify-space-between">
 | 
				
			||||||
 | 
					                        <v-col cols="12">
 | 
				
			||||||
 | 
					                            <v-text-field
 | 
				
			||||||
 | 
					                                    v-model="server.publicKey"
 | 
				
			||||||
 | 
					                                    label="Clé publique"
 | 
				
			||||||
 | 
					                                    disabled
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
 | 
					                            <v-text-field
 | 
				
			||||||
 | 
					                                    v-model="server.listenPort"
 | 
				
			||||||
 | 
					                                    type="number"
 | 
				
			||||||
 | 
					                                    :rules="[
 | 
				
			||||||
 | 
					                          v => !!v || 'Le port en écoute est requis',
 | 
				
			||||||
 | 
					                        ]"
 | 
				
			||||||
 | 
					                                    label="Port en écoute"
 | 
				
			||||||
 | 
					                                    required
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
 | 
					                            <v-combobox
 | 
				
			||||||
 | 
					                                    v-model="server.address"
 | 
				
			||||||
 | 
					                                    chips
 | 
				
			||||||
 | 
					                                    hint="Saisir le CIDR IPv4 ou IPv6 et appuyez sur touche Entrée"
 | 
				
			||||||
 | 
					                                    label="Adresse interfaces serveur"
 | 
				
			||||||
 | 
					                                    multiple
 | 
				
			||||||
 | 
					                                    dark
 | 
				
			||||||
 | 
					                            >
 | 
				
			||||||
 | 
					                                <template v-slot:selection="{ attrs, item, select, selected }">
 | 
				
			||||||
 | 
					                                    <v-chip
 | 
				
			||||||
 | 
					                                            v-bind="attrs"
 | 
				
			||||||
 | 
					                                            :input-value="selected"
 | 
				
			||||||
 | 
					                                            close
 | 
				
			||||||
 | 
					                                            @click="select"
 | 
				
			||||||
 | 
					                                            @click:close="server.address.splice(server.address.indexOf(item), 1)"
 | 
				
			||||||
 | 
					                                    >
 | 
				
			||||||
 | 
					                                        <strong>{{ item }}</strong> 
 | 
				
			||||||
 | 
					                                    </v-chip>
 | 
				
			||||||
 | 
					                                </template>
 | 
				
			||||||
 | 
					                            </v-combobox>
 | 
				
			||||||
 | 
					                        </v-col>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </v-card>
 | 
				
			||||||
 | 
					            </v-col>
 | 
				
			||||||
 | 
					            <v-col cols="12">
 | 
				
			||||||
 | 
					                <v-card dark>
 | 
				
			||||||
 | 
					                    <v-list-item>
 | 
				
			||||||
 | 
					                        <v-list-item-content>
 | 
				
			||||||
 | 
					                            <v-list-item-title class="headline">Configuration globale du client</v-list-item-title>
 | 
				
			||||||
 | 
					                        </v-list-item-content>
 | 
				
			||||||
 | 
					                    </v-list-item>
 | 
				
			||||||
 | 
					                    <div class="d-flex flex-no-wrap justify-space-between">
 | 
				
			||||||
 | 
					                        <v-col cols="12">
 | 
				
			||||||
 | 
					                            <v-text-field
 | 
				
			||||||
 | 
					                                    v-model="server.endpoint"
 | 
				
			||||||
 | 
					                                    label="Point accès public connexion client"
 | 
				
			||||||
 | 
					                                    :rules="[
 | 
				
			||||||
 | 
					                          v => !!v || 'Point accès public connexion client est requis',
 | 
				
			||||||
 | 
					                        ]"
 | 
				
			||||||
 | 
					                                    required
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
 | 
					                            <v-combobox
 | 
				
			||||||
 | 
					                                    v-model="server.dns"
 | 
				
			||||||
 | 
					                                    chips
 | 
				
			||||||
 | 
					                                    hint="Saisir adresse IPv4 ou IPv6 et appui sur touche Entrée"
 | 
				
			||||||
 | 
					                                    label="Serveurs DNS pour les clients"
 | 
				
			||||||
 | 
					                                    multiple
 | 
				
			||||||
 | 
					                                    dark
 | 
				
			||||||
 | 
					                            >
 | 
				
			||||||
 | 
					                                <template v-slot:selection="{ attrs, item, select, selected }">
 | 
				
			||||||
 | 
					                                    <v-chip
 | 
				
			||||||
 | 
					                                            v-bind="attrs"
 | 
				
			||||||
 | 
					                                            :input-value="selected"
 | 
				
			||||||
 | 
					                                            close
 | 
				
			||||||
 | 
					                                            @click="select"
 | 
				
			||||||
 | 
					                                            @click:close="server.dns.splice(server.dns.indexOf(item), 1)"
 | 
				
			||||||
 | 
					                                    >
 | 
				
			||||||
 | 
					                                        <strong>{{ item }}</strong> 
 | 
				
			||||||
 | 
					                                    </v-chip>
 | 
				
			||||||
 | 
					                                </template>
 | 
				
			||||||
 | 
					                            </v-combobox>
 | 
				
			||||||
 | 
					                            <v-text-field
 | 
				
			||||||
 | 
					                                    type="number"
 | 
				
			||||||
 | 
					                                    v-model="server.mtu"
 | 
				
			||||||
 | 
					                                    label="Définir le MTU global"
 | 
				
			||||||
 | 
					                                    hint="Laisser à 0 et laisser wg-quick gérer le MTU"
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
 | 
					                            <v-text-field
 | 
				
			||||||
 | 
					                                    type="number"
 | 
				
			||||||
 | 
					                                    v-model="server.persistentKeepalive"
 | 
				
			||||||
 | 
					                                    label="Keepalive persistant"
 | 
				
			||||||
 | 
					                                    hint="Laissez à 0 pour ne pas spécifier un keepalive persistant"
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
 | 
					                        </v-col>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </v-card>
 | 
				
			||||||
 | 
					            </v-col>
 | 
				
			||||||
 | 
					        </v-row>
 | 
				
			||||||
 | 
					        <v-row>
 | 
				
			||||||
 | 
					            <v-col cols="12">
 | 
				
			||||||
 | 
					                <v-card dark>
 | 
				
			||||||
 | 
					                    <v-list-item>
 | 
				
			||||||
 | 
					                        <v-list-item-content>
 | 
				
			||||||
 | 
					                            <v-list-item-title class="headline">Interface configuration hooks</v-list-item-title>
 | 
				
			||||||
 | 
					                        </v-list-item-content>
 | 
				
			||||||
 | 
					                    </v-list-item>
 | 
				
			||||||
 | 
					                    <div class="d-flex flex-no-wrap justify-space-between">
 | 
				
			||||||
 | 
					                        <v-col cols="12">
 | 
				
			||||||
 | 
					                            <v-text-field
 | 
				
			||||||
 | 
					                                    v-model="server.preUp"
 | 
				
			||||||
 | 
					                                    label="PreUp: extraits de scripts qui seront exécutés par bash avant la mise en place de l'interface"
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
 | 
					                            <v-text-field
 | 
				
			||||||
 | 
					                                    v-model="server.postUp"
 | 
				
			||||||
 | 
					                                    label="PostUp: extraits de scripts qui seront exécutés par bash après la mise en place de l'interface"
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
 | 
					                            <v-text-field
 | 
				
			||||||
 | 
					                                    v-model="server.preDown"
 | 
				
			||||||
 | 
					                                    label="PreDown: extraits de scripts qui seront exécutés par bash avant la mise en place de l'interface"
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
 | 
					                            <v-text-field
 | 
				
			||||||
 | 
					                                    v-model="server.postDown "
 | 
				
			||||||
 | 
					                                    label="PostDown : extraits de scripts qui seront exécutés par bash après la mise en place de l'interface"
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
 | 
					                        </v-col>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </v-card>
 | 
				
			||||||
 | 
					            </v-col>
 | 
				
			||||||
 | 
					        </v-row>
 | 
				
			||||||
 | 
					        <v-row>
 | 
				
			||||||
 | 
					            <v-divider dark/>
 | 
				
			||||||
 | 
					            <v-btn
 | 
				
			||||||
 | 
					                    class="ma-2"
 | 
				
			||||||
 | 
					                    color="success"
 | 
				
			||||||
 | 
					                    :href="`${apiBaseUrl}/server/config`"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                Télécharger la configuration du serveur
 | 
				
			||||||
 | 
					                <v-icon right dark>mdi-cloud-download-outline</v-icon>
 | 
				
			||||||
 | 
					            </v-btn>
 | 
				
			||||||
 | 
					            <v-spacer></v-spacer>
 | 
				
			||||||
 | 
					            <v-btn
 | 
				
			||||||
 | 
					                    class="ma-2"
 | 
				
			||||||
 | 
					                    color="warning"
 | 
				
			||||||
 | 
					                    @click="updateServer"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                Mise à jour configuration serveur
 | 
				
			||||||
 | 
					                <v-icon right dark>mdi-update</v-icon>
 | 
				
			||||||
 | 
					            </v-btn>
 | 
				
			||||||
 | 
					            <v-divider dark/>
 | 
				
			||||||
 | 
					        </v-row>
 | 
				
			||||||
 | 
					        <Notification v-bind:notification="notification"/>
 | 
				
			||||||
 | 
					    </v-container>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					  import {API_BASE_URL, ApiService} from "../services/ApiService";
 | 
				
			||||||
 | 
					  import Notification from '../components/Notification'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export default {
 | 
				
			||||||
 | 
					    name: 'Server',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    components: {
 | 
				
			||||||
 | 
					      Notification
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    data: () => ({
 | 
				
			||||||
 | 
					      api: null,
 | 
				
			||||||
 | 
					      server: null,
 | 
				
			||||||
 | 
					      apiBaseUrl: API_BASE_URL,
 | 
				
			||||||
 | 
					      notification: {
 | 
				
			||||||
 | 
					        show: false,
 | 
				
			||||||
 | 
					        color: '',
 | 
				
			||||||
 | 
					        text: '',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mounted () {
 | 
				
			||||||
 | 
					      this.api = new ApiService();
 | 
				
			||||||
 | 
					      this.getServer()
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    methods: {
 | 
				
			||||||
 | 
					      getServer() {
 | 
				
			||||||
 | 
					        this.api.get('/server').then((res) => {
 | 
				
			||||||
 | 
					          this.server = res;
 | 
				
			||||||
 | 
					        }).catch((e) => {
 | 
				
			||||||
 | 
					          this.notify('error', e.response.status + ' ' + e.response.statusText);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      updateServer () {
 | 
				
			||||||
 | 
					        // convert int values
 | 
				
			||||||
 | 
					        this.server.listenPort = parseInt(this.server.listenPort, 10);
 | 
				
			||||||
 | 
					        this.server.persistentKeepalive = parseInt(this.server.persistentKeepalive, 10);
 | 
				
			||||||
 | 
					        this.server.mtu = parseInt(this.server.mtu, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // check server addresses
 | 
				
			||||||
 | 
					        if (this.server.address.length < 1) {
 | 
				
			||||||
 | 
					          this.notify('error', 'Veuillez fournir au moins une adresse CIDR valide pour le serveur');
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (let i = 0; i < this.server.address.length; i++){
 | 
				
			||||||
 | 
					          if (this.$isCidr(this.server.address[i]) === 0) {
 | 
				
			||||||
 | 
					            this.notify('error', `Un CIDR invalide a été détecté, veuillez corriger ${this.server.address[i]} avant de soumettre`);
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // check DNS correct
 | 
				
			||||||
 | 
					        for (let i = 0; i < this.server.dns.length; i++){
 | 
				
			||||||
 | 
					          if (this.$isCidr(this.server.dns[i] + '/32') === 0) {
 | 
				
			||||||
 | 
					            this.notify('error', `IP invalide détectée, veuillez corriger ${this.server.dns[i]} avant de soumettre`);
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.api.patch('/server', this.server).then((res) => {
 | 
				
			||||||
 | 
					          this.notify('success', "Mise à jour du serveur réussie");
 | 
				
			||||||
 | 
					          this.server = res;
 | 
				
			||||||
 | 
					        }).catch((e) => {
 | 
				
			||||||
 | 
					          this.notify('error', e.response.status + ' ' + e.response.statusText);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      notify(color, msg) {
 | 
				
			||||||
 | 
					        this.notification.show = true;
 | 
				
			||||||
 | 
					        this.notification.color = color;
 | 
				
			||||||
 | 
					        this.notification.text = msg;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
							
								
								
									
										14
									
								
								ui/src/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								ui/src/main.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					import Vue from 'vue'
 | 
				
			||||||
 | 
					import App from './App.vue'
 | 
				
			||||||
 | 
					import router from './router'
 | 
				
			||||||
 | 
					import vuetify from './plugins/vuetify';
 | 
				
			||||||
 | 
					import './plugins/moment';
 | 
				
			||||||
 | 
					import './plugins/cidr'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Vue.config.productionTip = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					new Vue({
 | 
				
			||||||
 | 
					  router,
 | 
				
			||||||
 | 
					  vuetify,
 | 
				
			||||||
 | 
					  render: function (h) { return h(App) }
 | 
				
			||||||
 | 
					}).$mount('#app')
 | 
				
			||||||
							
								
								
									
										11
									
								
								ui/src/plugins/cidr.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								ui/src/plugins/cidr.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					import Vue from 'vue'
 | 
				
			||||||
 | 
					const isCidr = require('is-cidr');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const plugin = {
 | 
				
			||||||
 | 
					  install () {
 | 
				
			||||||
 | 
					    Vue.isCidr = isCidr;
 | 
				
			||||||
 | 
					    Vue.prototype.$isCidr = isCidr
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Vue.use(plugin);
 | 
				
			||||||
							
								
								
									
										15
									
								
								ui/src/plugins/moment.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								ui/src/plugins/moment.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					import Vue from 'vue'
 | 
				
			||||||
 | 
					import moment from 'moment';
 | 
				
			||||||
 | 
					import VueMoment from 'vue-moment'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					moment.locale('en');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Vue.use(VueMoment, {
 | 
				
			||||||
 | 
					  moment
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					// $moment() accessible in project
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Vue.filter('formatDate', function (value) {
 | 
				
			||||||
 | 
					  if (!value) return '';
 | 
				
			||||||
 | 
					  return moment(String(value)).format('DD/MM/YYYY HH:mm')
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										7
									
								
								ui/src/plugins/vuetify.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								ui/src/plugins/vuetify.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					import Vuetify from 'vuetify/lib';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Vue.use(Vuetify);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default new Vuetify({
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										36
									
								
								ui/src/router/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								ui/src/router/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					import Vue from 'vue'
 | 
				
			||||||
 | 
					import VueRouter from 'vue-router'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Vue.use(VueRouter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const routes = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    path: '/',
 | 
				
			||||||
 | 
					    name: 'index',
 | 
				
			||||||
 | 
					    component: function () {
 | 
				
			||||||
 | 
					      return import(/* webpackChunkName: "Index" */ '../views/Index.vue')
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    path: '/clients',
 | 
				
			||||||
 | 
					    name: 'clients',
 | 
				
			||||||
 | 
					    component: function () {
 | 
				
			||||||
 | 
					      return import(/* webpackChunkName: "Clients" */ '../views/Clients.vue')
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    path: '/server',
 | 
				
			||||||
 | 
					    name: 'server',
 | 
				
			||||||
 | 
					    component: function () {
 | 
				
			||||||
 | 
					      return import(/* webpackChunkName: "Server" */ '../views/Server.vue')
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const router = new VueRouter({
 | 
				
			||||||
 | 
					  mode: 'history',
 | 
				
			||||||
 | 
					  base: process.env.BASE_URL,
 | 
				
			||||||
 | 
					  routes
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default router
 | 
				
			||||||
							
								
								
									
										40
									
								
								ui/src/services/ApiService.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								ui/src/services/ApiService.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					import axios from 'axios'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let baseUrl = "/api/v1.0";
 | 
				
			||||||
 | 
					if (process.env.NODE_ENV === "development"){
 | 
				
			||||||
 | 
					  baseUrl = process.env.VUE_APP_API_BASE_URL
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const API_BASE_URL = baseUrl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class ApiService {
 | 
				
			||||||
 | 
					  get(resource) {
 | 
				
			||||||
 | 
					    return axios
 | 
				
			||||||
 | 
					      .get(`${API_BASE_URL}${resource}`)
 | 
				
			||||||
 | 
					      .then(response => response.data)
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  post(resource, data) {
 | 
				
			||||||
 | 
					    return axios
 | 
				
			||||||
 | 
					      .post(`${API_BASE_URL}${resource}`, data)
 | 
				
			||||||
 | 
					      .then(response => response.data)
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  put(resource, data) {
 | 
				
			||||||
 | 
					    return axios
 | 
				
			||||||
 | 
					      .put(`${API_BASE_URL}${resource}`, data)
 | 
				
			||||||
 | 
					      .then(response => response.data)
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  patch(resource, data) {
 | 
				
			||||||
 | 
					    return axios
 | 
				
			||||||
 | 
					      .patch(`${API_BASE_URL}${resource}`, data)
 | 
				
			||||||
 | 
					      .then(response => response.data)
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  delete(resource) {
 | 
				
			||||||
 | 
					    return axios
 | 
				
			||||||
 | 
					      .delete(`${API_BASE_URL}${resource}`)
 | 
				
			||||||
 | 
					      .then(response => response.data)
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										16
									
								
								ui/src/views/Clients.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								ui/src/views/Clients.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <v-content>
 | 
				
			||||||
 | 
					    <Clients/>
 | 
				
			||||||
 | 
					  </v-content>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					  import Clients from '../components/Clients'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export default {
 | 
				
			||||||
 | 
					    name: 'clients',
 | 
				
			||||||
 | 
					    components: {
 | 
				
			||||||
 | 
					      Clients
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
							
								
								
									
										10
									
								
								ui/src/views/Index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								ui/src/views/Index.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					  export default {
 | 
				
			||||||
 | 
					    created () {
 | 
				
			||||||
 | 
					      this.$router.replace({ name: 'clients' })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
							
								
								
									
										16
									
								
								ui/src/views/Server.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								ui/src/views/Server.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <v-content>
 | 
				
			||||||
 | 
					        <Server/>
 | 
				
			||||||
 | 
					    </v-content>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					  import Server from '../components/Server'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export default {
 | 
				
			||||||
 | 
					    name: 'server',
 | 
				
			||||||
 | 
					    components: {
 | 
				
			||||||
 | 
					      Server
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
							
								
								
									
										9
									
								
								ui/vue.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								ui/vue.config.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					module.exports = {
 | 
				
			||||||
 | 
					  devServer: {
 | 
				
			||||||
 | 
					    port: 8081,
 | 
				
			||||||
 | 
					    disableHostCheck: true,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "transpileDependencies": [
 | 
				
			||||||
 | 
					    "vuetify"
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										135
									
								
								util/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								util/util.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,135 @@
 | 
				
			|||||||
 | 
					package util
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"net"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						RegexpEmail = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
 | 
				
			||||||
 | 
						// pushed build time
 | 
				
			||||||
 | 
						Version string
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReadFile file content
 | 
				
			||||||
 | 
					func ReadFile(path string) (bytes []byte, err error) {
 | 
				
			||||||
 | 
						bytes, err = ioutil.ReadFile(path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return bytes, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WriteFile content to file
 | 
				
			||||||
 | 
					func WriteFile(path string, bytes []byte) (err error) {
 | 
				
			||||||
 | 
						err = ioutil.WriteFile(path, bytes, 0644)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FileExists check if file exists
 | 
				
			||||||
 | 
					func FileExists(name string) bool {
 | 
				
			||||||
 | 
						info, err := os.Stat(name)
 | 
				
			||||||
 | 
						if os.IsNotExist(err) {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return !info.IsDir()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DirectoryExists check if directory exists
 | 
				
			||||||
 | 
					func DirectoryExists(name string) bool {
 | 
				
			||||||
 | 
						info, err := os.Stat(name)
 | 
				
			||||||
 | 
						if os.IsNotExist(err) {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return info.IsDir()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetAvailableIp search for an available in cidr against a list of reserved ips
 | 
				
			||||||
 | 
					func GetAvailableIp(cidr string, reserved []string) (string, error) {
 | 
				
			||||||
 | 
						ip, ipnet, err := net.ParseCIDR(cidr)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// this two addresses are not usable
 | 
				
			||||||
 | 
						broadcastAddr := BroadcastAddr(ipnet).String()
 | 
				
			||||||
 | 
						networkAddr := ipnet.IP.String()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) {
 | 
				
			||||||
 | 
							ok := true
 | 
				
			||||||
 | 
							address := ip.String()
 | 
				
			||||||
 | 
							for _, r := range reserved {
 | 
				
			||||||
 | 
								if address == r {
 | 
				
			||||||
 | 
									ok = false
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if ok && address != networkAddr && address != broadcastAddr {
 | 
				
			||||||
 | 
								return address, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return "", errors.New("no more available address from cidr")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsIPv6 check if given ip is IPv6
 | 
				
			||||||
 | 
					func IsIPv6(address string) bool {
 | 
				
			||||||
 | 
						ip := net.ParseIP(address)
 | 
				
			||||||
 | 
						if ip == nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ip.To4() == nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsValidIp check if ip is valid
 | 
				
			||||||
 | 
					func IsValidIp(ip string) bool {
 | 
				
			||||||
 | 
						return net.ParseIP(ip) != nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsValidCidr check if CIDR is valid
 | 
				
			||||||
 | 
					func IsValidCidr(cidr string) bool {
 | 
				
			||||||
 | 
						_, _, err := net.ParseCIDR(cidr)
 | 
				
			||||||
 | 
						return err == nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetIpFromCidr get ip from cidr
 | 
				
			||||||
 | 
					func GetIpFromCidr(cidr string) (string, error) {
 | 
				
			||||||
 | 
						ip, _, err := net.ParseCIDR(cidr)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ip.String(), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//  http://play.golang.org/p/m8TNTtygK0
 | 
				
			||||||
 | 
					func inc(ip net.IP) {
 | 
				
			||||||
 | 
						for j := len(ip) - 1; j >= 0; j-- {
 | 
				
			||||||
 | 
							ip[j]++
 | 
				
			||||||
 | 
							if ip[j] > 0 {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BroadcastAddr returns the last address in the given network, or the broadcast address.
 | 
				
			||||||
 | 
					func BroadcastAddr(n *net.IPNet) net.IP {
 | 
				
			||||||
 | 
						// The golang net package doesn't make it easy to calculate the broadcast address. :(
 | 
				
			||||||
 | 
						var broadcast net.IP
 | 
				
			||||||
 | 
						if len(n.IP) == 4 {
 | 
				
			||||||
 | 
							broadcast = net.ParseIP("0.0.0.0").To4()
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							broadcast = net.ParseIP("::")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for i := 0; i < len(n.IP); i++ {
 | 
				
			||||||
 | 
							broadcast[i] = n.IP[i] | ^n.Mask[i]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return broadcast
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								web-ui-a.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web-ui-a.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 93 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								web-ui.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web-ui.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 14 KiB  | 
		Reference in New Issue
	
	Block a user