1
0
mirror of https://github.com/donaldzou/WGDashboard.git synced 2024-11-19 22:10:10 +01:00

Merge pull request #17 from donaldzou/testing

v2.0-beta1 Merge
This commit is contained in:
Donald Zou 2021-05-04 01:37:27 -04:00 committed by GitHub
commit cc7e6f852d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 629 additions and 284 deletions

2
.gitignore vendored
View File

@ -5,3 +5,5 @@ wg.db
*.json *.json
.idea .idea
src/test.py src/test.py
tmp
__pycache__

View File

@ -59,7 +59,7 @@ $ python3 -m pip install flask tinydb
**2. Install Wireguard Dashboard** **2. Install Wireguard Dashboard**
``` ```
$ git clone https://github.com/donaldzou/Wireguard-Dashboard.git $ git clone -b v1.1.2 https://github.com/donaldzou/Wireguard-Dashboard.git
$ cd Wireguard-Dashboard/src $ cd Wireguard-Dashboard/src
$ python3 dashboard.py $ python3 dashboard.py
``` ```

View File

@ -1,2 +1,4 @@
Flask==1.1.2 Flask==1.1.2
tinydb==4.3.0 tinydb==4.3.0
ifcfg==0.21
configparser==5.0.2

View File

@ -1,14 +1,23 @@
# Python Built-in Library
import os import os
from flask import Flask, request, render_template, redirect, url_for from flask import Flask, request, render_template, redirect, url_for, session, abort
import subprocess import subprocess
from datetime import datetime, date, time, timedelta from datetime import datetime, date, time, timedelta
from operator import itemgetter from operator import itemgetter
from tinydb import TinyDB, Query import secrets
import hashlib
import json, urllib.request
# PIP installed library
import ifcfg
from tinydb import TinyDB, Query
import configparser
dashboard_conf = 'wg-dashboard.ini'
conf_location = "/etc/wireguard" conf_location = "/etc/wireguard"
app = Flask("Wireguard Dashboard") app = Flask("Wireguard Dashboard")
app.secret_key = secrets.token_urlsafe(16)
app.config['TEMPLATES_AUTO_RELOAD'] = True app.config['TEMPLATES_AUTO_RELOAD'] = True
css = ""
conf_data = {} conf_data = {}
@ -41,21 +50,15 @@ def get_conf_running_peer_number(config_name):
count += 2 count += 2
return running return running
def read_conf_file(config_name):
def get_conf_peers_data(config_name):
db = TinyDB('db/' + config_name + '.json')
peers = Query()
peer_data = {}
# Read Configuration File Start # Read Configuration File Start
conf_location = "/etc/wireguard/"+config_name+".conf" conf_location = "/etc/wireguard/" + 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 = {
"Interface": {}, "Interface": {},
"Peers": [] "Peers": []
} }
interface = []
peers_start = 0 peers_start = 0
for i in range(len(file)): for i in range(len(file)):
if file[i] == "[Peer]": if file[i] == "[Peer]":
@ -79,20 +82,19 @@ def get_conf_peers_data(config_name):
if len(tmp) == 2: if len(tmp) == 2:
conf_peer_data["Peers"][peer][tmp[0]] = tmp[1] conf_peer_data["Peers"][peer][tmp[0]] = tmp[1]
# Read Configuration File End # Read Configuration File End
return conf_peer_data
# Get key
try:
peer_key = subprocess.check_output("wg show " + config_name + " peers", shell=True) def get_conf_peers_data(config_name):
except Exception: db = TinyDB('db/' + config_name + '.json')
return "stopped" peers = Query()
peer_key = peer_key.decode("UTF-8").split() conf_peer_data = read_conf_file(config_name)
now = datetime.now()
current_time = now.strftime("%Y-%m-%d %H:%M:%S") for i in conf_peer_data['Peers']:
for i in peer_key: if not db.search(peers.id == i['PublicKey']):
peer_data[i] = {}
if not db.search(peers.id == i):
db.insert({ db.insert({
"id": i, "id": i['PublicKey'],
"name": "", "name": "",
"total_receive": 0, "total_receive": 0,
"total_sent": 0, "total_sent": 0,
@ -104,6 +106,28 @@ def get_conf_peers_data(config_name):
"traffic": [] "traffic": []
}) })
# 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
# Get transfer # Get transfer
try: try:
data_usage = subprocess.check_output("wg show " + config_name + " transfer", shell=True) data_usage = subprocess.check_output("wg show " + config_name + " transfer", shell=True)
@ -112,19 +136,31 @@ def get_conf_peers_data(config_name):
data_usage = data_usage.decode("UTF-8").split() data_usage = data_usage.decode("UTF-8").split()
count = 0 count = 0
for i in range(int(len(data_usage) / 3)): for i in range(int(len(data_usage) / 3)):
db.update({"total_receive": round(int(data_usage[count + 1]) / (1024 ** 3), 4), cur_i = db.search(peers.id == data_usage[count])
"total_sent": round(int(data_usage[count + 2]) / (1024 ** 3), 4), total_sent = cur_i[0]['total_sent']
"total_data": round((int(data_usage[count + 2]) + int(data_usage[count + 1])) / (1024 ** 3), 4)}, total_receive = cur_i[0]['total_receive']
peers.id == data_usage[count]) cur_total_sent = round(int(data_usage[count + 2]) / (1024 ** 3), 4)
peer_data[data_usage[count]]['total_receive'] = round(int(data_usage[count + 1]) / (1024 ** 3), 4) cur_total_receive = round(int(data_usage[count + 1]) / (1024 ** 3), 4)
peer_data[data_usage[count]]['total_sent'] = round(int(data_usage[count + 2]) / (1024 ** 3), 4) if cur_i[0]["status"] == "running":
peer_data[data_usage[count]]['total_data'] = round( if total_sent <= cur_total_sent:
(int(data_usage[count + 2]) + int(data_usage[count + 1])) / (1024 ** 3), 4) total_sent = cur_total_sent
traffic = db.search(peers.id == data_usage[count])[0]['traffic'] else: total_sent += cur_total_sent
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)}) if total_receive <= cur_total_receive:
db.update({"traffic": traffic}, peers.id == data_usage[count]) 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 count += 3
# Get endpoint # Get endpoint
try: try:
data_usage = subprocess.check_output("wg show " + config_name + " endpoints", shell=True) data_usage = subprocess.check_output("wg show " + config_name + " endpoints", shell=True)
@ -134,41 +170,14 @@ def get_conf_peers_data(config_name):
count = 0 count = 0
for i in range(int(len(data_usage) / 2)): for i in range(int(len(data_usage) / 2)):
db.update({"endpoint": data_usage[count + 1]}, peers.id == data_usage[count]) db.update({"endpoint": data_usage[count + 1]}, peers.id == data_usage[count])
peer_data[data_usage[count]]['endpoint'] = data_usage[count + 1]
count += 2
# 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]))
status = ""
if minus < b:
peer_data[data_usage[count]]['status'] = "running"
status = "running"
else:
peer_data[data_usage[count]]['status'] = "stopped"
status = "stopped"
if (int(data_usage[count + 1]) > 0):
db.update({"latest_handshake": str(minus).split(".")[0], "status": status}, peers.id == data_usage[count])
peer_data[data_usage[count]]['latest_handshake'] = str(minus).split(".")[0]
else:
db.update({"latest_handshake": "(None)", "status": status}, peers.id == data_usage[count])
peer_data[data_usage[count]]['latest_handshake'] = "(None)"
count += 2 count += 2
# Get allowed ip # Get allowed ip
for i in conf_peer_data["Peers"]: for i in conf_peer_data["Peers"]:
db.update({"allowed_ip":i.get('AllowedIPs', '(None)')}, peers.id == i["PublicKey"]) db.update({"allowed_ip":i.get('AllowedIPs', '(None)')}, peers.id == i["PublicKey"])
def getdb(config_name):
def get_peers(config_name):
get_conf_peers_data(config_name) get_conf_peers_data(config_name)
db = TinyDB('db/' + config_name + '.json') db = TinyDB('db/' + config_name + '.json')
result = db.all() result = db.all()
@ -177,53 +186,39 @@ def getdb(config_name):
def get_conf_pub_key(config_name): def get_conf_pub_key(config_name):
try: conf = configparser.ConfigParser(strict=False)
pub_key = subprocess.check_output("wg show " + config_name + " public-key", shell=True, conf.read(conf_location+"/"+config_name+".conf")
stderr=subprocess.STDOUT) pri = conf.get("Interface", "PrivateKey")
except Exception: pub = subprocess.check_output("echo '" + pri + "' | wg pubkey", shell=True)
return "stopped" conf.clear()
return pub_key.decode("UTF-8") return pub.decode().strip("\n")
def get_conf_listen_port(config_name): def get_conf_listen_port(config_name):
try: conf = configparser.ConfigParser(strict=False)
pub_key = subprocess.check_output("wg show " + config_name + " listen-port", shell=True, conf.read(conf_location + "/" + config_name + ".conf")
stderr=subprocess.STDOUT) port = conf.get("Interface", "ListenPort")
except Exception: conf.clear()
return "stopped" return port
return pub_key.decode("UTF-8")
def get_conf_total_data(config_name): def get_conf_total_data(config_name):
try: db = TinyDB('db/' + config_name + '.json')
data_usage = subprocess.check_output("wg show " + config_name + " transfer", shell=True,
stderr=subprocess.STDOUT)
except Exception:
return "stopped"
data_usage = data_usage.decode("UTF-8").split()
count = 0
upload_total = 0 upload_total = 0
download_total = 0 download_total = 0
total = 0 for i in db.all():
for i in range(int(len(data_usage) / 3)): upload_total += round(i['total_sent'],4)
upload_total += int(data_usage[count + 1]) download_total += round(i['total_receive'],4)
download_total += int(data_usage[count + 2]) total = round(upload_total + download_total, 4)
count += 3
total = round(((((upload_total + download_total) / 1024) / 1024) / 1024), 4)
upload_total = round(((((upload_total) / 1024) / 1024) / 1024), 4)
download_total = round(((((download_total) / 1024) / 1024) / 1024), 4)
return [total, upload_total, download_total] return [total, upload_total, download_total]
def get_conf_status(config_name): def get_conf_status(config_name):
try: ifconfig = dict(ifcfg.interfaces().items())
status = subprocess.check_output("wg show " + config_name, shell=True, stderr=subprocess.STDOUT) if config_name in ifconfig.keys():
except Exception:
return "stopped"
else:
return "running" return "running"
else:
return "stopped"
def get_conf_list(): def get_conf_list():
@ -233,32 +228,154 @@ 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('status')) conf = sorted(conf, key=itemgetter('conf'))
return conf return conf
def get_running_conf_list():
conf = []
for i in os.listdir(conf_location):
if not i.startswith('.'):
if ".conf" in i:
i = i.replace('.conf', '')
if get_conf_status(i) == "running":
conf.append(i)
return conf
@app.before_request
def auth_req():
conf = configparser.ConfigParser(strict=False)
conf.read(dashboard_conf)
req = conf.get("Server", "auth_req")
if req == "true":
if '/static/' not in request.path and \
request.endpoint != "signin" and \
request.endpoint != "signout" and \
request.endpoint != "auth" and \
"username" not in session:
print(request.path)
print("not loggedin")
return redirect(url_for("signin"))
else:
if request.endpoint in ['signin', 'signout', 'auth', 'settings', 'update_acct', 'update_pwd', 'update_app_ip_port']:
return redirect(url_for("index"))
@app.route('/signin', methods=['GET'])
def signin():
message = ""
if "message" in session:
message = session['message']
session.pop("message")
return render_template('signin.html', message=message)
@app.route('/signout', methods=['GET'])
def signout():
if "username" in session:
session.pop("username")
message = "Sign out successfully!"
return render_template('signin.html', message=message)
@app.route('/settings', methods=['GET'])
def settings():
message = ""
status = ""
config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf)
if "message" in session and "message_status" in session:
message = session['message']
status = session['message_status']
session.pop("message")
session.pop("message_status")
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)
@app.route('/auth', methods=['POST'])
def auth():
config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf)
password = hashlib.sha256(request.form['password'].encode())
if password.hexdigest() == config["Account"]["password"] and request.form['username'] == config["Account"]["username"]:
session['username'] = request.form['username']
config.clear()
return redirect(url_for("index"))
else:
session['message'] = "Username or Password is correct."
config.clear()
return redirect(url_for("signin"))
@app.route('/update_acct', methods=['POST'])
def update_acct():
config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf)
config.set("Account", "username", request.form['username'])
try:
config.write(open(dashboard_conf, "w"))
session['message'] = "Username update successfully!"
session['message_status'] = "success"
session['username'] = request.form['username']
config.clear()
return redirect(url_for("settings"))
except Exception:
session['message'] = "Username update failed."
session['message_status'] = "danger"
config.clear()
return redirect(url_for("settings"))
@app.route('/update_pwd', methods=['POST'])
def update_pwd():
config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf)
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():
config.set("Account", "password", hashlib.sha256(request.form['repnewpass'].encode()).hexdigest())
try:
config.write(open(dashboard_conf, "w"))
session['message'] = "Password update successfully!"
session['message_status'] = "success"
config.clear()
return redirect(url_for("settings"))
except Exception:
session['message'] = "Password update failed"
session['message_status'] = "danger"
config.clear()
return redirect(url_for("settings"))
else:
session['message'] = "Your New Password does not match."
session['message_status'] = "danger"
config.clear()
return redirect(url_for("settings"))
else:
session['message'] = "Your Password does not match."
session['message_status'] = "danger"
config.clear()
return redirect(url_for("settings"))
@app.route('/update_app_ip_port', methods=['POST'])
def update_app_ip_port():
config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf)
config.set("Server", "app_ip", request.form['app_ip'])
config.set("Server", "app_port", request.form['app_port'])
config.write(open(dashboard_conf, "w"))
config.clear()
os.system('bash wgd.sh restart')
@app.route('/check_update_dashboard', methods=['GET'])
def check_update_dashboard():
conf = configparser.ConfigParser(strict=False)
conf.read(dashboard_conf)
data = urllib.request.urlopen("https://api.github.com/repos/donaldzou/wireguard-dashboard/releases").read()
output = json.loads(data)
if conf.get("Server", "version") == output[0]["tag_name"]:
return "false"
else:
return "true"
@app.route('/', methods=['GET']) @app.route('/', methods=['GET'])
def index(): def index():
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 = {
@ -267,34 +384,37 @@ def conf(config_name):
"checked": "" "checked": ""
} }
if conf_data['status'] == "stopped": if conf_data['status'] == "stopped":
return redirect('/') 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) return render_template('configuration.html', conf=get_conf_list(), conf_data=conf_data)
@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') db = TinyDB('db/' + config_name + '.json')
conf_data = { conf_data = {
"peer_data": get_peers(config_name),
"name": config_name, "name": config_name,
"status": get_conf_status(config_name), "status": get_conf_status(config_name),
"total_data_usage": get_conf_total_data(config_name), "total_data_usage": get_conf_total_data(config_name),
"public_key": get_conf_pub_key(config_name), "public_key": get_conf_pub_key(config_name),
"listen_port": get_conf_listen_port(config_name), "listen_port": get_conf_listen_port(config_name),
"peer_data": getdb(config_name),
"running_peer": get_conf_running_peer_number(config_name), "running_peer": get_conf_running_peer_number(config_name),
} }
if conf_data['status'] == "stopped": if conf_data['status'] == "stopped":
return redirect('/') # return redirect('/')
conf_data['checked'] = "nope"
else: else:
conf_data['checked'] = "checked" conf_data['checked'] = "checked"
return render_template('get_conf.html', conf=get_conf_list(), conf_data=conf_data) return render_template('get_conf.html', conf=get_conf_list(), conf_data=conf_data)
@app.route('/switch/<config_name>', methods=['GET']) @app.route('/switch/<config_name>', methods=['GET'])
def switch(config_name): def switch(config_name):
if "username" not in session:
print("not loggedin")
return redirect(url_for("signin"))
status = get_conf_status(config_name) status = get_conf_status(config_name)
if status == "running": if status == "running":
try: try:
@ -327,7 +447,6 @@ def add_peer(config_name):
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) # return redirect('/configuration/'+config_name)
@ -371,10 +490,11 @@ def get_peer_name(config_name):
peers = Query() peers = Query()
result = db.search(peers.id == id) result = db.search(peers.id == id)
return result[0]['name'] return result[0]['name']
# db.update({"name": name}, peers.id == id)
if __name__ == "__main__": if __name__ == "__main__":
app.run(host='0.0.0.0', debug=False, port=10086) config = configparser.ConfigParser(strict=False)
# for i in get_running_conf_list(): config.read('wg-dashboard.ini')
# p = Process(target=get_conf_peers_data, args=(i,)) app_ip = config.get("Server", "app_ip")
# p.start() app_port = config.get("Server", "app_port")
config.clear()
app.run(host=app_ip, debug=False, port=app_port)

