1
0
mirror of https://github.com/donaldzou/WGDashboard.git synced 2024-06-28 21:54:34 +02:00

Getting ready for v3.0

This commit is contained in:
Donald Cheng Hong Zou 2022-01-08 15:26:17 -05:00
parent 584118805a
commit 6d56967a0f
7 changed files with 335 additions and 69 deletions

View File

@ -1,19 +1,9 @@
<hr>
> Hi! I'm planning the next major update for this project, please let me know if you have any suggestions or feature requests ;) You can create an issue with the "Feature request" template. Cheers!
### Help Wanted
> If anyone know a better way to distribute releases of python application other than GitHub, please let me know in <a href="https://github.com/donaldzou/wireguard-dashboard/issues/103">#103</a>!
> Please provide your OS name and version if you can run the dashboard on it perfectly in <a href="https://github.com/donaldzou/wireguard-dashboard/issues/31">#31</a>, since I only tested on Ubuntu. Thank you!
<hr>
<p align="center">
<img alt="WGDashboard" src="img/logo.png" width="128">
</p>
<h1 align="center">WGDashboard</h1>
<p align="center">
<img src="http://ForTheBadge.com/images/badges/made-with-python.svg">
</p>
@ -24,19 +14,25 @@
<p align="center">Monitoring WireGuard is not convinient, need to login into server and type <code>wg show</code>. That's why this platform is being created, to view all configurations and manage them in a easier way.</p>
<p align="center"><small>Note: This project is not affiliate to the official WireGuard Project ;)</small></p>
## 📣 What's New: v2.3
## 📣 What's New: v3.0
- 🎉 **New Features**
- **Update directly from `wgd.sh`:** Now you can update WGDashboard directly from the bash script.
- **Displaying Peers:** You can switch the display mode between list and table in the configuration page.
- **Add Peers by Bulk: ** Now you can add peers by bulk, just simply set the amount and click add.
- **Delete Peers by Bulk**: User can click the menu button (three-dots) on the bottom right of each configuration, and click **Delete Peers**, then select peers.
- **Added Pre-shared Key to peers:** Now each peer can add with a pre-shared key to enhance security. Previously added peers can add the pre-shared key through the peer setting button.
- 🪚 **Bug Fixed**
- [Peer DNS Validation Fails #67](issues/67): Added DNS format check. [❤️ @realfian]
- [configparser.NoSectionError: No section: 'Interface' #66](issues/66): Changed permission requirement for `etc/wireguard` from `744` to `755`. [❤️ @ramalmaty]
- [Feature request: Interface not loading when information missing #73](issues/73): Fixed when Configuration Address and Listen Port is missing will crash the dashboard. [❤️ @js32]
- [Remote Peer, MTU and PersistentKeepalives added #70](pull/70): Added MTU, remote peer and Persistent Keepalive. [❤️ @realfian]
- [Fixes DNS check to support search domain #65](pull/65): Added allow input domain into DNS. [❤️@davejlong]
- [IP Sorting range issues #99](https://github.com/donaldzou/WGDashboard/issues/99) [❤️ @barryboom]
- [INvalid character written to tunnel json file #108](https://github.com/donaldzou/WGDashboard/issues/108) [❤️ @ ikidd]
- [Add IPv6 #91](https://github.com/donaldzou/WGDashboard/pull/91) [❤️ @ pgalonza]
- [Added MTU and PersistentKeepalive to QR code and download files #112](https://github.com/donaldzou/WGDashboard/pull/112) [:heart: @reafian]
- **🧐 Other Changes**
- Moved Add Peer Button into the right bottom corner.
- **Key generating moved to front-end**: No longer need to use the server's WireGuard to generate keys, thanks to the `wireguard.js` from the [official repository](https://git.zx2c4.com/wireguard-tools/tree/contrib/keygen-html/wireguard.js)!
- **Peer transfer calculation**: each peer will now show all transfer amount (previously was only showing transfer amount from the last configuration start-up).
- **UI adjustment on running peers**: peers will have a new style indicating that it is running.
- **Moved from TinyDB to SQLite**: This could provide better performance and loading speed when showing peers, also avoided changing the database could crash.
- UI adjustment on the whole dashboard.
- **`wgd.sh` finally can update itself**: So now user could update the whole dashboard from `wgd.sh`, with the `update` command.
@ -358,6 +354,24 @@ Endpoint = 0.0.0.0:51820
## ⏰ Changelog
#### v2.3.1 - Sep 8, 2021
- Updated dashboard's name to **WGDashboard**!!
#### v2.3 - Sep 8, 2021
- 🎉 **New Features**
- **Update directly from `wgd.sh`:** Now you can update WGDashboard directly from the bash script.
- **Displaying Peers:** You can switch the display mode between list and table in the configuration page.
- 🪚 **Bug Fixed**
- [Peer DNS Validation Fails #67](issues/67): Added DNS format check. [❤️ @realfian]
- [configparser.NoSectionError: No section: 'Interface' #66](issues/66): Changed permission requirement for `etc/wireguard` from `744` to `755`. [❤️ @ramalmaty]
- [Feature request: Interface not loading when information missing #73](issues/73): Fixed when Configuration Address and Listen Port is missing will crash the dashboard. [❤️ @js32]
- [Remote Peer, MTU and PersistentKeepalives added #70](pull/70): Added MTU, remote peer and Persistent Keepalive. [❤️ @realfian]
- [Fixes DNS check to support search domain #65](pull/65): Added allow input domain into DNS. [❤️@davejlong]
- **🧐 Other Changes**
- Moved Add Peer Button into the right bottom corner.
#### v2.2.1 - Aug 16, 2021
Bug Fixed:

View File

@ -1000,24 +1000,29 @@ def remove_peer(config_name):
if get_conf_status(config_name) == "stopped":
return "Your need to turn on " + config_name + " first."
data = request.get_json()
delete_key = data['peer_id']
delete_keys = data['peer_ids']
keys = get_conf_peer_key(config_name)
if not isinstance(keys, list):
return config_name + " is not running."
if delete_key not in keys:
return "This key does not exist"
else:
sql_command = []
wg_command = ["wg", "set", config_name]
for delete_key in delete_keys:
if delete_key not in keys:
return "This key does not exist"
sql_command.append("DELETE FROM " + config_name + " WHERE id = '"+delete_key+"';")
wg_command.append("peer")
wg_command.append(delete_key)
wg_command.append("remove")
try:
remove_wg = subprocess.check_output(f"wg set {config_name} peer {delete_key} remove",
remove_wg = subprocess.check_output(" ".join(wg_command),
shell=True, stderr=subprocess.STDOUT)
save_wg = subprocess.check_output(f"wg-quick save {config_name}",
shell=True, stderr=subprocess.STDOUT)
sql = "DELETE FROM " + config_name + " WHERE id = ?"
g.cur.execute(sql, (delete_key,))
save_wg = subprocess.check_output(f"wg-quick save {config_name}", shell=True, stderr=subprocess.STDOUT)
g.cur.executescript(' '.join(sql_command))
g.db.commit()
return "true"
except subprocess.CalledProcessError as exc:
return exc.output.strip()
return "true"
# Save peer settings

View File

@ -241,18 +241,77 @@ main{
}
.peer_list{
margin-bottom: 4rem !important;
margin-bottom: 7rem !important;
}
}
.add_btn{
.btn-manage-group{
z-index: 99;
position: fixed;
bottom: 3rem;
right: 2rem;
display: flex;
}
.btn-manage-group .setting_btn_menu{
position: absolute;
top: -124px;
background-color: white;
padding: 1rem 0;
right: 0;
box-shadow: 0 10px 20px rgb(0 0 0 / 19%), 0 6px 6px rgb(0 0 0 / 23%);
border-radius: 10px;
min-width: 200px;
display: none;
transform: translateY(-30px);
opacity: 0;
transition: all 0.3s cubic-bezier(0.58, 0.03, 0.05, 1.28);
}
.btn-manage-group .setting_btn_menu.show{
display: block;
}
.setting_btn_menu.showing{
transform: translateY(0px);
opacity: 1;
}
.setting_btn_menu a{
display: flex;
padding: 0.5rem 1rem;
transition: all 0.1s ease-in-out;
font-size: 1rem;
align-items: center;
cursor: pointer;
}
.setting_btn_menu a:hover{
background-color: #efefef;
text-decoration: none;
}
.setting_btn_menu a i{
margin-right: auto !important;
}
.add_btn{
height: 54px;
z-index: 99;
border-radius: 100px !important;
padding: 10px 20px;
padding: 0 14px;
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
margin-right: 1rem;
font-size: 1.5rem;
}
.setting_btn{
height: 54px;
z-index: 99;
border-radius: 100px !important;
padding: 0 14px;
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
font-size: 1.5rem;
}
@ -394,11 +453,22 @@ main{
width: 100%;
}
#selected_ip_list .badge{
#selected_ip_list .badge, #selected_peer_list .badge{
margin: 0.1rem
}
#add_modal.ip_modal_open{
transition: filter 0.2s ease-in-out;
filter: brightness(0.5);
}
#delete_bulk_modal .list-group a.active{
background-color: #dc3545;
border-color: #dc3545;
}
#selected_peer_list{
max-height: 80px;
overflow-y: scroll;
overflow-x: hidden;
}

File diff suppressed because one or more lines are too long

View File

@ -6,7 +6,7 @@ let available_ips = [];
let $save_peer = $("#save_peer");
$(".add_btn").on("click", function(){
addModal.toggle();
$addModal.toggle();
});
/**
@ -157,7 +157,8 @@ $body.on("click", ".available-ip-item", function () {
});
let $ipModal = new bootstrap.Modal(document.getElementById('available_ip_modal'), {
keyboard: false
keyboard: false,
backdrop: 'static'
});
$("#search_available_ip").on("click", function () {
@ -209,8 +210,9 @@ $("#re_generate_key").on("click",function (){
generate_key();
});
let addModal = new bootstrap.Modal(document.getElementById('add_modal'), {
keyboard: false
let $addModal = new bootstrap.Modal(document.getElementById('add_modal'), {
keyboard: false,
backdrop: 'static'
});
function clean_ip(val){
@ -220,8 +222,9 @@ function clean_ip(val){
}
let bulk_add_peers = () => {
let $new_add_amount = $("#new_add_amount");
$save_peer.attr("disabled","disabled");
$save_peer.html("Adding...");
$save_peer.html("Adding "+$new_add_amount.val()+" peers...");
let $new_add_DNS = $("#new_add_DNS");
$new_add_DNS.val(clean_ip($new_add_DNS.val()));
@ -230,7 +233,6 @@ let bulk_add_peers = () => {
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() !== ""){
@ -263,8 +265,8 @@ let bulk_add_peers = () => {
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();
showToast($new_add_amount.val()+" peers added successful!");
$addModal.toggle();
}
}
})
@ -334,7 +336,7 @@ $save_peer.on("click",function(){
$("#add_peer_form").trigger("reset");
$save_peer.removeAttr("disabled").html("Save");
showToast("Add peer successful!");
addModal.toggle();
$addModal.toggle();
}
}
});
@ -366,7 +368,8 @@ $body.on("click", ".btn-qrcode-peer", function (){
// Delete Peer Modal
let deleteModal = new bootstrap.Modal(document.getElementById('delete_modal'), {
keyboard: false
keyboard: false,
backdrop: 'static'
});
$body.on("click", ".btn-delete-peer", function(){
@ -380,19 +383,40 @@ $("#delete_peer").on("click",function(){
$(this).html("Deleting...");
let peer_id = $(this).attr("peer_id");
let config = $(this).attr("conf_id");
let peer_ids = [peer_id];
deletePeers(config, peer_ids);
});
function deletePeers(config, peer_ids){
$.ajax({
method: "POST",
url: "/remove_peer/"+config,
headers:{
"Content-Type": "application/json"
},
data: JSON.stringify({"action": "delete", "peer_id": peer_id}),
data: JSON.stringify({"action": "delete", "peer_ids": peer_ids}),
success: function (response){
if(response !== "true"){
$("#remove_peer_alert").html(response+$("#add_peer_alert").html()).removeClass("d-none");
if (deleteModal._isShown) {
$("#remove_peer_alert").html(response+$("#add_peer_alert").html())
.removeClass("d-none");
}
if (deleteBulkModal._isShown){
$("#bulk_remove_peer_alert").html(response+$("#bulk_remove_peer_alert").html())
.removeClass("d-none");
}
}
else{
deleteModal.toggle();
if (deleteModal._isShown) {
deleteModal.toggle()
}
if (deleteBulkModal._isShown){
$("#confirm_delete_bulk_peers").removeAttr("disabled").html("Delete");
$("#selected_peer_list").html('');
$(".delete-bulk-peer-item.active").removeClass('active');
deleteBulkModal.toggle();
}
load_data($('#search_peer_textbox').val());
$('#alertToast').toast('show');
$('#alertToast .toast-body').html("Peer deleted!");
@ -400,11 +424,12 @@ $("#delete_peer").on("click",function(){
}
}
});
});
}
// Peer Setting Modal
let settingModal = new bootstrap.Modal(document.getElementById('setting_modal'), {
keyboard: false
keyboard: false,
backdrop: 'static'
});
$body.on("click", ".btn-setting-peer", function(){
startProgressBar();
@ -638,6 +663,7 @@ $body.on("click", ".display_mode", function(){
});
});
// Toggle bulk add mode
$("#bulk_add").on("change", function (){
let hide = $(".non-bulk").find("input");
let amount = $("#new_add_amount");
@ -655,4 +681,125 @@ $("#bulk_add").on("change", function (){
}
amount.attr("disabled", "disabled");
}
})
// Configuration sub menu
let $setting_btn_menu = $(".setting_btn_menu");
$setting_btn_menu.css("top", ($setting_btn_menu.height() + 54)*(-1));
let $setting_btn = $(".setting_btn");
$setting_btn.on("click", function(){
if ($setting_btn_menu.hasClass("show")){
$setting_btn_menu.removeClass("showing");
setTimeout(function(){
$setting_btn_menu.removeClass("show");
}, 201)
}else{
$setting_btn_menu.addClass("show");
setTimeout(function(){
$setting_btn_menu.addClass("showing");
},10)
}
})
$body.on("click", function(r){
if (document.querySelector(".setting_btn") !== r.target){
if (!document.querySelector(".setting_btn").contains(r.target)){
if (!document.querySelector(".setting_btn_menu").contains(r.target)){
$setting_btn_menu.removeClass("showing");
setTimeout(function(){
$setting_btn_menu.removeClass("show");
}, 310)
}
}
}
});
// Delete peers by bulk
let deleteBulkModal = new bootstrap.Modal(document.getElementById('delete_bulk_modal'), {
keyboard: false,
backdrop: 'static'
});
$("#delete_peers_by_bulk_btn").on("click", () => {
let $delete_bulk_modal_list = $("#delete_bulk_modal .list-group");
$delete_bulk_modal_list.html('');
peers.forEach((peer) => {
let name = ""
if (peer["name"] === "") { name = "Untitled Peer"; }
else { name = peer["name"]; }
$delete_bulk_modal_list.append('<a class="list-group-item list-group-item-action delete-bulk-peer-item" style="cursor: pointer" data-id="'
+peer['id']+'" data-name="'+name+'">'+name+'<br><code>'+peer['id']+'</code></a>');
});
deleteBulkModal.toggle();
});
function toggleBulkIP(element){
let $selected_peer_list = $("#selected_peer_list");
let id = element.data("id");
let name = element.data("name") === "" ? "Untitled Peer" : element.data("name");
if (element.hasClass("active")){
element.removeClass("active");
$("#selected_peer_list .badge[data-id='"+id+"']").remove();
}else{
element.addClass("active");
$selected_peer_list.append('<span class="badge badge-danger delete-peer-bulk-badge" style="cursor: pointer; text-overflow: ellipsis; max-width: 100%; overflow-x: hidden" data-id="'+id+'">'+name+' - '+id+'</span>')
}
}
$body.on("click", ".delete-bulk-peer-item", function(){
toggleBulkIP($(this));
}).on("click", ".delete-peer-bulk-badge", function(){
toggleBulkIP($(".delete-bulk-peer-item[data-id='" + $(this).data("id") + "']"));
});
let $selected_peer_list = document.getElementById("selected_peer_list");
let changeObserver = new MutationObserver(function(mutationsList, observer){
if ($selected_peer_list.hasChildNodes()){
$("#confirm_delete_bulk_peers").removeAttr("disabled");
}else{
$("#confirm_delete_bulk_peers").attr("disabled", "disabled");
}
});
changeObserver.observe($selected_peer_list, {
attributes: true,
childList: true,
characterData: true
})
let confirm_delete_bulk_peers_interval = undefined;
$("#confirm_delete_bulk_peers").on("click", function(){
let btn = $(this);
if (confirm_delete_bulk_peers_interval !== undefined){
clearInterval(confirm_delete_bulk_peers_interval);
confirm_delete_bulk_peers_interval = undefined;
btn.html("Delete");
}else{
let timer = 5;
btn.html(`Deleting in ${timer} secs... Click to cancel`);
confirm_delete_bulk_peers_interval = setInterval(function(){
timer -= 1;
btn.html(`Deleting in ${timer} secs... Click to cancel`);
if (timer === 0){
btn.html(`Deleting...`);
btn.attr("disabled", "disabled");
let ips = [];
$selected_peer_list.childNodes.forEach((ele) => ips.push(ele.dataset.id));
deletePeers(btn.data("conf"), ips);
clearInterval(confirm_delete_bulk_peers_interval);
confirm_delete_bulk_peers_interval = undefined;
}
}, 1000)
}
});
$("#select_all_delete_bulk_peers").on("click", function(){
$(".delete-bulk-peer-item").each(function(){
if (!$(this).hasClass("active")) toggleBulkIP($(this));
});
});
$(deleteBulkModal._element).on("hidden.bs.modal", function(){
$(".delete-bulk-peer-item").each(function(){
if ($(this).hasClass("active")) toggleBulkIP($(this));
});
})

File diff suppressed because one or more lines are too long

View File

@ -100,7 +100,15 @@
</div>
</div>
</div>
<button type="button" class="btn btn-primary add_btn"><i class="bi bi-plus-circle-fill" style=""></i> Add Peer</button>
<div class="btn-manage-group">
<button type="button" class="btn btn-primary add_btn"><i class="bi bi-plus-circle-fill" style=""></i></button>
<button type="button" class="btn btn-secondary setting_btn"><i class="bi bi-three-dots"></i></button>
<div class="setting_btn_menu">
<a class="text-danger" id="delete_peers_by_bulk_btn"><i class="bi bi-trash-fill"></i> Delete Peers</a>
<a class="text-info"><i class="bi bi-cloud-download-fill"></i> Download All Peers</a>
</div>
</div>
</div>
</div>
<div class="row peer_list"></div>
@ -113,26 +121,23 @@
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Add a new peer</h5>
<h5 class="modal-title" id="staticBackdropLabel">Add New Peer</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<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="custom-control custom-switch" style="margin-bottom: 1rem">
<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 class="col-sm">
<div class="form-group" style="margin: 0">
<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>
<input type="number" class="form-control" id="new_add_amount" min="1" placeholder="Amount" disabled>
</div>
</div>
<hr>
<div id="add_peer_alert" class="alert alert-danger alert-dismissible fade show d-none" role="alert">
@ -242,7 +247,6 @@
</div>
</div>
</div>
<div class="modal fade" id="setting_modal" data-backdrop="static" data-keyboard="false" tabindex="-1"
aria-labelledby="staticBackdropLabel" aria-hidden="true" conf_id={{conf_data['name']}} peer_id="">
<div class="modal-dialog modal-dialog-centered modal-lg">
@ -316,7 +320,7 @@
</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 fade" id="available_ip_modal" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
@ -341,7 +345,34 @@
</div>
</div>
</div>
<div class="modal fade" id="delete_bulk_modal" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Select Peers to Delete</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div id="bulk_remove_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">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="selected_peers" style="padding: 1rem; border-bottom: 1px solid #dee2e6;">
<small class="text-muted"><strong>SELECTED PEERS (CLICK TO REMOVE)</strong></small>
<div id="selected_peer_list"></div>
</div>
<div class="modal-body" style="max-height: 400px; overflow-y: scroll;">
<div class="list-group"></div>
</div>
<div class="modal-footer">
<a class="text-danger" id="select_all_delete_bulk_peers" style="cursor: pointer; margin-right: auto;"><small><strong>SELECT ALL</strong></small></a>
<button type="button" class="btn btn-danger" id="confirm_delete_bulk_peers" disabled data-conf="{{conf_data['name']}}">Delete</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="qrcode_modal" data-backdrop="static" data-keyboard="false" tabindex="-1"
aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
@ -358,7 +389,6 @@
</div>
</div>
</div>
<div class="position-fixed top-0 right-0 p-3" style="z-index: 5; right: 0; top: 50px;">
<div id="alertToast" class="toast hide" role="alert" aria-live="assertive" aria-atomic="true" data-delay="5000">
<div class="toast-header">
@ -381,6 +411,7 @@
let load_timeout;
let load_interval = 0;
let conf_name = "{{ conf_data['name'] }}"
let peers = [];
$(".sb-"+conf_name+"-url").addClass("active");
function load_data(search){
startProgressBar()
@ -392,6 +423,7 @@
"Content-Type": "application/json"
},
success: function (response){
peers = response["peer_data"];
{# Check all status #}
if (response["listen_port"] === "" && response["status"] === "stopped"){
$("config_info_alert").append('<div class="alert alert-warning" role="alert">Peer QR Code and configuration file download required a specified <strong>Listen Port</strong>.</div>')
@ -480,11 +512,9 @@
},response["dashboard_refresh_interval"])
}
}
{#$("#config_body").html(response);#}
$(".dot.dot-running").attr("title","Peer Running").tooltip();
$(".dot.dot-stopped").attr("title","Peer Stopped").tooltip();
$("i[data-toggle='tooltip']").tooltip();
{#$("#search_peer_textbox").css("display", "block")#}
endProgressBar()
}
})