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

general refactoring

This commit is contained in:
Galonza Peter 2021-12-26 02:26:39 +03:00
parent 710d631ffe
commit f5ad7a49ce

View File

@ -3,30 +3,32 @@
Under Apache-2.0 License Under Apache-2.0 License
""" """
# Import other python files
from util import *
# Python Built-in Library
import os
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 configparser
import re import hashlib
import ipaddress import ipaddress
import sqlite3 import json
# Python Built-in Library
import logging
import os
import secrets
import subprocess
import threading import threading
import time
import urllib.parse
import urllib.request
from datetime import datetime, time, timedelta
from operator import itemgetter
# PIP installed library # PIP installed library
import ifcfg import ifcfg
from flask import Flask, request, render_template, redirect, url_for, session, jsonify
from flask_qrcode import QRcode from flask_qrcode import QRcode
from icmplib import ping, traceroute
from tinydb import TinyDB, Query from tinydb import TinyDB, Query
from tinydb.storages import JSONStorage
from tinydb.middlewares import CachingMiddleware # Import other python files
from icmplib import ping, multiping, traceroute, resolve, Host, Hop from util import *
# Dashboard Version # Dashboard Version
dashboard_version = 'v3.0' dashboard_version = 'v3.0'
# Dashboard Config Name # Dashboard Config Name
@ -45,36 +47,44 @@ QRcode(app)
sem = threading.RLock() sem = threading.RLock()
# Read / Write Dashboard Config File
def get_dashboard_conf():
""" """
Dashboard Configuration Related Dashboard Configuration Related
""" """
# Read / Write Dashboard Config File
def get_dashboard_conf():
config = configparser.ConfigParser(strict=False) config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf) config.read(dashboard_conf)
return config return config
def set_dashboard_conf(config): def set_dashboard_conf(config):
config.write(open(dashboard_conf, "w")) config.write(open(dashboard_conf, "w"))
# Get all keys from a configuration
def get_conf_peer_key(config_name):
""" """
Configuration Related Configuration Related
""" """
# Get all keys from a configuration
def get_conf_peer_key(config_name):
try: try:
peer_key = subprocess.check_output("wg show " + config_name + " peers", shell=True) peer_key = subprocess.run(f"wg show {config_name} peers",
check=True, shell=True, capture_output=True).stdout
peer_key = peer_key.decode("UTF-8").split() peer_key = peer_key.decode("UTF-8").split()
return peer_key return peer_key
except Exception: except subprocess.CalledProcessError:
return config_name + " is not running." return config_name + " is not running."
# Get numbers of connected peer of a configuration # Get numbers of connected peer of a configuration
def get_conf_running_peer_number(config_name): def get_conf_running_peer_number(config_name):
running = 0 running = 0
# Get latest handshakes # Get latest handshakes
try: try:
data_usage = subprocess.check_output("wg show " + config_name + " latest-handshakes", shell=True) data_usage = subprocess.run(f"wg show {config_name} latest-handshakes",
except Exception: check=True, shell=True, capture_output=True).stdout
except subprocess.CalledProcessError:
return "stopped" return "stopped"
data_usage = data_usage.decode("UTF-8").split() data_usage = data_usage.decode("UTF-8").split()
count = 0 count = 0
@ -87,13 +97,13 @@ def get_conf_running_peer_number(config_name):
count += 2 count += 2
return running return running
# Read [Interface] section from configuration file # Read [Interface] section from configuration file
def read_conf_file_interface(config_name): def read_conf_file_interface(config_name):
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")
data = {} data = {}
peers_start = 0
for i in range(len(file)): for i in range(len(file)):
if not regex_match("#(.*)", file[i]): if not regex_match("#(.*)", file[i]):
if len(file[i]) > 0: if len(file[i]) > 0:
@ -104,6 +114,7 @@ def read_conf_file_interface(config_name):
f.close() f.close()
return data return data
# Read the whole configuration file # Read the whole configuration file
def read_conf_file(config_name): def read_conf_file(config_name):
# Read Configuration File Start # Read Configuration File Start
@ -143,12 +154,14 @@ def read_conf_file(config_name):
# Read Configuration File End # Read Configuration File End
return conf_peer_data return conf_peer_data
# Get latest handshake from all peers of a configuration # Get latest handshake from all peers of a configuration
def get_latest_handshake(config_name, db, peers): def get_latest_handshake(config_name, db, peers):
# Get latest handshakes # Get latest handshakes
try: try:
data_usage = subprocess.check_output("wg show " + config_name + " latest-handshakes", shell=True) data_usage = subprocess.run(f"wg show {config_name} latest-handshakes",
except Exception: check=True, shell=True, capture_output=True).stdout
except subprocess.CalledProcessError:
return "stopped" return "stopped"
data_usage = data_usage.decode("UTF-8").split() data_usage = data_usage.decode("UTF-8").split()
count = 0 count = 0
@ -167,12 +180,14 @@ def get_latest_handshake(config_name, db, peers):
db.update({"latest_handshake": "(None)", "status": status}, peers.id == data_usage[count]) db.update({"latest_handshake": "(None)", "status": status}, peers.id == data_usage[count])
count += 2 count += 2
# Get transfer from all peers of a configuration # Get transfer from all peers of a configuration
def get_transfer(config_name, db, peers): def get_transfer(config_name, db, peers):
# Get transfer # Get transfer
try: try:
data_usage = subprocess.check_output("wg show " + config_name + " transfer", shell=True) data_usage = subprocess.run(f"wg show {config_name} transfer",
except Exception: check=True, shell=True, capture_output=True).stdout
except subprocess.CalledProcessError:
return "stopped" return "stopped"
data_usage = data_usage.decode("UTF-8").split() data_usage = data_usage.decode("UTF-8").split()
count = 0 count = 0
@ -202,12 +217,14 @@ def get_transfer(config_name, db, peers):
count += 3 count += 3
# Get endpoint from all peers of a configuration # Get endpoint from all peers of a configuration
def get_endpoint(config_name, db, peers): def get_endpoint(config_name, db, peers):
# Get endpoint # Get endpoint
try: try:
data_usage = subprocess.check_output("wg show " + config_name + " endpoints", shell=True) data_usage = subprocess.run(f"wg show {config_name} endpoints",
except Exception: check=True, shell=True, capture_output=True).stdout
except subprocess.CalledProcessError:
return "stopped" return "stopped"
data_usage = data_usage.decode("UTF-8").split() data_usage = data_usage.decode("UTF-8").split()
count = 0 count = 0
@ -215,12 +232,14 @@ def get_endpoint(config_name, db, peers):
db.update({"endpoint": data_usage[count + 1]}, peers.id == data_usage[count]) db.update({"endpoint": data_usage[count + 1]}, peers.id == data_usage[count])
count += 2 count += 2
# Get allowed ips from all peers of a configuration # Get allowed ips from all peers of a configuration
def get_allowed_ip(config_name, db, peers, conf_peer_data): def get_allowed_ip(db, peers, conf_peer_data):
# 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"])
# Look for new peers from WireGuard # Look for new peers from WireGuard
def get_all_peers_data(config_name): def get_all_peers_data(config_name):
sem.acquire() sem.acquire()
@ -279,17 +298,19 @@ def get_all_peers_data(config_name):
get_latest_handshake(config_name, db, peers) get_latest_handshake(config_name, db, peers)
get_transfer(config_name, db, peers) get_transfer(config_name, db, peers)
get_endpoint(config_name, db, peers) get_endpoint(config_name, db, peers)
get_allowed_ip(config_name, db, peers, conf_peer_data) get_allowed_ip(db, peers, conf_peer_data)
toc = time.perf_counter() toc = time.perf_counter()
print(f"Finish fetching data in {toc - tic:0.4f} seconds") print(f"Finish fetching data in {toc - tic:0.4f} seconds")
db.close() db.close()
sem.release() sem.release()
# Search for peers
def get_peers(config_name, search, sort_t):
""" """
Frontend Related Functions Frontend Related Functions
""" """
# Search for peers
def get_peers(config_name, search, sort_t):
get_all_peers_data(config_name) get_all_peers_data(config_name)
sem.acquire() sem.acquire()
@ -308,17 +329,16 @@ def get_peers(config_name, search, sort_t):
return result return result
# Get configuration public key # Get configuration public key
def get_conf_pub_key(config_name): def get_conf_pub_key(config_name):
conf = configparser.ConfigParser(strict=False) conf = configparser.ConfigParser(strict=False)
conf.read(wg_conf_path + "/" + config_name + ".conf") conf.read(wg_conf_path + "/" + config_name + ".conf")
pri = conf.get("Interface", "PrivateKey") pri = conf.get("Interface", "PrivateKey")
pub = subprocess.check_output("echo '" + pri + "' | wg pubkey", shell=True) pub = subprocess.run(f"echo '{pri}' | wg pubkey", check=True, shell=True, capture_output=True).stdout
conf.clear() conf.clear()
return pub.decode().strip("\n") return pub.decode().strip("\n")
# Get configuration listen port # Get configuration listen port
def get_conf_listen_port(config_name): def get_conf_listen_port(config_name):
conf = configparser.ConfigParser(strict=False) conf = configparser.ConfigParser(strict=False)
@ -326,13 +346,15 @@ def get_conf_listen_port(config_name):
port = "" port = ""
try: try:
port = conf.get("Interface", "ListenPort") port = conf.get("Interface", "ListenPort")
except: except (configparser.NoSectionError, configparser.NoOptionError):
if get_conf_status(config_name) == "running": if get_conf_status(config_name) == "running":
port = subprocess.check_output("wg show "+config_name+" listen-port", shell=True) port = subprocess.run(f"wg show {config_name} listen-port",
check=True, shell=True, capture_output=True).stdout
port = port.decode("UTF-8") port = port.decode("UTF-8")
conf.clear() conf.clear()
return port return port
# Get configuration total data # Get configuration total data
def get_conf_total_data(config_name): def get_conf_total_data(config_name):
sem.acquire() sem.acquire()
@ -353,6 +375,7 @@ def get_conf_total_data(config_name):
sem.release() sem.release()
return [total, upload_total, download_total] return [total, upload_total, download_total]
# Get configuration status # Get configuration status
def get_conf_status(config_name): def get_conf_status(config_name):
ifconfig = dict(ifcfg.interfaces().items()) ifconfig = dict(ifcfg.interfaces().items())
@ -361,6 +384,7 @@ def get_conf_status(config_name):
else: else:
return "stopped" return "stopped"
# Get all configuration as a list # Get all configuration as a list
def get_conf_list(): def get_conf_list():
conf = [] conf = []
@ -377,10 +401,9 @@ def get_conf_list():
conf = sorted(conf, key=itemgetter('conf')) conf = sorted(conf, key=itemgetter('conf'))
return conf return conf
# Generate private key # Generate private key
def gen_private_key(): def gen_private_key():
gen = subprocess.check_output('wg genkey > private_key.txt && wg pubkey < private_key.txt > public_key.txt',
shell=True)
private = open('private_key.txt') private = open('private_key.txt')
private_key = private.readline().strip() private_key = private.readline().strip()
public = open('public_key.txt') public = open('public_key.txt')
@ -392,22 +415,23 @@ def gen_private_key():
os.remove('public_key.txt') os.remove('public_key.txt')
return data return data
# Generate public key # Generate public key
def gen_public_key(private_key): def gen_public_key(private_key):
pri_key_file = open('private_key.txt', 'w') pri_key_file = open('private_key.txt', 'w')
pri_key_file.write(private_key) pri_key_file.write(private_key)
pri_key_file.close() pri_key_file.close()
try: try:
check = subprocess.check_output("wg pubkey < private_key.txt > public_key.txt", shell=True)
public = open('public_key.txt') public = open('public_key.txt')
public_key = public.readline().strip() public_key = public.readline().strip()
os.remove('private_key.txt') os.remove('private_key.txt')
os.remove('public_key.txt') os.remove('public_key.txt')
return {"status": 'success', "msg": "", "data": public_key} return {"status": 'success', "msg": "", "data": public_key}
except subprocess.CalledProcessError as exc: except subprocess.CalledProcessError:
os.remove('private_key.txt') os.remove('private_key.txt')
return {"status": 'failed', "msg": "Key is not the correct length or format", "data": ""} return {"status": 'failed', "msg": "Key is not the correct length or format", "data": ""}
# Check if private key and public key match # Check if private key and public key match
def checkKeyMatch(private_key, public_key, config_name): def checkKeyMatch(private_key, public_key, config_name):
result = gen_public_key(private_key) result = gen_public_key(private_key)
@ -428,9 +452,9 @@ def checkKeyMatch(private_key, public_key, config_name):
sem.release() sem.release()
return {'status': 'success'} return {'status': 'success'}
# Check if there is repeated allowed IP # Check if there is repeated allowed IP
def check_repeat_allowed_IP(public_key, ip, config_name): def check_repeat_allowed_IP(public_key, ip, config_name):
sem.acquire() sem.acquire()
db = TinyDB('db/' + config_name + '.json') db = TinyDB('db/' + config_name + '.json')
@ -454,10 +478,10 @@ def check_repeat_allowed_IP(public_key, ip, config_name):
Flask Functions Flask Functions
""" """
# Before request # Before request
@app.before_request @app.before_request
def auth_req(): def auth_req():
conf = configparser.ConfigParser(strict=False) conf = configparser.ConfigParser(strict=False)
conf.read(dashboard_conf) conf.read(dashboard_conf)
req = conf.get("Server", "auth_req") req = conf.get("Server", "auth_req")
@ -480,9 +504,12 @@ def auth_req():
'update_app_ip_port', 'update_wg_conf_path']: 'update_app_ip_port', 'update_wg_conf_path']:
return redirect(url_for("index")) return redirect(url_for("index"))
""" """
Sign In / Sign Out Sign In / Sign Out
""" """
# Sign In # Sign In
@app.route('/signin', methods=['GET']) @app.route('/signin', methods=['GET'])
def signin(): def signin():
@ -492,6 +519,7 @@ def signin():
session.pop("message") session.pop("message")
return render_template('signin.html', message=message) return render_template('signin.html', message=message)
# Sign Out # Sign Out
@app.route('/signout', methods=['GET']) @app.route('/signout', methods=['GET'])
def signout(): def signout():
@ -500,14 +528,15 @@ def signout():
message = "Sign out successfully!" message = "Sign out successfully!"
return render_template('signin.html', message=message) return render_template('signin.html', message=message)
# Authentication # Authentication
@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"][ if password.hexdigest() == config["Account"]["password"] \
"username"]: 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"))
@ -516,20 +545,23 @@ def auth():
config.clear() config.clear()
return redirect(url_for("signin")) return redirect(url_for("signin"))
@app.route('/', methods=['GET'])
def index():
""" """
Index Page Related Index Page Related
""" """
@app.route('/', methods=['GET'])
def index():
update = check_update()
return render_template('index.html', conf=get_conf_list()) return render_template('index.html', conf=get_conf_list())
"""
Setting Page Related
"""
# Setting Page # Setting Page
@app.route('/settings', methods=['GET']) @app.route('/settings', methods=['GET'])
def settings(): def settings():
"""
Setting Page Related
"""
message = "" message = ""
status = "" status = ""
config = configparser.ConfigParser(strict=False) config = configparser.ConfigParser(strict=False)
@ -549,6 +581,7 @@ def settings():
peer_keepalive=config.get("Peers", "peer_keep_alive"), peer_keepalive=config.get("Peers", "peer_keep_alive"),
peer_remote_endpoint=config.get("Peers", "remote_endpoint")) peer_remote_endpoint=config.get("Peers", "remote_endpoint"))
# Update account username # Update account username
@app.route('/update_acct', methods=['POST']) @app.route('/update_acct', methods=['POST'])
def update_acct(): def update_acct():
@ -566,12 +599,14 @@ def update_acct():
session['username'] = request.form['username'] session['username'] = request.form['username']
config.clear() config.clear()
return redirect(url_for("settings")) return redirect(url_for("settings"))
except Exception: except Exception as exc:
logging.error(exc)
session['message'] = "Username update failed." session['message'] = "Username update failed."
session['message_status'] = "danger" session['message_status'] = "danger"
config.clear() config.clear()
return redirect(url_for("settings")) return redirect(url_for("settings"))
# Update peer default settting # Update peer default settting
@app.route('/update_peer_default_config', methods=['POST']) @app.route('/update_peer_default_config', methods=['POST'])
def update_peer_default_config(): def update_peer_default_config():
@ -584,40 +619,45 @@ def update_peer_default_config():
session['message_status'] = "danger" session['message_status'] = "danger"
return redirect(url_for("settings")) return redirect(url_for("settings"))
# Check DNS Format # Check DNS Format
DNS = request.form['peer_global_DNS'] dns_addresses = request.form['peer_global_DNS']
if not check_DNS(DNS): if not check_DNS(dns_addresses):
session['message'] = "Peer DNS Format Incorrect." session['message'] = "Peer DNS Format Incorrect."
session['message_status'] = "danger" session['message_status'] = "danger"
return redirect(url_for("settings")) return redirect(url_for("settings"))
DNS = DNS.replace(" ","").split(',') dns_addresses = dns_addresses.replace(" ", "").split(',')
DNS = ",".join(DNS) dns_addresses = ",".join(dns_addresses)
# Check Endpoint Allowed IPs # Check Endpoint Allowed IPs
ip = request.form['peer_endpoint_allowed_ip'] ip = request.form['peer_endpoint_allowed_ip']
if not check_Allowed_IPs(ip): if not check_Allowed_IPs(ip):
session['message'] = "Peer Endpoint Allowed IPs Format Incorrect. Example: 192.168.1.1/32 or 192.168.1.1/32,192.168.1.2/32" session['message'] = "Peer Endpoint Allowed IPs Format Incorrect. " \
"Example: 192.168.1.1/32 or 192.168.1.1/32,192.168.1.2/32"
session['message_status'] = "danger" session['message_status'] = "danger"
return redirect(url_for("settings")) return redirect(url_for("settings"))
# Check MTU Format # Check MTU Format
if len(request.form['peer_mtu']) > 0: if len(request.form['peer_mtu']) > 0:
try: try:
# TODO need to using
mtu = int(request.form['peer_mtu']) mtu = int(request.form['peer_mtu'])
except: except Exception as exc:
logging.info(exc)
session['message'] = "MTU format is incorrect." session['message'] = "MTU format is incorrect."
session['message_status'] = "danger" session['message_status'] = "danger"
return redirect(url_for("settings")) return redirect(url_for("settings"))
# Check keepalive Format # Check keepalive Format
if len(request.form['peer_keep_alive']) > 0: if len(request.form['peer_keep_alive']) > 0:
try: try:
# TODO need to using
mtu = int(request.form['peer_keep_alive']) mtu = int(request.form['peer_keep_alive'])
except: except Exception as exc:
logging.error(exc)
session['message'] = "Persistent keepalive format is incorrect." session['message'] = "Persistent keepalive format is incorrect."
session['message_status'] = "danger" session['message_status'] = "danger"
return redirect(url_for("settings")) return redirect(url_for("settings"))
# Check peer remote endpoint # Check peer remote endpoint
if not check_remote_endpoint(request.form['peer_remote_endpoint']): if not check_remote_endpoint(request.form['peer_remote_endpoint']):
session[ session['message'] = "Peer Remote Endpoint format is incorrect. It can only be a valid " \
'message'] = "Peer Remote Endpoint format is incorrect. It can only be a valid IP address or valid domain (without http:// or https://). " "IP address or valid domain (without http:// or https://). "
session['message_status'] = "danger" session['message_status'] = "danger"
return redirect(url_for("settings")) return redirect(url_for("settings"))
@ -625,8 +665,7 @@ def update_peer_default_config():
config.set("Peers", "peer_keep_alive", request.form['peer_keep_alive']) config.set("Peers", "peer_keep_alive", request.form['peer_keep_alive'])
config.set("Peers", "peer_mtu", request.form['peer_mtu']) config.set("Peers", "peer_mtu", request.form['peer_mtu'])
config.set("Peers", "peer_endpoint_allowed_ip", ','.join(clean_IP_with_range(ip))) config.set("Peers", "peer_endpoint_allowed_ip", ','.join(clean_IP_with_range(ip)))
config.set("Peers", "peer_global_DNS", DNS) config.set("Peers", "peer_global_DNS", dns_addresses)
try: try:
config.write(open(dashboard_conf, "w")) config.write(open(dashboard_conf, "w"))
@ -634,12 +673,14 @@ def update_peer_default_config():
session['message_status'] = "success" session['message_status'] = "success"
config.clear() config.clear()
return redirect(url_for("settings")) return redirect(url_for("settings"))
except Exception: except Exception as exc:
logging.error(exc)
session['message'] = "Peer Default Settings update failed." session['message'] = "Peer Default Settings update failed."
session['message_status'] = "danger" session['message_status'] = "danger"
config.clear() config.clear()
return redirect(url_for("settings")) return redirect(url_for("settings"))
# Update dashboard password # Update dashboard password
@app.route('/update_pwd', methods=['POST']) @app.route('/update_pwd', methods=['POST'])
def update_pwd(): def update_pwd():
@ -655,7 +696,8 @@ def update_pwd():
session['message_status'] = "success" session['message_status'] = "success"
config.clear() config.clear()
return redirect(url_for("settings")) return redirect(url_for("settings"))
except Exception: except Exception as exc:
logging.error(exc)
session['message'] = "Password update failed" session['message'] = "Password update failed"
session['message_status'] = "danger" session['message_status'] = "danger"
config.clear() config.clear()
@ -671,6 +713,7 @@ def update_pwd():
config.clear() config.clear()
return redirect(url_for("settings")) return redirect(url_for("settings"))
# Update dashboard IP and port # Update dashboard IP and port
@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():
@ -682,6 +725,7 @@ def update_app_ip_port():
config.clear() config.clear()
os.system('bash wgd.sh restart') os.system('bash wgd.sh restart')
# Update WireGuard configuration file path # Update WireGuard configuration file path
@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():
@ -694,12 +738,14 @@ def update_wg_conf_path():
config.clear() config.clear()
os.system('bash wgd.sh restart') os.system('bash wgd.sh restart')
"""
Configuration Page Related
"""
# Update configuration sorting # Update configuration sorting
@app.route('/update_dashboard_sort', methods=['POST']) @app.route('/update_dashboard_sort', methods=['POST'])
def update_dashbaord_sort(): def update_dashbaord_sort():
"""
Configuration Page Related
"""
config = configparser.ConfigParser(strict=False) config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf) config.read(dashboard_conf)
data = request.get_json() data = request.get_json()
@ -712,6 +758,7 @@ def update_dashbaord_sort():
config.clear() config.clear()
return "true" return "true"
# Update configuration refresh interval # Update configuration refresh interval
@app.route('/update_dashboard_refresh_interval', methods=['POST']) @app.route('/update_dashboard_refresh_interval', methods=['POST'])
def update_dashboard_refresh_interval(): def update_dashboard_refresh_interval():
@ -722,6 +769,7 @@ def update_dashboard_refresh_interval():
config.clear() config.clear()
return "true" return "true"
# Configuration Page # Configuration Page
@app.route('/configuration/<config_name>', methods=['GET']) @app.route('/configuration/<config_name>', methods=['GET'])
def conf(config_name): def conf(config_name):
@ -749,12 +797,14 @@ def conf(config_name):
mtu=config.get("Peers", "peer_MTU"), mtu=config.get("Peers", "peer_MTU"),
keep_alive=config.get("Peers", "peer_keep_alive")) keep_alive=config.get("Peers", "peer_keep_alive"))
# Get configuration details # Get configuration details
@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):
config_interface = read_conf_file_interface(config_name) config_interface = read_conf_file_interface(config_name)
search = request.args.get('search') search = request.args.get('search')
if len(search) == 0: search = "" if search == 0:
search = ""
search = urllib.parse.unquote(search) search = urllib.parse.unquote(search)
config = configparser.ConfigParser(strict=False) config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf) config.read(dashboard_conf)
@ -779,8 +829,11 @@ def get_conf(config_name):
else: else:
conf_data['checked'] = "checked" conf_data['checked'] = "checked"
print(config.get("Peers", "remote_endpoint")) print(config.get("Peers", "remote_endpoint"))
return render_template('get_conf.html', conf_data=conf_data, wg_ip=config.get("Peers","remote_endpoint"), sort_tag=sort, return render_template('get_conf.html', conf_data=conf_data, wg_ip=config.get("Peers", "remote_endpoint"),
dashboard_refresh_interval=int(config.get("Server", "dashboard_refresh_interval")), peer_display_mode=peer_display_mode) sort_tag=sort,
dashboard_refresh_interval=int(config.get("Server", "dashboard_refresh_interval")),
peer_display_mode=peer_display_mode)
# Turn on / off a configuration # Turn on / off a configuration
@app.route('/switch/<config_name>', methods=['GET']) @app.route('/switch/<config_name>', methods=['GET'])
@ -791,17 +844,20 @@ def switch(config_name):
status = get_conf_status(config_name) status = get_conf_status(config_name)
if status == "running": if status == "running":
try: try:
status = subprocess.check_output("wg-quick down " + config_name, shell=True) subprocess.run("wg-quick down " + config_name,
except Exception: check=True, shell=True, capture_output=True).stdout
except subprocess.CalledProcessError:
return redirect('/') return redirect('/')
elif status == "stopped": elif status == "stopped":
try: try:
status = subprocess.check_output("wg-quick up " + config_name, shell=True) subprocess.run("wg-quick up " + config_name,
except Exception: check=True, shell=True, capture_output=True).stdout
except subprocess.CalledProcessError:
return redirect('/') return redirect('/')
return redirect(request.referrer) return redirect(request.referrer)
# Add peer # Add peer
@app.route('/add_peer/<config_name>', methods=['POST']) @app.route('/add_peer/<config_name>', methods=['POST'])
def add_peer(config_name): def add_peer(config_name):
@ -813,9 +869,9 @@ 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']
endpoint_allowed_ip = data['endpoint_allowed_ip'] endpoint_allowed_ip = data['endpoint_allowed_ip']
DNS = data['DNS'] dns_addresses = data['DNS']
keys = get_conf_peer_key(config_name) keys = get_conf_peer_key(config_name)
if len(public_key) == 0 or len(DNS) == 0 or len(allowed_ips) == 0 or len(endpoint_allowed_ip) == 0: if len(public_key) == 0 or len(dns_addresses) == 0 or len(allowed_ips) == 0 or len(endpoint_allowed_ip) == 0:
db.close() db.close()
sem.release() sem.release()
return "Please fill in all required box." return "Please fill in all required box."
@ -831,7 +887,7 @@ def add_peer(config_name):
db.close() db.close()
sem.release() sem.release()
return "Allowed IP already taken by another peer." return "Allowed IP already taken by another peer."
if not check_DNS(DNS): if not check_DNS(dns_addresses):
db.close() db.close()
sem.release() sem.release()
return "DNS formate is incorrect. Example: 1.1.1.1" return "DNS formate is incorrect. Example: 1.1.1.1"
@ -841,23 +897,27 @@ def add_peer(config_name):
return "Endpoint Allowed IPs format is incorrect." return "Endpoint Allowed IPs format is incorrect."
if len(data['MTU']) != 0: if len(data['MTU']) != 0:
try: try:
# TODO need to using
mtu = int(data['MTU']) mtu = int(data['MTU'])
except: except Exception as exc:
logging.error(exc)
db.close() db.close()
sem.release() sem.release()
return "MTU format is not correct." return "MTU format is not correct."
if len(data['keep_alive']) != 0: if len(data['keep_alive']) != 0:
try: try:
# TODO need to using
keep_alive = int(data['keep_alive']) keep_alive = int(data['keep_alive'])
except: except Exception as exc:
logging.error(exc)
db.close() db.close()
sem.release() sem.release()
return "Persistent Keepalive format is not correct." return "Persistent Keepalive format is not correct."
try: try:
status = subprocess.check_output( subprocess.run(f"wg set {config_name} peer {public_key} allowed-ips {allowed_ips}",
"wg set " + config_name + " peer " + public_key + " allowed-ips " + allowed_ips, shell=True, check=True, shell=True, capture_output=True, stderr=subprocess.STDOUT)
stderr=subprocess.STDOUT) subprocess.run("wg-quick save " + config_name,
status = subprocess.check_output("wg-quick save " + config_name, shell=True, stderr=subprocess.STDOUT) check=True, shell=True, capture_output=True, stderr=subprocess.STDOUT)
get_all_peers_data(config_name) get_all_peers_data(config_name)
db.update({"name": data['name'], "private_key": data['private_key'], "DNS": data['DNS'], db.update({"name": data['name'], "private_key": data['private_key'], "DNS": data['DNS'],
"endpoint_allowed_ip": endpoint_allowed_ip}, "endpoint_allowed_ip": endpoint_allowed_ip},
@ -870,6 +930,7 @@ def add_peer(config_name):
sem.release() sem.release()
return exc.output.strip() return exc.output.strip()
# Remove peer # Remove peer
@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):
@ -889,9 +950,10 @@ def remove_peer(config_name):
return "This key does not exist" return "This key does not exist"
else: else:
try: try:
status = subprocess.check_output("wg set " + config_name + " peer " + delete_key + " remove", shell=True, subprocess.run(f"wg set {config_name} peer {delete_key} remove",
stderr=subprocess.STDOUT) check=True, shell=True, capture_output=True, stderr=subprocess.STDOUT)
status = subprocess.check_output("wg-quick save " + config_name, shell=True, stderr=subprocess.STDOUT) subprocess.run("wg-quick save " + config_name,
check=True, shell=True, capture_output=True, stderr=subprocess.STDOUT)
db.remove(peers.id == delete_key) db.remove(peers.id == delete_key)
db.close() db.close()
sem.release() sem.release()
@ -901,6 +963,7 @@ def remove_peer(config_name):
sem.release() sem.release()
return exc.output.strip() return exc.output.strip()
# Save peer settings # Save peer settings
@app.route('/save_peer_setting/<config_name>', methods=['POST']) @app.route('/save_peer_setting/<config_name>', methods=['POST'])
def save_peer_setting(config_name): def save_peer_setting(config_name):
@ -908,7 +971,7 @@ def save_peer_setting(config_name):
id = data['id'] id = data['id']
name = data['name'] name = data['name']
private_key = data['private_key'] private_key = data['private_key']
DNS = data['DNS'] dns_addresses = data['DNS']
allowed_ip = data['allowed_ip'] allowed_ip = data['allowed_ip']
endpoint_allowed_ip = data['endpoint_allowed_ip'] endpoint_allowed_ip = data['endpoint_allowed_ip']
sem.acquire() sem.acquire()
@ -921,21 +984,25 @@ def save_peer_setting(config_name):
db.close() db.close()
sem.release() sem.release()
return jsonify({"status": "failed", "msg": "Endpoint Allowed IPs format is incorrect."}) return jsonify({"status": "failed", "msg": "Endpoint Allowed IPs format is incorrect."})
if not check_DNS(DNS): if not check_DNS(dns_addresses):
db.close() db.close()
sem.release() sem.release()
return jsonify({"status": "failed", "msg": "DNS format is incorrect."}) return jsonify({"status": "failed", "msg": "DNS format is incorrect."})
if len(data['MTU']) != 0: if len(data['MTU']) != 0:
try: try:
# TODO need to use
mtu = int(data['MTU']) mtu = int(data['MTU'])
except: except Exception as exc:
logging.error(exc)
db.close() db.close()
sem.release() sem.release()
return jsonify({"status": "failed", "msg": "MTU format is not correct."}) return jsonify({"status": "failed", "msg": "MTU format is not correct."})
if len(data['keep_alive']) != 0: if len(data['keep_alive']) != 0:
try: try:
# TODO need to using
keep_alive = int(data['keep_alive']) keep_alive = int(data['keep_alive'])
except: except Exception as exc:
logging.error(exc)
db.close() db.close()
sem.release() sem.release()
return jsonify({"status": "failed", "msg": "Persistent Keepalive format is not correct."}) return jsonify({"status": "failed", "msg": "Persistent Keepalive format is not correct."})
@ -953,17 +1020,17 @@ def save_peer_setting(config_name):
if allowed_ip == "": if allowed_ip == "":
allowed_ip = '""' allowed_ip = '""'
allowed_ip = allowed_ip.replace(" ", "") allowed_ip = allowed_ip.replace(" ", "")
change_ip = subprocess.check_output('wg set ' + config_name + " peer " + id + " allowed-ips " + allowed_ip, change_ip = subprocess.run('wg set ' + config_name + " peer " + id + " allowed-ips " + allowed_ip,
shell=True, stderr=subprocess.STDOUT) check=True, shell=True, capture_output=True, stderr=subprocess.STDOUT).stdout
save_change_ip = subprocess.check_output('wg-quick save ' + config_name, shell=True, subprocess.run('wg-quick save ' + config_name,
stderr=subprocess.STDOUT) check=True, shell=True, capture_output=True, stderr=subprocess.STDOUT)
if change_ip.decode("UTF-8") != "": if change_ip.decode("UTF-8") != "":
db.close() db.close()
sem.release() sem.release()
return jsonify({"status": "failed", "msg": change_ip.decode("UTF-8")}) return jsonify({"status": "failed", "msg": change_ip.decode("UTF-8")})
db.update( db.update(
{"name": name, "private_key": private_key, {"name": name, "private_key": private_key,
"DNS": DNS, "endpoint_allowed_ip": endpoint_allowed_ip, "DNS": dns_addresses, "endpoint_allowed_ip": endpoint_allowed_ip,
"mtu": data['MTU'], "mtu": data['MTU'],
"keepalive": data['keep_alive']}, "keepalive": data['keep_alive']},
peers.id == id) peers.id == id)
@ -979,6 +1046,7 @@ def save_peer_setting(config_name):
sem.release() sem.release()
return jsonify({"status": "failed", "msg": "This peer does not exist."}) return jsonify({"status": "failed", "msg": "This peer does not exist."})
# Get peer settings # Get peer settings
@app.route('/get_peer_data/<config_name>', methods=['POST']) @app.route('/get_peer_data/<config_name>', methods=['POST'])
def get_peer_name(config_name): def get_peer_name(config_name):
@ -996,11 +1064,13 @@ def get_peer_name(config_name):
sem.release() sem.release()
return jsonify(data) return jsonify(data)
# Generate a private key # Generate a private key
@app.route('/generate_peer', methods=['GET']) @app.route('/generate_peer', methods=['GET'])
def generate_peer(): def generate_peer():
return jsonify(gen_private_key()) return jsonify(gen_private_key())
# Generate a public key from a private key # Generate a public key from a private key
@app.route('/generate_public_key', methods=['POST']) @app.route('/generate_public_key', methods=['POST'])
def generate_public_key(): def generate_public_key():
@ -1008,6 +1078,7 @@ def generate_public_key():
private_key = data['private_key'] private_key = data['private_key']
return jsonify(gen_public_key(private_key)) return jsonify(gen_public_key(private_key))
# Check if both key match # Check if both key match
@app.route('/check_key_match/<config_name>', methods=['POST']) @app.route('/check_key_match/<config_name>', methods=['POST'])
def check_key_match(config_name): def check_key_match(config_name):
@ -1033,8 +1104,8 @@ def generate_qrcode(config_name):
endpoint = config.get("Peers", "remote_endpoint") + ":" + listen_port endpoint = config.get("Peers", "remote_endpoint") + ":" + listen_port
private_key = peer['private_key'] private_key = peer['private_key']
allowed_ip = peer['allowed_ip'] allowed_ip = peer['allowed_ip']
DNS = peer['DNS'] dns_addresses = peer['DNS']
MTU = peer['mtu'] mtu_value = peer['mtu']
endpoint_allowed_ip = peer['endpoint_allowed_ip'] endpoint_allowed_ip = peer['endpoint_allowed_ip']
keepalive = peer['keepalive'] keepalive = peer['keepalive']
conf = { conf = {
@ -1043,8 +1114,8 @@ def generate_qrcode(config_name):
"endpoint": endpoint, "endpoint": endpoint,
"private_key": private_key, "private_key": private_key,
"allowed_ip": allowed_ip, "allowed_ip": allowed_ip,
"DNS": DNS, "DNS": dns_addresses,
"mtu": MTU, "mtu": mtu_value,
"endpoint_allowed_ip": endpoint_allowed_ip, "endpoint_allowed_ip": endpoint_allowed_ip,
"keepalive": keepalive, "keepalive": keepalive,
} }
@ -1055,6 +1126,8 @@ def generate_qrcode(config_name):
db.close() db.close()
sem.release() sem.release()
return redirect("/configuration/" + config_name) return redirect("/configuration/" + config_name)
# Download configuration file # Download configuration file
@app.route('/<config_name>', methods=['GET']) @app.route('/<config_name>', methods=['GET'])
def download(config_name): def download(config_name):
@ -1073,8 +1146,8 @@ def download(config_name):
endpoint = config.get("Peers", "remote_endpoint") + ":" + listen_port endpoint = config.get("Peers", "remote_endpoint") + ":" + listen_port
private_key = peer['private_key'] private_key = peer['private_key']
allowed_ip = peer['allowed_ip'] allowed_ip = peer['allowed_ip']
DNS = peer['DNS'] dns_addresses = peer['DNS']
MTU = peer['mtu'] mtu_value = peer['mtu']
endpoint_allowed_ip = peer['endpoint_allowed_ip'] endpoint_allowed_ip = peer['endpoint_allowed_ip']
keepalive = peer['keepalive'] keepalive = peer['keepalive']
filename = peer['name'] filename = peer['name']
@ -1093,17 +1166,21 @@ def download(config_name):
filename = "".join(filename.split(' ')) filename = "".join(filename.split(' '))
filename = filename + "_" + config_name filename = filename + "_" + config_name
def generate(private_key, allowed_ip, DNS, MTU, public_key, endpoint, keepalive):
yield "[Interface]\nPrivateKey = " + private_key + "\nAddress = " + allowed_ip + "\nDNS = " + DNS + "\nMTU = " + MTU + "\n\n[Peer]\nPublicKey = " + public_key + "\nAllowedIPs = " + endpoint_allowed_ip + "\nEndpoint = " + endpoint+ "\nPersistentKeepalive = " + keepalive
db.close() db.close()
sem.release() sem.release()
return app.response_class(generate(private_key, allowed_ip, DNS, MTU, public_key, endpoint, keepalive), result = "[Interface]\nPrivateKey = " + private_key + "\nAddress = " + allowed_ip + "\nDNS = " + \
dns_addresses + "\nMTU = " + mtu_value + "\n\n[Peer]\nPublicKey = " + \
public_key + "\nAllowedIPs = " + endpoint_allowed_ip + "\nEndpoint = " + \
endpoint + "\nPersistentKeepalive = " + keepalive
return app.response_class((yield result),
mimetype='text/conf', mimetype='text/conf',
headers={"Content-Disposition": "attachment;filename=" + filename + ".conf"}) headers={"Content-Disposition": "attachment;filename=" + filename + ".conf"})
else: else:
db.close() db.close()
return redirect("/configuration/" + config_name) return redirect("/configuration/" + config_name)
# Switch peer displate mode # Switch peer displate mode
@app.route('/switch_display_mode/<mode>', methods=['GET']) @app.route('/switch_display_mode/<mode>', methods=['GET'])
def switch_display_mode(mode): def switch_display_mode(mode):
@ -1119,6 +1196,8 @@ def switch_display_mode(mode):
""" """
Dashboard Tools Related Dashboard Tools Related
""" """
# Get all IP for ping # Get all IP for ping
@app.route('/get_ping_ip', methods=['POST']) @app.route('/get_ping_ip', methods=['POST'])
def get_ping_ip(): def get_ping_ip():
@ -1141,6 +1220,7 @@ def get_ping_ip():
sem.release() sem.release()
return html return html
# Ping IP # Ping IP
@app.route('/ping_ip', methods=['POST']) @app.route('/ping_ip', methods=['POST'])
def ping_ip(): def ping_ip():
@ -1159,9 +1239,11 @@ def ping_ip():
if returnjson['package_loss'] == 1.0: if returnjson['package_loss'] == 1.0:
returnjson['package_loss'] = returnjson['package_sent'] returnjson['package_loss'] = returnjson['package_sent']
return jsonify(returnjson) return jsonify(returnjson)
except Exception: except Exception as exc:
logging.error(exc)
return "Error" return "Error"
# Traceroute IP # Traceroute IP
@app.route('/traceroute_ip', methods=['POST']) @app.route('/traceroute_ip', methods=['POST'])
def traceroute_ip(): def traceroute_ip():
@ -1176,16 +1258,20 @@ def traceroute_ip():
"max_rtt": hop.max_rtt}) "max_rtt": hop.max_rtt})
last_distance = hop.distance last_distance = hop.distance
return jsonify(returnjson) return jsonify(returnjson)
except Exception: except Exception as exc:
logging.error(exc)
return "Error" return "Error"
""" """
Dashboard Initialization Dashboard Initialization
""" """
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"):
conf_file = open("wg-dashboard.ini", "w+") open("wg-dashboard.ini", "w")
config = configparser.ConfigParser(strict=False) config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf) config.read(dashboard_conf)
# Defualt dashboard account setting # Defualt dashboard account setting
@ -1231,17 +1317,20 @@ def init_dashboard():
config.write(open(dashboard_conf, "w")) config.write(open(dashboard_conf, "w"))
config.clear() config.clear()
def check_update():
""" """
Dashboard check update Dashboard 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/WGDashboard/releases").read() data = urllib.request.urlopen("https://api.github.com/repos/donaldzou/WGDashboard/releases").read()
output = json.loads(data) output = json.loads(data)
release = [] release = []
for i in output: for i in output:
if i["prerelease"] == False: release.append(i) if not i["prerelease"]:
release.append(i)
if conf.get("Server", "version") == release[0]["tag_name"]: if conf.get("Server", "version") == release[0]["tag_name"]:
return "false" return "false"
else: else:
@ -1254,7 +1343,7 @@ if __name__ == "__main__":
config = configparser.ConfigParser(strict=False) config = configparser.ConfigParser(strict=False)
config.read('wg-dashboard.ini') config.read('wg-dashboard.ini')
app_ip = config.get("Server", "app_ip") app_ip = config.get("Server", "app_ip")
app_port = config.get("Server", "app_port") app_port = int(config.get("Server", "app_port"))
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)