Browse Source

Added new edit client button

pull/38/head
Raul P 5 years ago
parent
commit
4edeee5fed
  1. 5
      src/lib/Server.js
  2. 82
      src/lib/WireGuard.js
  3. 91
      src/www/index.html
  4. 8
      src/www/js/api.js
  5. 12
      src/www/js/app.js

5
src/lib/Server.js

@ -102,6 +102,11 @@ module.exports = class Server {
const { name, number } = req.body;
return WireGuard.createClient({ name, number });
}))
.post('/api/wireguard/client/:clientId/update', Util.promisify(async req => {
const { clientId } = req.params;
const { name, number } = req.body;
return WireGuard.updateClient({ clientId, name, number });
}))
.delete('/api/wireguard/client/:clientId', Util.promisify(async req => {
const { clientId } = req.params;
return WireGuard.deleteClient({ clientId });

82
src/lib/WireGuard.js

@ -65,6 +65,39 @@ module.exports = class WireGuard {
return this.__configPromise;
}
__composeIP(clientId, number, config) {
let address;
if (!number) {
// Calculate next unused IP
for (let i = 2; i < 255; i++) {
const client = Object.values(config.clients).find((client, j) => {
return client.address === WG_DEFAULT_ADDRESS.replace('x', i.toString()) && Object.keys(config.clients)[j] !== clientId;
});
if (!client) {
address = WG_DEFAULT_ADDRESS.replace('x', i.toString());
break;
}
}
if (!address) {
throw new Error('Maximum number of clients reached.');
}
} else {
// Search selected number for IP
const client = Object.values(config.clients).find((client, j) => {
return client.address === WG_DEFAULT_ADDRESS.replace('x', number.toString()) && Object.keys(config.clients)[j] !== clientId;
});
if (client) {
throw new Error('Number in use, please select another or leave empty.');
}
return WG_DEFAULT_ADDRESS.replace('x', number);
}
return address;
}
async saveConfig() {
const config = await this.getConfig();
await this.__saveConfig(config);
@ -202,39 +235,11 @@ Endpoint = ${WG_HOST}:${WG_PORT}`;
const publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`);
const preSharedKey = await Util.exec('wg genpsk');
// IP address operations
let address;
if (!number) {
// Calculate next IP
for (let i = 2; i < 255; i++) {
const client = Object.values(config.clients).find(client => {
return client.address === WG_DEFAULT_ADDRESS.replace('x', i.toString());
});
if (!client) {
address = WG_DEFAULT_ADDRESS.replace('x', i.toString());
break;
}
}
if (!address) {
throw new Error('Maximum number of clients reached.');
}
} else {
// Search & use selected number for IP
const client = Object.values(config.clients).find(client => {
return client.address === WG_DEFAULT_ADDRESS.replace('x', number);
});
if (client) {
throw new Error('Number in use, please select another or leave empty.');
}
address = WG_DEFAULT_ADDRESS.replace('x', number);
}
// Create Client
const clientId = uuid.v4();
const client = {
const address = this.__composeIP(clientId, number, config);
config.clients[clientId] = {
name,
address,
privateKey,
@ -247,7 +252,22 @@ Endpoint = ${WG_HOST}:${WG_PORT}`;
enabled: true,
};
config.clients[clientId] = client;
await this.saveConfig();
}
async updateClient({ clientId, name, number }) {
if (!name) {
throw new Error('Missing: Name');
}
const config = await this.getConfig();
const client = config.clients[clientId];
if (!client) {
throw new ServerError(`Client Not Found: ${clientId}`, 404);
}
client.name = name;
client.address = this.__composeIP(clientId, number, config);
client.updatedAt = new Date();
await this.saveConfig();
}

91
src/www/index.html

@ -128,6 +128,16 @@
</svg>
</a>
<!-- Edit Client -->
<button class="align-middle bg-gray-100 hover:bg-red-800 hover:text-white p-2 rounded transition"
title="Edit client" @click="clientEdit = client; clientEditName = client.name; clientEditNumber = client.address.split('.')[3]">
<svg class="w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
d="M11 5H6a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2v-5m-1.414-9.414a2 2 0 1 1 2.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
</svg>
</button>
<!-- Delete -->
<button class="align-middle bg-gray-100 hover:bg-red-800 hover:text-white p-2 rounded transition"
title="Delete Client" @click="clientDelete = client">
@ -261,6 +271,87 @@
</div>
</div>
<!-- Edit Dialog -->
<div v-if="clientEdit" class="fixed z-10 inset-0 overflow-y-auto">
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<!--
Background overlay, show/hide based on modal state.
Entering: "ease-out duration-300"
From: "opacity-0"
To: "opacity-100"
Leaving: "ease-in duration-200"
From: "opacity-100"
To: "opacity-0"
-->
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
</div>
<!-- This element is to trick the browser into centering the modal contents. -->
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<!--
Modal panel, show/hide based on modal state.
Entering: "ease-out duration-300"
From: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
To: "opacity-100 translate-y-0 sm:scale-100"
Leaving: "ease-in duration-200"
From: "opacity-100 translate-y-0 sm:scale-100"
To: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
-->
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog" aria-modal="true" aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-800 sm:mx-0 sm:h-10 sm:w-10">
<svg class="h-6 w-6 text-white" inline xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M11 5H6a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2v-5m-1.414-9.414a2 2 0 1 1 2.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
</div>
<div class="flex-grow mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-headline">
Edit Client
</h3>
<div class="mt-2">
<p class="text-sm text-gray-500">
<input class="rounded p-2 border-2 border-gray-100 focus:border-gray-200 outline-none w-full"
type="text" v-model.trim="clientEditName" placeholder="Name" />
</p>
</div>
<div class="mt-2">
<p class="text-sm text-gray-500">
<input class="rounded p-2 border-2 border-gray-100 focus:border-gray-200 outline-none w-full"
type="number" min="2" max="254" step="1" v-model="clientEditNumber"
placeholder="Last number of client IP (optional)"/>
</p>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button v-if="clientEditName.length && (!clientEditNumber || (clientEditNumber > 1 && clientEditNumber < 255))"
type="button" @click="updateClient(clientEdit); clientEdit = null"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-800 text-base font-medium text-white hover:bg-red-700 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm">
Save
</button>
<button v-else type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-gray-200 text-base font-medium text-white sm:ml-3 sm:w-auto sm:text-sm cursor-not-allowed">
Save
</button>
<button type="button" @click="clientEdit = null"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
Cancel
</button>
</div>
</div>
</div>
</div>
<!-- Delete Dialog -->
<div v-if="clientDelete" class="fixed z-10 inset-0 overflow-y-auto">
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">

8
src/www/js/api.js

@ -73,6 +73,14 @@ class API {
});
}
async updateClient({ clientId, name, number }) {
return this.call({
method: 'post',
path: `/wireguard/client/${clientId}/update`,
body: { name, number },
});
}
async deleteClient({ clientId }) {
return this.call({
method: 'delete',

12
src/www/js/app.js

@ -15,6 +15,9 @@ new Vue({
clients: null,
clientDelete: null,
clientEdit: null,
clientEditName: '',
clientEditNumber: '',
clientCreate: null,
clientCreateName: '',
clientCreateNumber: '',
@ -94,6 +97,15 @@ new Vue({
.catch(err => alert(err.message || err.toString()))
.finally(() => this.refresh().catch(console.error));
},
updateClient(client) {
const name = this.clientEditName;
const number = this.clientEditNumber;
if (!name) return;
this.api.updateClient({ clientId: client.id, name, number })
.catch(err => alert(err.message || err.toString()))
.finally(() => this.refresh().catch(console.error));
},
deleteClient(client) {
this.api.deleteClient({ clientId: client.id })
.catch(err => alert(err.message || err.toString()))

Loading…
Cancel
Save