1
0
mirror of https://github.com/donaldzou/WGDashboard.git synced 2024-11-17 13:00:12 +01:00

Feature Added

Moved key generating to front-end, by using wireguard.js from WireGuard's official repository. Added "Add Peers by bulk" feature.
This commit is contained in:
Donald Cheng Hong Zou 2022-01-06 15:17:43 -05:00
parent 77a82cb84b
commit 584118805a
10 changed files with 469 additions and 127 deletions

View File

@ -464,13 +464,41 @@ def check_repeat_allowed_ip(public_key, ip, config_name):
return {'status': 'failed', 'msg': 'Peer does not exist'} return {'status': 'failed', 'msg': 'Peer does not exist'}
else: else:
existed_ip = g.cur.execute("SELECT COUNT(*) FROM " + existed_ip = g.cur.execute("SELECT COUNT(*) FROM " +
config_name + " WHERE id != ? AND allowed_ip = ?", (public_key, ip)).fetchone() config_name + " WHERE id != ? AND allowed_ip LIKE '" + ip + "/%'", (public_key,))\
.fetchone()
if existed_ip[0] != 0: if existed_ip[0] != 0:
return {'status': 'failed', 'msg': "Allowed IP already taken by another peer."} return {'status': 'failed', 'msg': "Allowed IP already taken by another peer."}
else: else:
return {'status': 'success'} return {'status': 'success'}
def f_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 available
else:
return []
""" """
Flask Functions Flask Functions
""" """
@ -830,8 +858,6 @@ def get_conf(config_name):
conf_data['checked'] = "checked" conf_data['checked'] = "checked"
config.clear() config.clear()
return jsonify(conf_data) return jsonify(conf_data)
# return render_template('get_conf.html', conf_data=conf_data, wg_ip=config.get("Peers","remote_endpoint"), sort_tag=sort,
# dashboard_refresh_interval=int(config.get("Server", "dashboard_refresh_interval")), peer_display_mode=peer_display_mode)
# Turn on / off a configuration # Turn on / off a configuration
@ -856,6 +882,64 @@ def switch(config_name):
return redirect(request.referrer) return redirect(request.referrer)
@app.route('/add_peer_bulk/<config_name>', methods=['POST'])
def add_peer_bulk(config_name):
data = request.get_json()
keys = data['keys']
endpoint_allowed_ip = data['endpoint_allowed_ip']
dns_addresses = data['DNS']
enable_preshared_key = data["enable_preshared_key"]
amount = data['amount']
if not amount.isdigit() or int(amount) < 1:
return "Amount must be integer larger than 0"
amount = int(amount)
if not check_DNS(dns_addresses):
return "DNS formate is incorrect. Example: 1.1.1.1"
if not check_Allowed_IPs(endpoint_allowed_ip):
return "Endpoint Allowed IPs format is incorrect."
if len(data['MTU']) == 0 or not data['MTU'].isdigit():
return "MTU format is not correct."
if len(data['keep_alive']) == 0 or not data['keep_alive'].isdigit():
return "Persistent Keepalive format is not correct."
ips = f_available_ips(config_name)
wg_command = ["wg", "set", config_name]
sql_command = []
for i in range(amount):
keys[i]['name'] = f"{config_name}_{datetime.now().strftime('%m%d%Y%H%M%S')}_Peer_#_{(i + 1)}"
wg_command.append("peer")
wg_command.append(keys[i]['publicKey'])
keys[i]['allowed_ips'] = ips.pop(0)
if enable_preshared_key:
keys[i]['psk_file'] = f"{keys[i]['name']}.txt"
f = open(keys[i]['psk_file'], "w+")
f.write(keys[i]['presharedKey'])
f.close()
wg_command.append("preshared-key")
wg_command.append(keys[i]['psk_file'])
else:
keys[i]['psk_file'] = ""
wg_command.append("allowed-ips")
wg_command.append(keys[i]['allowed_ips'])
update = ["UPDATE ", config_name, " SET name = '", keys[i]['name'],
"', private_key = '", keys[i]['privateKey'], "', DNS = '", dns_addresses,
"', endpoint_allowed_ip = '", endpoint_allowed_ip, "' WHERE id = '", keys[i]['publicKey'], "'"]
sql_command.append(update)
try:
status = subprocess.check_output(" ".join(wg_command), 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)
if enable_preshared_key:
for i in keys:
os.remove(i['psk_file'])
for i in range(len(sql_command)):
sql_command[i] = "".join(sql_command[i])
g.cur.executescript("; ".join(sql_command))
return "true"
except subprocess.CalledProcessError as exc:
return exc.output.strip()
# Add peer # Add peer
@app.route('/add_peer/<config_name>', methods=['POST']) @app.route('/add_peer/<config_name>', methods=['POST'])
def add_peer(config_name): def add_peer(config_name):
@ -865,6 +949,7 @@ def add_peer(config_name):
endpoint_allowed_ip = data['endpoint_allowed_ip'] endpoint_allowed_ip = data['endpoint_allowed_ip']
dns_addresses = data['DNS'] dns_addresses = data['DNS']
enable_preshared_key = data["enable_preshared_key"] enable_preshared_key = data["enable_preshared_key"]
preshared_key = data['preshared_key']
keys = get_conf_peer_key(config_name) keys = get_conf_peer_key(config_name)
if len(public_key) == 0 or len(dns_addresses) == 0 or len(allowed_ips) == 0 or len(endpoint_allowed_ip) == 0: if len(public_key) == 0 or len(dns_addresses) == 0 or len(allowed_ips) == 0 or len(endpoint_allowed_ip) == 0:
return "Please fill in all required box." return "Please fill in all required box."
@ -873,7 +958,7 @@ def add_peer(config_name):
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( check_dup_ip = g.cur.execute(
"SELECT COUNT(*) FROM " + config_name + " WHERE allowed_ip LIKE '" + allowed_ips + "%'", ) \ "SELECT COUNT(*) FROM " + config_name + " WHERE allowed_ip LIKE '" + allowed_ips + "/%'", ) \
.fetchone() .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."
@ -887,11 +972,16 @@ def add_peer(config_name):
return "Persistent Keepalive format is not correct." return "Persistent Keepalive format is not correct."
try: try:
if enable_preshared_key: if enable_preshared_key:
key = subprocess.check_output("wg genpsk > tmp_psk.txt", shell=True) now = str(datetime.now().strftime("%m%d%Y%H%M%S"))
f_name = now + "_tmp_psk.txt"
print(f_name)
f = open(f_name, "w+")
f.write(preshared_key)
f.close()
status = subprocess.check_output( status = subprocess.check_output(
f"wg set {config_name} peer {public_key} allowed-ips {allowed_ips} preshared-key tmp_psk.txt", f"wg set {config_name} peer {public_key} allowed-ips {allowed_ips} preshared-key {f_name}",
shell=True, stderr=subprocess.STDOUT) shell=True, stderr=subprocess.STDOUT)
os.remove("tmp_psk.txt") os.remove(f_name)
elif not enable_preshared_key: elif not enable_preshared_key:
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)
@ -924,6 +1014,7 @@ def remove_peer(config_name):
shell=True, stderr=subprocess.STDOUT) shell=True, stderr=subprocess.STDOUT)
sql = "DELETE FROM " + config_name + " WHERE id = ?" sql = "DELETE FROM " + config_name + " WHERE id = ?"
g.cur.execute(sql, (delete_key,)) g.cur.execute(sql, (delete_key,))
g.db.commit()
return "true" return "true"
except subprocess.CalledProcessError as exc: except subprocess.CalledProcessError as exc:
return exc.output.strip() return exc.output.strip()
@ -1000,30 +1091,7 @@ def get_peer_name(config_name):
# Return available IPs # Return available IPs
@app.route('/available_ips/<config_name>', methods=['GET']) @app.route('/available_ips/<config_name>', methods=['GET'])
def available_ips(config_name): def available_ips(config_name):
config_interface = read_conf_file_interface(config_name) return jsonify(f_available_ips(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

BIN
src/static/.DS_Store vendored

Binary file not shown.

View File

@ -90,6 +90,14 @@ body {
right: 1rem; right: 1rem;
} }
.form-control{
transition: all 0.2s ease-in-out;
}
.form-control:disabled{
cursor: not-allowed;
}
.navbar .form-control { .navbar .form-control {
padding: .75rem 1rem; padding: .75rem 1rem;
border-width: 0; border-width: 0;

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}#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)} @-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}.form-control{transition:all .2s ease-in-out}.form-control:disabled{cursor:not-allowed}.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,4 +1,5 @@
$("[data-toggle='tooltip']").tooltip() $("[data-toggle='tooltip']").tooltip()
$("[data-toggle='popover']").popover()
let $body = $("body"); let $body = $("body");
let $progress_bar = $(".progress-bar"); let $progress_bar = $(".progress-bar");
let available_ips = []; let available_ips = [];
@ -74,44 +75,21 @@ $body.on("click", ".switch", function (){
* Generate Private and Public key for a new peer * Generate Private and Public key for a new peer
*/ */
function generate_key(){ function generate_key(){
$.ajax({ let keys = wireguard.generateKeypair();
"url": "/generate_peer", $("#private_key").val(keys.privateKey);
"method": "GET", $("#public_key").val(keys.publicKey);
}).done(function(res){
$("#private_key").val(res.private_key);
$("#public_key").val(res.public_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");
}); $("#enable_preshare_key").val(keys.presharedKey);
}
/**
* Generate public for existing peer
*/
function generate_public_key(){
$.ajax({
"url": "/generate_public_key",
"method": "POST",
"headers":{"Content-Type": "application/json"},
"data": JSON.stringify({"private_key": $("#private_key").val()})
}).done(function(res){
if(res.status === "failed"){
$("#add_peer_alert").html(res.msg).removeClass("d-none");
}else{
$("#add_peer_alert").addClass("d-none");
}
$("#public_key").val(res.data);
$("#re_generate_key i").removeClass("rotating");
});
} }
/** /**
* Generate Public key when private got change * 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 === 44){
$("#re_generate_key i").addClass("rotating"); $("#re_generate_key i").addClass("rotating");
generate_public_key(); $("#public_key").val(wireguard.generatePublicKey($("#private_key").val()));
}else{ }else{
$("#public_key").removeAttr("disabled").val(""); $("#public_key").removeAttr("disabled").val("");
} }
@ -241,7 +219,73 @@ function clean_ip(val){
return clean_ip.filter(Boolean).join(","); return clean_ip.filter(Boolean).join(",");
} }
let bulk_add_peers = () => {
$save_peer.attr("disabled","disabled");
$save_peer.html("Adding...");
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");
$new_add_endpoint_allowed_ip.val(clean_ip($new_add_endpoint_allowed_ip.val()));
let $new_add_MTU = $("#new_add_MTU");
let $new_add_keep_alive = $("#new_add_keep_alive");
let $enable_preshare_key = $("#enable_preshare_key");
let $new_add_amount = $("#new_add_amount");
let data_list = [$new_add_DNS, $new_add_endpoint_allowed_ip,$new_add_MTU, $new_add_keep_alive];
if ($new_add_amount.val() > 0){
if ($new_add_DNS.val() !== "" && $new_add_endpoint_allowed_ip.val() !== ""){
let conf = $save_peer.attr('conf_id');
let keys = [];
for (let i = 0; i < $new_add_amount.val(); i++) keys.push(wireguard.generateKeypair());
$.ajax({
method: "POST",
url: "/add_peer_bulk/"+conf,
headers:{
"Content-Type": "application/json"
},
data: JSON.stringify({
"DNS": $new_add_DNS.val(),
"endpoint_allowed_ip": $new_add_endpoint_allowed_ip.val(),
"MTU": $new_add_MTU.val(),
"keep_alive": $new_add_keep_alive.val(),
"enable_preshared_key": $enable_preshare_key.prop("checked"),
"keys": keys,
"amount": $new_add_amount.val()
}),
success: function (response){
if(response !== "true"){
$("#add_peer_alert").html(response).removeClass("d-none");
data_list.forEach((ele) => ele.removeAttr("disabled"));
$save_peer.removeAttr("disabled").html("Save");
}
else{
load_data("");
data_list.forEach((ele) => ele.removeAttr("disabled"));
$("#add_peer_form").trigger("reset");
$save_peer.removeAttr("disabled").html("Save");
showToast($new_add_amount.val()+"peers added successful!");
addModal.toggle();
}
}
})
}else{
$("#add_peer_alert").html("Please fill in all required box.").removeClass("d-none");
$save_peer.removeAttr("disabled");
$save_peer.html("Add");
}
}else{
$("#add_peer_alert").html("Please enter 1 or more amount.").removeClass("d-none");
$save_peer.removeAttr("disabled");
$save_peer.html("Add");
}
}
$save_peer.on("click",function(){ $save_peer.on("click",function(){
let $bulk_add = $("#bulk_add");
if ($bulk_add.prop("checked")){
bulk_add_peers()
}else {
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");
@ -254,9 +298,8 @@ $save_peer.on("click",function(){
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("Adding...");
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() !== ""){
let conf = $(this).attr('conf_id'); let conf = $(this).attr('conf_id');
let data_list = [$private_key, $allowed_ips, $new_add_name, $new_add_DNS, $new_add_endpoint_allowed_ip,$new_add_MTU, $new_add_keep_alive]; let data_list = [$private_key, $allowed_ips, $new_add_name, $new_add_DNS, $new_add_endpoint_allowed_ip,$new_add_MTU, $new_add_keep_alive];
@ -277,6 +320,7 @@ $save_peer.on("click",function(){
"MTU": $new_add_MTU.val(), "MTU": $new_add_MTU.val(),
"keep_alive": $new_add_keep_alive.val(), "keep_alive": $new_add_keep_alive.val(),
"enable_preshared_key": $enable_preshare_key.prop("checked"), "enable_preshared_key": $enable_preshare_key.prop("checked"),
"preshared_key": $enable_preshare_key.val()
}), }),
success: function (response){ success: function (response){
if(response !== "true"){ if(response !== "true"){
@ -297,7 +341,8 @@ $save_peer.on("click",function(){
}else{ }else{
$("#add_peer_alert").html("Please fill in all required box.").removeClass("d-none"); $("#add_peer_alert").html("Please fill in all required box.").removeClass("d-none");
$(this).removeAttr("disabled"); $(this).removeAttr("disabled");
$(this).html("Save"); $(this).html("Add");
}
} }
}); });
@ -592,3 +637,22 @@ $body.on("click", ".display_mode", function(){
} }
}); });
}); });
$("#bulk_add").on("change", function (){
let hide = $(".non-bulk").find("input");
let amount = $("#new_add_amount");
if ($(this).prop("checked") === true){
for(let i = 0; i < hide.length; i++){
$(hide[i]).attr("disabled", "disabled");
}
amount.removeAttr("disabled");
}
else{
for(let i = 0; i < hide.length; i++){
if ($(hide[i]).attr('id') !== "public_key"){
$(hide[i]).removeAttr("disabled");
}
}
amount.attr("disabled", "disabled");
}
})

File diff suppressed because one or more lines are too long

189
src/static/js/wireguard.js Normal file
View File

@ -0,0 +1,189 @@
/*! SPDX-License-Identifier: GPL-2.0
*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
(function() {
function gf(init) {
var r = new Float64Array(16);
if (init) {
for (var i = 0; i < init.length; ++i)
r[i] = init[i];
}
return r;
}
function pack(o, n) {
var b, m = gf(), t = gf();
for (var i = 0; i < 16; ++i)
t[i] = n[i];
carry(t);
carry(t);
carry(t);
for (var j = 0; j < 2; ++j) {
m[0] = t[0] - 0xffed;
for (var i = 1; i < 15; ++i) {
m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
m[i - 1] &= 0xffff;
}
m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
b = (m[15] >> 16) & 1;
m[14] &= 0xffff;
cswap(t, m, 1 - b);
}
for (var i = 0; i < 16; ++i) {
o[2 * i] = t[i] & 0xff;
o[2 * i + 1] = t[i] >> 8;
}
}
function carry(o) {
var c;
for (var i = 0; i < 16; ++i) {
o[(i + 1) % 16] += (i < 15 ? 1 : 38) * Math.floor(o[i] / 65536);
o[i] &= 0xffff;
}
}
function cswap(p, q, b) {
var t, c = ~(b - 1);
for (var i = 0; i < 16; ++i) {
t = c & (p[i] ^ q[i]);
p[i] ^= t;
q[i] ^= t;
}
}
function add(o, a, b) {
for (var i = 0; i < 16; ++i)
o[i] = (a[i] + b[i]) | 0;
}
function subtract(o, a, b) {
for (var i = 0; i < 16; ++i)
o[i] = (a[i] - b[i]) | 0;
}
function multmod(o, a, b) {
var t = new Float64Array(31);
for (var i = 0; i < 16; ++i) {
for (var j = 0; j < 16; ++j)
t[i + j] += a[i] * b[j];
}
for (var i = 0; i < 15; ++i)
t[i] += 38 * t[i + 16];
for (var i = 0; i < 16; ++i)
o[i] = t[i];
carry(o);
carry(o);
}
function invert(o, i) {
var c = gf();
for (var a = 0; a < 16; ++a)
c[a] = i[a];
for (var a = 253; a >= 0; --a) {
multmod(c, c, c);
if (a !== 2 && a !== 4)
multmod(c, c, i);
}
for (var a = 0; a < 16; ++a)
o[a] = c[a];
}
function clamp(z) {
z[31] = (z[31] & 127) | 64;
z[0] &= 248;
}
function generatePublicKey(privateKey) {
var r, z = new Uint8Array(32);
var a = gf([1]),
b = gf([9]),
c = gf(),
d = gf([1]),
e = gf(),
f = gf(),
_121665 = gf([0xdb41, 1]),
_9 = gf([9]);
for (var i = 0; i < 32; ++i)
z[i] = privateKey[i];
clamp(z);
for (var i = 254; i >= 0; --i) {
r = (z[i >>> 3] >>> (i & 7)) & 1;
cswap(a, b, r);
cswap(c, d, r);
add(e, a, c);
subtract(a, a, c);
add(c, b, d);
subtract(b, b, d);
multmod(d, e, e);
multmod(f, a, a);
multmod(a, c, a);
multmod(c, b, e);
add(e, a, c);
subtract(a, a, c);
multmod(b, a, a);
subtract(c, d, f);
multmod(a, c, _121665);
add(a, a, d);
multmod(c, c, a);
multmod(a, d, f);
multmod(d, b, _9);
multmod(b, e, e);
cswap(a, b, r);
cswap(c, d, r);
}
invert(c, c);
multmod(a, a, c);
pack(z, a);
return z;
}
function generatePresharedKey() {
var privateKey = new Uint8Array(32);
window.crypto.getRandomValues(privateKey);
return privateKey;
}
function generatePrivateKey() {
var privateKey = generatePresharedKey();
clamp(privateKey);
return privateKey;
}
function encodeBase64(dest, src) {
var input = Uint8Array.from([(src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63]);
for (var i = 0; i < 4; ++i)
dest[i] = input[i] + 65 +
(((25 - input[i]) >> 8) & 6) -
(((51 - input[i]) >> 8) & 75) -
(((61 - input[i]) >> 8) & 15) +
(((62 - input[i]) >> 8) & 3);
}
function keyToBase64(key) {
var i, base64 = new Uint8Array(44);
for (i = 0; i < 32 / 3; ++i)
encodeBase64(base64.subarray(i * 4), key.subarray(i * 3));
encodeBase64(base64.subarray(i * 4), Uint8Array.from([key[i * 3 + 0], key[i * 3 + 1], 0]));
base64[43] = 61;
return String.fromCharCode.apply(null, base64);
}
window.wireguard = {
generateKeypair: function() {
var privateKey = generatePrivateKey();
var publicKey = generatePublicKey(privateKey);
var presharedKey = generatePresharedKey();
return {
publicKey: keyToBase64(publicKey),
privateKey: keyToBase64(privateKey),
presharedKey: keyToBase64(presharedKey)
};
},
generatePublicKey: function (privateKey){
return keyToBase64(generatePublicKey(privateKey))
}
};
})();

1
src/static/js/wireguard.min.js vendored Normal file
View File

@ -0,0 +1 @@
(function(){function gf(init){var r=new Float64Array(16);if(init){for(var i=0;i<init.length;++i)r[i]=init[i]}return r}function pack(o,n){var b,m=gf(),t=gf();for(var i=0;i<16;++i)t[i]=n[i];carry(t);carry(t);carry(t);for(var j=0;j<2;++j){m[0]=t[0]-65517;for(var i=1;i<15;++i){m[i]=t[i]-65535-(m[i-1]>>16&1);m[i-1]&=65535}m[15]=t[15]-32767-(m[14]>>16&1);b=m[15]>>16&1;m[14]&=65535;cswap(t,m,1-b)}for(var i=0;i<16;++i){o[2*i]=t[i]&255;o[2*i+1]=t[i]>>8}}function carry(o){var c;for(var i=0;i<16;++i){o[(i+1)%16]+=(i<15?1:38)*Math.floor(o[i]/65536);o[i]&=65535}}function cswap(p,q,b){var t,c=~(b-1);for(var i=0;i<16;++i){t=c&(p[i]^q[i]);p[i]^=t;q[i]^=t}}function add(o,a,b){for(var i=0;i<16;++i)o[i]=a[i]+b[i]|0}function subtract(o,a,b){for(var i=0;i<16;++i)o[i]=a[i]-b[i]|0}function multmod(o,a,b){var t=new Float64Array(31);for(var i=0;i<16;++i){for(var j=0;j<16;++j)t[i+j]+=a[i]*b[j]}for(var i=0;i<15;++i)t[i]+=38*t[i+16];for(var i=0;i<16;++i)o[i]=t[i];carry(o);carry(o)}function invert(o,i){var c=gf();for(var a=0;a<16;++a)c[a]=i[a];for(var a=253;a>=0;--a){multmod(c,c,c);if(a!==2&&a!==4)multmod(c,c,i)}for(var a=0;a<16;++a)o[a]=c[a]}function clamp(z){z[31]=z[31]&127|64;z[0]&=248}function generatePublicKey(privateKey){var r,z=new Uint8Array(32);var a=gf([1]),b=gf([9]),c=gf(),d=gf([1]),e=gf(),f=gf(),_121665=gf([56129,1]),_9=gf([9]);for(var i=0;i<32;++i)z[i]=privateKey[i];clamp(z);for(var i=254;i>=0;--i){r=z[i>>>3]>>>(i&7)&1;cswap(a,b,r);cswap(c,d,r);add(e,a,c);subtract(a,a,c);add(c,b,d);subtract(b,b,d);multmod(d,e,e);multmod(f,a,a);multmod(a,c,a);multmod(c,b,e);add(e,a,c);subtract(a,a,c);multmod(b,a,a);subtract(c,d,f);multmod(a,c,_121665);add(a,a,d);multmod(c,c,a);multmod(a,d,f);multmod(d,b,_9);multmod(b,e,e);cswap(a,b,r);cswap(c,d,r)}invert(c,c);multmod(a,a,c);pack(z,a);return z}function generatePresharedKey(){var privateKey=new Uint8Array(32);window.crypto.getRandomValues(privateKey);return privateKey}function generatePrivateKey(){var privateKey=generatePresharedKey();clamp(privateKey);return privateKey}function encodeBase64(dest,src){var input=Uint8Array.from([src[0]>>2&63,(src[0]<<4|src[1]>>4)&63,(src[1]<<2|src[2]>>6)&63,src[2]&63]);for(var i=0;i<4;++i)dest[i]=input[i]+65+(25-input[i]>>8&6)-(51-input[i]>>8&75)-(61-input[i]>>8&15)+(62-input[i]>>8&3)}function keyToBase64(key){var i,base64=new Uint8Array(44);for(i=0;i<32/3;++i)encodeBase64(base64.subarray(i*4),key.subarray(i*3));encodeBase64(base64.subarray(i*4),Uint8Array.from([key[i*3+0],key[i*3+1],0]));base64[43]=61;return String.fromCharCode.apply(null,base64)}window.wireguard={generateKeypair:function(){var privateKey=generatePrivateKey();var publicKey=generatePublicKey(privateKey);var presharedKey=generatePresharedKey();return{publicKey:keyToBase64(publicKey),privateKey:keyToBase64(privateKey),presharedKey:keyToBase64(presharedKey)}},generatePublicKey:function(privateKey){return keyToBase64(generatePublicKey(privateKey))}}})();

View File

@ -100,9 +100,7 @@
</div> </div>
</div> </div>
</div> </div>
<button type="button" class="btn btn-primary add_btn"> <button type="button" class="btn btn-primary add_btn"><i class="bi bi-plus-circle-fill" style=""></i> Add Peer</button>
<i class="bi bi-plus-circle-fill" style=""></i> Add Peer
</button>
</div> </div>
</div> </div>
<div class="row peer_list"></div> <div class="row peer_list"></div>
@ -121,16 +119,29 @@
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="row">
<div class="col-sm" style="display: flex; align-items: center;">
<div class="custom-control custom-switch">
<input class="custom-control-input" type="checkbox" id="bulk_add">
<label class="custom-control-label" for="bulk_add"><strong>Add Peers by bulk</strong></label>
<i class="bi bi-question-circle-fill" style="cursor: pointer" data-container="body" data-toggle="popover" data-placement="right" data-trigger="click" data-content="By adding peers by bulk, each peer's name will be auto generated, and Allowed IP will be assign to the next available IP."></i>
</div>
</div>
<div class="col-sm">
<div class="form-group" style="margin: 0">
{# <label for="new_add_amount">Amount</label>#}
<input type="number" class="form-control" id="new_add_amount" min="1" placeholder="Amount" disabled>
</div>
</div>
</div>
<hr>
<div id="add_peer_alert" class="alert alert-danger alert-dismissible fade show d-none" role="alert"> <div id="add_peer_alert" class="alert alert-danger alert-dismissible fade show d-none" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> <button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<form id="add_peer_form"> <form id="add_peer_form">
<div class="alert alert-warning" role="alert" style="font-size: 0.8rem"> <div class="form-group non-bulk">
To generate QR code for this new peer, you need to provide the private key, or use the generated key. If you don't need the QR code, simply remove the private key and insert your existed public key.
</div>
<div class="form-group">
<div> <div>
<label for="private_key">Private Key</label> <label for="private_key">Private Key</label>
</div> </div>
@ -141,18 +152,18 @@
</div> </div>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group non-bulk">
<label for="public_key">Public Key <code>(Required)</code></label> <label for="public_key">Public Key <code>(Required)</code></label>
<input type="text" class="form-control" id="public_key" aria-describedby="public_key" disabled> <input type="text" class="form-control" id="public_key" aria-describedby="public_key" disabled>
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-6"> <div class="col-sm-6 non-bulk">
<div class="form-group"> <div class="form-group">
<label for="new_add_name">Name</label> <label for="new_add_name">Name</label>
<input type="text" class="form-control" id="new_add_name"> <input type="text" class="form-control" id="new_add_name">
</div> </div>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6 non-bulk">
<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>
<div class="input-group"> <div class="input-group">
@ -201,7 +212,7 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="save_peer" conf_id={{conf_data['name']}}>Save</button> <button type="button" class="btn btn-primary" id="save_peer" conf_id={{conf_data['name']}}>Add</button>
</div> </div>
</div> </div>
</div> </div>
@ -363,6 +374,7 @@
{% include "tools.html" %} {% include "tools.html" %}
</body> </body>
{% include "footer.html" %} {% include "footer.html" %}
<script src="{{ url_for('static',filename='js/wireguard.min.js') }}"></script>
<script src="{{ url_for('static',filename='js/configuration.min.js') }}"></script> <script src="{{ url_for('static',filename='js/configuration.min.js') }}"></script>
<script> <script>

View File

@ -99,8 +99,8 @@ 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
# git pull https://github.com/donaldzou/wireguard-dashboard.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"