1
0
mirror of https://github.com/donaldzou/WGDashboard.git synced 2024-11-22 15:20:09 +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 --> <!-- 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 from flask_socketio import SocketIO
# Import other python files # Import other python files
from util import regex_match, check_DNS, check_Allowed_IPs, check_remote_endpoint, \ from util import *
check_IP_with_range, clean_IP_with_range
# Dashboard Version # Dashboard Version
DASHBOARD_VERSION = 'v3.0.5' DASHBOARD_VERSION = 'v3.0.5'
@ -375,6 +374,12 @@ def get_all_peers_data(config_name):
get_endpoint(config_name) get_endpoint(config_name)
get_allowed_ip(conf_peer_data, 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): 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, total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL,
status VARCHAR NULL, latest_handshake VARCHAR NULL, allowed_ip 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, 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) PRIMARY KEY (id)
) )
""" """
@ -620,7 +637,6 @@ def f_available_ips(config_name):
Flask Functions Flask Functions
""" """
@app.teardown_request @app.teardown_request
def close_DB(exception): def close_DB(exception):
""" """
@ -1056,7 +1072,8 @@ def get_conf(config_name):
"wg_ip": wg_ip, "wg_ip": wg_ip,
"sort_tag": sort, "sort_tag": sort,
"dashboard_refresh_interval": int(config.get("Server", "dashboard_refresh_interval")), "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": if result['data']['status'] == "stopped":
result['data']['checked'] = "nope" result['data']['checked'] = "nope"
@ -1087,16 +1104,15 @@ def switch(config_name):
shell=True, stderr=subprocess.STDOUT) shell=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as exc: except subprocess.CalledProcessError as exc:
session["switch_msg"] = exc.output.strip().decode("utf-8") session["switch_msg"] = exc.output.strip().decode("utf-8")
return redirect('/') return jsonify({"status": False, "reason":"Can't stop peer"})
elif status == "stopped": elif status == "stopped":
try: try:
subprocess.check_output("wg-quick up " + config_name, subprocess.check_output("wg-quick up " + config_name,
shell=True, stderr=subprocess.STDOUT) shell=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as exc: except subprocess.CalledProcessError as exc:
session["switch_msg"] = exc.output.strip().decode("utf-8") session["switch_msg"] = exc.output.strip().decode("utf-8")
return redirect('/') return jsonify({"status": False, "reason":"Can't turn on peer"})
return redirect(request.referrer) return jsonify({"status": True, "reason":""})
@app.route('/add_peer_bulk/<config_name>', methods=['POST']) @app.route('/add_peer_bulk/<config_name>', methods=['POST'])
def add_peer_bulk(config_name): def add_peer_bulk(config_name):
@ -1240,24 +1256,7 @@ def remove_peer(config_name):
if not isinstance(keys, list): if not isinstance(keys, list):
return config_name + " is not running." return config_name + " is not running."
else: else:
sql_command = [] return deletePeers(config_name, delete_keys, g.cur, g.db)
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"
@app.route('/save_peer_setting/<config_name>', methods=['POST']) @app.route('/save_peer_setting/<config_name>', methods=['POST'])
@ -1530,6 +1529,51 @@ def switch_display_mode(mode):
return "false" 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 Dashboard Tools Related
""" """
@ -1624,6 +1668,7 @@ def init_dashboard():
""" """
Create dashboard default configuration. Create dashboard default configuration.
""" """
# Set Default INI File # Set Default INI File
if not os.path.isfile(DASHBOARD_CONF): if not os.path.isfile(DASHBOARD_CONF):

View File

@ -9,7 +9,7 @@ body {
vertical-align: text-bottom; vertical-align: text-bottom;
} }
.btn-primary{ .btn-primary {
font-weight: bold; font-weight: bold;
} }
@ -22,8 +22,10 @@ body {
top: 0; top: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
z-index: 100; /* Behind the navbar */ z-index: 100;
padding: 48px 0 0; /* Height of navbar */ /* Behind the navbar */
padding: 48px 0 0;
/* Height of navbar */
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1); box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
} }
@ -33,7 +35,8 @@ body {
height: calc(100vh - 48px); height: calc(100vh - 48px);
padding-top: .5rem; padding-top: .5rem;
overflow-x: hidden; 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)) { @supports ((position: -webkit-sticky) or (position: sticky)) {
@ -73,6 +76,7 @@ body {
text-transform: uppercase; text-transform: uppercase;
} }
/* /*
* Navbar * Navbar
*/ */
@ -90,11 +94,11 @@ body {
right: 1rem; right: 1rem;
} }
.form-control{ .form-control {
transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out;
} }
.form-control:disabled{ .form-control:disabled {
cursor: not-allowed; cursor: not-allowed;
} }
@ -115,7 +119,7 @@ body {
box-shadow: 0 0 0 3px rgba(255, 255, 255, .25); box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);
} }
.dot{ .dot {
width: 10px; width: 10px;
height: 10px; height: 10px;
border-radius: 50px; border-radius: 50px;
@ -123,157 +127,171 @@ body {
margin-left: auto !important; margin-left: auto !important;
} }
.dot-running{ .dot-running {
background-color: #28a745!important; background-color: #28a745!important;
box-shadow: 0 0 0 0.2rem #28a74545; box-shadow: 0 0 0 0.2rem #28a74545;
} }
.h6-dot-running{ .h6-dot-running {
margin-left: 0.3rem; margin-left: 0.3rem;
} }
.dot-stopped{ .dot-stopped {
background-color: #6c757d!important; background-color: #6c757d!important;
} }
.card-running{ .card-running {
border-color: #28a745; border-color: #28a745;
} }
.info h6{ .info h6 {
line-break: anywhere; 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; display: flex;
flex-direction: column; flex-direction: column;
} }
.info .row .col-sm small{ .info .row .col-sm small {
display: flex; display: flex;
} }
.info .row .col-sm small strong:last-child(1){ .info .row .col-sm small strong:last-child(1) {
margin-left: auto !important; margin-left: auto !important;
} }
.btn-control{ .btn-control {
border: none !important; border: none !important;
padding: 0 1rem 0 0; padding: 0 1rem 0 0;
} }
.btn-control:active, .btn-control:focus{ .btn-control:active,
.btn-control:focus {
background-color: transparent !important; background-color: transparent !important;
border: none !important; border: none !important;
box-shadow: none; box-shadow: none;
} }
.btn-qrcode-peer{ .btn-qrcode-peer {
padding: 0 !important; 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); transform: scale(0.9) rotate(180deg);
border: 0 !important; border: 0 !important;
} }
.btn-download-peer:active, .btn-download-peer:hover{ .btn-download-peer:active,
.btn-download-peer:hover {
color: #17a2b8 !important; color: #17a2b8 !important;
transform: translateY(5px); transform: translateY(5px);
} }
.share_peer_btn_group .btn-control{ .share_peer_btn_group .btn-control {
margin: 0 0 0 1rem; margin: 0 0 0 1rem;
padding: 0 !important; padding: 0 !important;
transition: all 0.4s cubic-bezier(1, -0.43, 0, 1.37); transition: all 0.4s cubic-bezier(1, -0.43, 0, 1.37);
} }
.btn-control:hover{ .btn-control:hover {
background: white; background: white;
} }
.btn-delete-peer:hover{ .btn-delete-peer:hover {
color: #dc3545; color: #dc3545;
} }
.btn-lock-peer:hover{ .btn-lock-peer:hover {
color: #6c757d; color: #28a745;
} }
.btn-setting-peer:hover{ .btn-lock-peer.lock{
color:#007bff 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; color: #17a2b8;
} }
.login-container{ .login-container {
padding: 2rem; padding: 2rem;
} }
@media (max-width: 992px){ @media (max-width: 992px) {
.card-col{ .card-col {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
} }
.switch{ .switch {
font-size: 2rem; font-size: 2rem;
} }
.switch:hover{
.switch:hover {
text-decoration: none text-decoration: none
} }
.btn-group-label:hover{ .btn-group-label:hover {
color: #007bff; color: #007bff;
border-color: #007bff; border-color: #007bff;
background: white; background: white;
} }
.peer_data_group{ .peer_data_group {
text-align: right; text-align: right;
display: flex; display: flex;
margin-bottom: 0.5rem margin-bottom: 0.5rem
} }
.peer_data_group p{ .peer_data_group p {
text-transform: uppercase; text-transform: uppercase;
margin-bottom: 0; margin-bottom: 0;
margin-right: 1rem margin-right: 1rem
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.peer_data_group{ .peer_data_group {
text-align: left; text-align: left;
} }
} }
.index-switch{ .index-switch {
text-align: right; text-align: right;
display: flex;
align-items: center;
justify-content: flex-end;
} }
main{ main {
margin-bottom: 3rem; margin-bottom: 3rem;
} }
.peer_list{ .peer_list {
margin-bottom: 7rem margin-bottom: 7rem
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.add_btn{ .add_btn {
bottom: 1.5rem !important; bottom: 1.5rem !important;
} }
.peer_list {
.peer_list{
margin-bottom: 7rem !important; margin-bottom: 7rem !important;
} }
} }
.btn-manage-group{ .btn-manage-group {
z-index: 99; z-index: 99;
position: fixed; position: fixed;
bottom: 3rem; bottom: 3rem;
@ -281,7 +299,7 @@ main{
display: flex; display: flex;
} }
.btn-manage-group .setting_btn_menu{ .btn-manage-group .setting_btn_menu {
position: absolute; position: absolute;
top: -124px; top: -124px;
background-color: white; background-color: white;
@ -296,16 +314,16 @@ main{
transition: all 0.3s cubic-bezier(0.58, 0.03, 0.05, 1.28); 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; display: block;
} }
.setting_btn_menu.showing{ .setting_btn_menu.showing {
transform: translateY(0px); transform: translateY(0px);
opacity: 1; opacity: 1;
} }
.setting_btn_menu a{ .setting_btn_menu a {
display: flex; display: flex;
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
transition: all 0.1s ease-in-out; transition: all 0.1s ease-in-out;
@ -314,36 +332,38 @@ main{
cursor: pointer; cursor: pointer;
} }
.setting_btn_menu a:hover{ .setting_btn_menu a:hover {
background-color: #efefef; background-color: #efefef;
text-decoration: none; text-decoration: none;
} }
.setting_btn_menu a i{ .setting_btn_menu a i {
margin-right: auto !important; margin-right: auto !important;
} }
.add_btn{ .add_btn {
height: 54px; height: 54px;
z-index: 99; z-index: 99;
border-radius: 100px !important; border-radius: 100px !important;
padding: 0 14px; 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; margin-right: 1rem;
font-size: 1.5rem; font-size: 1.5rem;
} }
.setting_btn{ .setting_btn {
height: 54px; height: 54px;
z-index: 99; z-index: 99;
border-radius: 100px !important; border-radius: 100px !important;
padding: 0 14px; 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; font-size: 1.5rem;
} }
@-webkit-keyframes rotating
/* Safari and Chrome */
@-webkit-keyframes rotating /* Safari and Chrome */ { {
from { from {
-webkit-transform: rotate(0deg); -webkit-transform: rotate(0deg);
-o-transform: rotate(0deg); -o-transform: rotate(0deg);
@ -355,6 +375,7 @@ main{
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
@keyframes rotating { @keyframes rotating {
from { from {
-ms-transform: rotate(0deg); -ms-transform: rotate(0deg);
@ -380,7 +401,7 @@ main{
animation: rotating 0.75s linear infinite; animation: rotating 0.75s linear infinite;
} }
.peer_private_key_textbox_switch{ .peer_private_key_textbox_switch {
position: absolute; position: absolute;
right: 2rem; right: 2rem;
transform: translateY(-28px); transform: translateY(-28px);
@ -388,139 +409,153 @@ main{
cursor: pointer; cursor: pointer;
} }
#peer_private_key_textbox, #private_key, #public_key, #peer_preshared_key_textbox{ #peer_private_key_textbox,
font-family: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; #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; transition: 0.3s ease-in-out;
} }
.key{ .key {
transition: 0.2s ease-in-out; transition: 0.2s ease-in-out;
cursor: pointer; cursor: pointer;
} }
.key:hover{ .key:hover {
color: #007bff; color: #007bff;
} }
.card{ .card {
border-radius: 10px; border-radius: 10px;
} }
.peer_list .card .button-group{ .peer_list .card .button-group {
height: 22px; height: 22px;
} }
.form-control{ .form-control {
border-radius: 10px; border-radius: 10px;
} }
.btn{ .btn {
border-radius: 8px; border-radius: 8px;
/*padding: 0.6rem 0.9em;*/ /*padding: 0.6rem 0.9em;*/
} }
#username, #password{ #username,
padding: 0.6rem calc( 0.9rem + 32px ); #password {
padding: 0.6rem calc( 0.9rem + 32px);
height: inherit; height: inherit;
} }
label[for="username"], label[for="password"]{ label[for="username"],
label[for="password"] {
font-size: 1rem; font-size: 1rem;
margin: 0 !important; margin: 0 !important;
transform: translateY(30px) translateX(16px); transform: translateY(30px) translateX(16px);
padding: 0; padding: 0;
} }
/*label[for="password"]{*/ /*label[for="password"]{*/
/* transform: translateY(32px) translateX(16px);*/ /* transform: translateY(32px) translateX(16px);*/
/*}*/ /*}*/
.modal-content {
.modal-content{
border-radius: 10px; border-radius: 10px;
} }
.tooltip-inner{ .tooltip-inner {
font-size: 0.8rem; font-size: 0.8rem;
} }
@-webkit-keyframes loading { @-webkit-keyframes loading {
0%{ 0% {
background-color: #dfdfdf; background-color: #dfdfdf;
} }
50%{ 50% {
background-color: #adadad; background-color: #adadad;
} }
100%{ 100% {
background-color: #dfdfdf;
}
}
@-moz-keyframes loading {
0%{
background-color: #dfdfdf;
}
50%{
background-color: #adadad;
}
100%{
background-color: #dfdfdf; 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; transition: 0.2s ease-in-out;
} }
.conf_card:hover{ .conf_card:hover {
border-color: #007bff; border-color: #007bff;
cursor: pointer; cursor: pointer;
} }
.info_loading{ .info_loading {
animation: loading 2s infinite ease-in-out; /* animation: loading 2s infinite ease-in-out;
border-radius: 5px; /* border-radius: 5px; */
height: 19px; height: 19.19px;
transition: 0.3s ease-in-out; /* transition: 0.3s ease-in-out; */
/* transform: translateX(40px); */
opacity: 0 !important;
} }
#conf_status_btn{ #conf_status_btn {
transition: 0.2s ease-in-out; transition: 0.2s ease-in-out;
} }
#conf_status_btn.info_loading{ #conf_status_btn.info_loading {
height: 38px; height: 38px;
border-radius: 5px; border-radius: 5px;
animation: loading 3s infinite ease-in-out; animation: loading 3s infinite ease-in-out;
} }
#qrcode_img img{ #qrcode_img img {
width: 100%; width: 100%;
} }
#selected_ip_list .badge, #selected_peer_list .badge{ #selected_ip_list .badge,
#selected_peer_list .badge {
margin: 0.1rem margin: 0.1rem
} }
#add_modal.ip_modal_open{ #add_modal.ip_modal_open {
transition: filter 0.2s ease-in-out; transition: filter 0.2s ease-in-out;
filter: brightness(0.5); filter: brightness(0.5);
} }
#delete_bulk_modal .list-group a.active{ #delete_bulk_modal .list-group a.active {
background-color: #dc3545; background-color: #dc3545;
border-color: #dc3545; border-color: #dc3545;
} }
#selected_peer_list{ #selected_peer_list {
max-height: 80px; max-height: 80px;
overflow-y: scroll; overflow-y: scroll;
overflow-x: hidden; overflow-x: hidden;
} }
.no-response{ .no-response {
width: 100%; width: 100%;
height: 100%; height: 100%;
position: fixed; position: fixed;
@ -534,93 +569,195 @@ label[for="username"], label[for="password"]{
transition: all 1s ease-in-out; transition: all 1s ease-in-out;
} }
.no-response.active{ .no-response.active {
display: flex; display: flex;
} }
.no-response.active.show{ .no-response.active.show {
opacity: 100; opacity: 100;
} }
.no-response .container > *{ .no-response .container>* {
text-align: center; text-align: center;
} }
.no-responding{ .no-responding {
transition: all 1s ease-in-out; transition: all 1s ease-in-out;
filter: blur(10px); filter: blur(10px);
} }
pre.index-alert{ pre.index-alert {
margin-bottom: 0; margin-bottom: 0;
padding: 1rem; padding: 1rem;
background-color: #343a40; background-color: #343a40;
border: 1px solid rgba(0,0,0,.125); border: 1px solid rgba(0, 0, 0, .125);
border-radius: .25rem; border-radius: .25rem;
margin-top: 1rem; margin-top: 1rem;
color: white; color: white;
} }
.peerNameCol{ .peerNameCol {
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 0.2rem margin-bottom: 0.2rem
} }
.peerName{ .peerName {
margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
.peerLightContainer{ .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); transform: translateX(10px);
} }
#config_body{ #config_body {
transition: 0.3s ease-in-out; transition: 0.3s ease-in-out;
} }
#config_body.firstLoading{ #config_body.firstLoading {
opacity: 0.2; opacity: 0.2;
} }
.chartTitle{ .chartTitle {
display: flex; display: flex;
} }
.chartControl{ .chartControl {
margin-bottom: 1rem; margin-bottom: 1rem;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.chartTitle h6{ .chartTitle h6 {
margin-bottom: 0; margin-bottom: 0;
line-height: 1; line-height: 1;
margin-right: 0.5rem; margin-right: 0.5rem;
} }
.chartContainer.fullScreen{ .chartContainer.fullScreen {
position: fixed; position: fixed;
z-index: 9999; z-index: 9999;
background-color: white; background-color: white;
top: 0; top: 0;
left: 0; left: 0;
width: calc( 100% + 15px ); width: calc( 100% + 15px);
height: 100%; height: 100%;
padding: 32px; padding: 32px;
} }
.chartContainer.fullScreen .col-sm{ .chartContainer.fullScreen .col-sm {
padding-right: 0; padding-right: 0;
height: 100%; height: 100%;
} }
.chartContainer.fullScreen .chartCanvasContainer{ .chartContainer.fullScreen .chartCanvasContainer {
width: 100%; width: 100%;
height: calc( 100% - 47px ) !important; height: calc( 100% - 47px) !important;
max-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>
<div class="col"> <div class="col">
<small class="text-muted"><strong>SWITCH</strong></small><br> <small class="text-muted"><strong>SWITCH</strong></small><br>
<div id="conf_status_btn" class="info_loading"></div> <!-- <div id="conf_status_btn" class="info_loading"></div> -->
<div class="spinner-border text-primary" role="status" style="display: none; margin-top: 10px"> <div id="switch" class="info_loading">
<span class="sr-only">Loading...</span> <input type="checkbox" class="toggle--switch" id="switch">
<label for="switch" class="toggleLabel"></label>
</div> </div>
</div> </div>
<div class="w-100"></div> <div class="w-100"></div>
@ -424,8 +425,8 @@
</div> </div>
</div> </div>
</div> </div>
<div class="position-fixed top-0 right-0 p-3" style="z-index: 5; right: 0; top: 50px;"> <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 id="alertToast" class="toast hide" role="alert" aria-live="assertive" aria-atomic="true" data-delay="5000">
<div class="toast-header"> <div class="toast-header">
<strong class="mr-auto">WGDashboard</strong> <strong class="mr-auto">WGDashboard</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close"> <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
@ -434,12 +435,13 @@
</div> </div>
<div class="toast-body"> <div class="toast-body">
</div> </div>
</div> </div> -->
</div> </div>
{% include "tools.html" %} {% include "tools.html" %}
</body> </body>
{% include "footer.html" %} {% include "footer.html" %}
<script src="{{ url_for('static',filename='js/configuration.js') }}"></script> <script src="{{ url_for('static',filename='js/configuration.js') }}"></script>
<script src="{{ url_for('static',filename='js/wireguard.min.js') }}"></script>
<script> <script>
configurations.setConfigurationName("{{ conf_data['name'] }}"); configurations.setConfigurationName("{{ conf_data['name'] }}");
configurations.setActiveConfigurationName(); configurations.setActiveConfigurationName();

View File

@ -23,8 +23,10 @@
{% if conf == [] %} {% 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> <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 %} {% endif %}
{% for i in conf%} {% 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="card-body">
<div class="row"> <div class="row">
<div class="col card-col"> <div class="col card-col">
@ -35,21 +37,25 @@
</div> </div>
<div class="col card-col"> <div class="col card-col">
<small class="text-muted"><strong>STATUS</strong></small> <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>
<div class="col-md card-col"> <div class="col-sm card-col">
<small class="text-muted"><strong>PUBLIC KEY</strong></small> <small class="text-muted"><strong>PUBLIC KEY</strong></small>
<h6 style="margin:0 !important;"><samp>{{i['public_key']}}</samp></h6> <h6 style="margin:0 !important;"><samp>{{i['public_key']}}</samp></h6>
</div> </div>
<div class="col-md index-switch"> <div class="col-sm index-switch">
{% if i['checked'] == "checked" %} <div class="switch-test">
<a href="#" id="{{i['conf']}}" {{i['checked']}} class="switch text-primary tt"><i class="bi bi-toggle2-on"></i></a> <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 %} {% else %}
<a href="#" id="{{i['conf']}}" {{i['checked']}} class="switch text-secondary"><i class="bi bi-toggle2-off"></i></a> <a href="#" id="{{i['conf']}}" {{i['checked']}} class="switch text-secondary"><i class="bi bi-toggle2-off"></i></a>
{% endif %} {% endif %} -->
<div class="spinner-border text-primary" role="status" style="display: none"> <!-- <div class="spinner-border text-primary" role="status" style="display: none">
<span class="sr-only">Loading...</span> <span class="sr-only">Loading...</span>
</div> </div> -->
</div> </div>
</div> </div>
</div> </div>
@ -57,10 +63,69 @@
{%endfor%} {%endfor%}
</main> </main>
</div> </div>
<div class="position-fixed top-0 right-0 p-3 toastContainer" style="z-index: 5; right: 0; top: 50px;">
</div>
{% include "tools.html" %} {% include "tools.html" %}
</body> </body>
{% include "footer.html" %} {% include "footer.html" %}
<script> <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() { $('.switch').on("click", function() {
$(this).siblings($(".spinner-border")).css("display", "inline-block") $(this).siblings($(".spinner-border")).css("display", "inline-block")
@ -70,7 +135,7 @@
$(".sb-home-url").addClass("active"); $(".sb-home-url").addClass("active");
$(".card-body").on("click", function(handle){ $(".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"); window.open($(this).find("a").attr("href"), "_self");
} }
}); });

View File

@ -1,5 +1,6 @@
import re import re
import subprocess
import dashboard
""" """
Helper Functions 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]", 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)) 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