Merge pull request #24 from donaldzou/v2.1-beta

v2.1 Merge
This commit is contained in:
Donald Zou 2021-07-02 13:28:36 -04:00 committed by GitHub
commit d3d0e7c615
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 651 additions and 223 deletions

3
.gitignore vendored
View File

@ -8,5 +8,6 @@ src/test.py
tmp tmp
__pycache__ __pycache__
src/wg-dashboard.ini src/wg-dashboard.ini
src/wg-dashboard.ini
src/static/pic.xd src/static/pic.xd
*.conf

102
README.md
View File

@ -7,7 +7,6 @@
</p> </p>
<h1 align="center"> Wireguard Dashboard</h1> <h1 align="center"> Wireguard Dashboard</h1>
<p align="center"> <p align="center">
<img src="http://ForTheBadge.com/images/badges/made-with-python.svg"> <img src="http://ForTheBadge.com/images/badges/made-with-python.svg">
</p> </p>
@ -16,29 +15,16 @@
</p> </p>
<p align="center">Monitoring WireGuard is not convinient, need to login into server and type <code>wg show</code>. That's why this platform is being created, to view all configurations and manage them in a easier way.</p> <p align="center">Monitoring WireGuard is not convinient, need to login into server and type <code>wg show</code>. That's why this platform is being created, to view all configurations and manage them in a easier way.</p>
## 📣 What's New: Version 2.1
## 📣 What's New: Version 2.0 - Added **Ping** and **Traceroute** tools!
- Adjusted the calculation of data usage on each peers
### ⚠️ **Update from v1.x.x** - Added refresh interval of the dashboard
- Bug fixed when no configuration on fresh install ([Bug report](https://github.com/donaldzou/wireguard-dashboard/issues/23#issuecomment-869189672))
1. Stop the dashboard if it is running. - Fixed crash when too many peers ([Bug report](https://github.com/donaldzou/wireguard-dashboard/issues/22#issuecomment-868840564))
2. You can use `git pull https://github.com/donaldzou/Wireguard-Dashboard.git v2.0` to get the new update inside `Wireguard-Dashboard` directory.
3. Proceed **Step 2 & 3** in the [Install](#-install) step down below.
<hr> <hr>
- Added login function to dashboard
- ***I'm not using the most ideal way to store the username and password, feel free to provide a better way to do this if you any good idea!***
- Added a config file to the dashboard
- Dashboard config can be change within the **Setting** tab on the side bar
- Adjusted UI
- And much more!
@ -65,9 +51,6 @@
- **Note: For peers, `PublicKey` & `AllowedIPs` is required.** - **Note: For peers, `PublicKey` & `AllowedIPs` is required.**
- Python 3.7+ & Pip3 - Python 3.7+ & Pip3
```
$ sudo apt-get install python3 python3-pip
```
@ -75,21 +58,22 @@
**1. Download Wireguard Dashboard** **1. Download Wireguard Dashboard**
``` ```shell
$ git clone -b v2.0 https://github.com/donaldzou/Wireguard-Dashboard.git git clone -b v2.1 https://github.com/donaldzou/Wireguard-Dashboard.git
``` ```
**2. Install Python Dependencies** **2. Install Python Dependencies**
``` ```shell
$ cd Wireguard-Dashboard/src cd Wireguard-Dashboard/src
$ python3 -m pip install -r requirements.txt python3 -m pip install -r requirements.txt
``` ```
**3. Install & run Wireguard Dashboard** **3. Install & run Wireguard Dashboard**
``` ```shell
$ sudo sh wgd.sh start chmod u+x wgd.sh
./wgd.sh start
``` ```
Access your server with port `10086` ! e.g (http://your_server_ip:10086), continue to read to on how to change port and ip that dashboard is running with. Access your server with port `10086` ! e.g (http://your_server_ip:10086), continue to read to on how to change port and ip that dashboard is running with.
@ -100,13 +84,14 @@ Access your server with port `10086` ! e.g (http://your_server_ip:10086), contin
**1. Start/Stop/Restart Wireguard Dashboard** **1. Start/Stop/Restart Wireguard Dashboard**
```
$ cd Wireguard-Dashboard/src ```shell
$ sudo sh wgd.sh start # Start the dashboard in background cd Wireguard-Dashboard/src
$ sudo sh wgd.sh debug # Start the dashboard in foreground (debug mode) ./wgd.sh start # Start the dashboard in background
$ sudo sh wgd.sh stop # Stop the dashboard ./wgd.sh debug # Start the dashboard in foreground (debug mode)
$ sudo sh wgd.sh restart # Restart the dasboard ./wgd.sh stop # Stop the dashboard
``` ./wgd.sh restart # Restart the dasboard
⚠️ **For first time user please also read the next section.** ⚠️ **For first time user please also read the next section.**
@ -138,17 +123,30 @@ Since version 2.0, Wireguard Dashboard will be using a configuration file called
All these settings will be able to configure within the dashboard in **Settings** on the sidebar, without changing the actual file. **Except `version` and `auth_req` due to security consideration.** All these settings will be able to configure within the dashboard in **Settings** on the sidebar, without changing the actual file. **Except `version` and `auth_req` due to security consideration.**
## ❓ How to update the dashboard? ## ❓ How to update the dashboard?
```{shell} 1. Change your directory to `wireguard-dashboard`
$ cd wireguard-dashboard ```
$ sudo git pull https://github.com/donaldzou/wireguard-dashboard.git v2.0 --force # Perform update $ cd wireguard-dashboard
$ sudo sh wgd.sh start # Start dashboard ```
``` 2. Get the newest version
```
$ sudo git pull https://github.com/donaldzou/wireguard-dashboard.git v2.1 --force
```
3. Update and install all python dependencies
```
$ python3 -m pip install -r requirements.txt
```
4. Start the dashboard
```
$ ./wgd.sh start
```
### ⚠️ **Update from v1.x.x**
1. Stop the dashboard if it is running.
2. You can use `git pull https://github.com/donaldzou/Wireguard-Dashboard.git v2.1` to get the new update inside `Wireguard-Dashboard` directory.
3. Proceed **Step 2 & 3** in the [Install](#-install) step down below.
## 🔍 Screenshot ## 🔍 Screenshot
@ -168,6 +166,22 @@ $ sudo sh wgd.sh start # Start dashboard
<p align=center>Settings Page</p> <p align=center>Settings Page</p>
## 🛒 Dependencies
- CSS/JS
- [Bootstrap](https://getbootstrap.com/docs/4.6/getting-started/introduction/) `v4.6.0`
- [Bootstrap Icon](https://icons.getbootstrap.com) `v1.4.0`
- [jQuery](https://jquery.com) `v3.5.1`
- Python
- [Flask](https://pypi.org/project/Flask/) `v1.1.2`
- [TinyDB](https://pypi.org/project/tinydb/) `v4.3.0`
- [ifcfg](https://pypi.org/project/ifcfg/) `v0.21`
- [icmplib](https://pypi.org/project/icmplib/) `v2.1.1`
## Contributors ✨ ## Contributors ✨
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --> <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->

View File

@ -1,38 +1,40 @@
dashboard_version = 'v2.0'
# Python Built-in Library # Python Built-in Library
import os import os
from flask import Flask, request, render_template, redirect, url_for, session, abort from flask import Flask, request, render_template, redirect, url_for, session, abort, jsonify
import subprocess import subprocess
from datetime import datetime, date, time, timedelta from datetime import datetime, date, time, timedelta
import time
from operator import itemgetter from operator import itemgetter
import secrets import secrets
import hashlib import hashlib
import json, urllib.request import json, urllib.request
import configparser import configparser
import re
# PIP installed library # PIP installed library
import ifcfg import ifcfg
from tinydb import TinyDB, Query from tinydb import TinyDB, Query
from icmplib import ping, multiping, traceroute, resolve, Host, Hop
# Dashboard Version
dashboard_version = 'v2.1'
# Dashboard Config Name
dashboard_conf = 'wg-dashboard.ini' dashboard_conf = 'wg-dashboard.ini'
# Upgrade Required
update = "" update = ""
# Flask App Configuration
app = Flask("Wireguard Dashboard") app = Flask("Wireguard Dashboard")
app.secret_key = secrets.token_urlsafe(16) app.secret_key = secrets.token_urlsafe(16)
app.config['TEMPLATES_AUTO_RELOAD'] = True app.config['TEMPLATES_AUTO_RELOAD'] = True
conf_data = {}
def get_conf_peer_key(config_name): def get_conf_peer_key(config_name):
keys = []
try: try:
peer_key = subprocess.check_output("wg show " + config_name + " peers", shell=True) peer_key = subprocess.check_output("wg show " + config_name + " peers", shell=True)
peer_key = peer_key.decode("UTF-8").split()
return peer_key
except Exception: except Exception:
return "stopped" return config_name+" is not running."
peer_key = peer_key.decode("UTF-8").split()
for i in peer_key: keys.append(i)
return keys
def get_conf_running_peer_number(config_name): def get_conf_running_peer_number(config_name):
running = 0 running = 0
@ -52,9 +54,14 @@ def get_conf_running_peer_number(config_name):
count += 2 count += 2
return running return running
def is_match(regex, text):
pattern = re.compile(regex)
return pattern.search(text) is not None
def read_conf_file(config_name): def read_conf_file(config_name):
# Read Configuration File Start # Read Configuration File Start
conf_location = wg_conf_path+"/" + config_name + ".conf" conf_location = wg_conf_path + "/" + config_name + ".conf"
f = open(conf_location, 'r') f = open(conf_location, 'r')
file = f.read().split("\n") file = f.read().split("\n")
conf_peer_data = { conf_peer_data = {
@ -63,30 +70,109 @@ def read_conf_file(config_name):
} }
peers_start = 0 peers_start = 0
for i in range(len(file)): for i in range(len(file)):
if file[i] == "[Peer]": if not is_match("^#(.*)",file[i]):
peers_start = i if file[i] == "[Peer]":
break peers_start = i
else: break
if len(file[i]) > 0: else:
if file[i] != "[Interface]": if len(file[i]) > 0:
tmp = file[i].replace(" ", "").split("=", 1) if file[i] != "[Interface]":
if len(tmp) == 2: tmp = re.split(r'\s*=\s*', file[i], 1)
conf_peer_data['Interface'][tmp[0]] = tmp[1] if len(tmp) == 2:
conf_peer_data['Interface'][tmp[0]] = tmp[1]
conf_peers = file[peers_start:] conf_peers = file[peers_start:]
peer = -1 peer = -1
for i in conf_peers: for i in conf_peers:
if i == "[Peer]": if not is_match("^#(.*)", i):
peer += 1 if i == "[Peer]":
conf_peer_data["Peers"].append({}) peer += 1
else: conf_peer_data["Peers"].append({})
if len(i) > 0: elif peer > -1:
tmp = i.replace(" ", "").split("=", 1) if len(i) > 0:
if len(tmp) == 2: tmp = re.split('\s*=\s*', i, 1)
conf_peer_data["Peers"][peer][tmp[0]] = tmp[1] if len(tmp) == 2:
conf_peer_data["Peers"][peer][tmp[0]] = tmp[1]
f.close()
# Read Configuration File End # Read Configuration File End
return conf_peer_data return conf_peer_data
def get_latest_handshake(config_name, db, peers):
# Get latest handshakes
try:
data_usage = subprocess.check_output("wg show " + config_name + " latest-handshakes", shell=True)
except Exception:
return "stopped"
data_usage = data_usage.decode("UTF-8").split()
count = 0
now = datetime.now()
b = timedelta(minutes=2)
for i in range(int(len(data_usage) / 2)):
minus = now - datetime.fromtimestamp(int(data_usage[count + 1]))
if minus < b:
status = "running"
else:
status = "stopped"
if int(data_usage[count + 1]) > 0:
db.update({"latest_handshake": str(minus).split(".")[0], "status": status},
peers.id == data_usage[count])
else:
db.update({"latest_handshake": "(None)", "status": status}, peers.id == data_usage[count])
count += 2
def get_transfer(config_name, db, peers):
# Get transfer
try:
data_usage = subprocess.check_output("wg show " + config_name + " transfer", shell=True)
except Exception:
return "stopped"
data_usage = data_usage.decode("UTF-8").split()
count = 0
for i in range(int(len(data_usage) / 3)):
cur_i = db.search(peers.id == data_usage[count])
total_sent = cur_i[0]['total_sent']
total_receive = cur_i[0]['total_receive']
traffic = cur_i[0]['traffic']
cur_total_sent = round(int(data_usage[count + 2]) / (1024 ** 3), 4)
cur_total_receive = round(int(data_usage[count + 1]) / (1024 ** 3), 4)
if cur_i[0]["status"] == "running":
if total_sent <= cur_total_sent and total_receive <= cur_total_receive:
total_sent = cur_total_sent
total_receive = cur_total_receive
else:
now = datetime.now()
ctime = now.strftime("%d/%m/%Y %H:%M:%S")
traffic.append(
{"time": ctime, "total_receive": round(total_receive, 4), "total_sent": round(total_sent, 4),
"total_data": round(total_receive + total_sent, 4)})
total_sent = 0
total_receive = 0
db.update({"traffic": traffic}, peers.id == data_usage[count])
db.update({"total_receive": round(total_receive, 4),
"total_sent": round(total_sent, 4),
"total_data": round(total_receive + total_sent, 4)}, peers.id == data_usage[count])
count += 3
def get_endpoint(config_name, db, peers):
# Get endpoint
try:
data_usage = subprocess.check_output("wg show " + config_name + " endpoints", shell=True)
except Exception:
return "stopped"
data_usage = data_usage.decode("UTF-8").split()
count = 0
for i in range(int(len(data_usage) / 2)):
db.update({"endpoint": data_usage[count + 1]}, peers.id == data_usage[count])
count += 2
def get_allowed_ip(config_name, db, peers, conf_peer_data):
# Get allowed ip
for i in conf_peer_data["Peers"]:
db.update({"allowed_ip": i.get('AllowedIPs', '(None)')}, peers.id == i["PublicKey"])
def get_conf_peers_data(config_name): def get_conf_peers_data(config_name):
db = TinyDB('db/' + config_name + '.json') db = TinyDB('db/' + config_name + '.json')
@ -108,75 +194,18 @@ def get_conf_peers_data(config_name):
"traffic": [] "traffic": []
}) })
# Get latest handshakes tic = time.perf_counter()
try: get_latest_handshake(config_name, db, peers)
data_usage = subprocess.check_output("wg show " + config_name + " latest-handshakes", shell=True) get_transfer(config_name, db, peers)
except Exception: get_endpoint(config_name, db, peers)
return "stopped" get_allowed_ip(config_name, db, peers, conf_peer_data)
data_usage = data_usage.decode("UTF-8").split() toc = time.perf_counter()
count = 0 print(f"Finish fetching data in {toc - tic:0.4f} seconds")
now = datetime.now() db.close()
b = timedelta(minutes=2)
for i in range(int(len(data_usage) / 2)):
minus = now - datetime.fromtimestamp(int(data_usage[count + 1]))
if minus < b:
status = "running"
else:
status = "stopped"
if int(data_usage[count + 1]) > 0:
db.update({"latest_handshake": str(minus).split(".")[0], "status": status},
peers.id == data_usage[count])
else:
db.update({"latest_handshake": "(None)", "status": status}, peers.id == data_usage[count])
count += 2
# Get transfer
try:
data_usage = subprocess.check_output("wg show " + config_name + " transfer", shell=True)
except Exception:
return "stopped"
data_usage = data_usage.decode("UTF-8").split()
count = 0
for i in range(int(len(data_usage) / 3)):
cur_i = db.search(peers.id == data_usage[count])
total_sent = cur_i[0]['total_sent']
total_receive = cur_i[0]['total_receive']
cur_total_sent = round(int(data_usage[count + 2]) / (1024 ** 3), 4)
cur_total_receive = round(int(data_usage[count + 1]) / (1024 ** 3), 4)
if cur_i[0]["status"] == "running":
if total_sent <= cur_total_sent:
total_sent = cur_total_sent
else: total_sent += cur_total_sent
if total_receive <= cur_total_receive:
total_receive = cur_total_receive
else: total_receive += cur_total_receive
db.update({"total_receive": round(total_receive,4),
"total_sent": round(total_sent,4),
"total_data": round(total_receive + total_sent, 4)}, peers.id == data_usage[count])
# Will get implement in the future
# traffic = db.search(peers.id == data_usage[count])[0]['traffic']
# traffic.append({"time": current_time, "total_receive": round(int(data_usage[count + 1]) / (1024 ** 3), 4),
# "total_sent": round(int(data_usage[count + 2]) / (1024 ** 3), 4)})
# db.update({"traffic": traffic}, peers.id == data_usage[count])
count += 3
# Get endpoint
try:
data_usage = subprocess.check_output("wg show " + config_name + " endpoints", shell=True)
except Exception:
return "stopped"
data_usage = data_usage.decode("UTF-8").split()
count = 0
for i in range(int(len(data_usage) / 2)):
db.update({"endpoint": data_usage[count + 1]}, peers.id == data_usage[count])
count += 2
# Get allowed ip
for i in conf_peer_data["Peers"]:
db.update({"allowed_ip":i.get('AllowedIPs', '(None)')}, peers.id == i["PublicKey"])
def get_peers(config_name): def get_peers(config_name):
@ -184,6 +213,7 @@ def get_peers(config_name):
db = TinyDB('db/' + config_name + '.json') db = TinyDB('db/' + config_name + '.json')
result = db.all() result = db.all()
result = sorted(result, key=lambda d: d['status']) result = sorted(result, key=lambda d: d['status'])
db.close()
return result return result
@ -209,9 +239,15 @@ def get_conf_total_data(config_name):
upload_total = 0 upload_total = 0
download_total = 0 download_total = 0
for i in db.all(): for i in db.all():
upload_total += round(i['total_sent'],4) upload_total += i['total_sent']
download_total += round(i['total_receive'],4) download_total += i['total_receive']
for k in i['traffic']:
upload_total += k['total_sent']
download_total += k['total_receive']
total = round(upload_total + download_total, 4) total = round(upload_total + download_total, 4)
upload_total = round(upload_total, 4)
download_total = round(download_total, 4)
db.close()
return [total, upload_total, download_total] return [total, upload_total, download_total]
@ -230,18 +266,16 @@ def get_conf_list():
if ".conf" in i: if ".conf" in i:
i = i.replace('.conf', '') i = i.replace('.conf', '')
temp = {"conf": i, "status": get_conf_status(i), "public_key": get_conf_pub_key(i)} temp = {"conf": i, "status": get_conf_status(i), "public_key": get_conf_pub_key(i)}
# get_conf_peers_data(i)
if temp['status'] == "running": if temp['status'] == "running":
temp['checked'] = 'checked' temp['checked'] = 'checked'
else: else:
temp['checked'] = "" temp['checked'] = ""
conf.append(temp) conf.append(temp)
conf = sorted(conf, key=itemgetter('conf')) if len(conf) > 0:
conf = sorted(conf, key=itemgetter('conf'))
return conf return conf
@app.before_request @app.before_request
def auth_req(): def auth_req():
conf = configparser.ConfigParser(strict=False) conf = configparser.ConfigParser(strict=False)
@ -255,13 +289,15 @@ def auth_req():
request.endpoint != "signout" and \ request.endpoint != "signout" and \
request.endpoint != "auth" and \ request.endpoint != "auth" and \
"username" not in session: "username" not in session:
print("not loggedin") print("User not loggedin - Attemped access: "+str(request.endpoint))
session['message'] = "You need to sign in first!" session['message'] = "You need to sign in first!"
return redirect(url_for("signin")) return redirect(url_for("signin"))
else: else:
if request.endpoint in ['signin', 'signout', 'auth', 'settings', 'update_acct', 'update_pwd', 'update_app_ip_port', 'update_wg_conf_path']: if request.endpoint in ['signin', 'signout', 'auth', 'settings', 'update_acct', 'update_pwd',
'update_app_ip_port', 'update_wg_conf_path']:
return redirect(url_for("index")) return redirect(url_for("index"))
@app.route('/signin', methods=['GET']) @app.route('/signin', methods=['GET'])
def signin(): def signin():
message = "" message = ""
@ -279,7 +315,6 @@ def signout():
return render_template('signin.html', message=message) return render_template('signin.html', message=message)
@app.route('/settings', methods=['GET']) @app.route('/settings', methods=['GET'])
def settings(): def settings():
message = "" message = ""
@ -292,14 +327,18 @@ def settings():
session.pop("message") session.pop("message")
session.pop("message_status") session.pop("message_status")
required_auth = config.get("Server", "auth_req") required_auth = config.get("Server", "auth_req")
return render_template('settings.html',conf=get_conf_list(),message=message, status=status, app_ip=config.get("Server", "app_ip"), app_port=config.get("Server", "app_port"), required_auth=required_auth, wg_conf_path=config.get("Server", "wg_conf_path")) return render_template('settings.html', conf=get_conf_list(), message=message, status=status,
app_ip=config.get("Server", "app_ip"), app_port=config.get("Server", "app_port"),
required_auth=required_auth, wg_conf_path=config.get("Server", "wg_conf_path"))
@app.route('/auth', methods=['POST']) @app.route('/auth', methods=['POST'])
def auth(): def auth():
config = configparser.ConfigParser(strict=False) config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf) config.read(dashboard_conf)
password = hashlib.sha256(request.form['password'].encode()) password = hashlib.sha256(request.form['password'].encode())
if password.hexdigest() == config["Account"]["password"] and request.form['username'] == config["Account"]["username"]: if password.hexdigest() == config["Account"]["password"] and request.form['username'] == config["Account"][
"username"]:
session['username'] = request.form['username'] session['username'] = request.form['username']
config.clear() config.clear()
return redirect(url_for("index")) return redirect(url_for("index"))
@ -308,6 +347,7 @@ def auth():
config.clear() config.clear()
return redirect(url_for("signin")) return redirect(url_for("signin"))
@app.route('/update_acct', methods=['POST']) @app.route('/update_acct', methods=['POST'])
def update_acct(): def update_acct():
config = configparser.ConfigParser(strict=False) config = configparser.ConfigParser(strict=False)
@ -326,12 +366,14 @@ def update_acct():
config.clear() config.clear()
return redirect(url_for("settings")) return redirect(url_for("settings"))
@app.route('/update_pwd', methods=['POST']) @app.route('/update_pwd', methods=['POST'])
def update_pwd(): def update_pwd():
config = configparser.ConfigParser(strict=False) config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf) config.read(dashboard_conf)
if hashlib.sha256(request.form['currentpass'].encode()).hexdigest() == config.get("Account", "password"): if hashlib.sha256(request.form['currentpass'].encode()).hexdigest() == config.get("Account", "password"):
if hashlib.sha256(request.form['newpass'].encode()).hexdigest() == hashlib.sha256(request.form['repnewpass'].encode()).hexdigest(): if hashlib.sha256(request.form['newpass'].encode()).hexdigest() == hashlib.sha256(
request.form['repnewpass'].encode()).hexdigest():
config.set("Account", "password", hashlib.sha256(request.form['repnewpass'].encode()).hexdigest()) config.set("Account", "password", hashlib.sha256(request.form['repnewpass'].encode()).hexdigest())
try: try:
config.write(open(dashboard_conf, "w")) config.write(open(dashboard_conf, "w"))
@ -355,6 +397,7 @@ def update_pwd():
config.clear() config.clear()
return redirect(url_for("settings")) return redirect(url_for("settings"))
@app.route('/update_app_ip_port', methods=['POST']) @app.route('/update_app_ip_port', methods=['POST'])
def update_app_ip_port(): def update_app_ip_port():
config = configparser.ConfigParser(strict=False) config = configparser.ConfigParser(strict=False)
@ -365,6 +408,7 @@ def update_app_ip_port():
config.clear() config.clear()
os.system('bash wgd.sh restart') os.system('bash wgd.sh restart')
@app.route('/update_wg_conf_path', methods=['POST']) @app.route('/update_wg_conf_path', methods=['POST'])
def update_wg_conf_path(): def update_wg_conf_path():
config = configparser.ConfigParser(strict=False) config = configparser.ConfigParser(strict=False)
@ -376,15 +420,71 @@ def update_wg_conf_path():
config.clear() config.clear()
os.system('bash wgd.sh restart') os.system('bash wgd.sh restart')
# @app.route('/check_update_dashboard', methods=['GET']) @app.route('/update_dashboard_refresh_interval', methods=['POST'])
# def check_update_dashboard(): def update_dashboard_refresh_interval():
# return have_update config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf)
config.set("Server", "dashboard_refresh_interval", str(request.form['interval']))
config.write(open(dashboard_conf, "w"))
config.clear()
return "true"
@app.route('/get_ping_ip', methods=['POST'])
def get_ping_ip():
config = request.form['config']
db = TinyDB('db/' + config + '.json')
html = ""
for i in db.all():
html += '<optgroup label="'+i['name']+' - '+i['id']+'">'
allowed_ip = str(i['allowed_ip']).split(",")
for k in allowed_ip:
k = k.split("/")
if len(k) == 2:
html += "<option value="+k[0]+">"+k[0]+"</option>"
endpoint = str(i['endpoint']).split(":")
if len(endpoint) == 2:
html += "<option value=" + endpoint[0] + ">" + endpoint[0] + "</option>"
html += "</optgroup>"
return html
@app.route('/ping_ip', methods=['POST'])
def ping_ip():
try:
result = ping(''+request.form['ip']+'', count=int(request.form['count']),privileged=True, source=None)
returnjson = {
"address": result.address,
"is_alive": result.is_alive,
"min_rtt": result.min_rtt,
"avg_rtt": result.avg_rtt,
"max_rtt": result.max_rtt,
"package_sent": result.packets_sent,
"package_received": result.packets_received,
"package_loss": result.packet_loss
}
return jsonify(returnjson)
except Exception:
return "Error"
@app.route('/traceroute_ip', methods=['POST'])
def traceroute_ip():
try:
result = traceroute(''+request.form['ip']+'', first_hop=1, max_hops=30, count=1, fast=True)
returnjson = []
last_distance = 0
for hop in result:
if last_distance + 1 != hop.distance:
returnjson.append({"hop":"*", "ip":"*", "avg_rtt":"", "min_rtt":"", "max_rtt":""})
returnjson.append({"hop": hop.distance, "ip": hop.address, "avg_rtt": hop.avg_rtt, "min_rtt": hop.min_rtt, "max_rtt": hop.max_rtt})
last_distance = hop.distance
return jsonify(returnjson)
except Exception:
return "Error"
@app.route('/', methods=['GET']) @app.route('/', methods=['GET'])
def index(): def index():
print(request.referrer)
return render_template('index.html', conf=get_conf_list()) return render_template('index.html', conf=get_conf_list())
@app.route('/configuration/<config_name>', methods=['GET']) @app.route('/configuration/<config_name>', methods=['GET'])
def conf(config_name): def conf(config_name):
conf_data = { conf_data = {
@ -396,12 +496,16 @@ def conf(config_name):
conf_data['checked'] = "nope" conf_data['checked'] = "nope"
else: else:
conf_data['checked'] = "checked" conf_data['checked'] = "checked"
return render_template('configuration.html', conf=get_conf_list(), conf_data=conf_data) config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf)
config_list = get_conf_list()
if config_name not in [conf['conf'] for conf in config_list]:
return render_template('index.html', conf=get_conf_list())
return render_template('configuration.html', conf=get_conf_list(), conf_data=conf_data, dashboard_refresh_interval=int(config.get("Server","dashboard_refresh_interval")))
@app.route('/get_config/<config_name>', methods=['GET']) @app.route('/get_config/<config_name>', methods=['GET'])
def get_conf(config_name): def get_conf(config_name):
db = TinyDB('db/' + config_name + '.json')
conf_data = { conf_data = {
"peer_data": get_peers(config_name), "peer_data": get_peers(config_name),
"name": config_name, "name": config_name,
@ -445,32 +549,39 @@ def add_peer(config_name):
public_key = data['public_key'] public_key = data['public_key']
allowed_ips = data['allowed_ips'] allowed_ips = data['allowed_ips']
keys = get_conf_peer_key(config_name) keys = get_conf_peer_key(config_name)
if type(keys) != list:
return config_name+" is not running."
if public_key in keys: if public_key in keys:
return "Key already exist." return "Key already exist."
else: else:
status = "" status = ""
try: try:
status = subprocess.check_output( status = subprocess.check_output(
"wg set " + config_name + " peer " + public_key + " allowed-ips " + allowed_ips, shell=True, "wg set " + config_name + " peer " + public_key + " allowed-ips " + allowed_ips, shell=True, stderr=subprocess.STDOUT)
stderr=subprocess.STDOUT)
status = subprocess.check_output("wg-quick save " + config_name, shell=True, stderr=subprocess.STDOUT) status = subprocess.check_output("wg-quick save " + config_name, shell=True, stderr=subprocess.STDOUT)
get_conf_peers_data(config_name)
db = TinyDB("db/" + config_name + ".json")
peers = Query()
db.update({"name": data['name']}, peers.id == public_key)
db.close()
return "true" return "true"
except subprocess.CalledProcessError as exc: except subprocess.CalledProcessError as exc:
return exc.output.strip() return exc.output.strip()
# return redirect('/configuration/'+config_name)
@app.route('/remove_peer/<config_name>', methods=['POST']) @app.route('/remove_peer/<config_name>', methods=['POST'])
def remove_peer(config_name): def remove_peer(config_name):
if get_conf_status(config_name) == "stopped": if get_conf_status(config_name) == "stopped":
return "Your need to turn on "+config_name+" first." return "Your need to turn on " + config_name + " first."
db = TinyDB("db/" + config_name + ".json") db = TinyDB("db/" + config_name + ".json")
peers = Query() peers = Query()
data = request.get_json() data = request.get_json()
delete_key = data['peer_id'] delete_key = data['peer_id']
keys = get_conf_peer_key(config_name) keys = get_conf_peer_key(config_name)
if type(keys) != list:
return config_name+" is not running."
if delete_key not in keys: if delete_key not in keys:
db.close()
return "This key does not exist" return "This key does not exist"
else: else:
try: try:
@ -478,6 +589,7 @@ def remove_peer(config_name):
stderr=subprocess.STDOUT) stderr=subprocess.STDOUT)
status = subprocess.check_output("wg-quick save " + config_name, shell=True, stderr=subprocess.STDOUT) status = subprocess.check_output("wg-quick save " + config_name, shell=True, stderr=subprocess.STDOUT)
db.remove(peers.id == delete_key) db.remove(peers.id == delete_key)
db.close()
return "true" return "true"
except subprocess.CalledProcessError as exc: except subprocess.CalledProcessError as exc:
return exc.output.strip() return exc.output.strip()
@ -490,8 +602,8 @@ def save_peer_name(config_name):
name = data['name'] name = data['name']
db = TinyDB("db/" + config_name + ".json") db = TinyDB("db/" + config_name + ".json")
peers = Query() peers = Query()
db.update({"name": name}, peers.id == id) db.update({"name": name}, peers.id == id)
db.close()
return id + " " + name return id + " " + name
@ -502,8 +614,12 @@ def get_peer_name(config_name):
db = TinyDB("db/" + config_name + ".json") db = TinyDB("db/" + config_name + ".json")
peers = Query() peers = Query()
result = db.search(peers.id == id) result = db.search(peers.id == id)
db.close()
return result[0]['name'] return result[0]['name']
def init_dashboard(): def init_dashboard():
# Set Default INI File # Set Default INI File
if not os.path.isfile("wg-dashboard.ini"): if not os.path.isfile("wg-dashboard.ini"):
@ -530,15 +646,21 @@ def init_dashboard():
config['Server']['auth_req'] = 'true' config['Server']['auth_req'] = 'true'
if 'version' not in config['Server'] or config['Server']['version'] != dashboard_version: if 'version' not in config['Server'] or config['Server']['version'] != dashboard_version:
config['Server']['version'] = dashboard_version config['Server']['version'] = dashboard_version
if 'dashboard_refresh_interval' not in config['Server']:
config['Server']['dashboard_refresh_interval'] = '15000'
config.write(open(dashboard_conf, "w")) config.write(open(dashboard_conf, "w"))
config.clear() config.clear()
def check_update(): def check_update():
conf = configparser.ConfigParser(strict=False) conf = configparser.ConfigParser(strict=False)
conf.read(dashboard_conf) conf.read(dashboard_conf)
data = urllib.request.urlopen("https://api.github.com/repos/donaldzou/wireguard-dashboard/releases").read() data = urllib.request.urlopen("https://api.github.com/repos/donaldzou/wireguard-dashboard/releases").read()
output = json.loads(data) output = json.loads(data)
if conf.get("Server", "version") == output[0]["tag_name"]: release = []
for i in output:
if i["prerelease"] == False: release.append(i)
if conf.get("Server", "version") == release[0]["tag_name"]:
return "false" return "false"
else: else:
return "true" return "true"
@ -554,4 +676,3 @@ if __name__ == "__main__":
wg_conf_path = config.get("Server", "wg_conf_path") wg_conf_path = config.get("Server", "wg_conf_path")
config.clear() config.clear()
app.run(host=app_ip, debug=False, port=app_port) app.run(host=app_ip, debug=False, port=app_port)

34
src/db/wg0.conf Normal file
View File

@ -0,0 +1,34 @@
[Interface]
Address = 10.200.200.1/24
SaveConfig = true
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
ListenPort = 51820
PrivateKey = 8DsSMli3okgUx5frKbFQ0fMW5ZMyqyxOdOW7+g21L18=
[Peer]
#Name = 10
PublicKey = Hw2sISygeuT4eQyubDdP0CA2RJHYcpHhBOnIn6riEA0=
AllowedIPs = 10.200.200.9/32, 10.200.200.2/32
Endpoint = 142.114.220.189:58403
[Peer]
PublicKey = 0pCjuGUyXIxTmCZCoFeVtMOgZFVkU9WPGilJxLpYVAI=
AllowedIPs = 10.200.200.3/32
Endpoint = 76.67.102.20:55349
[Peer]
PublicKey = sJrgj14BfToasB5EhQfUjw2xTj3FoU/lSca9bDx+2Ww=
AllowedIPs = 10.200.200.4/32
Endpoint = 175.0.140.3:59637
[Peer]
PublicKey = IxM4gsOWugRtQ0WmFnaAYUCVquTsSqxHiE7oqisKsRQ=
AllowedIPs = 10.200.200.5/32
Endpoint = 180.152.230.148:61618
[Peer]
PublicKey = TSsTB1NPTOHfqNkcNZbJQiz+XcJ32AudypDwa5ItmBM=
AllowedIPs = 10.200.200.6/32
Endpoint = 118.250.38.191:51820

View File

@ -1,3 +1,4 @@
Flask==1.1.2 Flask==1.1.2
tinydb==4.3.0 tinydb==4.3.0
ifcfg==0.21 ifcfg==0.21
icmplib==2.1.1

View File

@ -1 +0,0 @@
!function(a){"use strict";function l(t,e){this.$element=a(t),this.options=a.extend({},this.defaults(),e),this.render()}l.VERSION="3.6.0",l.DEFAULTS={on:"On",off:"Off",onstyle:"primary",offstyle:"light",size:"normal",style:"",width:null,height:null},l.prototype.defaults=function(){return{on:this.$element.attr("data-on")||l.DEFAULTS.on,off:this.$element.attr("data-off")||l.DEFAULTS.off,onstyle:this.$element.attr("data-onstyle")||l.DEFAULTS.onstyle,offstyle:this.$element.attr("data-offstyle")||l.DEFAULTS.offstyle,size:this.$element.attr("data-size")||l.DEFAULTS.size,style:this.$element.attr("data-style")||l.DEFAULTS.style,width:this.$element.attr("data-width")||l.DEFAULTS.width,height:this.$element.attr("data-height")||l.DEFAULTS.height}},l.prototype.render=function(){this._onstyle="btn-"+this.options.onstyle,this._offstyle="btn-"+this.options.offstyle;var t="large"===this.options.size||"lg"===this.options.size?"btn-lg":"small"===this.options.size||"sm"===this.options.size?"btn-sm":"mini"===this.options.size||"xs"===this.options.size?"btn-xs":"",e=a('<label for="'+this.$element.prop("id")+'" class="btn">').html(this.options.on).addClass(this._onstyle+" "+t),s=a('<label for="'+this.$element.prop("id")+'" class="btn">').html(this.options.off).addClass(this._offstyle+" "+t),o=a('<span class="toggle-handle btn btn-light">').addClass(t),i=a('<div class="toggle-group">').append(e,s,o),l=a('<div class="toggle btn" data-toggle="toggle" role="button">').addClass(this.$element.prop("checked")?this._onstyle:this._offstyle+" off").addClass(t).addClass(this.options.style);this.$element.wrap(l),a.extend(this,{$toggle:this.$element.parent(),$toggleOn:e,$toggleOff:s,$toggleGroup:i}),this.$toggle.append(i);var n=this.options.width||Math.max(e.outerWidth(),s.outerWidth())+o.outerWidth()/2,h=this.options.height||Math.max(e.outerHeight(),s.outerHeight());e.addClass("toggle-on"),s.addClass("toggle-off"),this.$toggle.css({width:n,height:h}),this.options.height&&(e.css("line-height",e.height()+"px"),s.css("line-height",s.height()+"px")),this.update(!0),this.trigger(!0)},l.prototype.toggle=function(){this.$element.prop("checked")?this.off():this.on()},l.prototype.on=function(t){if(this.$element.prop("disabled"))return!1;this.$toggle.removeClass(this._offstyle+" off").addClass(this._onstyle),this.$element.prop("checked",!0),t||this.trigger()},l.prototype.off=function(t){if(this.$element.prop("disabled"))return!1;this.$toggle.removeClass(this._onstyle).addClass(this._offstyle+" off"),this.$element.prop("checked",!1),t||this.trigger()},l.prototype.enable=function(){this.$toggle.removeClass("disabled"),this.$toggle.removeAttr("disabled"),this.$element.prop("disabled",!1)},l.prototype.disable=function(){this.$toggle.addClass("disabled"),this.$toggle.attr("disabled","disabled"),this.$element.prop("disabled",!0)},l.prototype.update=function(t){this.$element.prop("disabled")?this.disable():this.enable(),this.$element.prop("checked")?this.on(t):this.off(t)},l.prototype.trigger=function(t){this.$element.off("change.bs.toggle"),t||this.$element.change(),this.$element.on("change.bs.toggle",a.proxy(function(){this.update()},this))},l.prototype.destroy=function(){this.$element.off("change.bs.toggle"),this.$toggleGroup.remove(),this.$element.removeData("bs.toggle"),this.$element.unwrap()};var t=a.fn.bootstrapToggle;a.fn.bootstrapToggle=function(o){var i=Array.prototype.slice.call(arguments,1)[0];return this.each(function(){var t=a(this),e=t.data("bs.toggle"),s="object"==typeof o&&o;e||t.data("bs.toggle",e=new l(this,s)),"string"==typeof o&&e[o]&&"boolean"==typeof i?e[o](i):"string"==typeof o&&e[o]&&e[o]()})},a.fn.bootstrapToggle.Constructor=l,a.fn.toggle.noConflict=function(){return a.fn.bootstrapToggle=t,this},a(function(){a("input[type=checkbox][data-toggle^=toggle]").bootstrapToggle()}),a(document).on("click.bs.toggle","div[data-toggle^=toggle]",function(t){a(this).find("input[type=checkbox]").bootstrapToggle("toggle"),t.preventDefault()})}(jQuery);

View File

@ -162,3 +162,37 @@ body {
.switch:hover{ .switch:hover{
text-decoration: none text-decoration: none
} }
.btn-group-label:hover{
color: #007bff;
border-color: #007bff;
background: white;
}
.peer_data_group{
text-align: right;
}
@media (max-width: 768px) {
.peer_data_group{
text-align: left;
}
}
.index-switch{
text-align: right;
}
main{
margin-bottom: 3rem;
}
/*.add_btn{*/
/* position: fixed;*/
/* bottom: 1.75rem;*/
/* right: 1.75rem;*/
/* z-index: 1000;*/
/* padding: 0.75rem 1.5rem;*/
/* border-radius: 3rem;*/
/* font-size: 1rem;*/
/*}*/

68
src/static/tools.js Normal file
View File

@ -0,0 +1,68 @@
$(".ip_dropdown").change(function (){
$(".modal.show .btn").removeAttr("disabled")
});
$(".conf_dropdown").change(function (){
$(".modal.show .ip_dropdown").html('<option value="none" selected="selected" disabled>Loading...')
$.ajax({
url: "/get_ping_ip",
method: "POST",
data: "config="+$(this).children("option:selected").val(),
success: function (res){
$(".modal.show .ip_dropdown").html("")
$(".modal.show .ip_dropdown").append('<option value="none" selected="selected" disabled>Choose an IP')
$(".modal.show .ip_dropdown").append(res)
}
})
});
// Ping Tools
$(".send_ping").click(function (){
$(this).attr("disabled","disabled")
$(this).html("Pinging...")
$("#ping_modal .form-control").attr("disabled","disabled")
$.ajax({
method:"POST",
data: "ip="+$(':selected', $("#ping_modal .ip_dropdown")).val()+"&count="+$("#ping_modal .ping_count").val(),
url: "/ping_ip",
success: function (res){
$(".ping_result tbody").html("")
html = '<tr><th scope="row">Address</th><td>'+res['address']+'</td></tr>' +
'<tr><th scope="row">Is Alive</th><td>'+res['is_alive']+'</td></tr>' +
'<tr><th scope="row">Min RTT</th><td>'+res['min_rtt']+'ms</td></tr>' +
'<tr><th scope="row">Average RTT </th><td>'+res['avg_rtt']+'ms</td></tr>' +
'<tr><th scope="row">Max RTT</th><td>'+res['max_rtt']+'ms</td></tr>' +
'<tr><th scope="row">Package Sent</th><td>'+res['package_sent']+'</td></tr>' +
'<tr><th scope="row">Package Received</th><td>'+res['package_received']+'</td></tr>' +
'<tr><th scope="row">Package Loss</th><td>'+res['package_loss']+'</td></tr>'
$(".ping_result tbody").html(html)
$(".send_ping").removeAttr("disabled")
$(".send_ping").html("Ping")
$("#ping_modal .form-control").removeAttr("disabled")
}
})
});
// Traceroute Tools
$(".send_traceroute").click(function (){
$(this).attr("disabled","disabled")
$(this).html("Tracing...");
$("#traceroute_modal .form-control").attr("disabled","disabled")
$.ajax({
url: "/traceroute_ip",
method: "POST",
data: "ip="+$(':selected', $("#traceroute_modal .ip_dropdown")).val(),
success: function (res){
$(".traceroute_result tbody").html("");
for (i in res){
$(".traceroute_result tbody").append('<tr><th scope="row">'+res[i]['hop']+'</th><td>'+res[i]['ip']+'</td><td>'+res[i]['avg_rtt']+'</td><td>'+res[i]['min_rtt']+'</td><td>'+res[i]['max_rtt']+'</td></tr>')
}
$(".send_traceroute").removeAttr("disabled");
$(".send_traceroute").html("Traceroute");
$("#traceroute_modal .form-control").removeAttr("disabled")
}
})
})

View File

@ -1,14 +1,10 @@
<html> <html>
{% include "header.html" %} {% include "header.html" %}
<body> <body>
{% include "navbar.html" %} {% include "navbar.html" %}
<div class="container-fluid"> <div class="container-fluid">
{% include "sidebar.html" %} {% include "sidebar.html" %}
<div id="config_body"> <div id="config_body"></div>
</div>
</div> </div>
<div class="modal fade" id="add_modal" data-backdrop="static" data-keyboard="false" tabindex="-1" <div class="modal fade" id="add_modal" data-backdrop="static" data-keyboard="false" tabindex="-1"
aria-labelledby="staticBackdropLabel" aria-hidden="true"> aria-labelledby="staticBackdropLabel" aria-hidden="true">
@ -26,15 +22,19 @@
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<form> <form id="add_peer_form">
<div class="form-group"> <div class="form-group">
<label for="public_key">Public Key</label> <label for="public_key">Public Key<code>*</code></label>
<input type="text" class="form-control" id="public_key" aria-describedby="public_key"> <input type="text" class="form-control" id="public_key" aria-describedby="public_key">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="allowed_ips">Allowed IPs</label> <label for="allowed_ips">Allowed IPs<code>*</code></label>
<input type="text" class="form-control" id="allowed_ips"> <input type="text" class="form-control" id="allowed_ips">
</div> </div>
<div class="form-group">
<label for="allowed_ips">Name</label>
<input type="text" class="form-control" id="new_add_name">
</div>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
@ -107,12 +107,11 @@
</div> </div>
</div> </div>
</div> </div>
{% include "tools.html" %}
</body> </body>
{% include "footer.html" %} {% include "footer.html" %}
<script> <script>
$(".sb-{{conf_data['name']}}-url").addClass("active"); $(".sb-{{conf_data['name']}}-url").addClass("active");
function load_data(){ function load_data(){
$.ajax({ $.ajax({
method: "GET", method: "GET",
@ -123,6 +122,7 @@
async:false, async:false,
success: function (response){ success: function (response){
$("#config_body").html(response); $("#config_body").html(response);
$("[refresh-interval={{ dashboard_refresh_interval }}]").addClass("active")
} }
}) })
} }
@ -130,8 +130,22 @@
load_data(); load_data();
setInterval(function(){ setInterval(function(){
load_data(); load_data();
}, 15000) }, {{dashboard_refresh_interval}})
}); });
$("body").on("click", ".update_interval", function(){
$.ajax({
method:"POST",
data: "interval="+$(this).attr("refresh-interval"),
url: "/update_dashboard_refresh_interval",
success: function (res){
location.reload()
}
})
});
$("body").on("click", ".refresh", function (){
load_data();
});
</script> </script>
<script> <script>
$("body").on("click", ".switch", function (){ $("body").on("click", ".switch", function (){
@ -149,7 +163,9 @@
headers:{ headers:{
"Content-Type": "application/json" "Content-Type": "application/json"
}, },
data: JSON.stringify({"public_key":$("#public_key").val(), "allowed_ips": $("#allowed_ips").val()}), data: JSON.stringify({"public_key":$("#public_key").val(),
"allowed_ips": $("#allowed_ips").val(),
"name":$("#new_add_name").val()}),
success: function (response){ success: function (response){
if(response != "true"){ if(response != "true"){
$("#add_peer_alert").html(response+$("#add_peer_alert").html()); $("#add_peer_alert").html(response+$("#add_peer_alert").html());
@ -163,15 +179,18 @@
} }
}) })
var deleteModal = new bootstrap.Modal(document.getElementById('delete_modal'), {
keyboard: false
});
$("body").on("click", ".btn-delete-peer", function(){ $("body").on("click", ".btn-delete-peer", function(){
var peer_id = $(this).attr("id"); var peer_id = $(this).attr("id");
$("#delete_peer").attr("peer_id", peer_id); $("#delete_peer").attr("peer_id", peer_id);
deleteModal.toggle();
}) })
$(".btn-delete-peer").click(function(){
});
$("#delete_peer").click(function(){ $("#delete_peer").click(function(){
var peer_id = $(this).attr("peer_id"); var peer_id = $(this).attr("peer_id");
var config = $(this).attr("conf_id"); var config = $(this).attr("conf_id");
@ -183,24 +202,27 @@
}, },
data: JSON.stringify({"action": "delete", "peer_id": peer_id}), data: JSON.stringify({"action": "delete", "peer_id": peer_id}),
success: function (response){ success: function (response){
if(response != "true"){ if(response !== "true"){
$("#remove_peer_alert").html(response+$("#add_peer_alert").html()); $("#remove_peer_alert").html(response+$("#add_peer_alert").html());
$("#remove_peer_alert").removeClass("d-none"); $("#remove_peer_alert").removeClass("d-none");
} }
else{ else{
location.reload(); deleteModal.toggle();
} load_data();
$('#alertToast').toast('show');
$('#alertToast .toast-body').html("Peer deleted!");
}
} }
}) })
}); });
var settingModal = new bootstrap.Modal(document.getElementById('setting_modal'), {
var myModal = new bootstrap.Modal(document.getElementById('setting_modal'), {
keyboard: false keyboard: false
}) })
$("body").on("click", ".btn-setting-peer", function(){ $("body").on("click", ".btn-setting-peer", function(){
myModal.toggle(); settingModal.toggle();
var peer_id = $(this).attr("id"); var peer_id = $(this).attr("id");
$("#save_peer_name").attr("peer_id", peer_id); $("#save_peer_name").attr("peer_id", peer_id);
$.ajax({ $.ajax({
@ -233,7 +255,7 @@
}, },
data: JSON.stringify({id: peer_id, name: $("#peer_name_textbox").val()}), data: JSON.stringify({id: peer_id, name: $("#peer_name_textbox").val()}),
success: function (response){ success: function (response){
myModal.toggle(); settingModal.toggle();
load_data(); load_data();
$('#alertToast').toast('show'); $('#alertToast').toast('show');
$('#alertToast .toast-body').html("Name Saved!"); $('#alertToast .toast-body').html("Name Saved!");

View File

@ -6,3 +6,4 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js" <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js"
integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s" integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s"
crossorigin="anonymous"></script> crossorigin="anonymous"></script>
<script src="{{ url_for('static',filename='tools.js') }}"></script>

View File

@ -1,4 +1,4 @@
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4 mt-4"> <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4 mt-4 mb-4">
<div class="info mt-4"> <div class="info mt-4">
<div class="row"> <div class="row">
<div class="col"> <div class="col">
@ -48,11 +48,17 @@
</div> </div>
</div> </div>
<hr> <hr>
<div class="button-div" style="text-align: right;"> <div class="button-div mb-3" style="text-align: right;">
<button type="button" class="btn btn-outline-primary btn-sm mb-3" data-toggle="modal" data-target="#add_modal"> <div class="btn-group" role="group" aria-label="Basic example">
<strong>ADD PEER</strong> <button type="button" class="btn btn-outline-primary btn-sm btn-group-label refresh"><i class="bi bi-arrow-repeat"></i></button>
<button type="button" class="btn btn-outline-primary btn-sm update_interval" refresh-interval="5000">5s</button>
<button type="button" class="btn btn-outline-primary btn-sm update_interval" refresh-interval="10000">10s</button>
<button type="button" class="btn btn-outline-primary btn-sm update_interval" refresh-interval="30000">30s</button>
<button type="button" class="btn btn-outline-primary btn-sm update_interval" refresh-interval="60000">1m</button>
</div>
<button type="button" class="btn btn-primary add_btn btn-sm" data-toggle="modal" data-target="#add_modal">
<i class="bi bi-plus-circle-fill"></i> PEER
</button> </button>
</div> </div>
</div> </div>
@ -60,7 +66,7 @@
<div class="card mb-3"> <div class="card mb-3">
<div class="card-header"> <div class="card-header">
<div class="row"> <div class="row">
<div class="col"> <div class="col-sm">
<div class="card-header-body "> <div class="card-header-body ">
{% if not i['name']%} {% if not i['name']%}
{{ "Untitled Peer" }} {{ "Untitled Peer" }}
@ -70,7 +76,7 @@
<span class="dot dot-{{i['status']}}"></span> <span class="dot dot-{{i['status']}}"></span>
</div> </div>
</div> </div>
<div class="col" style="text-align: right"> <div class="col-sm peer_data_group">
<p class="text-primary" style="text-transform: uppercase; display: inline-block; margin-bottom: 0; margin-right: 1rem"><i class="bi bi-arrow-down-right"></i> {{i['total_receive']}} GB</p> <p class="text-primary" style="text-transform: uppercase; display: inline-block; margin-bottom: 0; margin-right: 1rem"><i class="bi bi-arrow-down-right"></i> {{i['total_receive']}} GB</p>
<p class="text-success" style="text-transform: uppercase; display: inline-block; margin-bottom: 0"><i class="bi bi-arrow-up-right"></i> {{i['total_sent']}} GB</p> <p class="text-success" style="text-transform: uppercase; display: inline-block; margin-bottom: 0"><i class="bi bi-arrow-up-right"></i> {{i['total_sent']}} GB</p>
</div> </div>
@ -81,10 +87,6 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="row"> <div class="row">
{# <div class="col-sm">#}
{# <small class="text-muted"><strong>STATUS</strong></small>#}
{# <h6 style="text-transform: uppercase;">{{i['status']}}<span class="dot dot-{{i['status']}}"></span></h6>#}
{# </div>#}
<div class="col-sm"> <div class="col-sm">
<small class="text-muted"><strong>PEER</strong></small> <small class="text-muted"><strong>PEER</strong></small>
<h6><samp class="ml-auto">{{i['id']}}</samp></h6> <h6><samp class="ml-auto">{{i['id']}}</samp></h6>
@ -105,11 +107,10 @@
</div> </div>
<div class="w-100"></div> <div class="w-100"></div>
<div class="col-sm"> <div class="col-sm">
<!-- <small class="text-muted"><strong>ACTION</strong></small> -->
<div class="button-group"> <div class="button-group">
<hr> <hr>
<button type="button" class="btn btn-outline-primary btn-setting-peer btn-control" id="{{i['id']}}" data-toggle="modal"><i class="bi bi-gear-fill"></i></button> <button type="button" class="btn btn-outline-primary btn-setting-peer btn-control" id="{{i['id']}}" data-toggle="modal"><i class="bi bi-gear-fill"></i></button>
<button type="button" class="btn btn-outline-danger btn-delete-peer btn-control" id="{{i['id']}}" data-toggle="modal" data-target="#delete_modal"><i class="bi bi-x-circle-fill"></i></button> <button type="button" class="btn btn-outline-danger btn-delete-peer btn-control" id="{{i['id']}}" data-toggle="modal"><i class="bi bi-x-circle-fill"></i></button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -5,9 +5,6 @@
<link rel="icon" href="{{ url_for('static',filename='logo.png') }}"/> <link rel="icon" href="{{ url_for('static',filename='logo.png') }}"/>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='dashboard.css') }}"> <link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='dashboard.css') }}">
<link href="https://cdn.jsdelivr.net/gh/gitbrent/bootstrap4-toggle@3.6.1/css/bootstrap4-toggle.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.0.1/dist/chart.min.js"></script>
</head> </head>

View File

@ -4,8 +4,11 @@
{% include "navbar.html" %} {% include "navbar.html" %}
<div class="container-fluid"> <div class="container-fluid">
{% include "sidebar.html" %} {% include "sidebar.html" %}
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4"> <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4 mb-4">
<h1 class="pb-4 mt-4">Home</h1> <h1 class="pb-4 mt-4">Home</h1>
{% if conf == [] %}
<p class="text-muted">You don't have any WireGuard configurations yet. Please check the configuration folder or change it in "Settings". By default the folder is "/etc/wireguard".</p>
{% endif %}
{% for i in conf%} {% for i in conf%}
<div class="card mt-3"> <div class="card mt-3">
<div class="card-body"> <div class="card-body">
@ -24,7 +27,7 @@
<small class="text-muted"><strong>PUBLIC KEY</strong></small> <small class="text-muted"><strong>PUBLIC KEY</strong></small>
<h6 style="text-transform: uppercase; margin:0 !important;"><samp>{{i['public_key']}}</samp></h6> <h6 style="text-transform: uppercase; margin:0 !important;"><samp>{{i['public_key']}}</samp></h6>
</div> </div>
<div class="col-md"> <div class="col-md index-switch">
{% if i['checked'] == "checked" %} {% if i['checked'] == "checked" %}
<a href="#" id="{{i['conf']}}" {{i['checked']}} class="switch text-primary tt"><i class="bi bi-toggle2-on"></i></a> <a href="#" id="{{i['conf']}}" {{i['checked']}} class="switch text-primary tt"><i class="bi bi-toggle2-on"></i></a>
{% else %} {% else %}
@ -40,6 +43,7 @@
{%endfor%} {%endfor%}
</main> </main>
</div> </div>
{% include "tools.html" %}
</body> </body>
{% include "footer.html" %} {% include "footer.html" %}
<script> <script>

View File

@ -26,7 +26,9 @@
<form action="/update_wg_conf_path" method="post" class="update_wg_conf_path"> <form action="/update_wg_conf_path" method="post" class="update_wg_conf_path">
<div class="form-group"> <div class="form-group">
<label for="username">Path</label> <label for="username">Path</label>
<input type="text" class="form-control mb-4" id="wg_conf_path" name="wg_conf_path" value="{{ wg_conf_path }}"> <input type="text" class="form-control mb-4" id="wg_conf_path" name="wg_conf_path" value="{{ wg_conf_path }}">
<p>Remember to remove <code>/</code> at the end of your path. e.g <code>/etc/wireguard</code></p>
<button class="btn btn-danger change_path">Update Path & Restart Dashboard</button> <button class="btn btn-danger change_path">Update Path & Restart Dashboard</button>
</div> </div>
</form> </form>

View File

@ -8,7 +8,9 @@
<li class="nav-item"><a class="nav-link sb-settings-url" href="/settings">Settings</a></li> <li class="nav-item"><a class="nav-link sb-settings-url" href="/settings">Settings</a></li>
{% endif %} {% endif %}
{% if session['update'] == "true" %} {% if session['update'] == "true" %}
<li class="nav-item sb-update-li"><a class="nav-link sb-update-url" href="https://github.com/donaldzou/wireguard-dashboard/#-how-to-update-the-dashboard">New Update Available!<span class="dot dot-running"></span></a></li> <li class="nav-item sb-update-li">
<a class="nav-link sb-update-url" href="https://github.com/donaldzou/wireguard-dashboard#-how-to-update-the-dashboard">New Update Available!<span class="dot dot-running"></span></a>
</li>
{% endif %} {% endif %}
</ul> </ul>
<hr> <hr>
@ -21,6 +23,16 @@
{%endfor%} {%endfor%}
</ul> </ul>
<hr> <hr>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Tools</span>
</h6>
<ul class="nav flex-column">
<ul class="nav flex-column">
<li class="nav-item"><a class="nav-link" data-toggle="modal" data-target="#ping_modal" href="#">Ping</a></li>
<li class="nav-item"><a class="nav-link" data-toggle="modal" data-target="#traceroute_modal" href="#">Traceroute</a></li>
</ul>
</ul>
<hr>
{% if "username" in session %} {% if "username" in session %}
<ul class="nav flex-column"> <ul class="nav flex-column">
<li class="nav-item"><a class="nav-link text-danger" href="/signout" style="font-weight: bold">Sign Out</a></li> <li class="nav-item"><a class="nav-link text-danger" href="/signout" style="font-weight: bold">Sign Out</a></li>

View File

@ -29,10 +29,6 @@
<label for="password" class="text-left">Password</label> <label for="password" class="text-left">Password</label>
<input type="password" class="form-control" id="password" name="password" required> <input type="password" class="form-control" id="password" name="password" required>
</div> </div>
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" id="exampleCheck1">
<label class="form-check-label" for="exampleCheck1">Keep you login</label>
</div>
<button type="submit" class="btn btn-dark" style="width: 100%;">Sign In</button> <button type="submit" class="btn btn-dark" style="width: 100%;">Sign In</button>
</form> </form>
</div> </div>

121
src/templates/tools.html Normal file
View File

@ -0,0 +1,121 @@
<div class="modal fade" id="ping_modal" data-backdrop="static" data-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Ping</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-sm">
<div class="mb-3">
<small>Configuration</small>
<select class="form-control mt-2 conf_dropdown">
<option value="none" selected="selected" disabled>Select Configuration</option>
{% for i in conf%}
<option value="{{ i['conf'] }}">{{ i['conf'] }}</option>
{%endfor%}
</select>
</div>
</div>
<div class="col-sm">
<div class="mb-3">
<small>IP</small>
<select class="form-control mt-2 ip_dropdown">
<option value="none" selected="selected" disabled>Choose an IP</option>
</select>
</div>
</div>
<div class="col-sm">
<div class="mb-3">
<small>Ping Count</small>
<input type="number" class="form-control mt-2 ping_count" min=1 value=4>
</div>
</div>
</div>
<hr>
<div class="ping_result">
<table class="table">
<tbody></tbody>
</table>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary send_ping" disabled>Ping</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="traceroute_modal" data-backdrop="static" data-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Traceroute</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-sm">
<div class="mb-3">
<small>Configuration</small>
<select class="form-control mt-2 conf_dropdown">
<option value="none" selected="selected" disabled>Select Configuration</option>
{% for i in conf%}
<option value="{{ i['conf'] }}">{{ i['conf'] }}</option>
{%endfor%}
</select>
</div>
</div>
<div class="col-sm">
<div class="mb-3">
<small>IP</small>
<select class="form-control mt-2 ip_dropdown">
<option value="none" selected="selected" disabled>Choose an IP</option>
</select>
</div>
</div>
</div>
<button class="btn btn-primary send_traceroute" disabled>Traceroute</button>
<hr>
<div class="traceroute_result">
<table class="table table-sm">
<thead>
<tr>
<th scope="col">Hop</th>
<th scope="col">IP</th>
<th scope="col">Avg RTT</th>
<th scope="col">Min RTT</th>
<th scope="col">Max RTT</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="update_modal" data-backdrop="static" data-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">How to update dashboard</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<pre><code>$ sudo sh wgd.sh stop</code><br><code>$ sudo sh wgd.sh update</code><br><code>$ sudo sh wgd.sh start</code></pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>

View File

@ -4,7 +4,7 @@ app_name="dashboard.py"
dashes='------------------------------------------------------------' dashes='------------------------------------------------------------'
help () { help () {
printf "<Wireguard Dashboard> by Donald Zou - https://github.com/donaldzou \n" printf "<Wireguard Dashboard> by Donald Zou - https://github.com/donaldzou \n"
printf "Usage: sh wg-dashboard.sh <option>" printf "Usage: sh wgd.sh <option>"
printf "\n \n" printf "\n \n"
printf "Available options: \n" printf "Available options: \n"
printf " start: To start Wireguard Dashboard.\n" printf " start: To start Wireguard Dashboard.\n"
@ -25,6 +25,7 @@ check_wgd_status(){
} }
start_wgd () { start_wgd () {
printf "%s" "$PLATFORM"
printf "Starting Wireguard Dashboard in the background. \n" printf "Starting Wireguard Dashboard in the background. \n"
if [ ! -d "log" ] if [ ! -d "log" ]
then mkdir "log" then mkdir "log"
@ -44,7 +45,6 @@ start_wgd_debug() {
} }
update_wgd() { update_wgd() {
new_ver=$(python3 -c "import json; import urllib.request; data = urllib.request.urlopen('https://api.github.com/repos/donaldzou/wireguard-dashboard/releases').read(); output = json.loads(data);print(output[0]['tag_name'])") new_ver=$(python3 -c "import json; import urllib.request; data = urllib.request.urlopen('https://api.github.com/repos/donaldzou/wireguard-dashboard/releases').read(); output = json.loads(data);print(output[0]['tag_name'])")
printf "%s\n" "$dashes" printf "%s\n" "$dashes"
printf "Are you sure you want to update to the %s? (Y/N): " "$new_ver" printf "Are you sure you want to update to the %s? (Y/N): " "$new_ver"