1
0
mirror of https://github.com/donaldzou/WGDashboard.git synced 2024-07-05 00:40:15 +02:00

Merge branch 'main' into change-path

# Conflicts:
#	src/dashboard.py
This commit is contained in:
Galonza Peter 2021-12-25 23:42:10 +03:00
commit 344770a0a4
8 changed files with 316 additions and 102 deletions

View File

@ -3,10 +3,10 @@
Under Apache-2.0 License Under Apache-2.0 License
""" """
# Import other python files
from util import *
# Python Built-in Library # Python Built-in Library
import os import os
from flask import Flask, request, render_template, redirect, url_for, session, abort, jsonify from flask import Flask, request, render_template, redirect, url_for, session, abort, jsonify
import subprocess import subprocess
from datetime import datetime, date, time, timedelta from datetime import datetime, date, time, timedelta
@ -17,10 +17,15 @@ import hashlib
import json, urllib.request import json, urllib.request
import configparser import configparser
import re import re
import ipaddress
import sqlite3
import threading
# PIP installed library # PIP installed library
import ifcfg import ifcfg
from flask_qrcode import QRcode from flask_qrcode import QRcode
from tinydb import TinyDB, Query from tinydb import TinyDB, Query
from tinydb.storages import JSONStorage
from tinydb.middlewares import CachingMiddleware
from icmplib import ping, multiping, traceroute, resolve, Host, Hop from icmplib import ping, multiping, traceroute, resolve, Host, Hop
# Dashboard Version # Dashboard Version
dashboard_version = 'v3.0' dashboard_version = 'v3.0'
@ -34,81 +39,14 @@ dashboard_conf = os.path.join(configuration_path, 'wg-dashboard.ini')
update = "" update = ""
# Flask App Configuration # Flask App Configuration
app = Flask("WGDashboard") app = Flask("WGDashboard")
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 5206928
app.secret_key = secrets.token_urlsafe(16) app.secret_key = secrets.token_urlsafe(16)
app.config['TEMPLATES_AUTO_RELOAD'] = True app.config['TEMPLATES_AUTO_RELOAD'] = True
# Enable QR Code Generator # Enable QR Code Generator
QRcode(app) QRcode(app)
""" # TODO: Testing semaphore on reading/writing database
Helper Functions sem = threading.RLock()
"""
# Regex Match
def regex_match(regex, text):
pattern = re.compile(regex)
return pattern.search(text) is not None
# Check IP format
def check_IP(ip):
ip_patterns = (
r"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}",
r"[0-9a-fA-F]{0,4}(:([0-9a-fA-F]{0,4})){1,7}$"
)
for match_pattern in ip_patterns:
match_result = regex_match(match_pattern, ip)
if match_result:
result = match_result
break
else:
result = None
return result
# Clean IP
def clean_IP(ip):
return ip.replace(' ', '')
# Clean IP with range
def clean_IP_with_range(ip):
return clean_IP(ip).split(',')
# Check IP with range
def check_IP_with_range(ip):
ip_patterns = (
r"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|\/)){4}([0-9]{1,2})(,|$)",
r"[0-9a-fA-F]{0,4}(:([0-9a-fA-F]{0,4})){1,7}\/([0-9]{1,3})(,|$)"
)
for match_pattern in ip_patterns:
match_result = regex_match(match_pattern, ip)
if match_result:
result = match_result
break
else:
result = None
return result
# Check allowed ips list
def check_Allowed_IPs(ip):
ip = clean_IP_with_range(ip)
for i in ip:
if not check_IP_with_range(i): return False
return True
# Check DNS
def check_DNS(dns):
dns = dns.replace(' ','').split(',')
status = True
for i in dns:
if not (check_IP(i) or regex_match("(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z]{0,61}[a-z]",i)):
return False
return True
# Check remote endpoint
def check_remote_endpoint(address):
return (check_IP(address) or regex_match("(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z]{0,61}[a-z]", address))
""" """
@ -289,7 +227,10 @@ def get_allowed_ip(config_name, db, peers, conf_peer_data):
# 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()
db = TinyDB(os.path.join(db_path, config_name + '.json')) db = TinyDB(os.path.join(db_path, config_name + '.json'))
peers = Query() peers = Query()
conf_peer_data = read_conf_file(config_name) conf_peer_data = read_conf_file(config_name)
config = get_dashboard_conf() config = get_dashboard_conf()
@ -346,6 +287,7 @@ def get_all_peers_data(config_name):
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()
""" """
Frontend Related Functions Frontend Related Functions
@ -353,14 +295,20 @@ Frontend Related Functions
# Search for peers # Search for peers
def get_peers(config_name, search, sort_t): def get_peers(config_name, search, sort_t):
get_all_peers_data(config_name) get_all_peers_data(config_name)
sem.acquire()
db = TinyDB(os.path.join(db_path, config_name + ".json")) db = TinyDB(os.path.join(db_path, config_name + ".json"))
peer = Query() peer = Query()
if len(search) == 0: if len(search) == 0:
result = db.all() result = db.all()
else: else:
result = db.search(peer.name.matches('(.*)(' + re.escape(search) + ')(.*)')) result = db.search(peer.name.matches('(.*)(' + re.escape(search) + ')(.*)'))
if sort_t == "allowed_ip":
result = sorted(result, key = lambda d: ipaddress.ip_network(d[sort_t].split(",")[0]))
else:
result = sorted(result, key=lambda d: d[sort_t]) result = sorted(result, key=lambda d: d[sort_t])
db.close() db.close()
sem.release()
return result return result
@ -391,6 +339,8 @@ def get_conf_listen_port(config_name):
# Get configuration total data # Get configuration total data
def get_conf_total_data(config_name): def get_conf_total_data(config_name):
sem.acquire()
db = TinyDB(os.path.join(db_path, config_name + ".json")) db = TinyDB(os.path.join(db_path, config_name + ".json"))
upload_total = 0 upload_total = 0
download_total = 0 download_total = 0
@ -404,6 +354,7 @@ def get_conf_total_data(config_name):
upload_total = round(upload_total, 4) upload_total = round(upload_total, 4)
download_total = round(download_total, 4) download_total = round(download_total, 4)
db.close() db.close()
sem.release()
return [total, upload_total, download_total] return [total, upload_total, download_total]
# Get configuration status # Get configuration status
@ -467,17 +418,27 @@ def checkKeyMatch(private_key, public_key, config_name):
if result['status'] == 'failed': if result['status'] == 'failed':
return result return result
else: else:
sem.acquire()
db = TinyDB(os.path.join(db_path, config_name + ".json")) db = TinyDB(os.path.join(db_path, config_name + ".json"))
peers = Query() peers = Query()
match = db.search(peers.id == result['data']) match = db.search(peers.id == result['data'])
if len(match) != 1 or result['data'] != public_key: if len(match) != 1 or result['data'] != public_key:
db.close()
sem.release()
return {'status': 'failed', 'msg': 'Please check your private key, it does not match with the public key.'} return {'status': 'failed', 'msg': 'Please check your private key, it does not match with the public key.'}
else: else:
db.close()
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):
db = TinyDB(os.path.join(db_path, config_name + ".json")) db = TinyDB(os.path.join(db_path, config_name + ".json"))
sem.acquire()
db = TinyDB('db/' + config_name + '.json')
peers = Query() peers = Query()
peer = db.search(peers.id == public_key) peer = db.search(peers.id == public_key)
if len(peer) != 1: if len(peer) != 1:
@ -485,8 +446,12 @@ def check_repeat_allowed_IP(public_key, ip, config_name):
else: else:
existed_ip = db.search((peers.id != public_key) & (peers.allowed_ip == ip)) existed_ip = db.search((peers.id != public_key) & (peers.allowed_ip == ip))
if len(existed_ip) != 0: if len(existed_ip) != 0:
db.close()
sem.release()
return {'status': 'failed', 'msg': "Allowed IP already taken by another peer."} return {'status': 'failed', 'msg': "Allowed IP already taken by another peer."}
else: else:
db.close()
sem.release()
return {'status': 'success'} return {'status': 'success'}
@ -497,6 +462,7 @@ 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")
@ -844,6 +810,8 @@ def switch(config_name):
# 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):
sem.acquire()
db = TinyDB(os.path.join(db_path, config_name + ".json")) db = TinyDB(os.path.join(db_path, config_name + ".json"))
peers = Query() peers = Query()
data = request.get_json() data = request.get_json()
@ -853,26 +821,42 @@ def add_peer(config_name):
DNS = data['DNS'] DNS = 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) == 0 or len(allowed_ips) == 0 or len(endpoint_allowed_ip) == 0:
db.close()
sem.release()
return "Please fill in all required box." return "Please fill in all required box."
if type(keys) != list: if type(keys) != list:
db.close()
sem.release()
return config_name + " is not running." return config_name + " is not running."
if public_key in keys: if public_key in keys:
db.close()
sem.release()
return "Public key already exist." return "Public key already exist."
if len(db.search(peers.allowed_ip.matches(allowed_ips))) != 0: if len(db.search(peers.allowed_ip.matches(allowed_ips))) != 0:
db.close()
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):
db.close()
sem.release()
return "DNS formate is incorrect. Example: 1.1.1.1" return "DNS formate is incorrect. Example: 1.1.1.1"
if not check_Allowed_IPs(endpoint_allowed_ip): if not check_Allowed_IPs(endpoint_allowed_ip):
db.close()
sem.release()
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:
mtu = int(data['MTU']) mtu = int(data['MTU'])
except: except:
db.close()
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:
keep_alive = int(data['keep_alive']) keep_alive = int(data['keep_alive'])
except: except:
db.close()
sem.release()
return "Persistent Keepalive format is not correct." return "Persistent Keepalive format is not correct."
try: try:
status = subprocess.check_output( status = subprocess.check_output(
@ -884,9 +868,11 @@ def add_peer(config_name):
"endpoint_allowed_ip": endpoint_allowed_ip}, "endpoint_allowed_ip": endpoint_allowed_ip},
peers.id == public_key) peers.id == public_key)
db.close() db.close()
sem.release()
return "true" return "true"
except subprocess.CalledProcessError as exc: except subprocess.CalledProcessError as exc:
db.close() db.close()
sem.release()
return exc.output.strip() return exc.output.strip()
# Remove peer # Remove peer
@ -894,6 +880,8 @@ def add_peer(config_name):
def remove_peer(config_name): def remove_peer(config_name):
if get_conf_status(config_name) == "stopped": if get_conf_status(config_name) == "stopped":
return "Your need to turn on " + config_name + " first." return "Your need to turn on " + config_name + " first."
sem.acquire()
db = TinyDB(os.path.join(db_path, config_name + ".json")) db = TinyDB(os.path.join(db_path, config_name + ".json"))
peers = Query() peers = Query()
data = request.get_json() data = request.get_json()
@ -911,8 +899,11 @@ def remove_peer(config_name):
status = subprocess.check_output("wg-quick save " + config_name, shell=True, stderr=subprocess.STDOUT) status = subprocess.check_output("wg-quick save " + config_name, shell=True, stderr=subprocess.STDOUT)
db.remove(peers.id == delete_key) db.remove(peers.id == delete_key)
db.close() db.close()
sem.release()
return "true" return "true"
except subprocess.CalledProcessError as exc: except subprocess.CalledProcessError as exc:
db.close()
sem.release()
return exc.output.strip() return exc.output.strip()
# Save peer settings # Save peer settings
@ -925,37 +916,55 @@ def save_peer_setting(config_name):
DNS = data['DNS'] DNS = 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()
db = TinyDB(os.path.join(db_path, config_name + ".json")) db = TinyDB(os.path.join(db_path, config_name + ".json"))
peers = Query() peers = Query()
if len(db.search(peers.id == id)) == 1: if len(db.search(peers.id == id)) == 1:
check_ip = check_repeat_allowed_IP(id, allowed_ip, config_name) check_ip = check_repeat_allowed_IP(id, allowed_ip, config_name)
if not check_IP_with_range(endpoint_allowed_ip): if not check_IP_with_range(endpoint_allowed_ip):
db.close()
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):
db.close()
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:
mtu = int(data['MTU']) mtu = int(data['MTU'])
except: except:
db.close()
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:
keep_alive = int(data['keep_alive']) keep_alive = int(data['keep_alive'])
except: except:
db.close()
sem.release()
return jsonify({"status": "failed", "msg": "Persistent Keepalive format is not correct."}) return jsonify({"status": "failed", "msg": "Persistent Keepalive format is not correct."})
if private_key != "": if private_key != "":
check_key = checkKeyMatch(private_key, id, config_name) check_key = checkKeyMatch(private_key, id, config_name)
if check_key['status'] == "failed": if check_key['status'] == "failed":
db.close()
sem.release()
return jsonify(check_key) return jsonify(check_key)
if check_ip['status'] == "failed": if check_ip['status'] == "failed":
db.close()
sem.release()
return jsonify(check_ip) return jsonify(check_ip)
try: try:
if allowed_ip == "": allowed_ip = '""' if allowed_ip == "":
allowed_ip = '""'
allowed_ip = allowed_ip.replace(" ", "")
change_ip = subprocess.check_output('wg set ' + config_name + " peer " + id + " allowed-ips " + allowed_ip, change_ip = subprocess.check_output('wg set ' + config_name + " peer " + id + " allowed-ips " + allowed_ip,
shell=True, stderr=subprocess.STDOUT) shell=True, stderr=subprocess.STDOUT)
save_change_ip = subprocess.check_output('wg-quick save ' + config_name, shell=True, save_change_ip = subprocess.check_output('wg-quick save ' + config_name, shell=True,
stderr=subprocess.STDOUT) stderr=subprocess.STDOUT)
if change_ip.decode("UTF-8") != "": if change_ip.decode("UTF-8") != "":
db.close()
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,
@ -964,10 +973,15 @@ def save_peer_setting(config_name):
"keepalive":data['keep_alive']}, "keepalive":data['keep_alive']},
peers.id == id) peers.id == id)
db.close() db.close()
sem.release()
return jsonify({"status": "success", "msg": ""}) return jsonify({"status": "success", "msg": ""})
except subprocess.CalledProcessError as exc: except subprocess.CalledProcessError as exc:
db.close()
sem.release()
return jsonify({"status": "failed", "msg": str(exc.output.decode("UTF-8").strip())}) return jsonify({"status": "failed", "msg": str(exc.output.decode("UTF-8").strip())})
else: else:
db.close()
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
@ -975,6 +989,8 @@ def save_peer_setting(config_name):
def get_peer_name(config_name): def get_peer_name(config_name):
data = request.get_json() data = request.get_json()
id = data['id'] id = data['id']
sem.acquire()
db = TinyDB(os.path.join(db_path, config_name + ".json")) db = TinyDB(os.path.join(db_path, config_name + ".json"))
peers = Query() peers = Query()
result = db.search(peers.id == id) result = db.search(peers.id == id)
@ -982,6 +998,7 @@ def get_peer_name(config_name):
data = {"name": result[0]['name'], "allowed_ip": result[0]['allowed_ip'], "DNS": result[0]['DNS'], data = {"name": result[0]['name'], "allowed_ip": result[0]['allowed_ip'], "DNS": result[0]['DNS'],
"private_key": result[0]['private_key'], "endpoint_allowed_ip": result[0]['endpoint_allowed_ip'], "private_key": result[0]['private_key'], "endpoint_allowed_ip": result[0]['endpoint_allowed_ip'],
"mtu": result[0]['mtu'], "keep_alive": result[0]['keepalive']} "mtu": result[0]['mtu'], "keep_alive": result[0]['keepalive']}
sem.release()
return jsonify(data) return jsonify(data)
# Generate a private key # Generate a private key
@ -1004,11 +1021,52 @@ def check_key_match(config_name):
public_key = data['public_key'] public_key = data['public_key']
return jsonify(checkKeyMatch(private_key, public_key, config_name)) return jsonify(checkKeyMatch(private_key, public_key, config_name))
@app.route("/qrcode/<config_name>", methods=['GET'])
def generate_qrcode(config_name):
id = request.args.get('id')
sem.acquire()
db = TinyDB('db/' + config_name + '.json')
peers = Query()
get_peer = db.search(peers.id == id)
config = get_dashboard_conf()
if len(get_peer) == 1:
peer = get_peer[0]
if peer['private_key'] != "":
public_key = get_conf_pub_key(config_name)
listen_port = get_conf_listen_port(config_name)
endpoint = config.get("Peers", "remote_endpoint") + ":" + listen_port
private_key = peer['private_key']
allowed_ip = peer['allowed_ip']
DNS = peer['DNS']
MTU = peer['mtu']
endpoint_allowed_ip = peer['endpoint_allowed_ip']
keepalive = peer['keepalive']
conf = {
"public_key": public_key,
"listen_port": listen_port,
"endpoint": endpoint,
"private_key": private_key,
"allowed_ip": allowed_ip,
"DNS": DNS,
"mtu": MTU,
"endpoint_allowed_ip": endpoint_allowed_ip,
"keepalive": keepalive,
}
db.close()
sem.release()
return render_template("qrcode.html", i=conf)
else:
db.close()
sem.release()
return redirect("/configuration/" + config_name)
# Download configuration file # Download configuration file
@app.route('/download/<config_name>', methods=['GET']) @app.route('/<config_name>', methods=['GET'])
def download(config_name): def download(config_name):
print(request.headers.get('User-Agent')) print(request.headers.get('User-Agent'))
id = request.args.get('id') id = request.args.get('id')
sem.acquire()
db = TinyDB(os.path.join(db_path, config_name + ".json")) db = TinyDB(os.path.join(db_path, config_name + ".json"))
peers = Query() peers = Query()
get_peer = db.search(peers.id == id) get_peer = db.search(peers.id == id)
@ -1043,11 +1101,13 @@ def download(config_name):
def generate(private_key, allowed_ip, DNS, MTU, public_key, endpoint, keepalive): 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 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()
sem.release()
return app.response_class(generate(private_key, allowed_ip, DNS, MTU, public_key, endpoint, keepalive), return app.response_class(generate(private_key, allowed_ip, DNS, MTU, public_key, endpoint, keepalive),
mimetype='text/conf', mimetype='text/conf',
headers={"Content-Disposition": "attachment;filename=" + filename + ".conf"}) headers={"Content-Disposition": "attachment;filename=" + filename + ".conf"})
else: else:
db.close()
return redirect("/configuration/" + config_name) return redirect("/configuration/" + config_name)
# Switch peer displate mode # Switch peer displate mode
@ -1069,6 +1129,8 @@ Dashboard Tools Related
@app.route('/get_ping_ip', methods=['POST']) @app.route('/get_ping_ip', methods=['POST'])
def get_ping_ip(): def get_ping_ip():
config = request.form['config'] config = request.form['config']
sem.acquire()
db = TinyDB(os.path.join(db_path, config + ".json")) db = TinyDB(os.path.join(db_path, config + ".json"))
html = "" html = ""
for i in db.all(): for i in db.all():
@ -1082,6 +1144,8 @@ def get_ping_ip():
if len(endpoint) == 2: if len(endpoint) == 2:
html += "<option value=" + endpoint[0] + ">" + endpoint[0] + "</option>" html += "<option value=" + endpoint[0] + ">" + endpoint[0] + "</option>"
html += "</optgroup>" html += "</optgroup>"
db.close()
sem.release()
return html return html
# Ping IP # Ping IP
@ -1101,8 +1165,6 @@ 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:
return "Error" return "Error"
@ -1203,3 +1265,12 @@ if __name__ == "__main__":
wg_conf_path = config.get("Server", "wg_conf_path") wg_conf_path = config.get("Server", "wg_conf_path")
config.clear() config.clear()
app.run(host=app_ip, debug=False, port=app_port) app.run(host=app_ip, debug=False, port=app_port)
else:
init_dashboard()
update = check_update()
config = configparser.ConfigParser(strict=False)
config.read('wg-dashboard.ini')
app_ip = config.get("Server", "app_ip")
app_port = config.get("Server", "app_port")
wg_conf_path = config.get("Server", "wg_conf_path")
config.clear()

