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
+2
View File
@@ -0,0 +1,2 @@
from dashboard import request, jsonify, app
+72 -27
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):
"""
@@ -499,7 +504,19 @@ def get_conf_list():
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,
keepalive INT NULL, remote_endpoint VARCHAR NULL, preshared_key VARCHAR NULL,
PRIMARY KEY (id)
)
"""
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)
)
"""
@@ -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
"""
@@ -1624,6 +1668,7 @@ def init_dashboard():
"""
Create dashboard default configuration.
"""
# Set Default INI File
if not os.path.isfile(DASHBOARD_CONF):
+263 -126
View File
@@ -9,7 +9,7 @@ body {
vertical-align: text-bottom;
}
.btn-primary{
.btn-primary {
font-weight: bold;
}
@@ -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
*/
@@ -90,11 +94,11 @@ body {
right: 1rem;
}
.form-control{
.form-control {
transition: all 0.2s ease-in-out;
}
.form-control:disabled{
.form-control:disabled {
cursor: not-allowed;
}
@@ -115,7 +119,7 @@ body {
box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);
}
.dot{
.dot {
width: 10px;
height: 10px;
border-radius: 50px;
@@ -123,157 +127,171 @@ body {
margin-left: auto !important;
}
.dot-running{
.dot-running {
background-color: #28a745!important;
box-shadow: 0 0 0 0.2rem #28a74545;
}
.h6-dot-running{
.h6-dot-running {
margin-left: 0.3rem;
}
.dot-stopped{
.dot-stopped {
background-color: #6c757d!important;
}
.card-running{
.card-running {
border-color: #28a745;
}
.info h6{
.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{
.info .row .col-sm {
display: flex;
flex-direction: column;
}
.info .row .col-sm small{
.info .row .col-sm small {
display: flex;
}
.info .row .col-sm small strong:last-child(1){
.info .row .col-sm small strong:last-child(1) {
margin-left: auto !important;
}
.btn-control{
.btn-control {
border: none !important;
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;
}
.btn-qrcode-peer{
.btn-qrcode-peer {
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);
}
.share_peer_btn_group .btn-control{
.share_peer_btn_group .btn-control {
margin: 0 0 0 1rem;
padding: 0 !important;
transition: all 0.4s cubic-bezier(1, -0.43, 0, 1.37);
}
.btn-control:hover{
.btn-control:hover {
background: white;
}
.btn-delete-peer:hover{
.btn-delete-peer:hover {
color: #dc3545;
}
.btn-lock-peer:hover{
color: #6c757d;
.btn-lock-peer:hover {
color: #28a745;
}
.btn-setting-peer:hover{
color:#007bff
.btn-lock-peer.lock{
color: #6c757d
}
.btn-download-peer:hover{
.btn-lock-peer.lock:hover{
color: #6c757d
}
.btn-setting-peer:hover {
color: #007bff
}
.btn-download-peer:hover {
color: #17a2b8;
}
.login-container{
.login-container {
padding: 2rem;
}
@media (max-width: 992px){
.card-col{
@media (max-width: 992px) {
.card-col {
margin-bottom: 1rem;
}
}
.switch{
.switch {
font-size: 2rem;
}
.switch:hover{
.switch:hover {
text-decoration: none
}
.btn-group-label:hover{
.btn-group-label:hover {
color: #007bff;
border-color: #007bff;
background: white;
}
.peer_data_group{
.peer_data_group {
text-align: right;
display: flex;
margin-bottom: 0.5rem
}
.peer_data_group p{
.peer_data_group p {
text-transform: uppercase;
margin-bottom: 0;
margin-right: 1rem
}
@media (max-width: 768px) {
.peer_data_group{
.peer_data_group {
text-align: left;
}
}
.index-switch{
.index-switch {
text-align: right;
display: flex;
align-items: center;
justify-content: flex-end;
}
main{
main {
margin-bottom: 3rem;
}
.peer_list{
.peer_list {
margin-bottom: 7rem
}
@media (max-width: 768px) {
.add_btn{
.add_btn {
bottom: 1.5rem !important;
}
.peer_list{
.peer_list {
margin-bottom: 7rem !important;
}
}
.btn-manage-group{
.btn-manage-group {
z-index: 99;
position: fixed;
bottom: 3rem;
@@ -281,7 +299,7 @@ main{
display: flex;
}
.btn-manage-group .setting_btn_menu{
.btn-manage-group .setting_btn_menu {
position: absolute;
top: -124px;
background-color: white;
@@ -296,16 +314,16 @@ main{
transition: all 0.3s cubic-bezier(0.58, 0.03, 0.05, 1.28);
}
.btn-manage-group .setting_btn_menu.show{
.btn-manage-group .setting_btn_menu.show {
display: block;
}
.setting_btn_menu.showing{
.setting_btn_menu.showing {
transform: translateY(0px);
opacity: 1;
}
.setting_btn_menu a{
.setting_btn_menu a {
display: flex;
padding: 0.5rem 1rem;
transition: all 0.1s ease-in-out;
@@ -314,36 +332,38 @@ main{
cursor: pointer;
}
.setting_btn_menu a:hover{
.setting_btn_menu a:hover {
background-color: #efefef;
text-decoration: none;
}
.setting_btn_menu a i{
.setting_btn_menu a i {
margin-right: auto !important;
}
.add_btn{
.add_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);
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{
.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);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
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);
@@ -380,7 +401,7 @@ main{
animation: rotating 0.75s linear infinite;
}
.peer_private_key_textbox_switch{
.peer_private_key_textbox_switch {
position: absolute;
right: 2rem;
transform: translateY(-28px);
@@ -388,139 +409,153 @@ main{
cursor: pointer;
}
#peer_private_key_textbox, #private_key, #public_key, #peer_preshared_key_textbox{
font-family: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
#peer_private_key_textbox,
#private_key,
#public_key,
#peer_preshared_key_textbox {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
.progress-bar{
.progress-bar {
transition: 0.3s ease-in-out;
}
.key{
.key {
transition: 0.2s ease-in-out;
cursor: pointer;
}
.key:hover{
.key:hover {
color: #007bff;
}
.card{
.card {
border-radius: 10px;
}
.peer_list .card .button-group{
.peer_list .card .button-group {
height: 22px;
}
.form-control{
.form-control {
border-radius: 10px;
}
.btn{
.btn {
border-radius: 8px;
/*padding: 0.6rem 0.9em;*/
}
#username, #password{
padding: 0.6rem calc( 0.9rem + 32px );
#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);*/
/*}*/
.modal-content{
.modal-content {
border-radius: 10px;
}
.tooltip-inner{
.tooltip-inner {
font-size: 0.8rem;
}
@-webkit-keyframes loading {
0%{
0% {
background-color: #dfdfdf;
}
50%{
50% {
background-color: #adadad;
}
100%{
background-color: #dfdfdf;
}
}
@-moz-keyframes loading {
0%{
background-color: #dfdfdf;
}
50%{
background-color: #adadad;
}
100%{
100% {
background-color: #dfdfdf;
}
}
.conf_card{
@-moz-keyframes loading {
0% {
background-color: #dfdfdf;
}
50% {
background-color: #adadad;
}
100% {
background-color: #dfdfdf;
}
}
.conf_card {
transition: 0.2s ease-in-out;
}
.conf_card:hover{
.conf_card:hover {
border-color: #007bff;
cursor: pointer;
}
.info_loading{
animation: loading 2s infinite ease-in-out;
border-radius: 5px;
height: 19px;
transition: 0.3s ease-in-out;
.info_loading {
/* 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{
#conf_status_btn {
transition: 0.2s ease-in-out;
}
#conf_status_btn.info_loading{
#conf_status_btn.info_loading {
height: 38px;
border-radius: 5px;
animation: loading 3s infinite ease-in-out;
}
#qrcode_img img{
#qrcode_img img {
width: 100%;
}
#selected_ip_list .badge, #selected_peer_list .badge{
#selected_ip_list .badge,
#selected_peer_list .badge {
margin: 0.1rem
}
#add_modal.ip_modal_open{
#add_modal.ip_modal_open {
transition: filter 0.2s ease-in-out;
filter: brightness(0.5);
}
#delete_bulk_modal .list-group a.active{
#delete_bulk_modal .list-group a.active {
background-color: #dc3545;
border-color: #dc3545;
}
#selected_peer_list{
#selected_peer_list {
max-height: 80px;
overflow-y: scroll;
overflow-x: hidden;
}
.no-response{
.no-response {
width: 100%;
height: 100%;
position: fixed;
@@ -534,93 +569,195 @@ label[for="username"], label[for="password"]{
transition: all 1s ease-in-out;
}
.no-response.active{
.no-response.active {
display: flex;
}
.no-response.active.show{
.no-response.active.show {
opacity: 100;
}
.no-response .container > *{
.no-response .container>* {
text-align: center;
}
.no-responding{
.no-responding {
transition: all 1s ease-in-out;
filter: blur(10px);
}
pre.index-alert{
pre.index-alert {
margin-bottom: 0;
padding: 1rem;
background-color: #343a40;
border: 1px solid rgba(0,0,0,.125);
border: 1px solid rgba(0, 0, 0, .125);
border-radius: .25rem;
margin-top: 1rem;
color: white;
}
.peerNameCol{
.peerNameCol {
display: flex;
align-items: center;
margin-bottom: 0.2rem
}
.peerName{
margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
.peerName {
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.peerLightContainer{
text-transform: uppercase; margin: 0; margin-left: auto !important;
.peerLightContainer {
text-transform: uppercase;
margin: 0;
margin-left: auto !important;
}
.conf_card .dot, .info .dot {
.conf_card .dot,
.info .dot {
transform: translateX(10px);
}
#config_body{
#config_body {
transition: 0.3s ease-in-out;
}
#config_body.firstLoading{
#config_body.firstLoading {
opacity: 0.2;
}
.chartTitle{
.chartTitle {
display: flex;
}
.chartControl{
margin-bottom: 1rem;
.chartControl {
margin-bottom: 1rem;
display: flex;
align-items: center;
}
.chartTitle h6{
.chartTitle h6 {
margin-bottom: 0;
line-height: 1;
margin-right: 0.5rem;
}
.chartContainer.fullScreen{
.chartContainer.fullScreen {
position: fixed;
z-index: 9999;
background-color: white;
top: 0;
left: 0;
width: calc( 100% + 15px );
width: calc( 100% + 15px);
height: 100%;
padding: 32px;
}
.chartContainer.fullScreen .col-sm{
.chartContainer.fullScreen .col-sm {
padding-right: 0;
height: 100%;
}
.chartContainer.fullScreen .chartCanvasContainer{
.chartContainer.fullScreen .chartCanvasContainer {
width: 100%;
height: calc( 100% - 47px ) !important;
max-height: calc( 100% - 47px ) !important;
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;
}
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+1 -23
View File
File diff suppressed because one or more lines are too long
+8 -6
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();
+75 -10
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");
}
});
+32 -1
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