View File

View File

@ -22,11 +22,11 @@ body {
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1); box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
} }
@media (max-width: 767.98px) { /*@media (max-width: 767.98px) {*/
.sidebar { /* .sidebar {*/
top: 5rem; /* top: 5rem;*/
} /* }*/
} /*}*/
.sidebar-sticky { .sidebar-sticky {
position: relative; position: relative;
@ -47,6 +47,11 @@ body {
.sidebar .nav-link { .sidebar .nav-link {
font-weight: 500; font-weight: 500;
color: #333; color: #333;
transition: 0.2s cubic-bezier(0.82, -0.07, 0, 1.01);
}
.nav-link:hover {
padding-left: 30px;
} }
.sidebar .nav-link .feather { .sidebar .nav-link .feather {
@ -140,3 +145,20 @@ body {
.btn-setting-peer:hover{ .btn-setting-peer:hover{
color:#007bff color:#007bff
} }
.login-container{
padding: 2rem;
}
@media (max-width: 992px){
.card-col{
margin-bottom: 1rem;
}
}
.switch{
font-size: 2rem;
}
.switch:hover{
text-decoration: none
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -1,45 +1,11 @@
<html> <html>
<head> {% include "header.html" %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Wireguard Dashboard</title>
<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 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">
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.0.1/dist/chart.min.js"></script>
</head>
<body> <body>
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow"> {% include "navbar.html" %}
<a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="/">Wireguard Dashboard</a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse"
data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</nav>
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> {% include "sidebar.html" %}
<div class="row">
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
<div class="sidebar-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item"><a class="nav-link" href="/">Home</a></li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Configurations</span>
</h6>
<ul class="nav flex-column">
{% for i in conf%}
<li class="nav-item"><a class="nav-link" href="/configuration/{{i['conf']}}">{{i['conf']}}</a></li>
{%endfor%}
</ul>
</div>
</nav>
</div>
</div>
<div id="config_body"> <div id="config_body">
</div> </div>
@ -150,6 +116,9 @@
integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s" integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s"
crossorigin="anonymous"></script> crossorigin="anonymous"></script>
<script> <script>
$(".sb-{{conf_data['name']}}-url").addClass("active");
function load_data(){ function load_data(){
$.ajax({ $.ajax({
method: "GET", method: "GET",
@ -176,16 +145,16 @@
}); });
</script> </script>
<script> <script>
$('.switch').change(function() { $("body").on("click", ".switch", function (){
if ($(this).prop('checked') == false){ if ($(this).prop('checked') === true){
if (confirm('Are you sure you want to turn off this connection?')){ if (confirm('Are you sure you want to turn off this connection?')){
location.replace("/switch/"+$(this).attr('id')); location.replace("/switch/"+$(this).attr('id'))
} }
} }
else{ else{
location.replace("/switch/"+$(this).attr('id')); location.replace("/switch/"+$(this).attr('id'))
} }
}); })
$("#save_peer").click(function(){ $("#save_peer").click(function(){
if ($("#allowed_ips") != "" && $("#public_key") != ""){ if ($("#allowed_ips") != "" && $("#public_key") != ""){
var conf = $(this).attr('conf_id') var conf = $(this).attr('conf_id')
@ -287,5 +256,7 @@
} }
}) })
}) })
</script> </script>
</html> </html>

20
src/templates/footer.html Normal file
View File

@ -0,0 +1,20 @@
<script src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js"
integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/gh/gitbrent/bootstrap4-toggle@3.6.1/js/bootstrap4-toggle.min.js"></script>
<script>
$.ajax({
method: "GET",
url: "/check_update_dashboard",
success: function (response){
if (response === "true"){
$(".sb-update-url").append("<span class=\"dot dot-running\"></span>")
}
}
})
</script>

View File

@ -7,7 +7,13 @@
</div> </div>
<div class="col-sm"> <div class="col-sm">
<small class="text-muted"><strong>ACTION</strong></small><br> <small class="text-muted"><strong>ACTION</strong></small><br>
<input class="mt-2 switch" id="{{conf_data['name']}}" type="checkbox" data-toggle="toggle" {{conf_data['checked']}} data-size="sm"> {# <input class="mt-2 switch" id="{{conf_data['name']}}" type="checkbox" data-toggle="toggle" {{conf_data['checked']}} data-size="sm">#}
{% if conf_data['checked'] == "checked" %}
<a href="#" id="{{conf_data['name']}}" {{conf_data['checked']}} class="switch text-primary"><i class="bi bi-toggle2-on"></i> ON</a>
{% else %}
<a href="#" id="{{conf_data['name']}}" {{conf_data['checked']}} class="switch text-secondary"><i class="bi bi-toggle2-off"></i> OFF</a>
{% endif %}
</div> </div>
<div class="w-100"></div> <div class="w-100"></div>
<div class="col-sm"> <div class="col-sm">
@ -96,50 +102,6 @@
<small class="text-muted"><strong>END POINT</strong></small> <small class="text-muted"><strong>END POINT</strong></small>
<h6 style="text-transform: uppercase;">{{i['endpoint']}}</h6> <h6 style="text-transform: uppercase;">{{i['endpoint']}}</h6>
</div> </div>
{# <div class="w-100"></div>#}
{# <div class="col-sm">#}
{# <canvas id="{{ i['id'] }}_chart" style="width: 100%" height="200"></canvas>#}
{# </div>#}
{# <script>#}
{# var data = "{{ i['traffic'] }}";#}
{# data = data.replaceAll("&#39;", "\"")#}
{# data = JSON.parse(data)#}
{# y_label = [];#}
{# data_point = [];#}
{# for (var i = 1; i < data.length; i++){#}
{# y_label.push(data[i]['time'])#}
{# data_point.push(data[i]['total_sent'] - data[i-1]['total_sent']);#}
{# }#}
{# var ctx = document.getElementById('{{ i['id'] }}_chart');#}
{# var plot_data =#}
{# {#}
{# labels: y_label,#}
{# datasets:[{#}
{# label: "Traffic",#}
{# data: data_point,#}
{# fill: false,#}
{# borderColor: 'rgb(75, 192, 192)',#}
{# tension: 0.1#}
{# }],#}
{# options: {#}
{# scales: {#}
{# xAxes: [{#}
{# type: 'time',#}
{# ticks: {#}
{# autoSkip: true,#}
{# maxTicksLimit: 12#}
{# }#}
{# }]#}
{# },#}
{# responsive: true#}
{# }#}
{# }#}
{##}
{# var myLineChart = new Chart(ctx, {#}
{# type: 'line',#}
{# data: plot_data#}
{# });#}
{# </script>#}
<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> --> <!-- <small class="text-muted"><strong>ACTION</strong></small> -->

13
src/templates/header.html Normal file
View File

@ -0,0 +1,13 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Wireguard Dashboard</title>
<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" 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">
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.0.1/dist/chart.min.js"></script>
</head>

View File

@ -1,70 +1,35 @@
<html> <html>
{% include "header.html" %}
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Wireguard Dashboard</title>
<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" 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">
</head>
<body> <body>
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow"> {% include "navbar.html" %}
<a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">Wireguard Dashboard</a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse"
data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</nav>
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> {% include "sidebar.html" %}
<div class="row">
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
<div class="sidebar-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item"><a class="nav-link" href="/">Home</a></li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Configurations</span>
</h6>
<ul class="nav flex-column">
{% for i in conf%}
<li class="nav-item"><a class="nav-link" href="/configuration/{{i['conf']}}">{{i['conf']}}</a></li>
{%endfor%}
</ul>
</div>
</nav>
</div>
</div>
<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">
{% 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">
<div class="row"> <div class="row">
<div class="col"> <div class="col card-col">
<small class="text-muted"><strong>CONFIGURATION</strong></small> <small class="text-muted"><strong>CONFIGURATION</strong></small>
<a href="/configuration/{{i['conf']}}"> <a href="/configuration/{{i['conf']}}">
<h5 class="card-title">{{i['conf']}}</h5> <h6 class="card-title" style="margin:0 !important;">{{i['conf']}}</h6>
</a> </a>
</div> </div>
<div class="col"> <div class="col card-col">
<input class="mt-2 switch" id="{{i['conf']}}" type="checkbox" data-toggle="toggle" {{i['checked']}} data-size="sm">
</div>
<div class="w-100"></div>
<div class="col-sm">
<small class="text-muted"><strong>STATUS</strong></small> <small class="text-muted"><strong>STATUS</strong></small>
<h6 style="text-transform: uppercase;">{{i['status']}}<span class="dot dot-{{i['status']}}"></span></h6> <h6 style="text-transform: uppercase; margin:0 !important;">{{i['status']}}<span class="dot dot-{{i['status']}}"></span></h6>
</div> </div>
<div class="col-sm"> <div class="col-md card-col">
<small class="text-muted"><strong>PUBLIC KEY</strong></small> <small class="text-muted"><strong>PUBLIC KEY</strong></small>
<h6 style="text-transform: uppercase;"><samp>{{i['public_key']}}</samp></h6> <h6 style="text-transform: uppercase; margin:0 !important;"><samp>{{i['public_key']}}</samp></h6>
</div>
<div class="col-md">
{% if i['checked'] == "checked" %}
<a href="#" id="{{i['conf']}}" {{i['checked']}} class="switch text-primary"><i class="bi bi-toggle2-on"></i></a>
{% else %}
<a href="#" id="{{i['conf']}}" {{i['checked']}} class="switch text-secondary"><i class="bi bi-toggle2-off"></i></a>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@ -72,25 +37,11 @@
</main> </main>
</div> </div>
</body> </body>
<script src="https://code.jquery.com/jquery-3.5.1.min.js" {% include "footer.html" %}
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js"
integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/gh/gitbrent/bootstrap4-toggle@3.6.1/js/bootstrap4-toggle.min.js"></script>
<script> <script>
$('.switch').change(function() { $('.switch').click(function() {
if ($(this).prop('checked') == false){ location.replace("/switch/"+$(this).attr('id'))
if (confirm('Are you sure you want to turn off this connection?')){
location.replace("/switch/"+$(this).attr('id'))
}
}
else{
location.replace("/switch/"+$(this).attr('id'))
}
}); });
$(".sb-home-url").addClass("active")
</script> </script>
</html> </html>

View File

@ -0,0 +1,7 @@
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="/">Wireguard Dashboard</a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse"
data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</nav>

107
src/templates/settings.html Normal file
View File

@ -0,0 +1,107 @@
<html>
{% include "header.html" %}
<body>
{% include "navbar.html" %}
<div class="container-fluid">
{% include "sidebar.html" %}
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
<div class="setting-container mt-4">
{% if message != ""%}
<div class="alert alert-{{ status }}" role="alert">
{{ message }}
</div>
{% endif %}
<h1 class="pb-4">Settings</h1>
{% if required_auth == "true" %}
<h3>Account</h3>
<form action="/update_acct" method="post">
<div class="form-group">
<label for="username">Username</label>
<input type="text" class="form-control mb-4" id="username" name="username" value="{{ session['username'] }}">
<button type="submit" class="btn btn-success" >Update Account</button>
</div>
</form>
<hr>
<h3>Security</h3>
<form action="/update_pwd", method="post">
<div class="form-group">
<label for="currentpass">Current Password</label>
<input type="password" class="form-control mb-2" id="currentpass" name="currentpass">
<label for="newpass">New Password</label>
<input type="password" class="form-control mb-2" id="newpass" name="newpass">
<label for="repnewpass">Repeat New Password</label>
<input type="password" class="form-control mb-4" id="repnewpass" name="repnewpass">
<button type="submit" class="btn btn-danger">Update Password</button>
</div>
</form>
<hr>
{% endif %}
<h3>Dashboard Configuration</h3>
<form action="/update_app_ip_port" method="post" class="update_app_ip_port">
<div class="form-group">
<div class="row">
<div class="col-sm">
<label for="app_ip" >Dashboard IP</label>
<input type="text" class="form-control mb-2" id="app_ip" name="app_ip" value="{{ app_ip }}">
<p><small class="text-danger mb-4">0.0.0.0 means it can be access by anyone with your server IP Address.</small></p>
</div>
<div class="col-sm">
<label for="app_port">Dashboard Port</label>
<input type="text" class="form-control mb-4" id="app_port" name="app_port" value="{{ app_port }}">
</div>
</div>
<button type="button" class="btn btn-danger confirm_modal" data-toggle="modal" data-target="#confirmModal">Update Configuration & Restart</button>
</div>
</form>
</div>
</main>
<!-- Modal -->
<div class="modal fade" id="confirmModal" data-backdrop="static" data-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Confirm Dashboard Configuration</h5>
</div>
<div class="modal-body">
<small>Dashboard Original IP</small>
<p>{{ app_ip }}</p>
<small style="font-weight: bold" class="text-bold">Dashboard New IP</small>
<p class="app_new_ip text-bold text-danger" style="font-weight: bold"></p>
<small>Dashboard Original Port</small>
<p>{{ app_port }}</p>
<small style="font-weight: bold" class="text-bold">Dashboard New Port</small>
<p class="app_new_port text-bold text-danger" style="font-weight: bold"></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary cancel_restart" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger confirm_restart">Confirm & Restart Dashboard</button>
</div>
</div>
</div>
</div>
</div>
</body>
{% include "footer.html" %}
<script>
$(".sb-settings-url").addClass("active")
$(".confirm_modal").click(function (){
$(".app_new_ip").html($("#app_ip")[0].value)
$(".app_new_port").html($("#app_port")[0].value)
})
$(".confirm_restart").click(function (){
$(".cancel_restart").remove()
countdown = 7;
$.post('/update_app_ip_port', $('.update_app_ip_port').serialize())
url = $("#app_ip")[0].value+":"+$("#app_port")[0].value;
$(".confirm_restart").attr("disabled", "disabled")
setInterval(function (){
if (countdown === 0){
window.location.replace("http://"+url);
}
$(".confirm_restart").html("Redirecting you in "+countdown+" seconds.")
countdown--;
},1000)
})
</script>
</html>

View File

@ -0,0 +1,30 @@
<div class="row">
<div class="row">
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
<div class="sidebar-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item"><a class="nav-link sb-home-url" href="/">Home</a></li>
{% if "username" in session %}
<li class="nav-item"><a class="nav-link sb-settings-url" href="/settings">Settings</a></li>
{% endif %}
<li class="nav-item"><a class="nav-link sb-update-url" href="/">Check Update</a></li>
</ul>
<hr>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Configurations</span>
</h6>
<ul class="nav flex-column">
{% for i in conf%}
<li class="nav-item"><a class="nav-link sb-{{i['conf']}}-url" href="/configuration/{{i['conf']}}">{{i['conf']}}</a></li>
{%endfor%}
</ul>
<hr>
{% if "username" in session %}
<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>
</ul>
{% endif %}
</div>
</nav>
</div>
</div>

43
src/templates/signin.html Normal file
View File

@ -0,0 +1,43 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Wireguard Dashboard | Login</title>
<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" type= "text/css" href= "{{ url_for('static',filename='dashboard.css') }}">
</head>
<body>
{% include "navbar.html" %}
<div class="container-fluid">
<main role="main" class="container login-container">
<div class="login-box" style="margin: auto !important;">
<h3 class="text-center">Sign In</h3>
<form style="margin-left: auto !important; margin-right: auto !important; max-width: 500px;" action="/auth" method="post">
{% if message != ""%}
<div class="alert alert-danger" role="alert">
{{ message }}
</div>
{% endif %}
<div class="form-group">
<label for="username" class="text-left">User Name</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password" class="text-left">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</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>
</form>
</div>
</main>
</div>
</body>
{% include "footer.html" %}
</html>

9
src/wg-dashboard.ini Normal file
View File

@ -0,0 +1,9 @@
[Account]
username = admin
password = 8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918
[Server]
app_ip = 0.0.0.0
app_port = 10086
auth_req = true
version = v2.0

86
src/wgd.sh Normal file
View File

@ -0,0 +1,86 @@
#!/bin/bash
app_name="dashboard.py"
help () {
printf "<Wireguard Dashboard> by Donald Zou - https://github.com/donaldzou \n"
printf "Usage: sh wg-dashboard.sh <option>"
printf "\n \n"
printf "Available options: \n"
printf " start: To start Wireguard Dashboard.\n"
printf " stop: To stop Wireguard Dashboard.\n"
printf " debug: To start Wireguard Dashboard in debug mode (i.e run in foreground).\n"
printf " update: To update Wireguard Dashboard to the newest version from GitHub.\n"
printf "Thank you for using this dashboard! Your support is my motivation ;) \n"
printf "\n"
}
check_wgd_status(){
if ps aux | grep '[p]ython3 '$app_name > /dev/null;
then
return 0
else
return 1
fi
}
start_wgd () {
printf "Starting Wireguard Dashboard in the background. \n"
if [ ! -d "log" ]
then mkdir "log"
fi
d=$(date '+%Y%m%d%H%M%S')
python3 "$app_name" > log/"$d".txt 2>&1 &
printf "Log file: log/%s""$d"".txt\n"
}
stop_wgd() {
kill "$(ps aux | grep "[p]ython3 $app_name" | awk '{print $2}')"
}
start_wgd_debug() {
printf "Starting Wireguard Dashboard in the foreground. \n"
python3 "$app_name"
}
if [ "$#" != 1 ];
then
help
else
if [ "$1" = "start" ]; then
if check_wgd_status; then
printf "Wireguard Dashboard is already running. \n"
else
start_wgd
fi
elif [ "$1" = "stop" ]; then
if check_wgd_status; then
stop_wgd
printf "Wireguard Dashboard is stopped. \n"
else
printf "Wireguard Dashboard is not running. \n"
fi
elif [ "$1" = "update" ]; then
echo "update";
elif [ "$1" = "restart" ]; then
if check_wgd_status; then
stop_wgd
sleep 2
printf "Wireguard Dashboard is stopped. \n"
start_wgd_debug
else
start_wgd_debug
fi
elif [ "$1" = "debug" ]; then
if check_wgd_status; then
printf "Wireguard Dashboard is already running. \n"
else
start_wgd_debug
fi
else
help
fi
fi