1
0
mirror of https://github.com/donaldzou/WGDashboard.git synced 2024-11-06 16:00:28 +01:00

Brand new switch button and toast UI

This commit is contained in:
Donald Cheng Hong Zou 2022-03-21 22:33:19 -04:00
parent 2d3dffe5fc
commit bdd984a887
10 changed files with 912 additions and 543 deletions

View File

@ -537,4 +537,3 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

2
src/api.py Normal file
View File

@ -0,0 +1,2 @@
from dashboard import request, jsonify, app

View File

@ -28,8 +28,7 @@ from icmplib import ping, traceroute
from flask_socketio import SocketIO
# Import other python files
from util import regex_match, check_DNS, check_Allowed_IPs, check_remote_endpoint, \
check_IP_with_range, clean_IP_with_range
from util import *
# Dashboard Version
DASHBOARD_VERSION = 'v3.0.5'
@ -375,6 +374,12 @@ def get_all_peers_data(config_name):
get_endpoint(config_name)
get_allowed_ip(conf_peer_data, config_name)
def getLockAccessPeers(config_name):
col = g.cur.execute(f"PRAGMA table_info({config_name}_restrict_access)").fetchall()
col = [a[1] for a in col]
data = g.cur.execute(f"SELECT * FROM {config_name}_restrict_access").fetchall()
result = [{col[i]: data[k][i] for i in range(len(col))} for k in range(len(data))]
return result
def get_peers(config_name, search, sort_t):
"""
@ -504,6 +509,18 @@ def get_conf_list():
)
"""
g.cur.execute(create_table)
create_table = f"""
CREATE TABLE IF NOT EXISTS {i}_restrict_access (
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)}
if temp['status'] == "running":
temp['checked'] = 'checked'
@ -620,7 +637,6 @@ def f_available_ips(config_name):
Flask Functions
"""
@app.teardown_request
def close_DB(exception):
"""
@ -1056,7 +1072,8 @@ def get_conf(config_name):
"wg_ip": wg_ip,
"sort_tag": sort,
"dashboard_refresh_interval": int(config.get("Server", "dashboard_refresh_interval")),
"peer_display_mode": peer_display_mode
"peer_display_mode": peer_display_mode,
"lock_access_peers": getLockAccessPeers(config_name)
}
if result['data']['status'] == "stopped":
result['data']['checked'] = "nope"
@ -1087,16 +1104,15 @@ def switch(config_name):
shell=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as exc:
session["switch_msg"] = exc.output.strip().decode("utf-8")
return redirect('/')
return jsonify({"status": False, "reason":"Can't stop peer"})
elif status == "stopped":
try:
subprocess.check_output("wg-quick up " + config_name,
shell=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as exc:
session["switch_msg"] = exc.output.strip().decode("utf-8")
return redirect('/')
return redirect(request.referrer)
return jsonify({"status": False, "reason":"Can't turn on peer"})
return jsonify({"status": True, "reason":""})
@app.route('/add_peer_bulk/<config_name>', methods=['POST'])
def add_peer_bulk(config_name):
@ -1240,24 +1256,7 @@ def remove_peer(config_name):
if not isinstance(keys, list):
return config_name + " is not running."
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(" ".join(wg_command),
shell=True, stderr=subprocess.STDOUT)
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()
except subprocess.CalledProcessError as exc:
return exc.output.strip()
return "true"
return deletePeers(config_name, delete_keys, g.cur, g.db)
@app.route('/save_peer_setting/<config_name>', methods=['POST'])
@ -1530,6 +1529,51 @@ def switch_display_mode(mode):
return "false"
# APIs
@app.route('/api/togglePeerAccess', methods=['POST'])
def togglePeerAccess():
data = request.get_json()
print(data['peerID'])
returnData = {"status": True, "reason": ""}
required = ['peerID', 'config']
if checkJSONAllParameter(required, data):
checkUnlock = g.cur.execute(f"SELECT * FROM {data['config']} WHERE id='{data['peerID']}'").fetchone()
if checkUnlock:
moveUnlockToLock = g.cur.execute(f"INSERT INTO {data['config']}_restrict_access SELECT * FROM {data['config']} WHERE id = '{data['peerID']}'")
if g.cur.rowcount == 1:
print(g.cur.rowcount)
print(deletePeers(data['config'], [data['peerID']], g.cur, g.db))
else:
moveLockToUnlock = g.cur.execute(f"SELECT * FROM {data['config']}_restrict_access WHERE id='{data['peerID']}'").fetchone()
try:
if len(moveLockToUnlock[-1]) == 0:
status = subprocess.check_output(f"wg set {data['config']} peer {moveLockToUnlock[0]} allowed-ips {moveLockToUnlock[11]}",
shell=True, stderr=subprocess.STDOUT)
else:
now = str(datetime.now().strftime("%m%d%Y%H%M%S"))
f_name = now + "_tmp_psk.txt"
f = open(f_name, "w+")
f.write(moveLockToUnlock[-1])
f.close()
subprocess.check_output(f"wg set {data['config']} peer {moveLockToUnlock[0]} allowed-ips {moveLockToUnlock[11]} preshared-key {f_name}",
shell=True, stderr=subprocess.STDOUT)
os.remove(f_name)
status = subprocess.check_output(f"wg-quick save {data['config']}", shell=True, stderr=subprocess.STDOUT)
g.cur.execute(f"INSERT INTO {data['config']} SELECT * FROM {data['config']}_restrict_access WHERE id = '{data['peerID']}'")
if g.cur.rowcount == 1:
g.cur.execute(f"DELETE FROM {data['config']}_restrict_access WHERE id = '{data['peerID']}'")
except subprocess.CalledProcessError as exc:
returnData["status"] = False
returnData["reason"] = exc.output.strip()
else:
returnData["status"] = False
returnData["reason"] = "Please provide all required parameters."
return jsonify(returnData)
"""
Dashboard Tools Related
"""
@ -1625,6 +1669,7 @@ def init_dashboard():
Create dashboard default configuration.
"""
# Set Default INI File
if not os.path.isfile(DASHBOARD_CONF):
open(DASHBOARD_CONF, "w+").close()

