diff --git a/.gitignore b/.gitignore index 49f78725..d22c6f09 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,6 @@ src/test.py tmp __pycache__ src/wg-dashboard.ini -src/wg-dashboard.ini src/static/pic.xd +*.conf + diff --git a/README.md b/README.md index 6b513423..c025ee32 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@
Monitoring WireGuard is not convinient, need to login into server and type wg show
. That's why this platform is being created, to view all configurations and manage them in a easier way.
Settings Page
+ + +## 🛒 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 ✨ diff --git a/src/dashboard.py b/src/dashboard.py index 153d463f..87280dd7 100644 --- a/src/dashboard.py +++ b/src/dashboard.py @@ -1,38 +1,40 @@ -dashboard_version = 'v2.0' - - # Python Built-in Library 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 from datetime import datetime, date, time, timedelta +import time from operator import itemgetter import secrets import hashlib import json, urllib.request import configparser +import re # PIP installed library import ifcfg 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' +# Upgrade Required update = "" +# Flask App Configuration app = Flask("Wireguard Dashboard") app.secret_key = secrets.token_urlsafe(16) app.config['TEMPLATES_AUTO_RELOAD'] = True -conf_data = {} - def get_conf_peer_key(config_name): - keys = [] try: 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: - return "stopped" - peer_key = peer_key.decode("UTF-8").split() - for i in peer_key: keys.append(i) - return keys - + return config_name+" is not running." def get_conf_running_peer_number(config_name): running = 0 @@ -52,9 +54,14 @@ def get_conf_running_peer_number(config_name): count += 2 return running + +def is_match(regex, text): + pattern = re.compile(regex) + return pattern.search(text) is not None + def read_conf_file(config_name): # 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') file = f.read().split("\n") conf_peer_data = { @@ -63,73 +70,58 @@ def read_conf_file(config_name): } peers_start = 0 for i in range(len(file)): - if file[i] == "[Peer]": - peers_start = i - break - else: - if len(file[i]) > 0: - if file[i] != "[Interface]": - tmp = file[i].replace(" ", "").split("=", 1) - if len(tmp) == 2: - conf_peer_data['Interface'][tmp[0]] = tmp[1] + if not is_match("^#(.*)",file[i]): + if file[i] == "[Peer]": + peers_start = i + break + else: + if len(file[i]) > 0: + if file[i] != "[Interface]": + tmp = re.split(r'\s*=\s*', file[i], 1) + if len(tmp) == 2: + conf_peer_data['Interface'][tmp[0]] = tmp[1] conf_peers = file[peers_start:] peer = -1 for i in conf_peers: - if i == "[Peer]": - peer += 1 - conf_peer_data["Peers"].append({}) - else: - if len(i) > 0: - tmp = i.replace(" ", "").split("=", 1) - if len(tmp) == 2: - conf_peer_data["Peers"][peer][tmp[0]] = tmp[1] + if not is_match("^#(.*)", i): + if i == "[Peer]": + peer += 1 + conf_peer_data["Peers"].append({}) + elif peer > -1: + if len(i) > 0: + tmp = re.split('\s*=\s*', i, 1) + if len(tmp) == 2: + conf_peer_data["Peers"][peer][tmp[0]] = tmp[1] + + f.close() # Read Configuration File End 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_conf_peers_data(config_name): - db = TinyDB('db/' + config_name + '.json') - peers = Query() - conf_peer_data = read_conf_file(config_name) - - for i in conf_peer_data['Peers']: - if not db.search(peers.id == i['PublicKey']): - db.insert({ - "id": i['PublicKey'], - "name": "", - "total_receive": 0, - "total_sent": 0, - "total_data": 0, - "endpoint": 0, - "status": 0, - "latest_handshake": 0, - "allowed_ip": 0, - "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 - +def get_transfer(config_name, db, peers): # Get transfer try: data_usage = subprocess.check_output("wg show " + config_name + " transfer", shell=True) @@ -141,28 +133,29 @@ def get_conf_peers_data(config_name): 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: + if total_sent <= cur_total_sent and total_receive <= cur_total_receive: 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), + 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]) - # 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 +def get_endpoint(config_name, db, peers): # Get endpoint try: data_usage = subprocess.check_output("wg show " + config_name + " endpoints", shell=True) @@ -174,9 +167,45 @@ def get_conf_peers_data(config_name): 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"]) + db.update({"allowed_ip": i.get('AllowedIPs', '(None)')}, peers.id == i["PublicKey"]) + + + +def get_conf_peers_data(config_name): + db = TinyDB('db/' + config_name + '.json') + peers = Query() + conf_peer_data = read_conf_file(config_name) + + for i in conf_peer_data['Peers']: + if not db.search(peers.id == i['PublicKey']): + db.insert({ + "id": i['PublicKey'], + "name": "", + "total_receive": 0, + "total_sent": 0, + "total_data": 0, + "endpoint": 0, + "status": 0, + "latest_handshake": 0, + "allowed_ip": 0, + "traffic": [] + }) + + tic = time.perf_counter() + get_latest_handshake(config_name, db, peers) + get_transfer(config_name, db, peers) + get_endpoint(config_name, db, peers) + get_allowed_ip(config_name, db, peers, conf_peer_data) + toc = time.perf_counter() + print(f"Finish fetching data in {toc - tic:0.4f} seconds") + db.close() + + + + def get_peers(config_name): @@ -184,6 +213,7 @@ def get_peers(config_name): db = TinyDB('db/' + config_name + '.json') result = db.all() result = sorted(result, key=lambda d: d['status']) + db.close() return result @@ -209,9 +239,15 @@ def get_conf_total_data(config_name): upload_total = 0 download_total = 0 for i in db.all(): - upload_total += round(i['total_sent'],4) - download_total += round(i['total_receive'],4) + upload_total += i['total_sent'] + 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) + upload_total = round(upload_total, 4) + download_total = round(download_total, 4) + db.close() return [total, upload_total, download_total] @@ -230,18 +266,16 @@ def get_conf_list(): if ".conf" in i: i = i.replace('.conf', '') temp = {"conf": i, "status": get_conf_status(i), "public_key": get_conf_pub_key(i)} - # get_conf_peers_data(i) if temp['status'] == "running": temp['checked'] = 'checked' else: temp['checked'] = "" conf.append(temp) - conf = sorted(conf, key=itemgetter('conf')) + if len(conf) > 0: + conf = sorted(conf, key=itemgetter('conf')) return conf - - @app.before_request def auth_req(): conf = configparser.ConfigParser(strict=False) @@ -255,13 +289,15 @@ def auth_req(): request.endpoint != "signout" and \ request.endpoint != "auth" and \ "username" not in session: - print("not loggedin") + print("User not loggedin - Attemped access: "+str(request.endpoint)) session['message'] = "You need to sign in first!" return redirect(url_for("signin")) 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")) + @app.route('/signin', methods=['GET']) def signin(): message = "" @@ -279,7 +315,6 @@ def signout(): return render_template('signin.html', message=message) - @app.route('/settings', methods=['GET']) def settings(): message = "" @@ -292,14 +327,18 @@ def settings(): 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, 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']) 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"]: + 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")) @@ -308,6 +347,7 @@ def auth(): config.clear() return redirect(url_for("signin")) + @app.route('/update_acct', methods=['POST']) def update_acct(): config = configparser.ConfigParser(strict=False) @@ -326,12 +366,14 @@ def update_acct(): 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(): + 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")) @@ -355,6 +397,7 @@ def update_pwd(): 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) @@ -365,6 +408,7 @@ def update_app_ip_port(): config.clear() os.system('bash wgd.sh restart') + @app.route('/update_wg_conf_path', methods=['POST']) def update_wg_conf_path(): config = configparser.ConfigParser(strict=False) @@ -376,15 +420,71 @@ def update_wg_conf_path(): config.clear() os.system('bash wgd.sh restart') -# @app.route('/check_update_dashboard', methods=['GET']) -# def check_update_dashboard(): -# return have_update +@app.route('/update_dashboard_refresh_interval', methods=['POST']) +def update_dashboard_refresh_interval(): + 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 += '" + 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']) def index(): - print(request.referrer) return render_template('index.html', conf=get_conf_list()) + @app.route('/configuration/