1
0
mirror of https://github.com/donaldzou/WGDashboard.git synced 2024-11-06 07:50:13 +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

@ -536,5 +536,4 @@ 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!
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):
"""
@ -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):

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;
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

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