View File

@ -22,8 +22,10 @@ body {
top: 0;
bottom: 0;
left: 0;
z-index: 100; /* Behind the navbar */
padding: 48px 0 0; /* Height of navbar */
z-index: 100;
/* Behind the navbar */
padding: 48px 0 0;
/* Height of navbar */
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
}
@ -33,7 +35,8 @@ body {
height: calc(100vh - 48px);
padding-top: .5rem;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
overflow-y: auto;
/* Scrollable contents if viewport is shorter than content. */
}
@supports ((position: -webkit-sticky) or (position: sticky)) {
@ -73,6 +76,7 @@ body {
text-transform: uppercase;
}
/*
* Navbar
*/
@ -142,7 +146,8 @@ body {
.info h6 {
line-break: anywhere;
transition: 0.2s ease-in-out;
transition: all 0.4s cubic-bezier(0.96, -0.07, 0.34, 1.01);
opacity: 1;
}
.info .row .col-sm {
@ -152,7 +157,6 @@ body {
.info .row .col-sm small {
display: flex;
}
.info .row .col-sm small strong:last-child(1) {
@ -164,7 +168,8 @@ body {
padding: 0 1rem 0 0;
}
.btn-control:active, .btn-control:focus{
.btn-control:active,
.btn-control:focus {
background-color: transparent !important;
border: none !important;
box-shadow: none;
@ -174,12 +179,14 @@ body {
padding: 0 !important;
}
.btn-qrcode-peer:active, .btn-qrcode-peer:hover{
.btn-qrcode-peer:active,
.btn-qrcode-peer:hover {
transform: scale(0.9) rotate(180deg);
border: 0 !important;
}
.btn-download-peer:active, .btn-download-peer:hover{
.btn-download-peer:active,
.btn-download-peer:hover {
color: #17a2b8 !important;
transform: translateY(5px);
}
@ -199,7 +206,15 @@ body {
}
.btn-lock-peer:hover {
color: #6c757d;
color: #28a745;
}
.btn-lock-peer.lock{
color: #6c757d
}
.btn-lock-peer.lock:hover{
color: #6c757d
}
.btn-setting-peer:hover {
@ -223,6 +238,7 @@ body {
.switch {
font-size: 2rem;
}
.switch:hover {
text-decoration: none
}
@ -253,6 +269,9 @@ body {
.index-switch {
text-align: right;
display: flex;
align-items: center;
justify-content: flex-end;
}
main {
@ -267,7 +286,6 @@ main{
.add_btn {
bottom: 1.5rem !important;
}
.peer_list {
margin-bottom: 7rem !important;
}
@ -342,8 +360,10 @@ main{
font-size: 1.5rem;
}
@-webkit-keyframes rotating
/* Safari and Chrome */
@-webkit-keyframes rotating /* Safari and Chrome */ {
{
from {
-webkit-transform: rotate(0deg);
-o-transform: rotate(0deg);
@ -355,6 +375,7 @@ main{
transform: rotate(360deg);
}
}
@keyframes rotating {
from {
-ms-transform: rotate(0deg);
@ -388,7 +409,10 @@ main{
cursor: pointer;
}
#peer_private_key_textbox, #private_key, #public_key, #peer_preshared_key_textbox{
#peer_private_key_textbox,
#private_key,
#public_key,
#peer_preshared_key_textbox {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
@ -422,22 +446,28 @@ main{
/*padding: 0.6rem 0.9em;*/
}
#username, #password{
#username,
#password {
padding: 0.6rem calc( 0.9rem + 32px);
height: inherit;
}
label[for="username"], label[for="password"]{
label[for="username"],
label[for="password"] {
font-size: 1rem;
margin: 0 !important;
transform: translateY(30px) translateX(16px);
padding: 0;
}
/*label[for="password"]{*/
/* transform: translateY(32px) translateX(16px);*/
/*}*/
/*label[for="password"]{*/
/* transform: translateY(32px) translateX(16px);*/
/*}*/
.modal-content {
border-radius: 10px;
@ -458,6 +488,7 @@ label[for="username"], label[for="password"]{
background-color: #dfdfdf;
}
}
@-moz-keyframes loading {
0% {
background-color: #dfdfdf;
@ -480,10 +511,13 @@ label[for="username"], label[for="password"]{
}
.info_loading {
animation: loading 2s infinite ease-in-out;
border-radius: 5px;
height: 19px;
transition: 0.3s ease-in-out;
/* animation: loading 2s infinite ease-in-out;
/* border-radius: 5px; */
height: 19.19px;
/* transition: 0.3s ease-in-out; */
/* transform: translateX(40px); */
opacity: 0 !important;
}
#conf_status_btn {
@ -500,7 +534,8 @@ label[for="username"], label[for="password"]{
width: 100%;
}
#selected_ip_list .badge, #selected_peer_list .badge{
#selected_ip_list .badge,
#selected_peer_list .badge {
margin: 0.1rem
}
@ -568,14 +603,20 @@ pre.index-alert{
}
.peerName {
margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.peerLightContainer {
text-transform: uppercase; margin: 0; margin-left: auto !important;
text-transform: uppercase;
margin: 0;
margin-left: auto !important;
}
.conf_card .dot, .info .dot {
.conf_card .dot,
.info .dot {
transform: translateX(10px);
}
@ -624,3 +665,99 @@ pre.index-alert{
height: calc( 100% - 47px) !important;
max-height: calc( 100% - 47px) !important;
}
#switch{
transition: all 350ms ease-in;
}
.toggle--switch{
display: none;
}
.toggleLabel{
width: 64px;
height: 32px;
background-color: #6c757d17;
display: flex;
position: relative;
border: 2px solid #6c757d8c;
border-radius: 100px;
transition: all 350ms ease-in;
cursor: pointer;
margin: 0;
}
.toggle--switch.waiting + .toggleLabel{
opacity: 0.5;
}
.toggleLabel::before{
background-color: #6c757d;
height: 26px;
width: 26px;
content: "";
border-radius: 100px;
margin: 1px;
position: absolute;
animation-name: off;
animation-duration: 350ms;
animation-fill-mode: forwards;
transition: all 350ms ease-in;
cursor: pointer;
}
.toggle--switch:checked + .toggleLabel{
background-color: #007bff17 !important;
border: 2px solid #007bff8c;
}
.toggle--switch:checked + .toggleLabel::before{
background-color: #007bff;
animation-name: on;
animation-duration: 350ms;
animation-fill-mode: forwards;
}
@keyframes on {
0%{
left: 0px;
}
60%{
left: 0px;
width: 40px;
}
100%{
left: 32px;
width: 26px;
}
}
@keyframes off {
0%{
left: 32px;
}
60%{
left: 18px;
width: 40px;
}
100%{
left: 0px;
width: 26px;
}
}
.toast{
min-width: 300px;
background-color: rgba(255,255,255,1);
}
.toast-header{
background-color: rgba(255,255,255);
}
.toast-progressbar{
width: 100%;
height: 4px;
background-color: #007bff;
border-bottom-left-radius: .25rem;
}

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,6 @@
/**
* Definitions
*/
let peers = [];
let configuration_name;
let configuration_interval;
@ -63,8 +62,7 @@
type: 'line',
data: {
labels: [],
datasets: [
{
datasets: [{
label: 'Data Sent',
data: [],
stroke: '#FFFFFF',
@ -224,17 +222,19 @@
* @param response
*/
function configurationHeader(response) {
let $conf_status_btn = document.getElementById("conf_status_btn");
let $conf_status_btn = $(".toggle--switch");
if (response.checked === "checked") {
$conf_status_btn.innerHTML = `<a href="#" id="${response.name}" ${response.checked} class="switch text-primary"><i class="bi bi-toggle2-on"></i> ON</a>`;
$conf_status_btn.prop("checked", true)
}else{
$conf_status_btn.innerHTML = `<a href="#" id="${response.name}" ${response.checked} class="switch text-primary"><i class="bi bi-toggle2-off"></i> OFF</a>`;
$conf_status_btn.prop("checked", false)
}
$conf_status_btn.data("conf-id", configuration_name)
if (response.running_peer > 0) {
let d = new Date();
let time = d.toLocaleString("en-us",
{hour: '2-digit', minute: '2-digit', second: "2-digit", hourCycle: 'h23'});
let time = d.toLocaleString("en-us", { hour: '2-digit', minute: '2-digit', second: "2-digit", hourCycle: 'h23' });
totalDataUsageChartObj.data.labels.push(`${time}`);
if (totalDataUsageChartObj.data.datasets[0].data.length === 0) {
@ -254,8 +254,7 @@
let k = 0;
if (chartUnit === "MB") {
k = 1024;
}
else if (chartUnit === "KB"){
} else if (chartUnit === "KB") {
k = 1048576;
} else {
k = 1;
@ -270,11 +269,9 @@
}
document.querySelector("#conf_name").textContent = configuration_name;
$conf_status_btn.classList.remove("info_loading");
$("#switch").removeClass("info_loading");
document.querySelectorAll("#sort_by_dropdown option").forEach(ele => ele.removeAttribute("selected"));
document.querySelector(`#sort_by_dropdown option[value="${response.sort_tag}"]`).setAttribute("selected", "selected");
// document.querySelectorAll(".interval-btn-group button").forEach(ele => ele.classList.remove("active"));
// document.querySelector(`button[data-refresh-interval="${response.dashboard_refresh_interval}"]`).classList.add("active");
document.querySelector("#conf_status").innerHTML = `${response.status}<span class="dot dot-${response.status}"></span>`;
document.querySelector("#conf_connected_peers").innerHTML = response.running_peer;
document.querySelector("#conf_total_data_usage").innerHTML = `${response.total_data_usage[0]} GB`;
@ -283,9 +280,15 @@
document.querySelector("#conf_public_key").innerHTML = response.public_key;
document.querySelector("#conf_listen_port").innerHTML = response.listen_port === "" ? "N/A" : response.listen_port;
document.querySelector("#conf_address").innerHTML = response.conf_address;
document.querySelectorAll(".info h6").forEach(ele => ele.classList.remove("info_loading"));
let delay = 0;
let h6 = $(".info h6");
for (let i = 0; i < h6.length; i++){
setTimeout(function(){
$(h6[i]).removeClass("info_loading");
}, delay)
delay += 40
}
}
/**
* Parse all responded information onto the peers list
* @param response
@ -324,14 +327,14 @@
<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">
<button type="button" class="btn btn-outline-primary btn-setting-peer btn-control" data-peer-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">
<button type="button" class="btn btn-outline-danger btn-delete-peer btn-control" data-peer-id="${peer.id}" data-toggle="modal">
<i class="bi bi-x-circle-fill" data-toggle="tooltip" data-placement="bottom" title="Delete Peer"></i>
</button>
<button type="button" class="btn btn-outline-secondary btn-lock-peer btn-control" id="${peer.id}" data-toggle="modal">
<i class="bi bi-lock-fill" data-toggle="tooltip" data-placement="bottom" title=""></i>
<button type="button" class="btn btn-outline-success btn-lock-peer btn-control" data-peer-id="${peer.id}" data-toggle="modal">
<i class="bi bi-ethernet" data-toggle="tooltip" data-placement="bottom" data-original-title='Peer enabled. Click to disable peer.' data-peer-name="${peer.name}"></i>
</button>`;
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" data-imgsrc="/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>';
@ -357,6 +360,59 @@
'</div></div>';
result += html;
});
response.lock_access_peers.forEach(function(peer) {
let total_r = 0;
let total_s = 0;
total_r += peer.cumu_receive;
total_s += peer.cumu_sent;
let spliter = '<div class="w-100"></div>';
let peer_name =
`<div class="col-sm peerNameCol">
<h5 class="peerName">${peer.name === "" ? "Untitled" : peer.name}</h5>
<h6 class="peerLightContainer"><span class="dot dot-${peer.status}" style="margin-left: auto !important;" data-toggle="tooltip" data-placement="left"></span></h6>
</div>`;
let peer_transfer =
`<div class="col-12 peer_data_group" style="">
<p class="text-primary" style="">
<small><i class="bi bi-arrow-down-right"></i> ${roundN(peer.total_receive + total_r, 4)} GB</small>
</p>
<p class="text-success">
<small><i class="bi bi-arrow-up-right"></i> ${roundN(peer.total_sent + total_s, 4)} GB</small>
</p>
</div>`;
let peer_key = '<div class="col-sm"><small class="text-muted" style="display: flex"><strong>PEER</strong><strong style="margin-left: auto!important; opacity: 0; transition: 0.2s ease-in-out" class="text-primary">CLICK TO COPY</strong></small> <h6><samp class="ml-auto key">' + peer.id + '</samp></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_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; align-items: center;">
<button type="button" class="btn btn-outline-success btn-lock-peer btn-control lock" data-peer-id="${peer.id}" data-toggle="modal">
<i class="bi bi-ethernet" data-toggle="tooltip" data-placement="bottom" data-original-title='Peer disabled. Click to enable peer.' data-peer-name="${peer.name}"></i>
</button>
<small class="text-muted" style="margin-left: auto">Peer Disabled</small>
</div>`;
let html = '<div class="' + mode + '" data-id="' + peer.id + '">' +
'<div class="card mb-3 card-' + peer.status + '">' +
'<div class="card-body">' +
'<div class="row">' +
peer_name +
spliter +
peer_transfer +
peer_key +
peer_allowed_ip +
peer_latest_handshake +
spliter +
peer_endpoint +
spliter +
peer_control +
'</div>' +
'</div>' +
'</div>' +
'</div></div>';
result += html;
});
document.querySelector(".peer_list").innerHTML = result;
if (configuration_interval === undefined) {
setConfigurationInterval();
@ -407,8 +463,7 @@
data_list.forEach((ele) => ele.removeAttr("disabled"));
$add_peer.removeAttribute("disabled");
$add_peer.innerHTML = "Save";
}
else{
} else {
window.configurations.loadPeers("");
data_list.forEach((ele) => ele.removeAttr("disabled"));
$("#add_peer_form").trigger("reset");
@ -456,8 +511,7 @@
.removeClass("d-none");
$("#confirm_delete_bulk_peers").removeAttr("disabled").html("Delete");
}
}
else{
} else {
if (window.configurations.deleteModal()._isShown) {
window.configurations.deleteModal().toggle();
}
@ -468,8 +522,7 @@
window.configurations.deleteBulkModal().toggle();
}
window.configurations.loadPeers($('#search_peer_textbox').val());
$('#alertToast').toast('show');
$('#alertToast .toast-body').html("Peer deleted!");
window.configurations.showToast(`Deleted ${peer_ids.length} peers`)
$("#delete_peer").removeAttr("disabled").html("Delete");
}
}
@ -567,6 +620,7 @@
let time = 0;
let count = 0;
let d1 = new Date();
function loadPeers(searchString) {
d1 = new Date();
let good = true;
@ -586,6 +640,7 @@
function parsePeers(response) {
if (response.status) {
removeAllTooltips();
let d2 = new Date();
let seconds = (d2 - d1);
time += seconds;
@ -612,6 +667,25 @@
}
}
function removeAllTooltips(){
$(".tooltip").remove()
}
function toggleAccess(peerID){
$.ajax({
url: "/api/togglePeerAccess",
method: "POST",
headers: {"Content-Type": "application/json"},
data: JSON.stringify({"peerID": peerID, "config": configuration_name})
}).done(function(res){
if(res.status){
loadPeers($('#search_peer_textbox').val());
}else{
showToast(res.reason);
}
});
}
/**
* Generate Private and Public key for a new peer
*/
@ -628,9 +702,24 @@
* Show toast
* @param msg
*/
let numberToast = 0;
function showToast(msg) {
$('#alertToast').toast('show');
$('#alertToast .toast-body').html(msg);
$(".toastContainer").append(
`<div id="${numberToast}-toast" class="toast hide" role="alert" data-delay="5000">
<div class="toast-header">
<strong class="mr-auto">WGDashboard</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">${msg}</div>
<div class="toast-progressbar"></div>
</div>` )
$(`#${numberToast}-toast`).toast('show');
$(`#${numberToast}-toast .toast-body`).html(msg);
$(`#${numberToast}-toast .toast-progressbar`).css("transition", `width ${$(`#${numberToast}-toast .toast-progressbar`).parent().data('delay')}ms cubic-bezier(0, 0, 0, 0)`);
$(`#${numberToast}-toast .toast-progressbar`).css("width", "0px");
numberToast++;
}
/**
@ -754,6 +843,8 @@
addPeersByBulk: () => { addPeersByBulk(); },
deletePeers: (config, peers_ids) => { deletePeers(config, peers_ids); },
parsePeers: (response) => { parsePeers(response); },
toggleAccess: (peerID) => { toggleAccess(peerID) },
setConfigurationName: (confName) => { configuration_name = confName; },
getConfigurationName: () => { return configuration_name; },
@ -793,13 +884,33 @@ document.querySelector(".add_btn").addEventListener("click", () => {
/**
* When configuration switch got click
*/
document.querySelector(".info").addEventListener("click", (event) => {
let selector = document.querySelector(".switch");
if (selector.contains(event.target)){
selector.style.display = "none";
document.querySelector('div[role=status]').style.display = "inline-block";
location.replace(`/switch/${selector.getAttribute("id")}`);
$(".toggle--switch").on("click", function(){
$(this).addClass("waiting").attr("disabled", "disabled");
let id = window.configurations.getConfigurationName();
let status = $(this).prop("checked");
let ele = $(this);
$.ajax({
url: `/switch/${id}`
}).done(function(res){
console.log();
if (res){
if (status){
window.configurations.showToast(`${id} is running.`)
}else{
window.configurations.showToast(`${id} is stopped.`)
}
ele.removeClass("waiting");
ele.removeAttr("disabled");
}else{
if (status){
$(this).prop("checked", false)
}else{
$(this).prop("checked", true)
}
}
window.configurations.loadPeers($('#search_peer_textbox').val())
});
});
/**
@ -909,8 +1020,7 @@ $add_peer.addEventListener("click",function(){
data_list.forEach((ele) => ele.removeAttr("disabled"));
$add_peer.removeAttribute("disabled");
$add_peer.innerHTML = "Save";
}
else{
} else {
window.configurations.loadPeers("");
data_list.forEach((ele) => ele.removeAttr("disabled"));
$("#add_peer_form").trigger("reset");
@ -965,8 +1075,7 @@ $("#bulk_add").on("change", function (){
$(hide[i]).attr("disabled", "disabled");
}
amount.removeAttr("disabled");
}
else{
} else {
for (let i = 0; i < hide.length; i++) {
if ($(hide[i]).attr('id') !== "public_key") {
$(hide[i]).removeAttr("disabled");
@ -1072,21 +1181,23 @@ $body.on("click", ".btn-qrcode-peer", function (){
* When the delete button got clicked on each peer
*/
$body.on("click", ".btn-delete-peer", function() {
let peer_id = $(this).attr("id");
let peer_id = $(this).data('peer-id')
$("#delete_peer").data("peer-id", peer_id);
window.configurations.deleteModal().toggle();
});
$body.on("click", ".btn-lock-peer", function() {
let $lockGlyph = "bi-lock-fill";
let $unlockGlyph = "bi-unlock-fill";
if ($(this).children().hasClass($lockGlyph)){
$(this).children().removeClass($lockGlyph).addClass($unlockGlyph);
$(this).children().tooltip('hide').attr('data-original-title', 'Lock Peer').tooltip('show');
window.configurations.toggleAccess($(this).data('peer-id'), window.configurations.getConfigurationName());
if ($(this).hasClass("lock")) {
console.log($(this).data("peer-name"))
window.configurations.showToast(`Enabled ${$(this).children().data("peer-name")}`)
$(this).removeClass("lock")
$(this).children().tooltip('hide').attr('data-original-title', 'Peer enabled. Click to disable peer.').tooltip('show');
} else {
$(this).children().removeClass($unlockGlyph).addClass($lockGlyph);
$(this).children().tooltip('hide').attr('data-original-title', 'Unlock Peer').tooltip('show');
// Currently unlocked
window.configurations.showToast(`Disabled ${$(this).children().data("peer-name")}`)
$(this).addClass("lock");
$(this).children().tooltip('hide').attr('data-original-title', 'Peer disabled. Click to enable peer.').tooltip('show');
}
});
@ -1112,7 +1223,7 @@ $("#delete_peer").on("click",function(){
*/
$body.on("click", ".btn-setting-peer", function() {
// window.configurations.startProgressBar();
let peer_id = $(this).attr("id");
let peer_id = $(this).data("peer-id");
$("#save_peer_setting").attr("peer_id", peer_id);
$.ajax({
method: "POST",
@ -1405,8 +1516,7 @@ $("#delete_peers_by_bulk_btn").on("click", () => {
$delete_bulk_modal_list.html('');
peers.forEach((peer) => {
let name;
if (peer.name === "") { name = "Untitled Peer"; }
else { name = peer.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>');
});

File diff suppressed because one or more lines are too long

View File

@ -27,9 +27,10 @@
</div>
<div class="col">
<small class="text-muted"><strong>SWITCH</strong></small><br>
<div id="conf_status_btn" class="info_loading"></div>
<div class="spinner-border text-primary" role="status" style="display: none; margin-top: 10px">
<span class="sr-only">Loading...</span>
<!-- <div id="conf_status_btn" class="info_loading"></div> -->
<div id="switch" class="info_loading">
<input type="checkbox" class="toggle--switch" id="switch">
<label for="switch" class="toggleLabel"></label>
</div>
</div>
<div class="w-100"></div>
@ -424,8 +425,8 @@
</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="position-fixed top-0 right-0 p-3 toastContainer" 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">
<strong class="mr-auto">WGDashboard</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
@ -434,12 +435,13 @@
</div>
<div class="toast-body">
</div>
</div>
</div> -->
</div>
{% include "tools.html" %}
</body>
{% include "footer.html" %}
<script src="{{ url_for('static',filename='js/configuration.js') }}"></script>
<script src="{{ url_for('static',filename='js/wireguard.min.js') }}"></script>
<script>
configurations.setConfigurationName("{{ conf_data['name'] }}");
configurations.setActiveConfigurationName();

View File

@ -23,8 +23,10 @@
{% if conf == [] %}
<p class="text-muted">You don't have any WireGuard configurations yet. Please check the configuration folder or change it in "Settings". By default the folder is "/etc/wireguard".</p>
{% endif %}
{% for i in conf%}
<div class="card mt-3 conf_card">
<div class="card mt-3 conf_card" data-conf-id="{{i['conf']}}">
<div class="card-body">
<div class="row">
<div class="col card-col">
@ -35,21 +37,25 @@
</div>
<div class="col card-col">
<small class="text-muted"><strong>STATUS</strong></small>
<h6 style="text-transform: uppercase; margin:0 !important;">{{i['status']}}<span class="dot dot-{{i['status']}}"></span></h6>
<h6 style="text-transform: uppercase; margin:0 !important;"><span>{{i['status']}}</span><span class="dot dot-{{i['status']}}"></span></h6>
</div>
<div class="col-md card-col">
<div class="col-sm card-col">
<small class="text-muted"><strong>PUBLIC KEY</strong></small>
<h6 style="margin:0 !important;"><samp>{{i['public_key']}}</samp></h6>
</div>
<div class="col-md index-switch">
{% if i['checked'] == "checked" %}
<a href="#" id="{{i['conf']}}" {{i['checked']}} class="switch text-primary tt"><i class="bi bi-toggle2-on"></i></a>
<div class="col-sm index-switch">
<div class="switch-test">
<input type="checkbox" class="toggle--switch" id="{{i['conf']}}-switch" {{i['checked']}} data-conf-id="{{i['conf']}}">
<label for="{{i['conf']}}-switch" class="toggleLabel"></label>
</div>
<!-- {% if i['checked'] == "checked" %}
<a href="#" id="{{i['conf']}}" class="switch text-primary tt"><i class="bi bi-toggle2-on"></i></a>
{% else %}
<a href="#" id="{{i['conf']}}" {{i['checked']}} class="switch text-secondary"><i class="bi bi-toggle2-off"></i></a>
{% endif %}
<div class="spinner-border text-primary" role="status" style="display: none">
{% endif %} -->
<!-- <div class="spinner-border text-primary" role="status" style="display: none">
<span class="sr-only">Loading...</span>
</div>
</div> -->
</div>
</div>
</div>
@ -57,10 +63,69 @@
{%endfor%}
</main>
</div>
<div class="position-fixed top-0 right-0 p-3 toastContainer" style="z-index: 5; right: 0; top: 50px;">
</div>
{% include "tools.html" %}
</body>
{% include "footer.html" %}
<script>
let numberToast = 0;
function showToast(msg){
$(".toastContainer").append(
`<div id="${numberToast}-toast" class="toast hide" role="alert" data-delay="5000">
<div class="toast-header">
<strong class="mr-auto">WGDashboard</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">${msg}</div>
<div class="toast-progressbar"></div>
</div>` )
$(`#${numberToast}-toast`).toast('show');
$(`#${numberToast}-toast .toast-body`).html(msg);
$(`#${numberToast}-toast .toast-progressbar`).css("transition", `width ${$(`#${numberToast}-toast .toast-progressbar`).parent().data('delay')}ms cubic-bezier(0, 0, 0, 0)`);
$(`#${numberToast}-toast .toast-progressbar`).css("width", "0px");
numberToast++;
}
$(".toggle--switch").on("change", function(){
$(this).addClass("waiting").attr("disabled", "disabled");
let id = $(this).data("conf-id");
let status = $(this).prop("checked");
let ele = $(this);
let label = $(this).siblings("label");
$.ajax({
url: `/switch/${id}`
}).done(function(res){
let dot = $(`div[data-conf-id="${id}"] .dot`);
console.log();
if (res){
if (status){
dot.removeClass("dot-stopped").addClass("dot-running");
dot.siblings().text("Running");
showToast(`${id} is running.`)
}else{
dot.removeClass("dot-running").addClass("dot-stopped");
showToast(`${id} is stopped.`)
}
ele.removeClass("waiting");
ele.removeAttr("disabled");
}else{
if (status){
$(this).prop("checked", false)
}else{
$(this).prop("checked", true)
}
}
})
});
$('.switch').on("click", function() {
$(this).siblings($(".spinner-border")).css("display", "inline-block")
@ -70,7 +135,7 @@
$(".sb-home-url").addClass("active");
$(".card-body").on("click", function(handle){
if ($(handle.target).attr("class") !== "bi bi-toggle2-off" && $(handle.target).attr("class") !== "bi bi-toggle2-on") {
if ($(handle.target).attr("class") !== "toggleLabel" && $(handle.target).attr("class") !== "toggle--switch") {
window.open($(this).find("a").attr("href"), "_self");
}
});

View File

@ -1,5 +1,6 @@
import re
import subprocess
import dashboard
"""
Helper Functions
"""
@ -79,3 +80,33 @@ 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]",
address))
def deletePeers(config_name, delete_keys, cur, db):
sql_command = []
wg_command = ["wg", "set", config_name]
for delete_key in delete_keys:
if delete_key not in dashboard.get_conf_peer_key(config_name):
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:
print("deleting...")
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)
cur.executescript(' '.join(sql_command))
db.commit()
except subprocess.CalledProcessError as exc:
return exc.output.strip()
return "true"
def checkJSONAllParameter(required, data):
if len(data) == 0:
print("length 0")
return False
for i in required:
if i not in list(data.keys()):
return False
return True