diff --git a/README.md b/README.md
index 62ca409..2fc2927 100644
--- a/README.md
+++ b/README.md
@@ -20,15 +20,26 @@
## 📣 What's New: Version v2.2
- 🎉 **New Features**
+ - **Add new peers**: Now you can add peers directly on dashboard, it will generate a pair of private key and public key. You can also set its DNS, endpoint allowed IPs. Both can set a default value in the setting page. [❤️ in [#44](https://github.com/donaldzou/wireguard-dashboard/issues/44)]
- **QR Code:** You can add the private key in peer setting of your existed peer to create a QR code. Or just create a new one, dashboard will now be able to auto generate a private key and public key ;) Don't worry, all keys will be generated on your machine, and **will delete all key files after they got generated**. [❤️ in [#29](https://github.com/donaldzou/wireguard-dashboard/issues/29)]
- **Peer configuration file download:** Same as QR code, you now can download the peer configuration file, so you don't need to manually input all the details on the peer machine! [❤️ in [#40](https://github.com/donaldzou/wireguard-dashboard/issues/40)]
+ - **Search peers**: You can now search peers by their name.
- **Autostart on boot:** Added a tutorial on how to start the dashboard to on boot! Please read the [tutorial below](#autostart-wireguard-dashboard-on-boot). [❤️ in [#29](https://github.com/donaldzou/wireguard-dashboard/issues/29)]
+ - **Click to copy**: You can now click and copy all peer's public key and configuration's public key.
+ - And many more...
- 🪚 **Bug Fixed**
- When there are comments in the wireguard config file, will cause the dashboard to crash.
- Used regex to search for config files.
- **🧐 Other Changes**
- Moved all external CSS and JavaScript file to local hosting (Except Bootstrap Icon, due to large amount of SVG files).
- - Updated `Flask` from`v1.1.2` to `v2.0.1`, and `Jinja` from `v2.10.1` to `v3.0.1`
+ - Updated Python dependencies
+ - Flask: `v1.1.2 => v2.0.1`
+ - Jinja: `v2.10.1 => v3.0.1`
+ - icmplib: `v2.1.1 => v3.0.1`
+ - Updated CSS/JS dependencies
+ - Bootstrap: `v4.5.3 => v4.6.0`
+ - UI adjustment
+ - Adjusted how peers will display in larger screens, used to be 1 row per peer, now is 3 peers in 1 row.
diff --git a/src/dashboard.py b/src/dashboard.py
index c1a1de3..7f96f80 100644
--- a/src/dashboard.py
+++ b/src/dashboard.py
@@ -66,6 +66,23 @@ def is_match(regex, text):
return pattern.search(text) is not None
+def read_conf_file_interface(config_name):
+ conf_location = wg_conf_path + "/" + config_name + ".conf"
+ f = open(conf_location, 'r')
+ file = f.read().split("\n")
+ data = {}
+ peers_start = 0
+ for i in range(len(file)):
+ if not is_match("#(.*)", file[i]):
+ if len(file[i]) > 0:
+ if file[i] != "[Interface]":
+ tmp = re.split(r'\s*=\s*', file[i], 1)
+ if len(tmp) == 2:
+ data[tmp[0]] = tmp[1]
+ f.close()
+ return data
+
+
def read_conf_file(config_name):
# Read Configuration File Start
conf_location = wg_conf_path + "/" + config_name + ".conf"
@@ -195,7 +212,7 @@ def get_all_peers_data(config_name):
"id": i['PublicKey'],
"private_key": "",
"DNS": "1.1.1.1",
- "endpoint_allowed_ip":"0.0.0.0/0",
+ "endpoint_allowed_ip": "0.0.0.0/0",
"name": "",
"total_receive": 0,
"total_sent": 0,
@@ -357,14 +374,23 @@ def checkAllowedIP(public_key, ip, config_name):
else:
return {'status': 'success'}
+
def checkIp(ip):
- return is_match("((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}",ip)
+ return is_match("((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}", ip)
+
+
def cleanIp(ip):
- return ip.replace(' ','')
+ return ip.replace(' ', '')
+
+
def cleanIpWithRange(ip):
return cleanIp(ip).split(',')
+
+
def checkIpWithRange(ip):
return is_match("((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|\/)){4}(0|8|16|24|32)(,|$)", ip)
+
+
def checkAllowedIPs(ip):
ip = cleanIpWithRange(ip)
for i in ip:
@@ -372,8 +398,6 @@ def checkAllowedIPs(ip):
return True
-
-
@app.before_request
def auth_req():
conf = configparser.ConfigParser(strict=False)
@@ -431,8 +455,8 @@ def settings():
return render_template('settings.html', conf=get_conf_list(), message=message, status=status,
app_ip=config.get("Server", "app_ip"), app_port=config.get("Server", "app_port"),
required_auth=required_auth, wg_conf_path=config.get("Server", "wg_conf_path"),
- peer_global_DNS=config.get("Peers","peer_global_DNS"),
- peer_endpoint_allowed_ip=config.get("Peers","peer_endpoint_allowed_ip"))
+ peer_global_DNS=config.get("Peers", "peer_global_DNS"),
+ peer_endpoint_allowed_ip=config.get("Peers", "peer_endpoint_allowed_ip"))
@app.route('/auth', methods=['POST'])
@@ -473,6 +497,7 @@ def update_acct():
config.clear()
return redirect(url_for("settings"))
+
@app.route('/update_peer_default_config', methods=['POST'])
def update_peer_default_config():
config = configparser.ConfigParser(strict=False)
@@ -492,13 +517,11 @@ def update_peer_default_config():
# Check Endpoint Allowed IPs
ip = request.form['peer_endpoint_allowed_ip']
if not checkAllowedIPs(ip):
- session['message'] = "Peer Endpoint Allowed IPs Format Incorrect. Example: 192.168.1.1/32 or 192.168.1.1/32,192.168.1.2/32"
+ session[
+ 'message'] = "Peer Endpoint Allowed IPs Format Incorrect. Example: 192.168.1.1/32 or 192.168.1.1/32,192.168.1.2/32"
session['message_status'] = "danger"
return redirect(url_for("settings"))
-
-
-
config.set("Peers", "peer_endpoint_allowed_ip", ','.join(cleanIpWithRange(ip)))
config.set("Peers", "peer_global_DNS", request.form['peer_global_DNS'])
try:
@@ -679,6 +702,8 @@ def conf(config_name):
@app.route('/get_config/', methods=['GET'])
def get_conf(config_name):
+ config_interface = read_conf_file_interface(config_name)
+
search = request.args.get('search')
if len(search) == 0: search = ""
search = urllib.parse.unquote(search)
@@ -693,12 +718,14 @@ def get_conf(config_name):
"public_key": get_conf_pub_key(config_name),
"listen_port": get_conf_listen_port(config_name),
"running_peer": get_conf_running_peer_number(config_name),
+ "conf_address": config_interface['Address']
}
if conf_data['status'] == "stopped":
conf_data['checked'] = "nope"
else:
conf_data['checked'] = "checked"
- return render_template('get_conf.html', conf_data=conf_data, wg_ip=wg_ip, sort_tag=sort, dashboard_refresh_interval=int(config.get("Server", "dashboard_refresh_interval")))
+ return render_template('get_conf.html', conf_data=conf_data, wg_ip=wg_ip, sort_tag=sort,
+ dashboard_refresh_interval=int(config.get("Server", "dashboard_refresh_interval")))
@app.route('/switch/', methods=['GET'])
@@ -731,6 +758,9 @@ def add_peer(config_name):
endpoint_allowed_ip = data['endpoint_allowed_ip']
DNS = data['DNS']
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:
+ return "Please fill in all required box."
+
if type(keys) != list:
return config_name + " is not running."
if public_key in keys:
@@ -750,7 +780,8 @@ def add_peer(config_name):
stderr=subprocess.STDOUT)
status = subprocess.check_output("wg-quick save " + config_name, shell=True, stderr=subprocess.STDOUT)
get_all_peers_data(config_name)
- db.update({"name": data['name'], "private_key": data['private_key'], "DNS": data['DNS'], "endpoint_allowed_ip": endpoint_allowed_ip},
+ db.update({"name": data['name'], "private_key": data['private_key'], "DNS": data['DNS'],
+ "endpoint_allowed_ip": endpoint_allowed_ip},
peers.id == public_key)
db.close()
return "true"
@@ -804,7 +835,6 @@ def save_peer_setting(config_name):
return jsonify(check_key)
if check_ip['status'] == "failed":
return jsonify(check_ip)
-
try:
if allowed_ip == "":
allowed_ip = '""'
@@ -815,16 +845,13 @@ def save_peer_setting(config_name):
if change_ip.decode("UTF-8") != "":
return jsonify({"status": "failed", "msg": change_ip.decode("UTF-8")})
-
- db.update({"name": name, "private_key": private_key, "DNS": DNS, "endpoint_allowed_ip":endpoint_allowed_ip}, peers.id == id)
+ db.update(
+ {"name": name, "private_key": private_key, "DNS": DNS, "endpoint_allowed_ip": endpoint_allowed_ip},
+ peers.id == id)
db.close()
return jsonify({"status": "success", "msg": ""})
except subprocess.CalledProcessError as exc:
return jsonify({"status": "failed", "msg": str(exc.output.decode("UTF-8").strip())})
-
-
-
-
else:
return jsonify({"status": "failed", "msg": "This peer does not exist."})
@@ -881,7 +908,6 @@ def download(config_name):
private_key = peer['private_key']
allowed_ip = peer['allowed_ip']
DNS = peer['DNS']
-
filename = peer['name']
if len(filename) == 0:
filename = "Untitled_Peers"
@@ -922,7 +948,6 @@ def init_dashboard():
config['Account']['username'] = 'admin'
if "password" not in config['Account']:
config['Account']['password'] = '8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918'
-
if "Server" not in config:
config['Server'] = {}
if 'wg_conf_path' not in config['Server']:
@@ -939,7 +964,6 @@ def init_dashboard():
config['Server']['dashboard_refresh_interval'] = '15000'
if 'dashboard_sort' not in config['Server']:
config['Server']['dashboard_sort'] = 'status'
-
if "Peers" not in config:
config['Peers'] = {}
if 'peer_global_DNS' not in config['Peers']:
diff --git a/src/requirements.txt b/src/requirements.txt
index 8127a46..511897c 100644
--- a/src/requirements.txt
+++ b/src/requirements.txt
@@ -1,5 +1,5 @@
Flask==2.0.1
tinydb==4.3.0
ifcfg==0.21
-icmplib==2.1.1
+icmplib==3.0.1
flask-qrcode==3.0.0
\ No newline at end of file
diff --git a/src/static/js/configuration.js b/src/static/js/configuration.js
index 8a5270d..329dabd 100644
--- a/src/static/js/configuration.js
+++ b/src/static/js/configuration.js
@@ -58,8 +58,17 @@ $("#re_generate_key").click(function (){
})
$("#save_peer").click(function(){
- if ($("#allowed_ips") !== "" && $("#public_key") !== ""){
+ $(this).attr("disabled","disabled")
+ $(this).html("Saving...")
+
+ if ($("#allowed_ips").val() !== "" && $("#public_key").val() !== "" && $("#new_add_DNS").val() !== "" && $("#new_add_endpoint_allowed_ip").val() != ""){
var conf = $(this).attr('conf_id')
+ var data_list = [$("#private_key"), $("#allowed_ips"), $("#new_add_name"), $("#new_add_DNS"), $("#new_add_endpoint_allowed_ip")]
+ for (var i = 0; i < data_list.length; i++){
+ data_list[i].attr("disabled", "disabled")
+ }
+
+
$.ajax({
method: "POST",
url: "/add_peer/"+conf,
@@ -84,6 +93,9 @@ $("#save_peer").click(function(){
}
}
})
+ }else{
+ $("#add_peer_alert").html("Please fill in all required box.");
+ $("#add_peer_alert").removeClass("d-none");
}
})
var qrcodeModal = new bootstrap.Modal(document.getElementById('qrcode_modal'), {
@@ -108,6 +120,8 @@ $("body").on("click", ".btn-delete-peer", function(){
})
$("#delete_peer").click(function(){
+ $(this).attr("disabled","disabled")
+ $(this).html("Deleting...")
var peer_id = $(this).attr("peer_id");
var config = $(this).attr("conf_id");
$.ajax({
@@ -127,6 +141,8 @@ $("#delete_peer").click(function(){
load_data($('#search_peer_textbox').val());
$('#alertToast').toast('show');
$('#alertToast .toast-body').html("Peer deleted!");
+ $("#delete_peer").removeAttr("disabled")
+ $("#delete_peer").html("Delete")
}
}
})
@@ -189,9 +205,16 @@ $("#peer_private_key_textbox").change(function(){
$("#save_peer_setting").click(function (){
$(this).attr("disabled","disabled")
$(this).html("Saving...")
- if ($("#peer_DNS_textbox").val() !== "" && $("#peer_allowed_ip_textbox").val() !== ""){
+ if ($("#peer_DNS_textbox").val() !== "" &&
+ $("#peer_allowed_ip_textbox").val() !== "" &&
+ $("#peer_endpoint_allowed_ips").val() != ""
+ ){
var peer_id = $(this).attr("peer_id");
var conf_id = $(this).attr("conf_id");
+ var data_list = [$("#peer_name_textbox"), $("#peer_DNS_textbox"), $("#peer_private_key_textbox"), $("#peer_allowed_ip_textbox"), $("#peer_endpoint_allowed_ips")]
+ for (var i = 0; i < data_list.length; i++){
+ data_list[i].attr("disabled", "disabled")
+ }
$.ajax({
method: "POST",
url: "/save_peer_setting/"+conf_id,
@@ -218,8 +241,14 @@ $("#save_peer_setting").click(function (){
}
$("#save_peer_setting").removeAttr("disabled")
$("#save_peer_setting").html("Save")
+ for (var i = 0; i < data_list.length; i++){
+ data_list[i].removeAttr("disabled")
+ }
}
})
+ }else{
+ $("#setting_peer_alert").html("Please fill in all required box.");
+ $("#setting_peer_alert").removeClass("d-none");
}
@@ -288,7 +317,16 @@ function copyToClipboard(element) {
$temp.remove();
}
-
-// $(".key").mouseenter(function(){
-//
-// })
+$("body").on("click", ".update_interval", function(){
+ $.ajax({
+ method:"POST",
+ data: "interval="+$(this).attr("refresh-interval"),
+ url: "/update_dashboard_refresh_interval",
+ success: function (res){
+ location.reload()
+ }
+ })
+ });
+$("body").on("click", ".refresh", function (){
+ load_data($('#search_peer_textbox').val());
+});
diff --git a/src/templates/configuration.html b/src/templates/configuration.html
index f1d001c..300c24d 100644
--- a/src/templates/configuration.html
+++ b/src/templates/configuration.html
@@ -69,14 +69,14 @@
@@ -228,7 +228,6 @@
function load_data(search){
startProgressBar()
-
$.ajax({
method: "GET",
url: "/get_config/"+conf_name+"?search="+encodeURIComponent(search),
@@ -237,12 +236,10 @@
},
success: function (response){
$("#config_body").html(response);
- {#$("[refresh-interval={{ dashboard_refresh_interval }}]").addClass("active")#}
$("#search_peer_textbox").css("display", "block")
if (bar.css("width") !== "0%"){
endProgressBar()
}
-
}
})
}
@@ -253,19 +250,7 @@
}, {{dashboard_refresh_interval}})
});
- $("body").on("click", ".update_interval", function(){
- $.ajax({
- method:"POST",
- data: "interval="+$(this).attr("refresh-interval"),
- url: "/update_dashboard_refresh_interval",
- success: function (res){
- location.reload()
- }
- })
- });
- $("body").on("click", ".refresh", function (){
- load_data($('#search_peer_textbox').val());
- });
+