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

Finished adding available IP and related UI adjustment

This commit is contained in:
Donald Cheng Hong Zou 2022-01-04 16:32:23 -05:00
parent b8b721f2bd
commit 77a82cb84b
7 changed files with 279 additions and 49 deletions

View File

@ -13,20 +13,17 @@ import json
import os import os
import secrets import secrets
import subprocess import subprocess
import threading
import time import time
import re import re
import urllib.parse import urllib.parse
import urllib.request import urllib.request
from datetime import datetime, timedelta from datetime import datetime, timedelta
from operator import itemgetter from operator import itemgetter
import signal
# PIP installed library # PIP installed library
import ifcfg import ifcfg
from flask import Flask, request, render_template, redirect, url_for, session, jsonify from flask import Flask, request, render_template, redirect, url_for, session, jsonify
from flask_qrcode import QRcode from flask_qrcode import QRcode
from icmplib import ping, traceroute from icmplib import ping, traceroute
from tinydb import TinyDB, Query
# Import other python files # Import other python files
from util import regex_match, check_DNS, check_Allowed_IPs, check_remote_endpoint, \ from util import regex_match, check_DNS, check_Allowed_IPs, check_remote_endpoint, \
@ -325,7 +322,7 @@ def get_peers(config_name, search, sort_t):
data = g.cur.execute("SELECT * FROM " + config_name).fetchall() data = g.cur.execute("SELECT * FROM " + config_name).fetchall()
result = [{col[i]: data[k][i] for i in range(len(col))} for k in range(len(data))] result = [{col[i]: data[k][i] for i in range(len(col))} for k in range(len(data))]
else: else:
sql = "SELECT * FROM " + config_name + " WHERE name LIKE '%"+search+"%'" sql = "SELECT * FROM " + config_name + " WHERE name LIKE '%" + search + "%'"
data = g.cur.execute(sql).fetchall() data = g.cur.execute(sql).fetchall()
result = [{col[i]: data[k][i] for i in range(len(col))} for k in range(len(data))] result = [{col[i]: data[k][i] for i in range(len(col))} for k in range(len(data))]
if sort_t == "allowed_ip": if sort_t == "allowed_ip":
@ -395,8 +392,18 @@ def get_conf_list():
for i in os.listdir(wg_conf_path): for i in os.listdir(wg_conf_path):
if regex_match("^(.{1,}).(conf)$", i): if regex_match("^(.{1,}).(conf)$", i):
i = i.replace('.conf', '') i = i.replace('.conf', '')
g.cur.execute( create_table = f"""
"CREATE TABLE IF NOT EXISTS " + i + " (id VARCHAR NULL, private_key VARCHAR NULL, DNS VARCHAR NULL, endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL, total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL, status VARCHAR NULL, latest_handshake VARCHAR NULL, allowed_ip VARCHAR NULL, cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, mtu INT NULL, keepalive INT NULL, remote_endpoint VARCHAR NULL, preshared_key VARCHAR NULL)") CREATE TABLE IF NOT EXISTS {i} (
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL,
endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL,
total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL,
status VARCHAR NULL, latest_handshake VARCHAR NULL, allowed_ip VARCHAR NULL,
cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, mtu INT NULL,
keepalive INT NULL, remote_endpoint VARCHAR NULL, preshared_key VARCHAR NULL,
PRIMARY KEY (id)
)
"""
g.cur.execute(create_table)
temp = {"conf": i, "status": get_conf_status(i), "public_key": get_conf_pub_key(i)} temp = {"conf": i, "status": get_conf_status(i), "public_key": get_conf_pub_key(i)}
if temp['status'] == "running": if temp['status'] == "running":
temp['checked'] = 'checked' temp['checked'] = 'checked'
@ -412,13 +419,11 @@ def get_conf_list():
def gen_private_key(): def gen_private_key():
gen = subprocess.check_output('wg genkey > private_key.txt && wg pubkey < private_key.txt > public_key.txt', gen = subprocess.check_output('wg genkey > private_key.txt && wg pubkey < private_key.txt > public_key.txt',
shell=True) shell=True)
gen_psk = subprocess.check_output('wg genpsk', shell=True)
preshare_key = gen_psk.decode("UTF-8").strip()
with open('private_key.txt', encoding='utf-8') as file_object: with open('private_key.txt', encoding='utf-8') as file_object:
private_key = file_object.readline().strip() private_key = file_object.readline().strip()
with open('public_key.txt', encoding='utf-8') as file_object: with open('public_key.txt', encoding='utf-8') as file_object:
public_key = file_object.readline().strip() public_key = file_object.readline().strip()
data = {"private_key": private_key, "public_key": public_key, "preshared_key": preshare_key} data = {"private_key": private_key, "public_key": public_key}
return data return data
@ -867,8 +872,9 @@ def add_peer(config_name):
return config_name + " is not running." return config_name + " is not running."
if public_key in keys: if public_key in keys:
return "Public key already exist." return "Public key already exist."
check_dup_ip = g.cur.execute("SELECT COUNT(*) FROM " + config_name + " WHERE allowed_ip = ?", check_dup_ip = g.cur.execute(
(allowed_ips,)).fetchone() "SELECT COUNT(*) FROM " + config_name + " WHERE allowed_ip LIKE '" + allowed_ips + "%'", ) \
.fetchone()
if check_dup_ip[0] != 0: if check_dup_ip[0] != 0:
return "Allowed IP already taken by another peer." return "Allowed IP already taken by another peer."
if not check_DNS(dns_addresses): if not check_DNS(dns_addresses):
@ -890,7 +896,6 @@ def add_peer(config_name):
status = subprocess.check_output(f"wg set {config_name} peer {public_key} allowed-ips {allowed_ips}", status = subprocess.check_output(f"wg set {config_name} peer {public_key} allowed-ips {allowed_ips}",
shell=True, stderr=subprocess.STDOUT) shell=True, stderr=subprocess.STDOUT)
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)
get_all_peers_data(config_name) get_all_peers_data(config_name)
sql = "UPDATE " + config_name + " SET name = ?, private_key = ?, DNS = ?, endpoint_allowed_ip = ? WHERE id = ?" sql = "UPDATE " + config_name + " SET name = ?, private_key = ?, DNS = ?, endpoint_allowed_ip = ? WHERE id = ?"
g.cur.execute(sql, (data['name'], data['private_key'], data['DNS'], endpoint_allowed_ip, public_key)) g.cur.execute(sql, (data['name'], data['private_key'], data['DNS'], endpoint_allowed_ip, public_key))
@ -992,6 +997,35 @@ def get_peer_name(config_name):
return jsonify(data) return jsonify(data)
# Return available IPs
@app.route('/available_ips/<config_name>', methods=['GET'])
def available_ips(config_name):
config_interface = read_conf_file_interface(config_name)
if "Address" in config_interface:
existed = []
conf_address = config_interface['Address']
address = conf_address.split(',')
for i in address:
add, sub = i.split("/")
existed.append(ipaddress.ip_address(add))
peers = g.cur.execute("SELECT allowed_ip FROM " + config_name).fetchall()
for i in peers:
add = i[0].split(",")
for k in add:
a, s = k.split("/")
existed.append(ipaddress.ip_address(a))
available = list(ipaddress.ip_network(address[0], False).hosts())
for i in existed:
try:
available.remove(i)
except ValueError as e:
pass
available = [str(i) for i in available]
return jsonify(available)
else:
return jsonify([])
# Generate a private key # Generate a private key
@app.route('/generate_peer', methods=['GET']) @app.route('/generate_peer', methods=['GET'])
def generate_peer(): def generate_peer():
@ -1018,8 +1052,9 @@ 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):
peer_id = request.args.get('id') peer_id = request.args.get('id')
get_peer = g.cur.execute("SELECT private_key, allowed_ip, DNS, mtu, endpoint_allowed_ip, keepalive, preshared_key FROM " get_peer = g.cur.execute(
+ config_name + " WHERE id = ?", (peer_id,)).fetchall() "SELECT private_key, allowed_ip, DNS, mtu, endpoint_allowed_ip, keepalive, preshared_key FROM "
+ config_name + " WHERE id = ?", (peer_id,)).fetchall()
config = get_dashboard_conf() config = get_dashboard_conf()
if len(get_peer) == 1: if len(get_peer) == 1:
peer = get_peer[0] peer = get_peer[0]
@ -1041,7 +1076,6 @@ def generate_qrcode(config_name):
+ str(keepalive) + "\nEndpoint = " + endpoint + str(keepalive) + "\nEndpoint = " + endpoint
if preshared_key != "": if preshared_key != "":
result += "\nPresharedKey = " + preshared_key result += "\nPresharedKey = " + preshared_key
return render_template("qrcode.html", i=result) return render_template("qrcode.html", i=result)
else: else:
return redirect("/configuration/" + config_name) return redirect("/configuration/" + config_name)
@ -1054,7 +1088,7 @@ def download(config_name):
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 "
+ config_name + " WHERE id = ?", (peer_id,)).fetchall() + config_name + " WHERE id = ?", (peer_id,)).fetchall()
config = get_dashboard_conf() config = get_dashboard_conf()
if len(get_peer) == 1: if len(get_peer) == 1:
peer = get_peer[0] peer = get_peer[0]
@ -1090,9 +1124,10 @@ def download(config_name):
def generate(): def generate():
yield "[Interface]\nPrivateKey = " + private_key + "\nAddress = " + allowed_ip + "\nDNS = " + \ yield "[Interface]\nPrivateKey = " + private_key + "\nAddress = " + allowed_ip + "\nDNS = " + \
dns_addresses + "\nMTU = " + str(mtu_value) + "\n\n[Peer]\nPublicKey = " + \ dns_addresses + "\nMTU = " + str(mtu_value) + "\n\n[Peer]\nPublicKey = " + \
public_key + "\nAllowedIPs = " + endpoint_allowed_ip + "\nEndpoint = " + \ public_key + "\nAllowedIPs = " + endpoint_allowed_ip + "\nEndpoint = " + \
endpoint + "\nPersistentKeepalive = " + str(keepalive) + psk endpoint + "\nPersistentKeepalive = " + str(keepalive) + psk
return app.response_class(generate(), mimetype='text/conf', return app.response_class(generate(), mimetype='text/conf',
headers={"Content-Disposition": "attachment;filename=" + filename + ".conf"}) headers={"Content-Disposition": "attachment;filename=" + filename + ".conf"})
return redirect("/configuration/" + config_name) return redirect("/configuration/" + config_name)

