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

Finalizing everything.

This commit is contained in:
Donald Cheng Hong Zou 2022-01-18 10:42:23 -05:00
parent e2e8222005
commit 258564208b
5 changed files with 245 additions and 246 deletions

View File

@ -18,16 +18,19 @@
- 🎉 **New Features** - 🎉 **New Features**
- **Moved from TinyDB to SQLite**: SQLite provide a better performance and loading speed when getting peers! Also avoided crashing the database due to **race condition**. - **Moved from TinyDB to SQLite**: SQLite provide a better performance and loading speed when getting peers! Also avoided crashing the database due to **race condition**.
- **Added Gunicorn WSGI Server**: This could provide more stable on handling HTTP request, and more flexibility in the future (such as HTTPS support). **BIG THANKS to @pgalonza :heart: **
- **Add Peers by Bulk: ** User can add peers by bulk, just simply set the amount and click add. - **Add Peers by Bulk: ** User can add peers by bulk, just simply set the amount and click add.
- **Delete Peers by Bulk**: User can delete peers by bulk, without deleting peers one by one. - **Delete Peers by Bulk**: User can delete peers by bulk, without deleting peers one by one.
- **Download Peers in Zip**: User can download all *downloadable* peers in a zip. - **Download Peers in Zip**: User can download all *downloadable* peers in a zip.
- **Added Pre-shared Key to peers:** Now each peer can add with a pre-shared key to enhance security. Previously added peers can add the pre-shared key through the peer setting button. - **Added Pre-shared Key to peers:** Now each peer can add with a pre-shared key to enhance security. Previously added peers can add the pre-shared key through the peer setting button.
- **Redirect Back to Previous Page:** The dashboard will now redirect you back to your previous page if the current session got timed out and you need to sign in again.
- 🪚 **Bug Fixed** - 🪚 **Bug Fixed**
- [IP Sorting range issues #99](https://github.com/donaldzou/WGDashboard/issues/99) [❤️ @barryboom] - [IP Sorting range issues #99](https://github.com/donaldzou/WGDashboard/issues/99) [❤️ @barryboom]
- [INvalid character written to tunnel json file #108](https://github.com/donaldzou/WGDashboard/issues/108) [❤️ @ ikidd] - [INvalid character written to tunnel json file #108](https://github.com/donaldzou/WGDashboard/issues/108) [❤️ @ ikidd]
- [Add IPv6 #91](https://github.com/donaldzou/WGDashboard/pull/91) [❤️ @ pgalonza] - [Add IPv6 #91](https://github.com/donaldzou/WGDashboard/pull/91) [❤️ @ pgalonza]
- [Added MTU and PersistentKeepalive to QR code and download files #112](https://github.com/donaldzou/WGDashboard/pull/112) [:heart: @reafian] - [Added MTU and PersistentKeepalive to QR code and download files #112](https://github.com/donaldzou/WGDashboard/pull/112) [:heart: @reafian]
- **And many other bugs provided by our beloved users** :heart:
- **🧐 Other Changes** - **🧐 Other Changes**
- **Key generating moved to front-end**: No longer need to use the server's WireGuard to generate keys, thanks to the `wireguard.js` from the [official repository](https://git.zx2c4.com/wireguard-tools/tree/contrib/keygen-html/wireguard.js)! - **Key generating moved to front-end**: No longer need to use the server's WireGuard to generate keys, thanks to the `wireguard.js` from the [official repository](https://git.zx2c4.com/wireguard-tools/tree/contrib/keygen-html/wireguard.js)!
- **Peer transfer calculation**: each peer will now show all transfer amount (previously was only showing transfer amount from the last configuration start-up). - **Peer transfer calculation**: each peer will now show all transfer amount (previously was only showing transfer amount from the last configuration start-up).
@ -35,12 +38,15 @@
- **`wgd.sh` finally can update itself**: So now user could update the whole dashboard from `wgd.sh`, with the `update` command. - **`wgd.sh` finally can update itself**: So now user could update the whole dashboard from `wgd.sh`, with the `update` command.
- **Minified JS and CSS files**: Although only a small changes on the file size, but I think is still a good practice to save a bit of bandwidth ;) - **Minified JS and CSS files**: Although only a small changes on the file size, but I think is still a good practice to save a bit of bandwidth ;)
*And many other small changes for performance and bug fixes! :laughing:* *And many other small changes for performance and bug fixes! :laughing:*
> If you have any other brilliant ideas for this project, please shout it in here [#129](https://github.com/donaldzou/WGDashboard/issues/129) :heart:
<hr> <hr>
## Table of Content ## Table of Content
- [💡 Features](#-features) - [💡 Features](#-features)
- [📝 Requirement](#-requirement) - [📝 Requirement](#-requirement)
- [🛠 Install](#-install) - [🛠 Install](#-install)
@ -332,7 +338,14 @@ Endpoint = 0.0.0.0:51820
## ❓ How to update the dashboard? ## ❓ How to update the dashboard?
#### **Please note for user who is using `v2.3.1` or below**
- For user who is using `v2.3.1` or below, please notice that all data that stored in the current database will **not** transfer to the new database. This is hard decision to move from TinyDB to SQLite. But SQLite does provide a thread-safe access and TinyDB doesn't. I couldn't find a safe way to transfer the data, so you need to do them manually... Sorry about that :pensive: . But I guess this would be a great start for future development :sunglasses:.
<hr>
1. Change your directory to `wgdashboard` 1. Change your directory to `wgdashboard`
```shell ```shell
cd wgdashboard cd wgdashboard
``` ```

View File

@ -17,6 +17,7 @@ import time
import re import re
import urllib.parse import urllib.parse
import urllib.request import urllib.request
import urllib.error
from datetime import datetime, timedelta from datetime import datetime, timedelta
from operator import itemgetter from operator import itemgetter
# PIP installed library # PIP installed library
@ -65,7 +66,6 @@ def get_dashboard_conf():
Get dashboard configuration Get dashboard configuration
@return: configparser.ConfigParser @return: configparser.ConfigParser
""" """
config = configparser.ConfigParser(strict=False) config = configparser.ConfigParser(strict=False)
config.read(DASHBOARD_CONF) config.read(DASHBOARD_CONF)
return config return config
@ -76,7 +76,6 @@ def set_dashboard_conf(config):
Write to configuration Write to configuration
@param config: Input configuration @param config: Input configuration
""" """
with open(DASHBOARD_CONF, "w", encoding='utf-8') as conf_object: with open(DASHBOARD_CONF, "w", encoding='utf-8') as conf_object:
config.write(conf_object) config.write(conf_object)
@ -129,7 +128,6 @@ def get_conf_running_peer_number(config_name):
return running return running
# TODO use modules for working with ini(configparser or wireguard)
# 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):
""" """
@ -154,10 +152,6 @@ def read_conf_file_interface(config_name):
return data return data
# TODO use modules for working with ini(configparser or wireguard)
# Tried to use configparser but it does not support sections with the same name
def read_conf_file(config_name): def read_conf_file(config_name):
""" """
Get configurations from file of wireguard interface. Get configurations from file of wireguard interface.
@ -375,7 +369,6 @@ def get_all_peers_data(config_name):
get_allowed_ip(conf_peer_data, config_name) get_allowed_ip(conf_peer_data, config_name)
# Search for peers
def get_peers(config_name, search, sort_t): def get_peers(config_name, search, sort_t):
""" """
Get all peers. Get all peers.
@ -428,7 +421,6 @@ def get_conf_pub_key(config_name):
return "" return ""
# Get configuration listen port
def get_conf_listen_port(config_name): def get_conf_listen_port(config_name):
""" """
Get listen port number. Get listen port number.
@ -452,8 +444,12 @@ def get_conf_listen_port(config_name):
return port return port
# Get configuration total data
def get_conf_total_data(config_name): def get_conf_total_data(config_name):
"""
Get configuration's total amount of data
@param config_name: Configuration name
@return: list
"""
data = g.cur.execute("SELECT total_sent, total_receive, cumu_sent, cumu_receive FROM " + config_name) data = g.cur.execute("SELECT total_sent, total_receive, cumu_sent, cumu_receive FROM " + config_name)
upload_total = 0 upload_total = 0
download_total = 0 download_total = 0
@ -468,19 +464,21 @@ def get_conf_total_data(config_name):
return [total, upload_total, download_total] return [total, upload_total, download_total]
# Get configuration status
def get_conf_status(config_name): def get_conf_status(config_name):
"""
Check if the configuration is running or not
@param config_name:
@return: Return a string indicate the running status
"""
ifconfig = dict(ifcfg.interfaces().items()) ifconfig = dict(ifcfg.interfaces().items())
return "running" if config_name in ifconfig.keys() else "stopped" return "running" if config_name in ifconfig.keys() else "stopped"
# Get all configuration as a list
def get_conf_list(): def get_conf_list():
"""Get all wireguard interfaces with status. """Get all wireguard interfaces with status.
:return: Return a list of dicts with interfaces and its statuses @return: Return a list of dicts with interfaces and its statuses
:rtype: list @rtype: list
""" """
conf = [] conf = []
@ -510,25 +508,13 @@ def get_conf_list():
return conf return conf
# Generate private key
def gen_private_key():
subprocess.run('wg genkey > private_key.txt && wg pubkey < private_key.txt > public_key.txt', shell=True)
with open('private_key.txt', encoding='utf-8') as file_object:
private_key = file_object.readline().strip()
with open('public_key.txt', encoding='utf-8') as file_object:
public_key = file_object.readline().strip()
data = {"private_key": private_key, "public_key": public_key}
return data
# Generate public key
def gen_public_key(private_key): def gen_public_key(private_key):
"""Generate the public key. """Generate the public key.
:param private_key: Pricate key @param private_key: Private key
:type private_key: str @type private_key: str
:return: Return dict with public key or error message @return: Return dict with public key or error message
:rtype: dict @rtype: dict
""" """
with open('private_key.txt', 'w', encoding='utf-8') as file_object: with open('private_key.txt', 'w', encoding='utf-8') as file_object:
@ -570,8 +556,14 @@ def f_check_key_match(private_key, public_key, config_name):
return {'status': 'success'} return {'status': 'success'}
# 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):
"""
Check if there are repeated IPs
@param public_key: Public key of the peer
@param ip: IP of the peer
@param config_name: configuration name
@return: a JSON object
"""
peer = g.cur.execute("SELECT COUNT(*) FROM " + config_name + " WHERE id = ?", (public_key,)).fetchone() peer = g.cur.execute("SELECT COUNT(*) FROM " + config_name + " WHERE id = ?", (public_key,)).fetchone()
if peer[0] != 1: if peer[0] != 1:
return {'status': 'failed', 'msg': 'Peer does not exist'} return {'status': 'failed', 'msg': 'Peer does not exist'}
@ -586,6 +578,11 @@ def check_repeat_allowed_ip(public_key, ip, config_name):
def f_available_ips(config_name): def f_available_ips(config_name):
"""
Get a list of available IPs
@param config_name: Configuration Name
@return: list
"""
config_interface = read_conf_file_interface(config_name) config_interface = read_conf_file_interface(config_name)
if "Address" in config_interface: if "Address" in config_interface:
existed = [] existed = []
@ -596,7 +593,6 @@ def f_available_ips(config_name):
existed.append(ipaddress.ip_address(add)) existed.append(ipaddress.ip_address(add))
peers = g.cur.execute("SELECT allowed_ip FROM " + config_name).fetchall() peers = g.cur.execute("SELECT allowed_ip FROM " + config_name).fetchall()
for i in peers: for i in peers:
print(i[0])
add = i[0].split(",") add = i[0].split(",")
for k in add: for k in add:
a, s = k.split("/") a, s = k.split("/")
@ -620,6 +616,11 @@ Flask Functions
@app.teardown_request @app.teardown_request
def close_DB(exception): def close_DB(exception):
"""
Commit to the database for every request
@param exception: Exception
@return: None
"""
if hasattr(g, 'db'): if hasattr(g, 'db'):
g.db.commit() g.db.commit()
g.db.close() g.db.close()
@ -628,6 +629,10 @@ def close_DB(exception):
# Before request # Before request
@app.before_request @app.before_request
def auth_req(): def auth_req():
"""
Action before every request
@return: Redirect
"""
if getattr(g, 'db', None) is None: if getattr(g, 'db', None) is None:
g.db = connect_db() g.db = connect_db()
g.cur = g.db.cursor() g.cur = g.db.cursor()
@ -647,7 +652,7 @@ def auth_req():
else: else:
session['message'] = "" session['message'] = ""
conf.clear() conf.clear()
return redirect(url_for("signin")) return redirect("/signin?redirect=" + str(request.url))
else: else:
if request.endpoint in ['signin', 'signout', 'auth', 'settings', 'update_acct', 'update_pwd', if request.endpoint in ['signin', 'signout', 'auth', 'settings', 'update_acct', 'update_pwd',
'update_app_ip_port', 'update_wg_conf_path']: 'update_app_ip_port', 'update_wg_conf_path']:
@ -662,13 +667,11 @@ Sign In / Sign Out
""" """
# Sign In
@app.route('/signin', methods=['GET']) @app.route('/signin', methods=['GET'])
def signin(): def signin():
"""Sign in request. """
Sign in request
:return: TODO @return: template
:rtype: TODO
""" """
message = "" message = ""
@ -681,46 +684,43 @@ def signin():
# Sign Out # Sign Out
@app.route('/signout', methods=['GET']) @app.route('/signout', methods=['GET'])
def signout(): def signout():
"""Sign out request.
:return: TODO
:rtype: TODO
""" """
Sign out request
@return: redirect back to sign in
"""
if "username" in session: if "username" in session:
session.pop("username") session.pop("username")
message = "Sign out successfully!" return redirect(url_for('signin'))
return render_template('signin.html', message=message)
# Authentication
@app.route('/auth', methods=['POST']) @app.route('/auth', methods=['POST'])
def auth(): def auth():
"""Authentication request.
:return: TODO
:rtype: TODO
""" """
Authentication request
@return: json object indicating verifying
"""
data = request.get_json()
config = get_dashboard_conf() config = get_dashboard_conf()
password = hashlib.sha256(request.form['password'].encode()) password = hashlib.sha256(data['password'].encode())
if password.hexdigest() == config["Account"]["password"] \ if password.hexdigest() == config["Account"]["password"] \
and request.form['username'] == config["Account"]["username"]: and data['username'] == config["Account"]["username"]:
session['username'] = request.form['username'] session['username'] = data['username']
config.clear() config.clear()
return redirect(url_for("index")) return jsonify({"status": True, "msg": ""})
config.clear()
return jsonify({"status": False, "msg": "Username or Password is incorrect."})
session['message'] = "Username or Password is incorrect."
config.clear() """
return redirect(url_for("signin")) Index Page
"""
@app.route('/', methods=['GET']) @app.route('/', methods=['GET'])
def index(): def index():
"""Index Page Related. """
Index page related
:return: TODO @return: Template
:rtype: TODO
""" """
msg = "" msg = ""
if "switch_msg" in session: if "switch_msg" in session:
@ -733,10 +733,9 @@ def index():
# Setting Page # Setting Page
@app.route('/settings', methods=['GET']) @app.route('/settings', methods=['GET'])
def settings(): def settings():
"""Setting Page Related. """
Settings page related
:return: TODO @return: Template
:rtype: TODO
""" """
message = "" message = ""
status = "" status = ""
@ -757,13 +756,11 @@ def settings():
peer_remote_endpoint=config.get("Peers", "remote_endpoint")) peer_remote_endpoint=config.get("Peers", "remote_endpoint"))
# Update account username
@app.route('/update_acct', methods=['POST']) @app.route('/update_acct', methods=['POST'])
def update_acct(): def update_acct():
"""Change account user name. """
Change dashboard username
:return: TODO @return: Redirect
:rtype: TODO
""" """
if len(request.form['username']) == 0: if len(request.form['username']) == 0:
@ -786,13 +783,12 @@ def update_acct():
return redirect(url_for("settings")) return redirect(url_for("settings"))
# Update peer default settting # Update peer default setting
@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():
"""Change default configurations for peers. """
Update new peers default setting
:return: TODO @return: None
:rtype: TODO
""" """
config = get_dashboard_conf() config = get_dashboard_conf()
@ -860,10 +856,9 @@ def update_peer_default_config():
# Update dashboard password # Update dashboard password
@app.route('/update_pwd', methods=['POST']) @app.route('/update_pwd', methods=['POST'])
def update_pwd(): def update_pwd():
"""Change account password. """
Update dashboard password
:return: TODO @return: Redirect
:rtype: TODO
""" """
config = get_dashboard_conf() config = get_dashboard_conf()
@ -894,10 +889,11 @@ def update_pwd():
return redirect(url_for("settings")) return redirect(url_for("settings"))
# 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():
"""Change port number of dashboard. """
Update dashboard ip and port
@return: None
""" """
config = get_dashboard_conf() config = get_dashboard_conf()
@ -905,13 +901,15 @@ def update_app_ip_port():
config.set("Server", "app_port", request.form['app_port']) config.set("Server", "app_port", request.form['app_port'])
set_dashboard_conf(config) set_dashboard_conf(config)
config.clear() config.clear()
os.system('bash wgd.sh restart') os.system('./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():
"""Change path to dashboard configuration. """
Update configuration path
@return: None
""" """
config = get_dashboard_conf() config = get_dashboard_conf()
@ -920,13 +918,14 @@ def update_wg_conf_path():
config.clear() config.clear()
session['message'] = "WireGuard Configuration Path Update Successfully!" session['message'] = "WireGuard Configuration Path Update Successfully!"
session['message_status'] = "success" session['message_status'] = "success"
os.system('bash wgd.sh restart') os.system('./wgd.sh restart')
# 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 """
Update configuration sorting
@return: Boolean
""" """
config = get_dashboard_conf() config = get_dashboard_conf()
@ -944,10 +943,10 @@ def update_dashbaord_sort():
# 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():
"""Change the refresh time. """
Change the refresh time.
:return: Return text with result @return: Return text with result
:rtype: str @rtype: str
""" """
preset_interval = ["5000", "10000", "30000", "60000"] preset_interval = ["5000", "10000", "30000", "60000"]
@ -964,12 +963,11 @@ def update_dashboard_refresh_interval():
# Configuration Page # Configuration Page
@app.route('/configuration/<config_name>', methods=['GET']) @app.route('/configuration/<config_name>', methods=['GET'])
def configuration(config_name): def configuration(config_name):
"""Show wireguard interface view. """
Show wireguard interface view.
:param config_name: Name of WG interface @param config_name: Name of WG interface
:type config_name: str @type config_name: str
:return: TODO @return: Template
:rtype: TODO
""" """
config = get_dashboard_conf() config = get_dashboard_conf()
@ -1004,12 +1002,11 @@ def configuration(config_name):
# 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):
"""Get configuration setting of wireguard interface. """
Get configuration setting of wireguard interface.
:param config_name: Name of WG interface @param config_name: Name of WG interface
:type config_name: str @type config_name: str
:return: TODO @return: TODO
:rtype: TODO
""" """
config_interface = read_conf_file_interface(config_name) config_interface = read_conf_file_interface(config_name)
@ -1050,12 +1047,11 @@ def get_conf(config_name):
# Turn on / off a configuration # Turn on / off a configuration
@app.route('/switch/<config_name>', methods=['GET']) @app.route('/switch/<config_name>', methods=['GET'])
def switch(config_name): def switch(config_name):
"""On/off the wireguard interface. """
On/off the wireguard interface.
:param config_name: Name of WG interface @param config_name: Name of WG interface
:type config_name: str @type config_name: str
:return: TODO @return: redirects
:rtype: TODO
""" """
status = get_conf_status(config_name) status = get_conf_status(config_name)
@ -1078,6 +1074,11 @@ def switch(config_name):
@app.route('/add_peer_bulk/<config_name>', methods=['POST']) @app.route('/add_peer_bulk/<config_name>', methods=['POST'])
def add_peer_bulk(config_name): def add_peer_bulk(config_name):
"""
Add peers by bulk
@param config_name: Configuration Name
@return: String
"""
data = request.get_json() data = request.get_json()
keys = data['keys'] keys = data['keys']
endpoint_allowed_ip = data['endpoint_allowed_ip'] endpoint_allowed_ip = data['endpoint_allowed_ip']
@ -1138,9 +1139,13 @@ def add_peer_bulk(config_name):
return exc.output.strip() return exc.output.strip()
# 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):
"""
Add Peers
@param config_name: configuration name
@return: string
"""
data = request.get_json() data = request.get_json()
public_key = data['public_key'] public_key = data['public_key']
allowed_ips = data['allowed_ips'] allowed_ips = data['allowed_ips']
@ -1172,7 +1177,6 @@ def add_peer(config_name):
if enable_preshared_key: if enable_preshared_key:
now = str(datetime.now().strftime("%m%d%Y%H%M%S")) now = str(datetime.now().strftime("%m%d%Y%H%M%S"))
f_name = now + "_tmp_psk.txt" f_name = now + "_tmp_psk.txt"
print(f_name)
f = open(f_name, "w+") f = open(f_name, "w+")
f.write(preshared_key) f.write(preshared_key)
f.close() f.close()
@ -1192,15 +1196,14 @@ def add_peer(config_name):
return exc.output.strip() return exc.output.strip()
# 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):
"""Remove peer. """
Remove peer.
:param config_name: Name of WG interface @param config_name: Name of WG interface
:type config_name: str @type config_name: str
:return: Return result of action or recommendations @return: Return result of action or recommendations
:rtype: str @rtype: str
""" """
if get_conf_status(config_name) == "stopped": if get_conf_status(config_name) == "stopped":
@ -1231,15 +1234,14 @@ def remove_peer(config_name):
return "true" return "true"
# 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):
"""Save peer configuration. """
Save peer configuration.
:param config_name: Name of WG interface @param config_name: Name of WG interface
:type config_name: str @type config_name: str
:return: Return status of action and text with recommendations @return: Return status of action and text with recommendations
:rtype: TODO
""" """
data = request.get_json() data = request.get_json()
@ -1296,12 +1298,12 @@ def save_peer_setting(config_name):
# 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):
"""Get peer settings. """
Get peer settings.
:param config_name: Name of WG interface @param config_name: Name of WG interface
:type config_name: str @type config_name: str
:return: Return settings of peer @return: Return settings of peer
:rtype: TODO
""" """
data = request.get_json() data = request.get_json()
@ -1321,41 +1323,14 @@ def available_ips(config_name):
return jsonify(f_available_ips(config_name)) return jsonify(f_available_ips(config_name))
# Generate a private key
@app.route('/generate_peer', methods=['GET'])
def generate_peer():
"""Generate the private key for peer.
:return: Return dict with private, public and preshared keys
:rtype: TODO
"""
return jsonify(gen_private_key())
# Generate a public key from a private key
@app.route('/generate_public_key', methods=['POST'])
def generate_public_key():
"""Generate the public key.
:return: Return dict with public key or error message
:rtype: TODO
"""
data = request.get_json()
private_key = data['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):
"""TODO """
Check key matches
:param config_name: Name of WG interface @param config_name: Name of WG interface
:type config_name: str @type config_name: str
:return: Return dictionary with status @return: Return dictionary with status
:rtype: TODO
""" """
data = request.get_json() data = request.get_json()
@ -1366,6 +1341,11 @@ def check_key_match(config_name):
@app.route("/qrcode/<config_name>", methods=['GET']) @app.route("/qrcode/<config_name>", methods=['GET'])
def generate_qrcode(config_name): def generate_qrcode(config_name):
"""
Generate QRCode
@param config_name: Configuration Name
@return: Template containing QRcode img
"""
peer_id = request.args.get('id') peer_id = request.args.get('id')
get_peer = g.cur.execute( get_peer = g.cur.execute(
"SELECT private_key, allowed_ip, DNS, mtu, endpoint_allowed_ip, keepalive, preshared_key FROM " "SELECT private_key, allowed_ip, DNS, mtu, endpoint_allowed_ip, keepalive, preshared_key FROM "
@ -1396,9 +1376,13 @@ def generate_qrcode(config_name):
return redirect("/configuration/" + config_name) return redirect("/configuration/" + config_name)
# Download all configuration file
@app.route('/download_all/<config_name>', methods=['GET']) @app.route('/download_all/<config_name>', methods=['GET'])
def download_all(config_name): def download_all(config_name):
"""
Download all configuration
@param config_name: Configuration Name
@return: JSON Object
"""
get_peer = g.cur.execute( get_peer = g.cur.execute(
"SELECT private_key, allowed_ip, DNS, mtu, endpoint_allowed_ip, keepalive, preshared_key, name FROM " "SELECT private_key, allowed_ip, DNS, mtu, endpoint_allowed_ip, keepalive, preshared_key, name FROM "
+ config_name + " WHERE private_key != ''").fetchall() + config_name + " WHERE private_key != ''").fetchall()
@ -1445,6 +1429,11 @@ def download_all(config_name):
# Download configuration file # Download configuration file
@app.route('/download/<config_name>', methods=['GET']) @app.route('/download/<config_name>', methods=['GET'])
def download(config_name): def download(config_name):
"""
Download one configuration
@param config_name: Configuration name
@return: JSON object
"""
peer_id = request.args.get('id') peer_id = request.args.get('id')
get_peer = g.cur.execute( get_peer = g.cur.execute(
"SELECT private_key, allowed_ip, DNS, mtu, endpoint_allowed_ip, keepalive, preshared_key, name FROM " "SELECT private_key, allowed_ip, DNS, mtu, endpoint_allowed_ip, keepalive, preshared_key, name FROM "
@ -1491,15 +1480,15 @@ def download(config_name):
return jsonify({"status": False, "filename": "", "content": ""}) return jsonify({"status": False, "filename": "", "content": ""})
# Switch peer display 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):
"""Change display view style. """
Change display view style.
:param mode: Mode name @param mode: Mode name
:type mode: str @type mode: str
:return: Return text with result @return: Return text with result
:rtype: str @rtype: str
""" """
if mode in ['list', 'grid']: if mode in ['list', 'grid']:
@ -1519,10 +1508,11 @@ 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():
"""Get ips for network testing. # TODO: convert return to json object
:return: TODO """
:rtype: TODO Get ips for network testing.
@return: HTML containing a list of IPs
""" """
config = request.form['config'] config = request.form['config']
@ -1545,10 +1535,10 @@ def get_ping_ip():
# Ping IP # Ping IP
@app.route('/ping_ip', methods=['POST']) @app.route('/ping_ip', methods=['POST'])
def ping_ip(): def ping_ip():
"""Execute ping command. """
Execute ping command.
:return: Return text with result @return: Return text with result
:rtype: str @rtype: str
""" """
try: try:
@ -1573,10 +1563,11 @@ def ping_ip():
# Traceroute IP # Traceroute IP
@app.route('/traceroute_ip', methods=['POST']) @app.route('/traceroute_ip', methods=['POST'])
def traceroute_ip(): def traceroute_ip():
"""Execute ping traceroute command. """
Execute ping traceroute command.
:return: Return text with result @return: Return text with result
:rtype: str @rtype: str
""" """
try: try:
@ -1600,21 +1591,22 @@ Dashboard Initialization
def init_dashboard(): def init_dashboard():
"""Create dashboard default configuration. """
Create dashboard default configuration.
""" """
# Set Default INI File # Set Default INI File
if not os.path.isfile(DASHBOARD_CONF): if not os.path.isfile(DASHBOARD_CONF):
open(DASHBOARD_CONF, "w+").close() open(DASHBOARD_CONF, "w+").close()
config = get_dashboard_conf() config = get_dashboard_conf()
# Defualt dashboard account setting # Default dashboard account setting
if "Account" not in config: if "Account" not in config:
config['Account'] = {} config['Account'] = {}
if "username" not in config['Account']: if "username" not in config['Account']:
config['Account']['username'] = 'admin' config['Account']['username'] = 'admin'
if "password" not in config['Account']: if "password" not in config['Account']:
config['Account']['password'] = '8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918' config['Account']['password'] = '8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918'
# Defualt dashboard server setting # Default dashboard server setting
if "Server" not in config: if "Server" not in config:
config['Server'] = {} config['Server'] = {}
if 'wg_conf_path' not in config['Server']: if 'wg_conf_path' not in config['Server']:
@ -1651,12 +1643,14 @@ def init_dashboard():
def check_update(): def check_update():
"""Dashboard check update """
Dashboard check update
:return: Retunt text with result @return: Retunt text with result
:rtype: str @rtype: str
""" """
config = get_dashboard_conf() config = get_dashboard_conf()
try:
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 = []
@ -1669,6 +1663,8 @@ def check_update():
result = "true" result = "true"
return result return result
except urllib.error.HTTPError:
return "false"
""" """

View File

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

View File

@ -16,11 +16,10 @@
<div class="login-box" style="margin: auto !important;"> <div class="login-box" style="margin: auto !important;">
<h1 class="text-center">Sign In</h1> <h1 class="text-center">Sign In</h1>
<form style="margin-left: auto !important; margin-right: auto !important; max-width: 500px;" action="/auth" method="post"> <form style="margin-left: auto !important; margin-right: auto !important; max-width: 500px;" action="/auth" method="post">
{% if message != ""%} {% if message != "" %}
<div class="alert alert-danger" role="alert"> <div class="alert alert-warning" role="alert">You need to sign in first</div>
{{ message }}
</div>
{% endif %} {% endif %}
<div class="alert alert-danger d-none" role="alert"></div>
<div class="form-group"> <div class="form-group">
<label for="username" class="text-left" style="font-size: 1rem"><i class="bi bi-person-circle"></i> Username</label> <label for="username" class="text-left" style="font-size: 1rem"><i class="bi bi-person-circle"></i> Username</label>
<input type="text" class="form-control" id="username" name="username" required> <input type="text" class="form-control" id="username" name="username" required>
@ -37,20 +36,43 @@
</body> </body>
{% include "footer.html" %} {% include "footer.html" %}
<script> <script>
$("button").on("click", function(){ $("button").on("click", function(e){
let req = $("input[required]"); let req = $("input[required]");
let check = true let check = true
for (let i = 0; i < req.length; i++){ for (let i = 0; i < req.length; i++){
if ($(req[i]).val().length === 0){ if ($(req[i]).val().length === 0){
$("button").html("Sign In"); $("button").html("Sign In");
check = false; check = false;
$("input[required]").addClass("is-invalid");
break; break;
} }
} }
if (check){ if (check){
$(this).html("Signing In..."); e.preventDefault();
} $(this).html("Signing In...").attr("disabled", "disabled");
$.ajax({
url: "/auth",
method: "POST",
headers:{"Content-Type": "application/json"},
data: JSON.stringify({
"username": $("#username").val(),
"password": $("#password").val()
}) })
}).done(function(res){
if (res.status === true){
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get("redirect")){
window.location.replace(urlParams.get("redirect"))
}else{
window.location.replace("/");
}
}else{
$(".alert").html(res.msg).removeClass("d-none");
$("button").html("Sign In").removeAttr("disabled");
$("input[required]").addClass("is-invalid");
}
});
}
});
</script> </script>
</html> </html>

View File

@ -4,6 +4,7 @@
# Under Apache-2.0 License # Under Apache-2.0 License
app_name="dashboard.py" app_name="dashboard.py"
app_official_name="WGDashboard" app_official_name="WGDashboard"
PID_FILE=./gunicorn.pid
environment=$(if [[ $ENVIRONMENT ]]; then echo $ENVIRONMENT; else echo 'develop'; fi) environment=$(if [[ $ENVIRONMENT ]]; then echo $ENVIRONMENT; else echo 'develop'; fi)
if [[ $CONFIGURATION_PATH ]]; then if [[ $CONFIGURATION_PATH ]]; then
cb_work_dir=$CONFIGURATION_PATH/letsencrypt/work-dir cb_work_dir=$CONFIGURATION_PATH/letsencrypt/work-dir
@ -66,18 +67,12 @@ install_wgd(){
check_wgd_status(){ check_wgd_status(){
if [[ $environment == 'production' ]]; then
PID_FILE=./gunicorn.pid
if test -f "$PID_FILE"; then if test -f "$PID_FILE"; then
if ps aux | grep -v grep | grep $(cat ./gunicorn.pid) > /dev/null; then if ps aux | grep -v grep | grep $(cat ./gunicorn.pid) > /dev/null; then
return 0 return 0
else else
return 1 return 1
fi fi
else
return 1
fi
else else
if ps aux | grep -v grep | grep '[p]ython3 '$app_name > /dev/null; then if ps aux | grep -v grep | grep '[p]ython3 '$app_name > /dev/null; then
return 0 return 0
@ -96,15 +91,8 @@ certbot_renew_ssl () {
} }
gunicorn_start () { gunicorn_start () {
# if [[ $SSL ]]; then
# if [ ! -d $cb_config_dir ]; then
# certbot_create_ssl
# else
# certbot_renew_ssl
# fi
# fi
printf "%s\n" "$dashes" printf "%s\n" "$dashes"
printf "| Starting WGDashboard in the background. |\n" printf "| Starting WGDashboard with Gunicorn in the background. |\n"
if [ ! -d "log" ]; then if [ ! -d "log" ]; then
mkdir "log" mkdir "log"
fi fi
@ -112,15 +100,8 @@ gunicorn_start () {
if [[ $USER == root ]]; then if [[ $USER == root ]]; then
export PATH=$PATH:/usr/local/bin:$HOME/.local/bin export PATH=$PATH:/usr/local/bin:$HOME/.local/bin
fi fi
# if [[ $SSL ]]; then
# gunicorn --certfile $cb_config_dir/live/"$SERVERURL"/cert.pem \
# --keyfile $cb_config_dir/live/"$SERVERURL"/privkey.pem \
# --access-logfile log/access_"$d".log \
# --error-logfile log/error_"$d".log 'dashboard:run_dashboard()'
# else
gunicorn --access-logfile log/access_"$d".log \ gunicorn --access-logfile log/access_"$d".log \
--error-logfile log/error_"$d".log 'dashboard:run_dashboard()' --error-logfile log/error_"$d".log 'dashboard:run_dashboard()'
# fi
printf "| Log files is under log/ |\n" printf "| Log files is under log/ |\n"
printf "%s\n" "$dashes" printf "%s\n" "$dashes"
} }
@ -130,23 +111,11 @@ gunicorn_stop () {
} }
start_wgd () { start_wgd () {
if [[ $environment == 'production' ]]; then
gunicorn_start gunicorn_start
else
printf "%s\n" "$dashes"
printf "| Starting WGDashboard in the background. |\n"
if [ ! -d "log" ]
then mkdir "log"
fi
d=$(date '+%Y%m%d%H%M%S')
python3 "$app_name" > log/"$d".txt 2>&1 &
printf "| Log files is under log/ |\n"
printf "%s\n" "$dashes"
fi
} }
stop_wgd() { stop_wgd() {
if [[ $environment == 'production' ]]; then if test -f "$PID_FILE"; then
gunicorn_stop gunicorn_stop
else else
kill "$(ps aux | grep "[p]ython3 $app_name" | awk '{print $2}')" kill "$(ps aux | grep "[p]ython3 $app_name" | awk '{print $2}')"