first commit
This commit is contained in:
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"
|
||||
]
|
||||
};
|
||||
Reference in New Issue
Block a user