Finishing documentation
59
README.md
@ -17,9 +17,12 @@
|
|||||||
## 📣 What's New: v3.0
|
## 📣 What's New: v3.0
|
||||||
|
|
||||||
- 🎉 **New Features**
|
- 🎉 **New Features**
|
||||||
- **Add Peers by Bulk: ** Now you can add peers by bulk, just simply set the amount and click add.
|
- **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**.
|
||||||
- **Delete Peers by Bulk**: User can click the menu button (three-dots) on the bottom right of each configuration, and click **Delete Peers**, then select peers.
|
- **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.
|
||||||
|
- **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.
|
||||||
|
|
||||||
- 🪚 **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]
|
||||||
@ -29,16 +32,12 @@
|
|||||||
- **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).
|
||||||
- **UI adjustment on running peers**: peers will have a new style indicating that it is running.
|
- **UI adjustment on running peers**: peers will have a new style indicating that it is running.
|
||||||
- **Moved from TinyDB to SQLite**: This could provide better performance and loading speed when showing peers, also avoided changing the database could crash.
|
|
||||||
- UI adjustment on the whole dashboard.
|
|
||||||
- **`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.
|
||||||
|
|
||||||
|
|
||||||
|
*And many other small changes for performance and bug fixes! :laughing:*
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
|
||||||
## Table of Content
|
## Table of Content
|
||||||
|
|
||||||
- [💡 Features](#-features)
|
- [💡 Features](#-features)
|
||||||
@ -99,11 +98,13 @@
|
|||||||
|
|
||||||
- Python 3.7+ & Pip3
|
- Python 3.7+ & Pip3
|
||||||
|
|
||||||
|
- Browser support CSS3 and ES6
|
||||||
|
|
||||||
## 🛠 Install
|
## 🛠 Install
|
||||||
1. Download WGDashboard
|
1. Download WGDashboard
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone -b v2.3.1 https://github.com/donaldzou/WGDashboard.git wgdashboard
|
git clone -b v3.0 https://github.com/donaldzou/WGDashboard.git wgdashboard
|
||||||
|
|
||||||
2. Open the WGDashboard folder
|
2. Open the WGDashboard folder
|
||||||
|
|
||||||
@ -276,22 +277,30 @@ In the `src` folder, it contained a file called `wg-dashboard.service`, we can u
|
|||||||
|
|
||||||
#### Dashboard Configuration file
|
#### Dashboard Configuration file
|
||||||
|
|
||||||
Since version 2.0, WGDashboard will be using a configuration file called `wg-dashboard.ini`, (It will generate automatically after first time running the dashboard). More options will include in future versions, and for now it included the following config:
|
Since version 2.0, WGDashboard will be using a configuration file called `wg-dashboard.ini`, (It will generate automatically after first time running the dashboard). More options will include in future versions, and for now it included the following configurations:
|
||||||
|
|
||||||
| | Description | Default | Available in Setting |
|
| | Description | Default | Edit Available |
|
||||||
| --------------- | ------------------------------------------------------------ | ------------------------ | -------------------- |
|
| ---------------------------- | ------------------------------------------------------------ | ---------------------------------------------------- | -------------- |
|
||||||
| **`[Account]`** | | | |
|
| **`[Account]`** | *Configuration on account* | | |
|
||||||
| `username` | Dashboard login username | `admin` | Yes |
|
| `username` | Dashboard login username | `admin` | Yes |
|
||||||
| `password` | Password, will be hash with SHA256 | `admin` hashed in SHA256 | Yes |
|
| `password` | Password, will be hash with SHA256 | `admin` hashed in SHA256 | Yes |
|
||||||
| **`[Server]`** | | | |
|
| | | | |
|
||||||
|
| **`[Server]`** | *Configuration on dashboard* | | |
|
||||||
| `wg_conf_path` | The path of all the Wireguard configurations | `/etc/wireguard` | Yes |
|
| `wg_conf_path` | The path of all the Wireguard configurations | `/etc/wireguard` | Yes |
|
||||||
| `app_ip` | IP address the dashboard will run with | `0.0.0.0` | Yes |
|
| `app_ip` | IP address the dashboard will run with | `0.0.0.0` | Yes |
|
||||||
| `app_port` | Port the the dashboard will run with | `10086` | Yes |
|
| `app_port` | Port the the dashboard will run with | `10086` | Yes |
|
||||||
| `auth_req` | Does the dashboard need authentication to access | `true` | No |
|
| `auth_req` | Does the dashboard need authentication to access, if `auth_req = false` , user will not be access the **Setting** tab due to security consideration. **User can only edit the file directly in system**. | `true` | **No** |
|
||||||
| | If `auth_req = false` , user will not be access the **Setting** tab due to security consideration. **User can only edit the file directly in system**. | | |
|
| `version` | Dashboard Version | `v3.0` | **No** |
|
||||||
| `version` | Dashboard Version | `v2.2` | No |
|
| `dashboard_refresh_interval` | How frequent the dashboard will refresh on the configuration page | `60000ms` | Yes |
|
||||||
|
| `dashboard_sort` | How configuration is sorting | `status` | Yes |
|
||||||
**Except `auth_req` due to security consideration.**
|
| | | | |
|
||||||
|
| **`[Peers]`** | *Default Settings on a new peer* | | |
|
||||||
|
| `peer_global_dns` | DNS Server | `1.1.1.1` | Yes |
|
||||||
|
| `peer_endpoint_allowed_ip` | Endpoint Allowed IP | `0.0.0.0/0` | Yes |
|
||||||
|
| `peer_display_mode` | How peer will display | `grid` | Yes |
|
||||||
|
| `remote_endpoint` | Remote Endpoint (i.e where your peers will connect to) | *depends on your server's default network interface* | Yes |
|
||||||
|
| `peer_mtu` | Maximum Transmit Unit | `1420` | |
|
||||||
|
| `peer_keep_alive` | Keep Alive | `21` | Yes |
|
||||||
|
|
||||||
#### Generating QR code and peer configuration file (.conf)
|
#### Generating QR code and peer configuration file (.conf)
|
||||||
|
|
||||||
@ -322,14 +331,18 @@ Endpoint = 0.0.0.0:51820
|
|||||||
|
|
||||||
## ❓ How to update the dashboard?
|
## ❓ How to update the dashboard?
|
||||||
|
|
||||||
1. Change your directory to `wireguard-dashboard`
|
1. Change your directory to `wgdashboard`
|
||||||
```shell
|
```shell
|
||||||
cd wireguard-dashboard/src
|
cd wgdashboard
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Update the dashboard
|
2. Update the dashboard
|
||||||
```shell
|
```shell
|
||||||
sudo ./wgd.sh update
|
git pull https://github.com/donaldzou/WGDashboard.git v3.0 --force
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Starting with `v3.0`, you can simply do `./wgd.sh update` !! (I hope, lol)
|
||||||
|
|
||||||
## 🔍 Screenshot
|
## 🔍 Screenshot
|
||||||
|
|
||||||
![Sign In Page](img/SignIn.png)
|
![Sign In Page](img/SignIn.png)
|
||||||
@ -342,7 +355,7 @@ Endpoint = 0.0.0.0:51820
|
|||||||
|
|
||||||
![Edit Peer](img/EditPeer.png)
|
![Edit Peer](img/EditPeer.png)
|
||||||
|
|
||||||
![Delete Peer](img/DeletePeer.png)
|
![Delete Peer](img/DeleteBulk.png)
|
||||||
|
|
||||||
![Dashboard Setting](img/DashboardSetting.png)
|
![Dashboard Setting](img/DashboardSetting.png)
|
||||||
|
|
||||||
@ -350,8 +363,6 @@ Endpoint = 0.0.0.0:51820
|
|||||||
|
|
||||||
![Traceroute](img/Traceroute.png)
|
![Traceroute](img/Traceroute.png)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## ⏰ Changelog
|
## ⏰ Changelog
|
||||||
|
|
||||||
#### v2.3.1 - Sep 8, 2021
|
#### v2.3.1 - Sep 8, 2021
|
||||||
|
BIN
img/AddPeer.png
Before Width: | Height: | Size: 221 KiB After Width: | Height: | Size: 156 KiB |
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 181 KiB |
Before Width: | Height: | Size: 234 KiB After Width: | Height: | Size: 114 KiB |
BIN
img/DeleteBulk.png
Normal file
After Width: | Height: | Size: 210 KiB |
BIN
img/EditPeer.png
Before Width: | Height: | Size: 198 KiB After Width: | Height: | Size: 149 KiB |
BIN
img/HomePage.png
Before Width: | Height: | Size: 202 KiB After Width: | Height: | Size: 141 KiB |
BIN
img/QRCode.png
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 239 KiB |
BIN
img/SearchIP.png
Normal file
After Width: | Height: | Size: 153 KiB |
244
src/dashboard.py
@ -3,7 +3,6 @@
|
|||||||
Under Apache-2.0 License
|
Under Apache-2.0 License
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# TODO: Testing migrate to sqlite
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from flask import g
|
from flask import g
|
||||||
import configparser
|
import configparser
|
||||||
@ -32,7 +31,7 @@ from util import regex_match, check_DNS, check_Allowed_IPs, check_remote_endpoin
|
|||||||
|
|
||||||
# Dashboard Version
|
# Dashboard Version
|
||||||
DASHBOARD_VERSION = 'v3.0'
|
DASHBOARD_VERSION = 'v3.0'
|
||||||
# WireGuard configuration path
|
# WireGuard's configuration path
|
||||||
WG_CONF_PATH = None
|
WG_CONF_PATH = None
|
||||||
# Dashboard Config Name
|
# Dashboard Config Name
|
||||||
configuration_path = os.getenv('CONFIGURATION_PATH', '.')
|
configuration_path = os.getenv('CONFIGURATION_PATH', '.')
|
||||||
@ -51,18 +50,20 @@ app.config['TEMPLATES_AUTO_RELOAD'] = True
|
|||||||
QRcode(app)
|
QRcode(app)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: use class and object oriented programming
|
||||||
|
|
||||||
def connect_db():
|
def connect_db():
|
||||||
|
"""
|
||||||
|
Connect to the database
|
||||||
|
@return: sqlite3.Connection
|
||||||
|
"""
|
||||||
return sqlite3.connect(os.path.join(configuration_path, 'db', 'wgdashboard.db'))
|
return sqlite3.connect(os.path.join(configuration_path, 'db', 'wgdashboard.db'))
|
||||||
|
|
||||||
|
|
||||||
# TODO use class and object oriented programming
|
|
||||||
|
|
||||||
# Read / Write Dashboard Config File
|
|
||||||
def get_dashboard_conf():
|
def get_dashboard_conf():
|
||||||
"""Dashboard Configuration Related
|
"""
|
||||||
|
Get dashboard configuration
|
||||||
:return: A config parser object
|
@return: configparser.ConfigParser
|
||||||
:rtype: configparser.ConfigParser
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
config = configparser.ConfigParser(strict=False)
|
config = configparser.ConfigParser(strict=False)
|
||||||
@ -71,10 +72,9 @@ def get_dashboard_conf():
|
|||||||
|
|
||||||
|
|
||||||
def set_dashboard_conf(config):
|
def set_dashboard_conf(config):
|
||||||
"""Configuration writer
|
"""
|
||||||
|
Write to configuration
|
||||||
:param config: A config parser object
|
@param config: Input configuration
|
||||||
:type config: configparser.ConfigParser
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with open(DASHBOARD_CONF, "w", encoding='utf-8') as conf_object:
|
with open(DASHBOARD_CONF, "w", encoding='utf-8') as conf_object:
|
||||||
@ -83,17 +83,17 @@ def set_dashboard_conf(config):
|
|||||||
|
|
||||||
# Get all keys from a configuration
|
# Get all keys from a configuration
|
||||||
def get_conf_peer_key(config_name):
|
def get_conf_peer_key(config_name):
|
||||||
"""Get the peers keys of wireguard interface.
|
"""
|
||||||
|
Get the peers keys 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: Return list of peers keys or text if configuration not running
|
@return: Return list of peers keys or text if configuration not running
|
||||||
:rtype: list, str
|
@rtype: list, str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
peers_keys = subprocess.run(f"wg show {config_name} peers",
|
peers_keys = subprocess.check_output(f"wg show {config_name} peers",
|
||||||
check=True, shell=True, capture_output=True).stdout
|
shell=True, stderr=subprocess.STDOUT)
|
||||||
peers_keys = peers_keys.decode("UTF-8").split()
|
peers_keys = peers_keys.decode("UTF-8").split()
|
||||||
return peers_keys
|
return peers_keys
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
@ -102,19 +102,19 @@ def get_conf_peer_key(config_name):
|
|||||||
|
|
||||||
# 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):
|
||||||
"""Get number of running peers on wireguard interface.
|
"""
|
||||||
|
Get number of running peers on 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: Number of running peers, or test if configuration not running
|
@return: Number of running peers, or test if configuration not running
|
||||||
:rtype: int, str
|
@rtype: int, str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
running = 0
|
running = 0
|
||||||
# Get latest handshakes
|
# Get latest handshakes
|
||||||
try:
|
try:
|
||||||
data_usage = subprocess.run(f"wg show {config_name} latest-handshakes",
|
data_usage = subprocess.check_output(f"wg show {config_name} latest-handshakes",
|
||||||
check=True, shell=True, capture_output=True).stdout
|
shell=True, stderr=subprocess.STDOUT)
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
return "stopped"
|
return "stopped"
|
||||||
data_usage = data_usage.decode("UTF-8").split()
|
data_usage = data_usage.decode("UTF-8").split()
|
||||||
@ -132,12 +132,12 @@ def get_conf_running_peer_number(config_name):
|
|||||||
# TODO use modules for working with ini(configparser or wireguard)
|
# 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):
|
||||||
"""Get interface settings.
|
"""
|
||||||
|
Get interface settings.
|
||||||
:param config_name: Name of WG interface
|
@param config_name: Name of WG interface
|
||||||
:type config_name: str
|
@type config_name: str
|
||||||
:return: Dictionary with interface settings
|
@return: Dictionary with interface settings
|
||||||
:rtype: dict
|
@rtype: dict
|
||||||
"""
|
"""
|
||||||
|
|
||||||
conf_location = WG_CONF_PATH + "/" + config_name + ".conf"
|
conf_location = WG_CONF_PATH + "/" + config_name + ".conf"
|
||||||
@ -155,14 +155,16 @@ def read_conf_file_interface(config_name):
|
|||||||
|
|
||||||
|
|
||||||
# TODO use modules for working with ini(configparser or wireguard)
|
# TODO use modules for working with ini(configparser or wireguard)
|
||||||
# Read the whole configuration file
|
# Tried to use configparser but it does not support sections with the same name
|
||||||
def read_conf_file(config_name):
|
|
||||||
"""Get configurations from file of wireguard interface.
|
|
||||||
|
|
||||||
:param config_name: Name of WG interface
|
|
||||||
:type config_name: str
|
def read_conf_file(config_name):
|
||||||
:return: Dictionary with interface and peers settings
|
"""
|
||||||
:rtype: dict
|
Get configurations from file of wireguard interface.
|
||||||
|
@param config_name: Name of WG interface
|
||||||
|
@type config_name: str
|
||||||
|
@return: Dictionary with interface and peers settings
|
||||||
|
@rtype: dict
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Read Configuration File Start
|
# Read Configuration File Start
|
||||||
@ -203,12 +205,17 @@ def read_conf_file(config_name):
|
|||||||
return conf_peer_data
|
return conf_peer_data
|
||||||
|
|
||||||
|
|
||||||
# Get the latest handshake from all peers of a configuration
|
|
||||||
def get_latest_handshake(config_name):
|
def get_latest_handshake(config_name):
|
||||||
|
"""
|
||||||
|
Get the latest handshake from all peers of a configuration
|
||||||
|
@param config_name: Configuration name
|
||||||
|
@return: str
|
||||||
|
"""
|
||||||
|
|
||||||
# Get latest handshakes
|
# Get latest handshakes
|
||||||
try:
|
try:
|
||||||
data_usage = subprocess.run(f"wg show {config_name} latest-handshakes",
|
data_usage = subprocess.check_output(f"wg show {config_name} latest-handshakes",
|
||||||
check=True, shell=True, capture_output=True).stdout
|
shell=True, stderr=subprocess.STDOUT)
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
return "stopped"
|
return "stopped"
|
||||||
data_usage = data_usage.decode("UTF-8").split()
|
data_usage = data_usage.decode("UTF-8").split()
|
||||||
@ -228,15 +235,18 @@ def get_latest_handshake(config_name):
|
|||||||
g.cur.execute("UPDATE %s SET latest_handshake = '(None)', status = '%s' WHERE id='%s'"
|
g.cur.execute("UPDATE %s SET latest_handshake = '(None)', status = '%s' WHERE id='%s'"
|
||||||
% (config_name, status, data_usage[count]))
|
% (config_name, status, data_usage[count]))
|
||||||
count += 2
|
count += 2
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
# Get transfer from all peers of a configuration
|
|
||||||
def get_transfer(config_name):
|
def get_transfer(config_name):
|
||||||
|
"""
|
||||||
|
Get transfer from all peers of a configuration
|
||||||
|
@param config_name: Configuration name
|
||||||
|
@return: str
|
||||||
|
"""
|
||||||
# Get transfer
|
# Get transfer
|
||||||
try:
|
try:
|
||||||
data_usage = subprocess.run(f"wg show {config_name} transfer",
|
data_usage = subprocess.check_output(f"wg show {config_name} transfer",
|
||||||
check=True, shell=True, capture_output=True).stdout
|
shell=True, stderr=subprocess.STDOUT)
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
return "stopped"
|
return "stopped"
|
||||||
data_usage = data_usage.decode("UTF-8").split("\n")
|
data_usage = data_usage.decode("UTF-8").split("\n")
|
||||||
@ -268,15 +278,18 @@ def get_transfer(config_name):
|
|||||||
g.cur.execute("UPDATE %s SET total_receive = %f, total_sent = %f, total_data = %f WHERE id = '%s'" %
|
g.cur.execute("UPDATE %s SET total_receive = %f, total_sent = %f, total_data = %f WHERE id = '%s'" %
|
||||||
(config_name, round(total_receive, 4), round(total_sent, 4),
|
(config_name, round(total_receive, 4), round(total_sent, 4),
|
||||||
round(total_receive + total_sent, 4), data_usage[i][0]))
|
round(total_receive + total_sent, 4), data_usage[i][0]))
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
# Get endpoint from all peers of a configuration
|
|
||||||
def get_endpoint(config_name):
|
def get_endpoint(config_name):
|
||||||
|
"""
|
||||||
|
Get endpoint from all peers of a configuration
|
||||||
|
@param config_name: Configuration name
|
||||||
|
@return: str
|
||||||
|
"""
|
||||||
# Get endpoint
|
# Get endpoint
|
||||||
try:
|
try:
|
||||||
data_usage = subprocess.run(f"wg show {config_name} endpoints",
|
data_usage = subprocess.check_output(f"wg show {config_name} endpoints",
|
||||||
check=True, shell=True, capture_output=True).stdout
|
shell=True, stderr=subprocess.STDOUT)
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
return "stopped"
|
return "stopped"
|
||||||
data_usage = data_usage.decode("UTF-8").split()
|
data_usage = data_usage.decode("UTF-8").split()
|
||||||
@ -285,19 +298,27 @@ def get_endpoint(config_name):
|
|||||||
g.cur.execute("UPDATE " + config_name + " SET endpoint = '%s' WHERE id = '%s'"
|
g.cur.execute("UPDATE " + config_name + " SET endpoint = '%s' WHERE id = '%s'"
|
||||||
% (data_usage[count + 1], data_usage[count]))
|
% (data_usage[count + 1], data_usage[count]))
|
||||||
count += 2
|
count += 2
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
# Get allowed ips from all peers of a configuration
|
|
||||||
def get_allowed_ip(conf_peer_data, config_name):
|
def get_allowed_ip(conf_peer_data, config_name):
|
||||||
|
"""
|
||||||
|
Get allowed ips from all peers of a configuration
|
||||||
|
@param conf_peer_data: Configuration peer data
|
||||||
|
@param config_name: Configuration name
|
||||||
|
@return: None
|
||||||
|
"""
|
||||||
# Get allowed ip
|
# Get allowed ip
|
||||||
for i in conf_peer_data["Peers"]:
|
for i in conf_peer_data["Peers"]:
|
||||||
g.cur.execute("UPDATE " + config_name + " SET allowed_ip = '%s' WHERE id = '%s'"
|
g.cur.execute("UPDATE " + config_name + " SET allowed_ip = '%s' WHERE id = '%s'"
|
||||||
% (i.get('AllowedIPs', '(None)'), i["PublicKey"]))
|
% (i.get('AllowedIPs', '(None)'), i["PublicKey"]))
|
||||||
|
|
||||||
|
|
||||||
# Look for new peers from WireGuard
|
|
||||||
def get_all_peers_data(config_name):
|
def get_all_peers_data(config_name):
|
||||||
|
"""
|
||||||
|
Look for new peers from WireGuard
|
||||||
|
@param config_name: Configuration name
|
||||||
|
@return: None
|
||||||
|
"""
|
||||||
conf_peer_data = read_conf_file(config_name)
|
conf_peer_data = read_conf_file(config_name)
|
||||||
config = get_dashboard_conf()
|
config = get_dashboard_conf()
|
||||||
failed_index = []
|
failed_index = []
|
||||||
@ -340,7 +361,6 @@ def get_all_peers_data(config_name):
|
|||||||
else:
|
else:
|
||||||
print("Trying to parse a peer doesn't have public key...")
|
print("Trying to parse a peer doesn't have public key...")
|
||||||
failed_index.append(i)
|
failed_index.append(i)
|
||||||
|
|
||||||
for i in failed_index:
|
for i in failed_index:
|
||||||
conf_peer_data['Peers'].pop(i)
|
conf_peer_data['Peers'].pop(i)
|
||||||
# Remove peers no longer exist in WireGuard configuration file
|
# Remove peers no longer exist in WireGuard configuration file
|
||||||
@ -349,26 +369,23 @@ def get_all_peers_data(config_name):
|
|||||||
for i in db_key:
|
for i in db_key:
|
||||||
if i not in wg_key:
|
if i not in wg_key:
|
||||||
g.cur.execute("DELETE FROM %s WHERE id = '%s'" % (config_name, i))
|
g.cur.execute("DELETE FROM %s WHERE id = '%s'" % (config_name, i))
|
||||||
tic = time.perf_counter()
|
|
||||||
get_latest_handshake(config_name)
|
get_latest_handshake(config_name)
|
||||||
get_transfer(config_name)
|
get_transfer(config_name)
|
||||||
get_endpoint(config_name)
|
get_endpoint(config_name)
|
||||||
get_allowed_ip(conf_peer_data, config_name)
|
get_allowed_ip(conf_peer_data, config_name)
|
||||||
toc = time.perf_counter()
|
|
||||||
print(f"Finish fetching data in {toc - tic:0.4f} seconds")
|
|
||||||
|
|
||||||
|
|
||||||
# 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.
|
"""
|
||||||
|
Get all peers.
|
||||||
:param config_name: Name of WG interface
|
@param config_name: Name of WG interface
|
||||||
:type config_name: str
|
@type config_name: str
|
||||||
:param search: Search string
|
@param search: Search string
|
||||||
:type search: str
|
@type search: str
|
||||||
:param sort_t: TODO
|
@param sort_t: Sorting tag
|
||||||
:type sort_t: str
|
@type sort_t: str
|
||||||
:return: TODO
|
@return: list
|
||||||
"""
|
"""
|
||||||
tic = time.perf_counter()
|
tic = time.perf_counter()
|
||||||
col = g.cur.execute("PRAGMA table_info(" + config_name + ")").fetchall()
|
col = g.cur.execute("PRAGMA table_info(" + config_name + ")").fetchall()
|
||||||
@ -391,21 +408,20 @@ def get_peers(config_name, search, sort_t):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
# Get configuration public key
|
|
||||||
def get_conf_pub_key(config_name):
|
def get_conf_pub_key(config_name):
|
||||||
"""Get public key for configuration.
|
"""
|
||||||
|
Get public key for 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 public key or empty string
|
@return: Return public key or empty string
|
||||||
:rtype: str
|
@rtype: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
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.run(f"echo '{pri}' | wg pubkey", check=True, shell=True, capture_output=True).stdout
|
pub = subprocess.check_output(f"echo '{pri}' | wg pubkey", shell=True, stderr=subprocess.STDOUT)
|
||||||
conf.clear()
|
conf.clear()
|
||||||
return pub.decode().strip("\n")
|
return pub.decode().strip("\n")
|
||||||
except configparser.NoSectionError:
|
except configparser.NoSectionError:
|
||||||
@ -414,12 +430,12 @@ def get_conf_pub_key(config_name):
|
|||||||
|
|
||||||
# Get configuration listen port
|
# 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.
|
||||||
:param config_name: Name of WG interface
|
@param config_name: Name of WG interface
|
||||||
:type config_name: str
|
@type config_name: str
|
||||||
:return: Return number of port or empty string
|
@return: Return number of port or empty string
|
||||||
:rtype: str
|
@rtype: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
conf = configparser.ConfigParser(strict=False)
|
conf = configparser.ConfigParser(strict=False)
|
||||||
@ -429,8 +445,8 @@ def get_conf_listen_port(config_name):
|
|||||||
port = conf.get("Interface", "ListenPort")
|
port = conf.get("Interface", "ListenPort")
|
||||||
except (configparser.NoSectionError, configparser.NoOptionError):
|
except (configparser.NoSectionError, configparser.NoOptionError):
|
||||||
if get_conf_status(config_name) == "running":
|
if get_conf_status(config_name) == "running":
|
||||||
port = subprocess.run(f"wg show {config_name} listen-port",
|
port = subprocess.check_output(f"wg show {config_name} listen-port",
|
||||||
check=True, shell=True, capture_output=True).stdout
|
shell=True, stderr=subprocess.STDOUT)
|
||||||
port = port.decode("UTF-8")
|
port = port.decode("UTF-8")
|
||||||
conf.clear()
|
conf.clear()
|
||||||
return port
|
return port
|
||||||
@ -529,18 +545,17 @@ def gen_public_key(private_key):
|
|||||||
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
|
|
||||||
def f_check_key_match(private_key, public_key, config_name):
|
def f_check_key_match(private_key, public_key, config_name):
|
||||||
"""TODO
|
"""
|
||||||
|
Check if private key and public key match
|
||||||
:param private_key: Private key
|
@param private_key: Private key
|
||||||
:type private_key: str
|
@type private_key: str
|
||||||
:param public_key: Public key
|
@param public_key: Public key
|
||||||
:type public_key: str
|
@type public_key: str
|
||||||
: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: dict
|
@rtype: dict
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result = gen_public_key(private_key)
|
result = gen_public_key(private_key)
|
||||||
@ -581,6 +596,7 @@ 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("/")
|
||||||
@ -706,7 +722,12 @@ def index():
|
|||||||
:return: TODO
|
:return: TODO
|
||||||
:rtype: TODO
|
:rtype: TODO
|
||||||
"""
|
"""
|
||||||
return render_template('index.html', conf=get_conf_list())
|
msg = ""
|
||||||
|
if "switch_msg" in session:
|
||||||
|
msg = session["switch_msg"]
|
||||||
|
session.pop("switch_msg")
|
||||||
|
|
||||||
|
return render_template('index.html', conf=get_conf_list(), msg=msg)
|
||||||
|
|
||||||
|
|
||||||
# Setting Page
|
# Setting Page
|
||||||
@ -1026,7 +1047,6 @@ def get_conf(config_name):
|
|||||||
return jsonify(conf_data)
|
return jsonify(conf_data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 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):
|
||||||
@ -1038,21 +1058,20 @@ def switch(config_name):
|
|||||||
:rtype: TODO
|
:rtype: TODO
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if "username" not in session:
|
|
||||||
print("User not logged in")
|
|
||||||
return redirect(url_for("signin"))
|
|
||||||
status = get_conf_status(config_name)
|
status = get_conf_status(config_name)
|
||||||
if status == "running":
|
if status == "running":
|
||||||
try:
|
try:
|
||||||
subprocess.run("wg-quick down " + config_name,
|
check = subprocess.check_output("wg-quick down " + config_name,
|
||||||
check=True, shell=True, capture_output=True).stdout
|
shell=True, stderr=subprocess.STDOUT)
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError as exc:
|
||||||
|
session["switch_msg"] = exc.output.strip().decode("utf-8")
|
||||||
return redirect('/')
|
return redirect('/')
|
||||||
elif status == "stopped":
|
elif status == "stopped":
|
||||||
try:
|
try:
|
||||||
subprocess.run("wg-quick up " + config_name,
|
subprocess.check_output("wg-quick up " + config_name,
|
||||||
check=True, shell=True, capture_output=True).stdout
|
shell=True, stderr=subprocess.STDOUT)
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError as exc:
|
||||||
|
session["switch_msg"] = exc.output.strip().decode("utf-8")
|
||||||
return redirect('/')
|
return redirect('/')
|
||||||
return redirect(request.referrer)
|
return redirect(request.referrer)
|
||||||
|
|
||||||
@ -1252,16 +1271,16 @@ def save_peer_setting(config_name):
|
|||||||
tmp_psk = open("tmp_edit_psk.txt", "w+")
|
tmp_psk = open("tmp_edit_psk.txt", "w+")
|
||||||
tmp_psk.write(preshared_key)
|
tmp_psk.write(preshared_key)
|
||||||
tmp_psk.close()
|
tmp_psk.close()
|
||||||
change_psk = subprocess.run(f"wg set {config_name} peer {id} preshared-key tmp_edit_psk.txt",
|
change_psk = subprocess.check_output(f"wg set {config_name} peer {id} preshared-key tmp_edit_psk.txt",
|
||||||
shell=True, check=True, stderr=subprocess.STDOUT)
|
shell=True, stderr=subprocess.STDOUT)
|
||||||
if change_psk.decode("UTF-8") != "":
|
if change_psk.decode("UTF-8") != "":
|
||||||
return jsonify({"status": "failed", "msg": change_psk.decode("UTF-8")})
|
return jsonify({"status": "failed", "msg": change_psk.decode("UTF-8")})
|
||||||
if allowed_ip == "":
|
if allowed_ip == "":
|
||||||
allowed_ip = '""'
|
allowed_ip = '""'
|
||||||
allowed_ip = allowed_ip.replace(" ", "")
|
allowed_ip = allowed_ip.replace(" ", "")
|
||||||
change_ip = subprocess.run(f"wg set {config_name} peer {id} allowed-ips {allowed_ip}",
|
change_ip = subprocess.check_output(f"wg set {config_name} peer {id} allowed-ips {allowed_ip}",
|
||||||
shell=True, check=True, stderr=subprocess.STDOUT)
|
shell=True, stderr=subprocess.STDOUT)
|
||||||
subprocess.run(f'wg-quick save {config_name}', shell=True, check=True, stderr=subprocess.STDOUT)
|
subprocess.check_output(f'wg-quick save {config_name}', shell=True, stderr=subprocess.STDOUT)
|
||||||
if change_ip.decode("UTF-8") != "":
|
if change_ip.decode("UTF-8") != "":
|
||||||
return jsonify({"status": "failed", "msg": change_ip.decode("UTF-8")})
|
return jsonify({"status": "failed", "msg": change_ip.decode("UTF-8")})
|
||||||
sql = "UPDATE " + config_name + " SET name = ?, private_key = ?, DNS = ?, endpoint_allowed_ip = ?, mtu = ?, keepalive = ?, preshared_key = ? WHERE id = ?"
|
sql = "UPDATE " + config_name + " SET name = ?, private_key = ?, DNS = ?, endpoint_allowed_ip = ?, mtu = ?, keepalive = ?, preshared_key = ? WHERE id = ?"
|
||||||
@ -1600,7 +1619,6 @@ def init_dashboard():
|
|||||||
config['Server'] = {}
|
config['Server'] = {}
|
||||||
if 'wg_conf_path' not in config['Server']:
|
if 'wg_conf_path' not in config['Server']:
|
||||||
config['Server']['wg_conf_path'] = '/etc/wireguard'
|
config['Server']['wg_conf_path'] = '/etc/wireguard'
|
||||||
# TODO: IPv6 for the app IP might need to configure with Gunicorn...
|
|
||||||
if 'app_ip' not in config['Server']:
|
if 'app_ip' not in config['Server']:
|
||||||
config['Server']['app_ip'] = '0.0.0.0'
|
config['Server']['app_ip'] = '0.0.0.0'
|
||||||
if 'app_port' not in config['Server']:
|
if 'app_port' not in config['Server']:
|
||||||
|
BIN
src/static/.DS_Store
vendored
@ -503,3 +503,13 @@ main{
|
|||||||
transition: all 1s ease-in-out;
|
transition: all 1s ease-in-out;
|
||||||
filter: blur(10px);
|
filter: blur(10px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pre.index-alert{
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: #343a40;
|
||||||
|
border: 1px solid rgba(0,0,0,.125);
|
||||||
|
border-radius: .25rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
color: white;
|
||||||
|
}
|
2
src/static/css/dashboard.min.css
vendored
@ -12,8 +12,13 @@
|
|||||||
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4 mb-4">
|
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4 mb-4">
|
||||||
<div style="display: flex; flex-direction: row; align-items: center;">
|
<div style="display: flex; flex-direction: row; align-items: center;">
|
||||||
<h1 class="pb-4 mt-4">Home</h1>
|
<h1 class="pb-4 mt-4">Home</h1>
|
||||||
{# <button class="btn btn-primary" style="margin-left: auto"><i class="bi bi-plus-circle-fill"></i> Configuration</button>#}
|
|
||||||
</div>
|
</div>
|
||||||
|
{% if msg != "" %}
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
Configuration toggle failed. Please check the following error message:
|
||||||
|
</div>
|
||||||
|
<pre class="index-alert"><code>{{ msg }}</code></pre>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if conf == [] %}
|
{% if conf == [] %}
|
||||||
<p class="text-muted">You don't have any WireGuard configurations yet. Please check the configuration folder or change it in "Settings". By default the folder is "/etc/wireguard".</p>
|
<p class="text-muted">You don't have any WireGuard configurations yet. Please check the configuration folder or change it in "Settings". By default the folder is "/etc/wireguard".</p>
|
||||||
|
@ -101,8 +101,7 @@ update_wgd() {
|
|||||||
mv wgd.sh wgd.sh.old
|
mv wgd.sh wgd.sh.old
|
||||||
printf "| Downloading %s from GitHub... |\n" "$new_ver"
|
printf "| Downloading %s from GitHub... |\n" "$new_ver"
|
||||||
git stash > /dev/null 2>&1
|
git stash > /dev/null 2>&1
|
||||||
# git pull
|
git pull https://github.com/donaldzou/WGDashboard.git $new_ver --force > /dev/null 2>&1
|
||||||
git pull https://github.com/donaldzou/wireguard-dashboard.git $new_ver --force > /dev/null 2>&1
|
|
||||||
printf "| Upgrading pip |\n"
|
printf "| Upgrading pip |\n"
|
||||||
python3 -m pip install -U pip > /dev/null 2>&1
|
python3 -m pip install -U pip > /dev/null 2>&1
|
||||||
printf "| Installing latest Python dependencies |\n"
|
printf "| Installing latest Python dependencies |\n"
|
||||||
|