View File

@ -1,5 +1,5 @@
Flask Flask
tinydb tinydb==4.5.2
ifcfg ifcfg
icmplib icmplib
flask-qrcode flask-qrcode

View File

@ -117,22 +117,50 @@ body {
.dot-running{ .dot-running{
background-color: #28a745!important; background-color: #28a745!important;
box-shadow: 0 0 0 0.2rem #28a74545;
}
.h6-dot-running{
margin-left: 0.3rem;
} }
.dot-stopped{ .dot-stopped{
background-color: #6c757d!important; background-color: #6c757d!important;
} }
.card-running{
border-color: #28a745;
}
.info h6{ .info h6{
line-break: anywhere; line-break: anywhere;
} }
.info .row .col-sm{
display: flex;
flex-direction: column;
}
.info .row .col-sm small{
display: flex;
}
.info .row .col-sm small strong:last-child(1){
margin-left: auto !important;
}
.btn-control{ .btn-control{
border: none !important; border: none !important;
padding: 0 1rem 0 0; padding: 0 1rem 0 0;
} }
.btn-control:active, .btn-control:focus{
background-color: transparent !important;
border: none !important;
box-shadow: none;
}
.share_peer_btn_group .btn-control{ .share_peer_btn_group .btn-control{
padding: 0 0 0 1rem; padding: 0 0 0 1rem;
} }
@ -213,7 +241,7 @@ main{
bottom: 3rem; bottom: 3rem;
right: 2rem; right: 2rem;
z-index: 99; z-index: 99;
border-radius: 100px; border-radius: 100px !important;
padding: 10px 20px; padding: 10px 20px;
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
} }
@ -275,3 +303,23 @@ main{
.key:hover{ .key:hover{
color: #007bff; color: #007bff;
} }
.card{
border-radius: 10px;
}
.peer_list .card .button-group{
height: 22px;
}
.form-control{
border-radius: 10px;
}
.btn{
border-radius: 8px;
}
.modal-content{
border-radius: 10px;
}

View File

@ -107,8 +107,14 @@ var qrcodeModal = new bootstrap.Modal(document.getElementById('qrcode_modal'), {
// QR Code // QR Code
$("body").on("click", ".btn-qrcode-peer", function (){ $("body").on("click", ".btn-qrcode-peer", function (){
var src = $(this).attr('img_src');
$.ajax({
"url": src,
"method": "GET"
}).done(function(res){
$("#qrcode_img").attr('src', res)
qrcodeModal.toggle(); qrcodeModal.toggle();
$("#qrcode_img").attr('src', $(this).attr('img_src')) })
}) })
// Delete Peer Modal // Delete Peer Modal

View File

@ -109,7 +109,6 @@
<i class="bi bi-plus-circle-fill" style=""></i> Add Peer <i class="bi bi-plus-circle-fill" style=""></i> Add Peer
</button> </button>
</div> </div>
<hr>
</div> </div>
</div> </div>
@ -126,26 +125,35 @@
{% else %} {% else %}
<div class="col-sm-6 col-lg-4"> <div class="col-sm-6 col-lg-4">
{% endif %} {% endif %}
<div class="card mb-3"> <div class="card mb-3 card-{{i['status']}}">
<div class="card-header"> {# <div class="card-header">#}
{# <div class="row">#}
{# <div class="col">#}
{# <div class="card-header-body ">#}
{# {% if not i['name']%}#}
{# {{ "Untitled" }}#}
{# {% else %}#}
{# {{i['name']}}#}
{# {% endif %}#}
{# </div>#}
{# </div>#}
{# </div>#}
{# </div>#}
<div class="card-body">
<div class="row"> <div class="row">
<div class="col"> <div class="col-sm">
<div class="card-header-body "> <h4>
{% if not i['name']%} {% if not i['name']%}
{{ "Untitled" }} {{ "Untitled" }}
{% else %} {% else %}
{{i['name']}} {{i['name']}}
{% endif %} {% endif %}
{# <span class="dot dot-{{i['status']}}"></span>#} </h4>
</div> </div>
</div> <div class="w-100"></div>
</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-6"> <div class="col-6">
<small class="text-muted"><strong>STATUS</strong></small> <small class="text-muted"><strong>STATUS</strong></small>
<h6 style="text-transform: uppercase;" class="mb-2"><span class="dot dot-{{i['status']}}" style="margin-left: 0 !important;margin-top: 5px"></span></h6> <h6 style="text-transform: uppercase;" class="mb-2 h6-dot-{{i['status']}}"><span class="dot dot-{{i['status']}}" style="margin-left: 0 !important;margin-top: 5px"></span></h6>
</div> </div>
<div class="col-6 peer_data_group" style="text-align: right"> <div class="col-6 peer_data_group" style="text-align: right">
<small class="text-muted"><strong>TRANSFER</strong></small> <small class="text-muted"><strong>TRANSFER</strong></small>
@ -162,7 +170,7 @@
<small class="text-muted"><strong>ALLOWED IP</strong></small> <small class="text-muted"><strong>ALLOWED IP</strong></small>
<h6 style="text-transform: uppercase;">{{i['allowed_ip']}}</h6> <h6 style="text-transform: uppercase;">{{i['allowed_ip']}}</h6>
</div> </div>
<div class="w-100"></div> {# <div class="w-100"></div>#}
<div class="col-sm"> <div class="col-sm">
<small class="text-muted"><strong>LATEST HANDSHAKE</strong></small> <small class="text-muted"><strong>LATEST HANDSHAKE</strong></small>
@ -182,7 +190,7 @@
<button type="button" class="btn btn-outline-danger btn-delete-peer btn-control" id="{{i['id']}}" data-toggle="modal"><i class="bi bi-x-circle-fill"></i></button> <button type="button" class="btn btn-outline-danger btn-delete-peer btn-control" id="{{i['id']}}" data-toggle="modal"><i class="bi bi-x-circle-fill"></i></button>
{% if i['private_key'] %} {% if i['private_key'] %}
<div class="share_peer_btn_group" style="margin-left: auto !important; display: inline"> <div class="share_peer_btn_group" style="margin-left: auto !important; display: inline">
<button type="button" class="btn btn-outline-success btn-qrcode-peer btn-control" img_src="{{ qrcode("[Interface]\nPrivateKey = "+i['private_key']+"\nAddress = "+i['allowed_ip']+"\nMTU = "+i['mtu']+"\nDNS = "+i['DNS']+"\n\n[Peer]\nPublicKey = "+conf_data['public_key']+"\nAllowedIPs = "+i['endpoint_allowed_ip']+"\nPersistentKeepalive = "+i['keepalive']+"\nEndpoint = "+wg_ip+":"+conf_data['listen_port']) }}"> <button type="button" class="btn btn-outline-success btn-qrcode-peer btn-control" img_src="/qrcode/{{ conf_data['name'] }}?id={{ i['id']|urlencode }}">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="width: 19px;" fill="#28a745"><path d="M3 11h8V3H3v8zm2-6h4v4H5V5zM3 21h8v-8H3v8zm2-6h4v4H5v-4zM13 3v8h8V3h-8zm6 6h-4V5h4v4zM13 13h2v2h-2zM15 15h2v2h-2zM13 17h2v2h-2zM17 17h2v2h-2zM19 19h2v2h-2zM15 19h2v2h-2zM17 13h2v2h-2zM19 15h2v2h-2z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="width: 19px;" fill="#28a745"><path d="M3 11h8V3H3v8zm2-6h4v4H5V5zM3 21h8v-8H3v8zm2-6h4v4H5v-4zM13 3v8h8V3h-8zm6 6h-4V5h4v4zM13 13h2v2h-2zM15 15h2v2h-2zM13 17h2v2h-2zM17 17h2v2h-2zM19 19h2v2h-2zM15 19h2v2h-2zM17 13h2v2h-2zM19 15h2v2h-2z"/></svg>
</button> </button>
<a href="/download/{{ conf_data['name'] }}?id={{ i['id']|urlencode }}" class="btn btn-outline-info btn-download-peer btn-control"> <a href="/download/{{ conf_data['name'] }}?id={{ i['id']|urlencode }}" class="btn btn-outline-info btn-download-peer btn-control">

View File

@ -0,0 +1 @@
{{ qrcode("[Interface]\nPrivateKey = "+i['private_key']+"\nAddress = "+i['allowed_ip']+"\nMTU = "+i['mtu']+"\nDNS = "+i['DNS']+"\n\n[Peer]\nPublicKey = "+i['public_key']+"\nAllowedIPs = "+i['endpoint_allowed_ip']+"\nPersistentKeepalive = "+i['keepalive']+"\nEndpoint = "+i['endpoint']) }}

80
src/util.py Normal file
View File

@ -0,0 +1,80 @@
import re
"""
Helper Functions
"""
# Regex Match
def regex_match(regex, text):
pattern = re.compile(regex)
return pattern.search(text) is not None
# Check IP format
def check_IP(ip):
ip_patterns = (
r"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}",
r"[0-9a-fA-F]{0,4}(:([0-9a-fA-F]{0,4})){1,7}$"
)
for match_pattern in ip_patterns:
match_result = regex_match(match_pattern, ip)
if match_result:
result = match_result
break
else:
result = None
return result
# Clean IP
def clean_IP(ip):
return ip.replace(' ', '')
# Clean IP with range
def clean_IP_with_range(ip):
return clean_IP(ip).split(',')
# Check IP with range
def check_IP_with_range(ip):
ip_patterns = (
r"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|\/)){4}([0-9]{1,2})(,|$)",
r"[0-9a-fA-F]{0,4}(:([0-9a-fA-F]{0,4})){1,7}\/([0-9]{1,3})(,|$)"
)
for match_pattern in ip_patterns:
match_result = regex_match(match_pattern, ip)
if match_result:
result = match_result
break
else:
result = None
return result
# Check allowed ips list
def check_Allowed_IPs(ip):
ip = clean_IP_with_range(ip)
for i in ip:
if not check_IP_with_range(i): return False
return True
# Check DNS
def check_DNS(dns):
dns = dns.replace(' ', '').split(',')
status = True
for i in dns:
if not (check_IP(i) or regex_match("(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z]{0,61}[a-z]", i)):
return False
return True
# Check remote endpoint
def check_remote_endpoint(address):
return (check_IP(address) or regex_match("(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z]{0,61}[a-z]",
address))

View File

@ -33,9 +33,9 @@ _check_and_set_venv(){
install_wgd(){ install_wgd(){
# Check Python3 version # Check Python3 version
version_pass=$(python3 -c 'import sys; print("1") if (sys.version_info.major == 3 and sys.version_info.minor >= 7) else print("0");') version_pass=$(python3 -c 'import sys; print("1") if (sys.version_info.major == 3 and sys.version_info.minor >= 8) else print("0");')
if [ $version_pass == "0" ] if [ $version_pass == "0" ]
then printf "| WGDashboard required Python3.7+ |\n" then printf "| WGDashboard required Python3.8+ |\n"
printf "%s\n" "$dashes" printf "%s\n" "$dashes"
exit 1 exit 1
fi fi
@ -47,14 +47,14 @@ install_wgd(){
# set up the local environment # set up the local environment
_check_and_set_venv _check_and_set_venv
${VIRTUAL_ENV}/bin/python3 -m pip -U pip
python3 -m pip install -r requirements.txt > /dev/null 2>&1 ${VIRTUAL_ENV}/bin/python3 -m pip install -U -r requirements.txt
printf "| WGDashboard installed successfully! |\n" printf "| WGDashboard installed successfully! |\n"
printf "| Preparing the systemctl unit file |\n" printf "| Preparing the systemctl unit file |\n"
sed -i "s#{{APP_ROOT}}#${APP_ROOT}#" wg-dashboard.service sed -i "s#{{APP_ROOT}}#${APP_ROOT}#" wg-dashboard.service
sed -i "s#{{VIRTUAL_ENV}}#${VIRTUAL_ENV}#" wg-dashboard.service sed -i "s#{{VIRTUAL_ENV}}#${VIRTUAL_ENV}#" wg-dashboard.service
cat wg-dashboard.service | sudo SYSTEMD_EDITOR=tee systemctl edit --force --full wg-dashboard.service # cat wg-dashboard.service | sudo SYSTEMD_EDITOR=tee systemctl edit --force --full wg-dashboard.service
systemctl daemon-reload systemctl daemon-reload
printf "| Consider 'systemctl enable wg-dashboard' |\n" printf "| Consider 'systemctl enable wg-dashboard' |\n"
printf " and 'systemctl start wg-dashboard'\n" printf " and 'systemctl start wg-dashboard'\n"