Prepared dashboard frontend

master
Keith Irwin 2022-09-12 13:49:18 -06:00
parent 0802cfdcb0
commit 6c76a7ce5f
Signed by: ki9
GPG Key ID: DF773B3F4A88DA86
8 changed files with 193 additions and 18 deletions

View File

@ -1,5 +1,6 @@
version: '3'
services:
dashboard-backend:
build:
context: back
@ -12,3 +13,13 @@ services:
- '/etc/ssl/private:/etc/ssl/private'
- '/etc/wgapi:/etc/wgapi:ro'
- '/var/log/wgapi:/var/log/wgapi'
dashboard-frontend:
build:
context: front
dockerfile: Dockerfile.dashboard
container_name: wgapi-dashboard-frontend
volumes:
- '/var/log/wgapi:/var/log/wgapi'
ports:
- '8081:80'

View File

@ -0,0 +1,3 @@
FROM httpd:2.4
COPY ./dashboard.html /usr/local/apache2/htdocs/index.html
COPY dashboard.js /usr/local/apache2/htdocs/dashboard.js

View File

@ -1,9 +0,0 @@
<html>
<head>
<title>Wireguard Dashboard</title>
</head>
<body>
<script src="index.js">
</body>
</html>

View File

34
front/dashboard.html Normal file
View File

@ -0,0 +1,34 @@
<html>
<head>
<title>Wireguard Dashboard</title>
</head>
<body>
<p>Use this console to edit your network-connected devices. </p>
<h2>Your peers</h2>
<table>
<thead><tr>
<th>Host</th><th>IPv4</th><th>IPv6</th><th></th>
</tr></thead>
<tbody data-bind="foreach:peers"><tr>
<td data-bind="text:name"></td>
<td data-bind="text:ipv4"></td>
<td data-bind="text:ipv6"></td>
<td><button style="float:right" data-bind="click:$parent.delPeer,disable:$data.isDeleting,text:deleteText">Delete</button></td>
</tr></tbody>
</table>
<h2>Add a peer</h2>
<p>To add a new peer, type in a hostname and click add. The hostname must be 3-10 lowercase letters and numbers <code>/[a-z0-9]{3,10}/</code>. Keep it short for your own sake!</p>
<div>
<input type="text" data-bind="textInput:newPeerName,event:{keypress:addKeyPress}" placeholder="mypc1"></input>
<button data-bind="click:addPeer,disable:isAdding,text:addText">Add</button>
</div>
<p>After clicking "Add", the new peer's config will appear below. Copy and paste it into your wireguard client and start the service. <b>This configuration will not be shown again!</b>If you lose the config, you will need to delete the peer and recreate it. </p>
<hr>
<pre data-bind="text:newConfigText"></pre>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.1/knockout-latest.js" integrity="sha512-2AL/VEauKkZqQU9BHgnv48OhXcJPx9vdzxN1JrKDVc4FPU/MEE/BZ6d9l0mP7VmvLsjtYwqiYQpDskK9dG8KBA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="/dashboard.js"></script>
</body>
</html>

145
front/dashboard.js Normal file
View File

@ -0,0 +1,145 @@
const API_URL = 'http://ksn.gf4:8080'
function Peer(data) {
this.name = ko.observable(data.name)
this.ipv4 = ko.observable(data.ipv4)
this.ipv6 = ko.observable(`:${data.ipv6.split(':').slice(-2).join(':')}`)
this.isDeleting = ko.observable(false)
this.deleteText = ko.computed(() => this.isDeleting()?'Deleting...':'Delete')
}
function PeerList() {
let self = this
self.peers = ko.observableArray([])
self.newPeerName = ko.observable('')
self.newConfigText = ko.observable('')
self.isAdding = ko.observable(false)
self.addText = ko.computed(() => self.isAdding()?'Adding...':'Add')
// Initial loading
self.getUser = async () => {
let res; try {
res = await fetch(`${API_URL}/`)
} catch (err) {
console.error(`Failed to GET ${API_URL}/`)
if (err) console.error(err)
}
if (!res.ok) {
console.log(`Got ${res.status} from GET ${API_URL}/`)
alert('Failed to contact API and load peers list. Check your wireguard connection. ')
} else {
let user; try {
user = await res.json()
} catch (err) {
console.error('Failed to parse JSON!')
if (err) console.error(err)
}
self.peers(
user.peers.sort(
(a,b) => a.ipv4.split('.')[3] - b.ipv4.split('.')[3])
.map( (i)=>new Peer(i))
)
self.token = user.token
}
}
self.addPeer = async () => {
self.isAdding(true)
const validName = self.newPeerName().trim().toLowerCase()
if (validName.length === 0) {
alert('Please enter a hostname.')
self.isAdding(false)
} else if (!/^([\-\_a-z0-9]{3,12})$/.test(validName)) {
alert('Name must be 3-12 alphanumeric chars.')
self.isAdding(false)
} else if (self.peers().map((peer)=>peer.name()).includes(validName)) {
alert(`You already have a peer named ${validName}!`)
self.isAdding(false)
} else {
const url = `${API_URL}/?token=${self.token}`
let res; try {
res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: validName,
}),
})
} catch (err) {
alert('Failed to contact server. Are you online?')
if (err) console.error(err)
self.isAdding(false)
}
let parsedRes; try {
parsedRes = await res.text()
} catch (err) {
if (err) console.error(err)
} finally { self.isAdding(false) }
if (!res.ok) {
alert(parsedRes)
} else {
self.newPeerName('')
self.newConfigText(parsedRes)
let interfaceLines = parsedRes.split('\n\n').filter(
(paragraph) => paragraph.includes('[Interface]')
)[0].split('\n')
let addresses = interfaceLines.filter(
(line) => line.includes('Address = ')
)[0].split('=')[1].trim().split(', ')
self.peers.push(new Peer({
name: interfaceLines[0]
.split('#')[1].trim().split('.')[0],
ipv4: addresses.filter(
(addr) => addr.includes('10.4.')
)[0].split('/')[0],
ipv6: addresses.filter(
(addr) => addr.includes('fd69:1337:0:420:f4:f4:')
)[0].split('/')[0],
}))
}
}
}
// Listen for user hitting enter key
self.addKeyPress = (d,e) => {
if (e.keyCode === 13) self.addPeer()
return true
}
self.delPeer = async (peer) => {
const name = peer.name()
if (confirm(`Are you sure you want to delete ${name}?`)) {
peer.isDeleting(true)
const url = `${API_URL}/?token=${self.token}`
try {
const res = await fetch(url, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: name,
}),
})
if (res.ok) self.peers.remove(peer)
else {
if (res.status===404) self.peers.remove(peer)
try {
alert(await res.text())
} catch (err) {
console.error(`Failed to parse DELETE response into text`)
if (err) console.error(err)
} finally { peer.isDeleting(false) }
}
} catch (err) {
alert(`Failed to contact the server. Are you online?`)
} finally { peer.isDeleting(false) }
}
}
self.getUser()
}
ko.applyBindings(new PeerList())

View File

@ -1,9 +0,0 @@
<html>
<head>
<title>Wireguard Dashboard</title>
</head>
<body>
<script src="index.js">
</body>
</html>