View File

@ -382,3 +382,15 @@ main{
animation: loading 3s infinite ease-in-out; animation: loading 3s infinite ease-in-out;
} }
#qrcode_img img{
width: 100%;
}
#selected_ip_list .badge{
margin: 0.1rem
}
#add_modal.ip_modal_open{
transition: filter 0.2s ease-in-out;
filter: brightness(0.5);
}

View File

@ -1 +1 @@
@-webkit-keyframes rotating{0%{-webkit-transform:rotate(0deg);-o-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes rotating{0%{-ms-transform:rotate(0deg);-moz-transform:rotate(0deg);-webkit-transform:rotate(0deg);-o-transform:rotate(0deg);transform:rotate(0deg)}to{-ms-transform:rotate(360deg);-moz-transform:rotate(360deg);-webkit-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes loading{0%,to{background-color:#dfdfdf}50%{background-color:#adadad}}@-moz-keyframes loading{0%,to{background-color:#dfdfdf}50%{background-color:#adadad}}body{font-size:.875rem}.feather{width:16px;height:16px;vertical-align:text-bottom}.sidebar{position:fixed;top:0;bottom:0;left:0;z-index:100;padding:48px 0 0;box-shadow:inset -1px 0 0 rgba(0,0,0,.1)}.sidebar-sticky{position:relative;top:0;height:calc(100vh - 48px);padding-top:.5rem;overflow-x:hidden;overflow-y:auto}@supports ((position:-webkit-sticky) or (position:sticky)){.sidebar-sticky{position:-webkit-sticky;position:sticky}}.sidebar .nav-link{font-weight:500;color:#333;transition:.2s cubic-bezier(.82,-.07,0,1.01)}.nav-link:hover{padding-left:30px}.sidebar .nav-link .feather{margin-right:4px;color:#999}.sidebar .nav-link.active{color:#007bff}.sidebar .nav-link.active .feather,.sidebar .nav-link:hover .feather{color:inherit}.sidebar-heading{font-size:.75rem;text-transform:uppercase}.navbar-brand{padding-top:.75rem;padding-bottom:.75rem;font-size:1rem;background-color:rgba(0,0,0,.25);box-shadow:inset -1px 0 0 rgba(0,0,0,.25)}.navbar .navbar-toggler{top:.25rem;right:1rem}.navbar .form-control{padding:.75rem 1rem;border-width:0;border-radius:0}.form-control-dark{color:#fff;background-color:rgba(255,255,255,.1);border-color:rgba(255,255,255,.1)}.form-control-dark:focus{border-color:transparent;box-shadow:0 0 0 3px rgba(255,255,255,.25)}.dot{width:10px;height:10px;border-radius:50px;display:inline-block;margin-left:10px}.dot-running{background-color:#28a745!important;box-shadow:0 0 0 .2rem #28a74545}.h6-dot-running{margin-left:.3rem}.dot-stopped{background-color:#6c757d!important}.card-running{border-color:#28a745}.info h6{line-break:anywhere;transition:.2s ease-in-out}.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{border:0!important;padding:0 1rem 0 0}.btn-control:active,.btn-control:focus{background-color:transparent!important;border:0!important;box-shadow:none}.share_peer_btn_group .btn-control{padding:0 0 0 1rem}.btn-control:hover{background:#fff}.btn-delete-peer:hover{color:#dc3545}.btn-setting-peer:hover{color:#007bff}.btn-download-peer:hover{color:#17a2b8}.login-container{padding:2rem}@media (max-width:992px){.card-col{margin-bottom:1rem}}.switch{font-size:2rem}.switch:hover{text-decoration:none}.btn-group-label:hover{color:#007bff;border-color:#007bff;background:#fff}@media (max-width:768px){.peer_data_group{text-align:left}}.index-switch{text-align:right}main{margin-bottom:3rem}.peer_list{margin-bottom:7rem}@media (max-width:768px){.add_btn{bottom:1.5rem!important}.peer_list{margin-bottom:4rem!important}}.add_btn{position:fixed;bottom:3rem;right:2rem;z-index:99;border-radius:100px!important;padding:10px 20px;box-shadow:0 10px 20px rgba(0,0,0,.19),0 6px 6px rgba(0,0,0,.23)}.rotating::before{-webkit-animation:rotating .75s linear infinite;-moz-animation:rotating .75s linear infinite;-ms-animation:rotating .75s linear infinite;-o-animation:rotating .75s linear infinite;animation:rotating .75s linear infinite}.peer_private_key_textbox_switch{position:absolute;right:2rem;transform:translateY(-28px);font-size:1.2rem;cursor:pointer}#peer_private_key_textbox,#private_key,#public_key{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}.progress-bar{transition:.3s ease-in-out}.key{transition:.2s ease-in-out;cursor:pointer}.key:hover{color:#007bff}.card,.form-control{border-radius:10px}.peer_list .card .button-group{height:22px}.btn{border-radius:8px}.modal-content{border-radius:10px}.tooltip-inner{font-size:.8rem}#conf_status_btn,.conf_card{transition:.2s ease-in-out}.conf_card:hover{border-color:#007bff;cursor:pointer}.info_loading{animation:loading 2s infinite ease-in-out;border-radius:5px;height:19px;transition:.3s ease-in-out}#conf_status_btn.info_loading{height:38px;border-radius:5px;animation:loading 3s infinite ease-in-out} @-webkit-keyframes rotating{0%{-webkit-transform:rotate(0deg);-o-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes rotating{0%{-ms-transform:rotate(0deg);-moz-transform:rotate(0deg);-webkit-transform:rotate(0deg);-o-transform:rotate(0deg);transform:rotate(0deg)}to{-ms-transform:rotate(360deg);-moz-transform:rotate(360deg);-webkit-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes loading{0%,to{background-color:#dfdfdf}50%{background-color:#adadad}}@-moz-keyframes loading{0%,to{background-color:#dfdfdf}50%{background-color:#adadad}}body{font-size:.875rem}.feather{width:16px;height:16px;vertical-align:text-bottom}.sidebar{position:fixed;top:0;bottom:0;left:0;z-index:100;padding:48px 0 0;box-shadow:inset -1px 0 0 rgba(0,0,0,.1)}.sidebar-sticky{position:relative;top:0;height:calc(100vh - 48px);padding-top:.5rem;overflow-x:hidden;overflow-y:auto}@supports ((position:-webkit-sticky) or (position:sticky)){.sidebar-sticky{position:-webkit-sticky;position:sticky}}.sidebar .nav-link{font-weight:500;color:#333;transition:.2s cubic-bezier(.82,-.07,0,1.01)}.nav-link:hover{padding-left:30px}.sidebar .nav-link .feather{margin-right:4px;color:#999}.sidebar .nav-link.active{color:#007bff}.sidebar .nav-link.active .feather,.sidebar .nav-link:hover .feather{color:inherit}.sidebar-heading{font-size:.75rem;text-transform:uppercase}.navbar-brand{padding-top:.75rem;padding-bottom:.75rem;font-size:1rem;background-color:rgba(0,0,0,.25);box-shadow:inset -1px 0 0 rgba(0,0,0,.25)}.navbar .navbar-toggler{top:.25rem;right:1rem}.navbar .form-control{padding:.75rem 1rem;border-width:0;border-radius:0}.form-control-dark{color:#fff;background-color:rgba(255,255,255,.1);border-color:rgba(255,255,255,.1)}.form-control-dark:focus{border-color:transparent;box-shadow:0 0 0 3px rgba(255,255,255,.25)}.dot{width:10px;height:10px;border-radius:50px;display:inline-block;margin-left:10px}.dot-running{background-color:#28a745!important;box-shadow:0 0 0 .2rem #28a74545}.h6-dot-running{margin-left:.3rem}.dot-stopped{background-color:#6c757d!important}.card-running{border-color:#28a745}.info h6{line-break:anywhere;transition:.2s ease-in-out}.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{border:0!important;padding:0 1rem 0 0}.btn-control:active,.btn-control:focus{background-color:transparent!important;border:0!important;box-shadow:none}.share_peer_btn_group .btn-control{padding:0 0 0 1rem}.btn-control:hover{background:#fff}.btn-delete-peer:hover{color:#dc3545}.btn-setting-peer:hover{color:#007bff}.btn-download-peer:hover{color:#17a2b8}.login-container{padding:2rem}@media (max-width:992px){.card-col{margin-bottom:1rem}}.switch{font-size:2rem}.switch:hover{text-decoration:none}.btn-group-label:hover{color:#007bff;border-color:#007bff;background:#fff}@media (max-width:768px){.peer_data_group{text-align:left}}.index-switch{text-align:right}main{margin-bottom:3rem}.peer_list{margin-bottom:7rem}@media (max-width:768px){.add_btn{bottom:1.5rem!important}.peer_list{margin-bottom:4rem!important}}.add_btn{position:fixed;bottom:3rem;right:2rem;z-index:99;border-radius:100px!important;padding:10px 20px;box-shadow:0 10px 20px rgba(0,0,0,.19),0 6px 6px rgba(0,0,0,.23)}.rotating::before{-webkit-animation:rotating .75s linear infinite;-moz-animation:rotating .75s linear infinite;-ms-animation:rotating .75s linear infinite;-o-animation:rotating .75s linear infinite;animation:rotating .75s linear infinite}.peer_private_key_textbox_switch{position:absolute;right:2rem;transform:translateY(-28px);font-size:1.2rem;cursor:pointer}#peer_private_key_textbox,#private_key,#public_key{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}.progress-bar{transition:.3s ease-in-out}.key{transition:.2s ease-in-out;cursor:pointer}.key:hover{color:#007bff}.card,.form-control{border-radius:10px}.peer_list .card .button-group{height:22px}.btn{border-radius:8px}.modal-content{border-radius:10px}.tooltip-inner{font-size:.8rem}#conf_status_btn,.conf_card{transition:.2s ease-in-out}.conf_card:hover{border-color:#007bff;cursor:pointer}.info_loading{animation:loading 2s infinite ease-in-out;border-radius:5px;height:19px;transition:.3s ease-in-out}#conf_status_btn.info_loading{height:38px;border-radius:5px;animation:loading 3s infinite ease-in-out}#qrcode_img img{width:100%}#selected_ip_list .badge{margin:.1rem}#add_modal.ip_modal_open{transition:filter .2s ease-in-out;filter:brightness(.5)}

View File

@ -1,12 +1,27 @@
$("[data-toggle='tooltip']").tooltip()
let $body = $("body"); let $body = $("body");
let $progress_bar = $(".progress-bar");
let available_ips = [];
let $save_peer = $("#save_peer");
$(".add_btn").on("click", function(){
addModal.toggle();
});
/**
* Round Transfer number into 4 digits
* @param value
* @param digits
* @returns {number}
*/
function roundN(value, digits) { function roundN(value, digits) {
let tenToN = 10 ** digits; let tenToN = 10 ** digits;
return (Math.round(value * tenToN)) / tenToN; return (Math.round(value * tenToN)) / tenToN;
} }
// Progress Bar /**
let $progress_bar = $(".progress-bar"); * Start Progress Bar
*/
function startProgressBar(){ function startProgressBar(){
$progress_bar.css("width","0%") $progress_bar.css("width","0%")
.css("opacity", "100") .css("opacity", "100")
@ -19,10 +34,16 @@ function startProgressBar(){
},300); },300);
} }
/**
* Still Loading Progress Bar
*/
function stillLoadingProgressBar(){ function stillLoadingProgressBar(){
$progress_bar.css("transition", "3s ease-in-out").css("width", "75%"); $progress_bar.css("transition", "3s ease-in-out").css("width", "75%");
} }
/**
* End Progress Bae
*/
function endProgressBar(){ function endProgressBar(){
$progress_bar.css("transition", "0.3s ease-in-out").css("width","100%"); $progress_bar.css("transition", "0.3s ease-in-out").css("width","100%");
setTimeout(function(){ setTimeout(function(){
@ -31,20 +52,27 @@ function endProgressBar(){
} }
/**
* Show toast
* @param msg
*/
function showToast(msg) { function showToast(msg) {
$('#alertToast').toast('show'); $('#alertToast').toast('show');
$('#alertToast .toast-body').html(msg); $('#alertToast .toast-body').html(msg);
} }
/**
// Config Toggle * When configuration switch got click
*/
$body.on("click", ".switch", function (){ $body.on("click", ".switch", function (){
$(this).siblings($(".spinner-border")).css("display", "inline-block"); $(this).siblings($(".spinner-border")).css("display", "inline-block");
$(this).remove(); $(this).remove();
location.replace("/switch/"+$(this).attr('id')); location.replace("/switch/"+$(this).attr('id'));
}); });
// Generating Keys /**
* Generate Private and Public key for a new peer
*/
function generate_key(){ function generate_key(){
$.ajax({ $.ajax({
"url": "/generate_peer", "url": "/generate_peer",
@ -52,11 +80,14 @@ function generate_key(){
}).done(function(res){ }).done(function(res){
$("#private_key").val(res.private_key); $("#private_key").val(res.private_key);
$("#public_key").val(res.public_key); $("#public_key").val(res.public_key);
$("#preshare_key").val(res.preshared_key);
$("#add_peer_alert").addClass("d-none"); $("#add_peer_alert").addClass("d-none");
$("#re_generate_key i").removeClass("rotating"); $("#re_generate_key i").removeClass("rotating");
}); });
} }
/**
* Generate public for existing peer
*/
function generate_public_key(){ function generate_public_key(){
$.ajax({ $.ajax({
"url": "/generate_public_key", "url": "/generate_public_key",
@ -70,11 +101,13 @@ function generate_public_key(){
$("#add_peer_alert").addClass("d-none"); $("#add_peer_alert").addClass("d-none");
} }
$("#public_key").val(res.data); $("#public_key").val(res.data);
$("#re_generate_key i").removeClass("rotating"); $("#re_generate_key i").removeClass("rotating");
}); });
} }
// Add Peer /**
* Generate Public key when private got change
*/
$("#private_key").on("change",function(){ $("#private_key").on("change",function(){
if ($(this).val().length > 0){ if ($(this).val().length > 0){
$("#re_generate_key i").addClass("rotating"); $("#re_generate_key i").addClass("rotating");
@ -84,33 +117,144 @@ $("#private_key").on("change",function(){
} }
}); });
/**
* Trigger IP badge and item
* @param ip
*/
function trigger_ip(ip){
let $ip_ele = $(".available-ip-item[data-ip='"+ip+"']");
if ($ip_ele.html()){
if ($ip_ele.hasClass("active")){
$ip_ele.removeClass("active");
$("#selected_ip_list .badge[data-ip='"+ip+"']").remove();
}else{
$ip_ele.addClass("active");
$("#selected_ip_list").append('<span class="badge badge-primary available-ip-badge" style="cursor: pointer" data-ip="'+ip+'">'+ip+'</span>')
}
}
}
/**
* Get all available IP for this configuration
*/
function get_available_ip(){
$.ajax({
"url": "/available_ips/"+$save_peer.attr("conf_id"),
"method": "GET",
}).done(function (res) {
available_ips = res;
let $list_group = $("#available_ip_modal .modal-body .list-group");
$list_group.html("");
$("#allowed_ips").val(available_ips[0]);
available_ips.forEach((ip) =>
$list_group.append('<a class="list-group-item list-group-item-action available-ip-item" style="cursor: pointer" data-ip="'+ip+'">'+ip+'</a>'));
});
}
$("#available_ip_modal").on("show.bs.modal", () => {
$('#add_modal').addClass("ip_modal_open");
}).on("hidden.bs.modal", function () {
$('#add_modal').removeClass("ip_modal_open");
let ips = [];
let $selected_ip_list = $("#selected_ip_list");
$selected_ip_list.children().each(function(){
ips.push($(this).data("ip"));
});
ips.forEach((ele) => trigger_ip(ele));
})
/**
* When IP Badge got click
*/
$body.on("click", ".available-ip-badge", function(){
$(".available-ip-item[data-ip='"+$(this).data("ip")+"']").removeClass("active");
$(this).remove();
})
/**
* When available ip item got click
*/
$body.on("click", ".available-ip-item", function () {
trigger_ip($(this).data("ip"));
});
let $ipModal = new bootstrap.Modal(document.getElementById('available_ip_modal'), {
keyboard: false
});
$("#search_available_ip").on("click", function () {
$ipModal.toggle();
let $allowed_ips = $("#allowed_ips");
if ($allowed_ips.val().length > 0){
let s = $allowed_ips.val().split(",");
for (let i = 0; i < s.length; i++){
s[i] = s[i].trim();
trigger_ip(s[i]);
}
}
}).tooltip();
$("#confirm_ip").on("click", () => {
$ipModal.toggle();
let ips = [];
let $selected_ip_list = $("#selected_ip_list");
$selected_ip_list.children().each(function(){
ips.push($(this).data("ip"));
});
$("#allowed_ips").val(ips.join(", "));
ips.forEach((ele) => trigger_ip(ele));
});
$("#allowed_ips").on("keyup", function(){
let s = clean_ip($(this).val());
s = s.split(",");
if (available_ips.includes(s[s.length - 1])){
$("#allowed_ips_indicator").removeClass().addClass("text-success")
.html('<i class="bi bi-check-circle-fill"></i>');
}else{
$("#allowed_ips_indicator").removeClass().addClass("text-warning")
.html('<i class="bi bi-exclamation-circle-fill"></i>');
}
})
$('#add_modal').on('show.bs.modal', function (event) { $('#add_modal').on('show.bs.modal', function (event) {
generate_key(); generate_key();
get_available_ip();
}).on('hide.bs.modal', function(){
$("#allowed_ips_indicator").html('');
}); });
$("#re_generate_key").on("click",function (){ $("#re_generate_key").on("click",function (){
$("#public_key").attr("disabled","disabled"); $("#public_key").attr("disabled","disabled");
$("#re_generate_key i").addClass("rotating"); $("#re_generate_key i").addClass("rotating");
generate_key(); generate_key();
}); });
let addModal = new bootstrap.Modal(document.getElementById('add_modal'), { let addModal = new bootstrap.Modal(document.getElementById('add_modal'), {
keyboard: false keyboard: false
}); });
$(".add_btn").on("click", function(){ function clean_ip(val){
addModal.toggle(); let clean_ip = val.split(',');
}); for (let i = 0; i < clean_ip.length; i++) clean_ip[i] = clean_ip[i].trim(' ');
return clean_ip.filter(Boolean).join(",");
}
$("#save_peer").on("click",function(){ $save_peer.on("click",function(){
let $public_key = $("#public_key"); let $public_key = $("#public_key");
let $private_key = $("#private_key"); let $private_key = $("#private_key");
let $allowed_ips = $("#allowed_ips"); let $allowed_ips = $("#allowed_ips");
$allowed_ips.val(clean_ip($allowed_ips.val()));
let $new_add_DNS = $("#new_add_DNS"); let $new_add_DNS = $("#new_add_DNS");
$new_add_DNS.val(clean_ip($new_add_DNS.val()));
let $new_add_endpoint_allowed_ip = $("#new_add_endpoint_allowed_ip"); let $new_add_endpoint_allowed_ip = $("#new_add_endpoint_allowed_ip");
$new_add_endpoint_allowed_ip.val(clean_ip($new_add_endpoint_allowed_ip.val()));
let $new_add_name = $("#new_add_name"); let $new_add_name = $("#new_add_name");
let $new_add_MTU = $("#new_add_MTU"); let $new_add_MTU = $("#new_add_MTU");
let $new_add_keep_alive = $("#new_add_keep_alive"); let $new_add_keep_alive = $("#new_add_keep_alive");
let $enable_preshare_key = $("#enable_preshare_key"); let $enable_preshare_key = $("#enable_preshare_key");
let p_key = $public_key.val()
$(this).attr("disabled","disabled"); $(this).attr("disabled","disabled");
$(this).html("Saving..."); $(this).html("Saving...");
if ($allowed_ips.val() !== "" && $public_key.val() !== "" && $new_add_DNS.val() !== "" && $new_add_endpoint_allowed_ip.val() !== ""){ if ($allowed_ips.val() !== "" && $public_key.val() !== "" && $new_add_DNS.val() !== "" && $new_add_endpoint_allowed_ip.val() !== ""){
@ -138,10 +282,14 @@ $("#save_peer").on("click",function(){
if(response !== "true"){ if(response !== "true"){
$("#add_peer_alert").html(response).removeClass("d-none"); $("#add_peer_alert").html(response).removeClass("d-none");
data_list.forEach((ele) => ele.removeAttr("disabled")); data_list.forEach((ele) => ele.removeAttr("disabled"));
$("#save_peer").removeAttr("disabled").html("Save"); $save_peer.removeAttr("disabled").html("Save");
} }
else{ else{
load_data(""); load_data("");
data_list.forEach((ele) => ele.removeAttr("disabled"));
$("#add_peer_form").trigger("reset");
$save_peer.removeAttr("disabled").html("Save");
showToast("Add peer successful!");
addModal.toggle(); addModal.toggle();
} }
} }
@ -159,6 +307,8 @@ let 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 (){
let src = $(this).attr('img_src'); let src = $(this).attr('img_src');
$.ajax({ $.ajax({
"url": src, "url": src,
@ -326,7 +476,6 @@ $(".peer_private_key_textbox_switch").on("click",function (){
$(".peer_private_key_textbox_switch i").removeClass().addClass(icon); $(".peer_private_key_textbox_switch i").removeClass().addClass(icon);
}); });
// Search Peer // Search Peer
let typingTimer; let typingTimer;
let doneTypingInterval = 200; let doneTypingInterval = 200;
@ -335,14 +484,15 @@ $input.on('keyup', function () {
clearTimeout(typingTimer); clearTimeout(typingTimer);
typingTimer = setTimeout(doneTyping, doneTypingInterval); typingTimer = setTimeout(doneTyping, doneTypingInterval);
}); });
$input.on('keydown', function () { $input.on('keydown', function () {
clearTimeout(typingTimer); clearTimeout(typingTimer);
}); });
function doneTyping () { function doneTyping () {
load_data($input.val()); load_data($input.val());
} }
// Sorting // Sorting
$body.on("change", "#sort_by_dropdown", function (){ $body.on("change", "#sort_by_dropdown", function (){
$.ajax({ $.ajax({
@ -360,16 +510,13 @@ $body.on("change", "#sort_by_dropdown", function (){
$body.on("mouseenter", ".key", function(){ $body.on("mouseenter", ".key", function(){
let label = $(this).parent().siblings().children()[1]; let label = $(this).parent().siblings().children()[1];
label.style.opacity = "100"; label.style.opacity = "100";
}) }).on("mouseout", ".key", function(){
$body.on("mouseout", ".key", function(){
let label = $(this).parent().siblings().children()[1]; let label = $(this).parent().siblings().children()[1];
label.style.opacity = "0"; label.style.opacity = "0";
setTimeout(function (){ setTimeout(function (){
label.innerHTML = "CLICK TO COPY"; label.innerHTML = "CLICK TO COPY";
},200); },200);
}); }).on("click", ".key", function(){
$body.on("click", ".key", function(){
var label = $(this).parent().siblings().children()[1]; var label = $(this).parent().siblings().children()[1];
copyToClipboard($(this)); copyToClipboard($(this));
label.innerHTML = "COPIED!"; label.innerHTML = "COPIED!";
@ -420,7 +567,6 @@ $body.on("click", ".refresh", function (){
load_data($('#search_peer_textbox').val()); load_data($('#search_peer_textbox').val());
}); });
// Switch display mode // Switch display mode
$body.on("click", ".display_mode", function(){ $body.on("click", ".display_mode", function(){
$(".display-btn-group button").removeClass("active"); $(".display-btn-group button").removeClass("active");

File diff suppressed because one or more lines are too long

View File

@ -83,7 +83,7 @@
<div class="form-group"> <div class="form-group">
<label><small class="text-muted">Refresh Interval</small></label><br> <label><small class="text-muted">Refresh Interval</small></label><br>
<div class="btn-group interval-btn-group" role="group" style="width: 100%"> <div class="btn-group interval-btn-group" role="group" style="width: 100%">
<button style="width: 20%" type="button" class="btn btn-outline-primary btn-group-label refresh"><i class="bi bi-arrow-repeat"></i></button> <button style="width: 20%" type="button" class="btn btn-outline-primary btn-group-label refresh" data-toggle="tooltip" data-placement="bottom" title="Refresh Peers"><i class="bi bi-arrow-repeat"></i></button>
<button style="width: 20%" type="button" class="btn btn-outline-primary update_interval" data-refresh-interval="5000">5s</button> <button style="width: 20%" type="button" class="btn btn-outline-primary update_interval" data-refresh-interval="5000">5s</button>
<button style="width: 20%" type="button" class="btn btn-outline-primary update_interval" data-refresh-interval="10000">10s</button> <button style="width: 20%" type="button" class="btn btn-outline-primary update_interval" data-refresh-interval="10000">10s</button>
<button style="width: 20%" type="button" class="btn btn-outline-primary update_interval" data-refresh-interval="30000">30s</button> <button style="width: 20%" type="button" class="btn btn-outline-primary update_interval" data-refresh-interval="30000">30s</button>
@ -109,6 +109,7 @@
</main> </main>
</div> </div>
</div> </div>
<div class="modal fade" id="add_modal" data-backdrop="static" data-keyboard="false" tabindex="-1" <div class="modal fade" id="add_modal" data-backdrop="static" data-keyboard="false" tabindex="-1"
aria-labelledby="staticBackdropLabel" aria-hidden="true"> aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg"> <div class="modal-dialog modal-dialog-centered modal-lg">
@ -136,7 +137,7 @@
<div class="input-group"> <div class="input-group">
<input type="text" class="form-control" id="private_key" aria-describedby="private_key"> <input type="text" class="form-control" id="private_key" aria-describedby="private_key">
<div class="input-group-append"> <div class="input-group-append">
<button type="button" class="btn btn-danger" id="re_generate_key"><i class="bi bi-arrow-repeat"></i></button> <button type="button" class="btn btn-danger" id="re_generate_key" data-toggle="tooltip" data-placement="top" title="Regenerate Key"><i class="bi bi-arrow-repeat"></i></button>
</div> </div>
</div> </div>
</div> </div>
@ -154,7 +155,15 @@
<div class="col-sm-6"> <div class="col-sm-6">
<div class="form-group"> <div class="form-group">
<label for="allowed_ips">Allowed IPs <code>(Required)</code></label> <label for="allowed_ips">Allowed IPs <code>(Required)</code></label>
<input type="text" class="form-control" id="allowed_ips"> <div class="input-group">
<input type="text" class="form-control" id="allowed_ips">
<div class="input-group-append">
<button type="button" class="btn btn-primary" id="search_available_ip" data-toggle="tooltip" data-placement="top" title="Search Available IPs">
<i class="bi bi-search"></i>
</button>
</div>
</div>
<p style="position: absolute; top: 4px; right: 1rem;" class="text-success" id="allowed_ips_indicator"></p>
</div> </div>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
@ -296,6 +305,31 @@
</div> </div>
</div> </div>
</div> </div>
<div class="modal fade" id="available_ip_modal" data-backdrop="static" data-keyboard="false" tabindex="1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Select available IP</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="selected_ip" style="padding: 1rem; border-bottom: 1px solid #dee2e6;">
<small class="text-muted"><strong>SELECTED IP (CLICK TO REMOVE)</strong></small>
<div id="selected_ip_list">
</div>
</div>
<div class="modal-body" style="max-height: 400px; overflow-y: scroll;">
<div class="list-group"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="confirm_ip">Confirm</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="qrcode_modal" data-backdrop="static" data-keyboard="false" tabindex="-1" <div class="modal fade" id="qrcode_modal" data-backdrop="static" data-keyboard="false" tabindex="-1"
aria-labelledby="staticBackdropLabel" aria-hidden="true"> aria-labelledby="staticBackdropLabel" aria-hidden="true">
@ -308,7 +342,7 @@
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<img src="" id="qrcode_img" style="width: 100%"> <img id="qrcode_img" style="width: 100%">
</div> </div>
</div> </div>
</div> </div>
@ -330,6 +364,7 @@
</body> </body>
{% include "footer.html" %} {% include "footer.html" %}
<script src="{{ url_for('static',filename='js/configuration.min.js') }}"></script> <script src="{{ url_for('static',filename='js/configuration.min.js') }}"></script>
<script> <script>
let load_timeout; let load_timeout;
let load_interval = 0; let load_interval = 0;
@ -400,12 +435,12 @@
let peer_allowed_ip = '<div class="col-sm"><small class="text-muted"><strong>ALLOWED IP</strong></small><h6 style="text-transform: uppercase;">'+peer["allowed_ip"]+'</h6></div>'; let peer_allowed_ip = '<div class="col-sm"><small class="text-muted"><strong>ALLOWED IP</strong></small><h6 style="text-transform: uppercase;">'+peer["allowed_ip"]+'</h6></div>';
let peer_latest_handshake = '<div class="col-sm"> <small class="text-muted"><strong>LATEST HANDSHAKE</strong></small> <h6 style="text-transform: uppercase;">'+peer['latest_handshake']+'</h6> </div>'; let peer_latest_handshake = '<div class="col-sm"> <small class="text-muted"><strong>LATEST HANDSHAKE</strong></small> <h6 style="text-transform: uppercase;">'+peer['latest_handshake']+'</h6> </div>';
let peer_endpoint = '<div class="col-sm"><small class="text-muted"><strong>END POINT</strong></small><h6 style="text-transform: uppercase;">'+peer["endpoint"]+'</h6></div>'; let peer_endpoint = '<div class="col-sm"><small class="text-muted"><strong>END POINT</strong></small><h6 style="text-transform: uppercase;">'+peer["endpoint"]+'</h6></div>';
let peer_control = '<div class="col-sm"><hr><div class="button-group" style="display:flex"> <button type="button" class="btn btn-outline-primary btn-setting-peer btn-control" id="'+peer["id"]+'" data-toggle="modal"><i class="bi bi-gear-fill"></i></button> <button type="button" class="btn btn-outline-danger btn-delete-peer btn-control" id="'+peer["id"]+'" data-toggle="modal"><i class="bi bi-x-circle-fill"></i></button>'; let peer_control = '<div class="col-sm"><hr><div class="button-group" style="display:flex"><button type="button" class="btn btn-outline-primary btn-setting-peer btn-control" id="'+peer["id"]+'" data-toggle="modal"><i class="bi bi-gear-fill" data-toggle="tooltip" data-placement="bottom" title="Peer Settings"></i></button> <button type="button" class="btn btn-outline-danger btn-delete-peer btn-control" id="'+peer["id"]+'" data-toggle="modal"><i class="bi bi-x-circle-fill" data-toggle="tooltip" data-placement="bottom" title="Delete Peer"></i></button>';
if (peer["private_key"] !== ""){ if (peer["private_key"] !== ""){
peer_control += '<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/'+response['name']+'?id='+encodeURIComponent(peer["id"])+'"><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><a href="/download/'+response["name"]+'?id='+encodeURIComponent(peer["id"])+'" class="btn btn-outline-info btn-download-peer btn-control"><i class="bi bi-download"></i></a></div>'; peer_control += '<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/'+response['name']+'?id='+encodeURIComponent(peer["id"])+'"><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><a href="/download/'+response["name"]+'?id='+encodeURIComponent(peer["id"])+'" class="btn btn-outline-info btn-download-peer btn-control"><i class="bi bi-download"></i></a></div>';
} }
peer_control += '</div>'; peer_control += '</div>';
let html = '<div class="'+display_mode+'">' + let html = '<div class="'+display_mode+'" data-id="'+peer["id"]+'">' +
'<div class="card mb-3 card-'+peer["status"]+'">' + '<div class="card mb-3 card-'+peer["status"]+'">' +
'<div class="card-body">' + '<div class="card-body">' +
'<div class="row">' '<div class="row">'
@ -436,6 +471,7 @@
{#$("#config_body").html(response);#} {#$("#config_body").html(response);#}
$(".dot.dot-running").attr("title","Peer Running").tooltip(); $(".dot.dot-running").attr("title","Peer Running").tooltip();
$(".dot.dot-stopped").attr("title","Peer Stopped").tooltip(); $(".dot.dot-stopped").attr("title","Peer Stopped").tooltip();
$("i[data-toggle='tooltip']").tooltip();
{#$("#search_peer_textbox").css("display", "block")#} {#$("#search_peer_textbox").css("display", "block")#}
endProgressBar() endProgressBar()
} }

View File

@ -78,3 +78,4 @@ def check_DNS(dns):
def check_remote_endpoint(address): 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]", 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)) address))