From 6c76a7ce5f1693544929d20d55a765fc3eec90d9 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Mon, 12 Sep 2022 13:49:18 -0600 Subject: [PATCH] Prepared dashboard frontend --- docker-compose.yml | 11 +++ front/Dockerfile.dashboard | 3 + front/admin/index.html | 9 --- front/admin/index.js | 0 front/dashboard.html | 34 +++++++++ front/dashboard.js | 145 +++++++++++++++++++++++++++++++++++++ front/dashboard/index.html | 9 --- front/dashboard/index.js | 0 8 files changed, 193 insertions(+), 18 deletions(-) create mode 100644 front/Dockerfile.dashboard delete mode 100644 front/admin/index.html delete mode 100644 front/admin/index.js create mode 100644 front/dashboard.html create mode 100644 front/dashboard.js delete mode 100644 front/dashboard/index.html delete mode 100644 front/dashboard/index.js diff --git a/docker-compose.yml b/docker-compose.yml index 891e6ab..fb38e25 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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' diff --git a/front/Dockerfile.dashboard b/front/Dockerfile.dashboard new file mode 100644 index 0000000..3fd0c42 --- /dev/null +++ b/front/Dockerfile.dashboard @@ -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 diff --git a/front/admin/index.html b/front/admin/index.html deleted file mode 100644 index c2a11b2..0000000 --- a/front/admin/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - Wireguard Dashboard - - - - + + + diff --git a/front/dashboard.js b/front/dashboard.js new file mode 100644 index 0000000..57eb3c7 --- /dev/null +++ b/front/dashboard.js @@ -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()) diff --git a/front/dashboard/index.html b/front/dashboard/index.html deleted file mode 100644 index c2a11b2..0000000 --- a/front/dashboard/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - Wireguard Dashboard - - - -