mirror of
https://github.com/donaldzou/WGDashboard.git
synced 2024-11-19 05:50:10 +01:00
Merge branch 'main' into fix-download
This commit is contained in:
commit
e0934f11d6
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,3 +14,4 @@ private_key.txt
|
|||||||
public_key.txt
|
public_key.txt
|
||||||
venv/**
|
venv/**
|
||||||
log/**
|
log/**
|
||||||
|
release/*
|
||||||
|
@ -30,7 +30,11 @@ from icmplib import ping, multiping, traceroute, resolve, Host, Hop
|
|||||||
# Dashboard Version
|
# Dashboard Version
|
||||||
dashboard_version = 'v3.0'
|
dashboard_version = 'v3.0'
|
||||||
# Dashboard Config Name
|
# Dashboard Config Name
|
||||||
dashboard_conf = 'wg-dashboard.ini'
|
configuration_path = os.getenv('CONFIGURATION_PATH', '.')
|
||||||
|
db_path = os.path.join(configuration_path, 'db')
|
||||||
|
if not os.path.isdir(db_path):
|
||||||
|
os.mkdir(db_path)
|
||||||
|
dashboard_conf = os.path.join(configuration_path, 'wg-dashboard.ini')
|
||||||
# Upgrade Required
|
# Upgrade Required
|
||||||
update = ""
|
update = ""
|
||||||
# Flask App Configuration
|
# Flask App Configuration
|
||||||
@ -223,10 +227,8 @@ def get_allowed_ip(config_name, db, peers, conf_peer_data):
|
|||||||
|
|
||||||
# Look for new peers from WireGuard
|
# Look for new peers from WireGuard
|
||||||
def get_all_peers_data(config_name):
|
def get_all_peers_data(config_name):
|
||||||
sem.acquire()
|
sem.acquire(timeout=1)
|
||||||
|
db = TinyDB(os.path.join(db_path, config_name + '.json'))
|
||||||
db = TinyDB('db/' + config_name + '.json')
|
|
||||||
|
|
||||||
peers = Query()
|
peers = Query()
|
||||||
conf_peer_data = read_conf_file(config_name)
|
conf_peer_data = read_conf_file(config_name)
|
||||||
config = get_dashboard_conf()
|
config = get_dashboard_conf()
|
||||||
@ -283,7 +285,10 @@ def get_all_peers_data(config_name):
|
|||||||
toc = time.perf_counter()
|
toc = time.perf_counter()
|
||||||
print(f"Finish fetching data in {toc - tic:0.4f} seconds")
|
print(f"Finish fetching data in {toc - tic:0.4f} seconds")
|
||||||
db.close()
|
db.close()
|
||||||
|
try:
|
||||||
sem.release()
|
sem.release()
|
||||||
|
except RuntimeError as e:
|
||||||
|
pass
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Frontend Related Functions
|
Frontend Related Functions
|
||||||
@ -291,9 +296,8 @@ Frontend Related Functions
|
|||||||
# Search for peers
|
# Search for peers
|
||||||
def get_peers(config_name, search, sort_t):
|
def get_peers(config_name, search, sort_t):
|
||||||
get_all_peers_data(config_name)
|
get_all_peers_data(config_name)
|
||||||
sem.acquire()
|
sem.acquire(timeout=1)
|
||||||
|
db = TinyDB(os.path.join(db_path, config_name + ".json"))
|
||||||
db = TinyDB('db/' + config_name + '.json')
|
|
||||||
peer = Query()
|
peer = Query()
|
||||||
if len(search) == 0:
|
if len(search) == 0:
|
||||||
result = db.all()
|
result = db.all()
|
||||||
@ -335,9 +339,8 @@ def get_conf_listen_port(config_name):
|
|||||||
|
|
||||||
# Get configuration total data
|
# Get configuration total data
|
||||||
def get_conf_total_data(config_name):
|
def get_conf_total_data(config_name):
|
||||||
sem.acquire()
|
sem.acquire(timeout=1)
|
||||||
|
db = TinyDB(os.path.join(db_path, config_name + ".json"))
|
||||||
db = TinyDB('db/' + config_name + '.json')
|
|
||||||
upload_total = 0
|
upload_total = 0
|
||||||
download_total = 0
|
download_total = 0
|
||||||
for i in db.all():
|
for i in db.all():
|
||||||
@ -414,9 +417,8 @@ def checkKeyMatch(private_key, public_key, config_name):
|
|||||||
if result['status'] == 'failed':
|
if result['status'] == 'failed':
|
||||||
return result
|
return result
|
||||||
else:
|
else:
|
||||||
sem.acquire()
|
sem.acquire(timeout=1)
|
||||||
|
db = TinyDB(os.path.join(db_path, config_name + ".json"))
|
||||||
db = TinyDB('db/' + config_name + '.json')
|
|
||||||
peers = Query()
|
peers = Query()
|
||||||
match = db.search(peers.id == result['data'])
|
match = db.search(peers.id == result['data'])
|
||||||
if len(match) != 1 or result['data'] != public_key:
|
if len(match) != 1 or result['data'] != public_key:
|
||||||
@ -430,10 +432,8 @@ def checkKeyMatch(private_key, public_key, config_name):
|
|||||||
|
|
||||||
# Check if there is repeated allowed IP
|
# Check if there is repeated allowed IP
|
||||||
def check_repeat_allowed_IP(public_key, ip, config_name):
|
def check_repeat_allowed_IP(public_key, ip, config_name):
|
||||||
|
sem.acquire(timeout=1)
|
||||||
sem.acquire()
|
db = TinyDB(os.path.join(db_path, config_name + ".json"))
|
||||||
|
|
||||||
db = TinyDB('db/' + config_name + '.json')
|
|
||||||
peers = Query()
|
peers = Query()
|
||||||
peer = db.search(peers.id == public_key)
|
peer = db.search(peers.id == public_key)
|
||||||
if len(peer) != 1:
|
if len(peer) != 1:
|
||||||
@ -754,7 +754,8 @@ def conf(config_name):
|
|||||||
def get_conf(config_name):
|
def get_conf(config_name):
|
||||||
config_interface = read_conf_file_interface(config_name)
|
config_interface = read_conf_file_interface(config_name)
|
||||||
search = request.args.get('search')
|
search = request.args.get('search')
|
||||||
if len(search) == 0: search = ""
|
if len(search) == 0:
|
||||||
|
search = ""
|
||||||
search = urllib.parse.unquote(search)
|
search = urllib.parse.unquote(search)
|
||||||
config = configparser.ConfigParser(strict=False)
|
config = configparser.ConfigParser(strict=False)
|
||||||
config.read(dashboard_conf)
|
config.read(dashboard_conf)
|
||||||
@ -772,15 +773,20 @@ def get_conf(config_name):
|
|||||||
"public_key": get_conf_pub_key(config_name),
|
"public_key": get_conf_pub_key(config_name),
|
||||||
"listen_port": get_conf_listen_port(config_name),
|
"listen_port": get_conf_listen_port(config_name),
|
||||||
"running_peer": get_conf_running_peer_number(config_name),
|
"running_peer": get_conf_running_peer_number(config_name),
|
||||||
"conf_address": conf_address
|
"conf_address": conf_address,
|
||||||
|
"wg_ip": config.get("Peers","remote_endpoint"),
|
||||||
|
"sort_tag": sort,
|
||||||
|
"dashboard_refresh_interval": int(config.get("Server", "dashboard_refresh_interval")),
|
||||||
|
"peer_display_mode": peer_display_mode
|
||||||
}
|
}
|
||||||
if conf_data['status'] == "stopped":
|
if conf_data['status'] == "stopped":
|
||||||
conf_data['checked'] = "nope"
|
conf_data['checked'] = "nope"
|
||||||
else:
|
else:
|
||||||
conf_data['checked'] = "checked"
|
conf_data['checked'] = "checked"
|
||||||
print(config.get("Peers","remote_endpoint"))
|
print(config.get("Peers","remote_endpoint"))
|
||||||
return render_template('get_conf.html', conf_data=conf_data, wg_ip=config.get("Peers","remote_endpoint"), sort_tag=sort,
|
return jsonify(conf_data)
|
||||||
dashboard_refresh_interval=int(config.get("Server", "dashboard_refresh_interval")), peer_display_mode=peer_display_mode)
|
# return render_template('get_conf.html', conf_data=conf_data, wg_ip=config.get("Peers","remote_endpoint"), sort_tag=sort,
|
||||||
|
# dashboard_refresh_interval=int(config.get("Server", "dashboard_refresh_interval")), peer_display_mode=peer_display_mode)
|
||||||
|
|
||||||
# Turn on / off a configuration
|
# Turn on / off a configuration
|
||||||
@app.route('/switch/<config_name>', methods=['GET'])
|
@app.route('/switch/<config_name>', methods=['GET'])
|
||||||
@ -805,9 +811,8 @@ def switch(config_name):
|
|||||||
# Add peer
|
# Add peer
|
||||||
@app.route('/add_peer/<config_name>', methods=['POST'])
|
@app.route('/add_peer/<config_name>', methods=['POST'])
|
||||||
def add_peer(config_name):
|
def add_peer(config_name):
|
||||||
sem.acquire()
|
sem.acquire(timeout=1)
|
||||||
|
db = TinyDB(os.path.join(db_path, config_name + ".json"))
|
||||||
db = TinyDB('db/' + config_name + '.json')
|
|
||||||
peers = Query()
|
peers = Query()
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
public_key = data['public_key']
|
public_key = data['public_key']
|
||||||
@ -875,9 +880,8 @@ def add_peer(config_name):
|
|||||||
def remove_peer(config_name):
|
def remove_peer(config_name):
|
||||||
if get_conf_status(config_name) == "stopped":
|
if get_conf_status(config_name) == "stopped":
|
||||||
return "Your need to turn on " + config_name + " first."
|
return "Your need to turn on " + config_name + " first."
|
||||||
sem.acquire()
|
sem.acquire(timeout=1)
|
||||||
|
db = TinyDB(os.path.join(db_path, config_name + ".json"))
|
||||||
db = TinyDB('db/' + config_name + '.json')
|
|
||||||
peers = Query()
|
peers = Query()
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
delete_key = data['peer_id']
|
delete_key = data['peer_id']
|
||||||
@ -911,9 +915,8 @@ def save_peer_setting(config_name):
|
|||||||
DNS = data['DNS']
|
DNS = data['DNS']
|
||||||
allowed_ip = data['allowed_ip']
|
allowed_ip = data['allowed_ip']
|
||||||
endpoint_allowed_ip = data['endpoint_allowed_ip']
|
endpoint_allowed_ip = data['endpoint_allowed_ip']
|
||||||
sem.acquire()
|
sem.acquire(timeout=1)
|
||||||
|
db = TinyDB(os.path.join(db_path, config_name + ".json"))
|
||||||
db = TinyDB('db/' + config_name + '.json')
|
|
||||||
peers = Query()
|
peers = Query()
|
||||||
if len(db.search(peers.id == id)) == 1:
|
if len(db.search(peers.id == id)) == 1:
|
||||||
check_ip = check_repeat_allowed_IP(id, allowed_ip, config_name)
|
check_ip = check_repeat_allowed_IP(id, allowed_ip, config_name)
|
||||||
@ -984,9 +987,8 @@ def save_peer_setting(config_name):
|
|||||||
def get_peer_name(config_name):
|
def get_peer_name(config_name):
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
id = data['id']
|
id = data['id']
|
||||||
sem.acquire()
|
sem.acquire(timeout=1)
|
||||||
|
db = TinyDB(os.path.join(db_path, config_name + ".json"))
|
||||||
db = TinyDB('db/' + config_name + '.json')
|
|
||||||
peers = Query()
|
peers = Query()
|
||||||
result = db.search(peers.id == id)
|
result = db.search(peers.id == id)
|
||||||
db.close()
|
db.close()
|
||||||
@ -1020,8 +1022,8 @@ def check_key_match(config_name):
|
|||||||
@app.route("/qrcode/<config_name>", methods=['GET'])
|
@app.route("/qrcode/<config_name>", methods=['GET'])
|
||||||
def generate_qrcode(config_name):
|
def generate_qrcode(config_name):
|
||||||
id = request.args.get('id')
|
id = request.args.get('id')
|
||||||
sem.acquire()
|
sem.acquire(timeout=1)
|
||||||
db = TinyDB('db/' + config_name + '.json')
|
db = TinyDB(os.path.join(db_path, config_name + ".json"))
|
||||||
peers = Query()
|
peers = Query()
|
||||||
get_peer = db.search(peers.id == id)
|
get_peer = db.search(peers.id == id)
|
||||||
config = get_dashboard_conf()
|
config = get_dashboard_conf()
|
||||||
@ -1060,8 +1062,8 @@ def generate_qrcode(config_name):
|
|||||||
def download(config_name):
|
def download(config_name):
|
||||||
print(request.headers.get('User-Agent'))
|
print(request.headers.get('User-Agent'))
|
||||||
id = request.args.get('id')
|
id = request.args.get('id')
|
||||||
sem.acquire()
|
sem.acquire(timeout=1)
|
||||||
db = TinyDB('db/' + config_name + '.json')
|
db = TinyDB(os.path.join(db_path, config_name + ".json"))
|
||||||
peers = Query()
|
peers = Query()
|
||||||
get_peer = db.search(peers.id == id)
|
get_peer = db.search(peers.id == id)
|
||||||
config = get_dashboard_conf()
|
config = get_dashboard_conf()
|
||||||
@ -1123,8 +1125,9 @@ Dashboard Tools Related
|
|||||||
@app.route('/get_ping_ip', methods=['POST'])
|
@app.route('/get_ping_ip', methods=['POST'])
|
||||||
def get_ping_ip():
|
def get_ping_ip():
|
||||||
config = request.form['config']
|
config = request.form['config']
|
||||||
sem.acquire()
|
sem.acquire(timeout=1)
|
||||||
db = TinyDB('db/' + config + '.json')
|
db = TinyDB(os.path.join(db_path, config_name + ".json"))
|
||||||
|
|
||||||
html = ""
|
html = ""
|
||||||
for i in db.all():
|
for i in db.all():
|
||||||
html += '<optgroup label="' + i['name'] + ' - ' + i['id'] + '">'
|
html += '<optgroup label="' + i['name'] + ' - ' + i['id'] + '">'
|
||||||
@ -1184,8 +1187,8 @@ Dashboard Initialization
|
|||||||
"""
|
"""
|
||||||
def init_dashboard():
|
def init_dashboard():
|
||||||
# Set Default INI File
|
# Set Default INI File
|
||||||
if not os.path.isfile("wg-dashboard.ini"):
|
if not os.path.isfile(dashboard_conf):
|
||||||
conf_file = open("wg-dashboard.ini", "w+")
|
conf_file = open(dashboard_conf, "w+")
|
||||||
config = configparser.ConfigParser(strict=False)
|
config = configparser.ConfigParser(strict=False)
|
||||||
config.read(dashboard_conf)
|
config.read(dashboard_conf)
|
||||||
# Defualt dashboard account setting
|
# Defualt dashboard account setting
|
||||||
@ -1252,7 +1255,7 @@ if __name__ == "__main__":
|
|||||||
init_dashboard()
|
init_dashboard()
|
||||||
update = check_update()
|
update = check_update()
|
||||||
config = configparser.ConfigParser(strict=False)
|
config = configparser.ConfigParser(strict=False)
|
||||||
config.read('wg-dashboard.ini')
|
config.read(dashboard_conf)
|
||||||
app_ip = config.get("Server", "app_ip")
|
app_ip = config.get("Server", "app_ip")
|
||||||
app_port = config.get("Server", "app_port")
|
app_port = config.get("Server", "app_port")
|
||||||
wg_conf_path = config.get("Server", "wg_conf_path")
|
wg_conf_path = config.get("Server", "wg_conf_path")
|
||||||
@ -1262,7 +1265,7 @@ else:
|
|||||||
init_dashboard()
|
init_dashboard()
|
||||||
update = check_update()
|
update = check_update()
|
||||||
config = configparser.ConfigParser(strict=False)
|
config = configparser.ConfigParser(strict=False)
|
||||||
config.read('wg-dashboard.ini')
|
config.read(dashboard_conf)
|
||||||
app_ip = config.get("Server", "app_ip")
|
app_ip = config.get("Server", "app_ip")
|
||||||
app_port = config.get("Server", "app_port")
|
app_port = config.get("Server", "app_port")
|
||||||
wg_conf_path = config.get("Server", "wg_conf_path")
|
wg_conf_path = config.get("Server", "wg_conf_path")
|
||||||
|
@ -303,7 +303,7 @@ $("body").on("change", "#sort_by_dropdown", function (){
|
|||||||
headers:{"Content-Type": "application/json"},
|
headers:{"Content-Type": "application/json"},
|
||||||
url: "/update_dashboard_sort",
|
url: "/update_dashboard_sort",
|
||||||
success: function (res){
|
success: function (res){
|
||||||
location.reload()
|
load_data($('#search_peer_textbox').val())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -336,26 +336,41 @@ function copyToClipboard(element) {
|
|||||||
|
|
||||||
// Update Interval
|
// Update Interval
|
||||||
$("body").on("click", ".update_interval", function(){
|
$("body").on("click", ".update_interval", function(){
|
||||||
|
$(".interval-btn-group button").removeClass("active");
|
||||||
|
$(this).addClass("active");
|
||||||
$.ajax({
|
$.ajax({
|
||||||
method:"POST",
|
method:"POST",
|
||||||
data: "interval="+$(this).attr("refresh-interval"),
|
data: "interval="+$(this).data("refresh-interval"),
|
||||||
url: "/update_dashboard_refresh_interval",
|
url: "/update_dashboard_refresh_interval",
|
||||||
success: function (res){
|
success: function (res){
|
||||||
location.reload()
|
|
||||||
|
load_data($('#search_peer_textbox').val())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
$("body").on("click", ".refresh", function (){
|
$("body").on("click", ".refresh", function (){
|
||||||
load_data($('#search_peer_textbox').val());
|
load_data($('#search_peer_textbox').val());
|
||||||
});
|
});
|
||||||
|
|
||||||
// Switch display mode
|
// Switch display mode
|
||||||
$("body").on("click", ".display_mode", function(){
|
$("body").on("click", ".display_mode", function(){
|
||||||
|
$(".display-btn-group button").removeClass("active");
|
||||||
|
$(this).addClass("active");
|
||||||
|
let display_mode = $(this).data("display-mode");
|
||||||
$.ajax({
|
$.ajax({
|
||||||
method:"GET",
|
method:"GET",
|
||||||
url: "/switch_display_mode/"+$(this).attr("display-mode"),
|
url: "/switch_display_mode/"+$(this).data("display-mode"),
|
||||||
success: function (res){
|
success: function (res){
|
||||||
location.reload()
|
// load_data($('#search_peer_textbox').val())
|
||||||
|
if (display_mode === "list"){
|
||||||
|
Array($(".peer_list").children()).forEach(function(child){
|
||||||
|
$(child).removeClass().addClass("col-12");
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
Array($(".peer_list").children()).forEach(function(child){
|
||||||
|
$(child).removeClass().addClass("col-sm-6 col-lg-4");
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
@ -1,4 +1,4 @@
|
|||||||
<html>
|
<html lang="en">
|
||||||
{% with title=title%}
|
{% with title=title%}
|
||||||
{% include "header.html"%}
|
{% include "header.html"%}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
@ -11,7 +11,103 @@
|
|||||||
<input type="text" class="form-control" id="search_peer_textbox" placeholder="Search Peer..." value="" style="display: none">
|
<input type="text" class="form-control" id="search_peer_textbox" placeholder="Search Peer..." value="" style="display: none">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="config_body"></div>
|
<div id="config_body">
|
||||||
|
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4 mt-4 mb-4">
|
||||||
|
<div class="info mt-4">
|
||||||
|
<div id="config_info_alert"></div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<small class="text-muted"><strong>CONFIGURATION</strong></small>
|
||||||
|
<h1 class="mb-3"><samp id="conf_name">{{ title }}</samp></h1>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<small class="text-muted"><strong>ACTION</strong></small><br>
|
||||||
|
<div id="conf_status_btn"></div>
|
||||||
|
<div class="spinner-border text-primary" role="status" style="display: none; margin-top: 10px">
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-100"></div>
|
||||||
|
<div class="col">
|
||||||
|
<small class="text-muted"><strong>STATUS</strong></small>
|
||||||
|
<h6 style="text-transform: uppercase;" id="conf_status"></h6>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<small class="text-muted"><strong>CONNECTED PEERS</strong></small>
|
||||||
|
<h6 style="text-transform: uppercase;" id="conf_connected_peers"></h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<small class="text-muted"><strong>TOTAL DATA USAGE</strong></small>
|
||||||
|
<h6 style="text-transform: uppercase;" id="conf_total_data_usage"></h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<small class="text-muted"><strong>TOTAL RECEIVED</strong></small>
|
||||||
|
<h6 style="text-transform: uppercase;" id="conf_total_data_received"></h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<small class="text-muted"><strong>TOTAL SENT</strong></small>
|
||||||
|
<h6 style="text-transform: uppercase;" id="conf_total_data_sent"></h6>
|
||||||
|
</div>
|
||||||
|
<div class="w-100"></div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<small class="text-muted">
|
||||||
|
<strong>PUBLIC KEY</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="key" id="conf_public_key"></samp></h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<small class="text-muted"><strong>LISTEN PORT</strong></small>
|
||||||
|
<h6 style="text-transform: uppercase;"><samp id="conf_listen_port"></samp></h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<small class="text-muted"><strong>ADDRESS</strong></small>
|
||||||
|
<h6 style="text-transform: uppercase;"><samp id="conf_address"></samp></h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="button-div mb-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sort_by_dropdown"><small class="text-muted">Sort Peers By</small></label>
|
||||||
|
<select class="form-control" id="sort_by_dropdown">
|
||||||
|
<option value="status">Status</option>
|
||||||
|
<option value="name">Name</option>
|
||||||
|
<option value="allowed_ip">Allowed IP</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label><small class="text-muted">Refresh Interval</small></label><br>
|
||||||
|
<div class="btn-group interval-btn-group" role="group" style="width: 100%">
|
||||||
|
<button style="width: 20%" type="button" class="btn btn-outline-primary btn-group-label refresh"><i class="bi bi-arrow-repeat"></i></button>
|
||||||
|
<button style="width: 20%" type="button" class="btn btn-outline-primary update_interval" data-refresh-interval="5000">5s</button>
|
||||||
|
<button style="width: 20%" type="button" class="btn btn-outline-primary update_interval" data-refresh-interval="10000">10s</button>
|
||||||
|
<button style="width: 20%" type="button" class="btn btn-outline-primary update_interval" data-refresh-interval="30000">30s</button>
|
||||||
|
<button style="width: 20%" type="button" class="btn btn-outline-primary update_interval" data-refresh-interval="60000">1m</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label><small class="text-muted">Display Mode</small></label><br>
|
||||||
|
<div class="btn-group display-btn-group" role="group" style="width: 100%">
|
||||||
|
<button style="width: 20%" type="button" class="btn btn-outline-primary display_mode" data-display-mode="grid"><i class="bi bi-grid-fill" style="font-size: 1.5rem;"></i></button>
|
||||||
|
<button style="width: 20%" type="button" class="btn btn-outline-primary display_mode" data-display-mode="list"><i class="bi bi-list" style="font-size: 1.5rem;"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-primary add_btn" data-toggle="modal" data-target="#add_modal">
|
||||||
|
<i class="bi bi-plus-circle-fill" style=""></i> Add Peer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row peer_list"></div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal fade" id="add_modal" data-backdrop="static" data-keyboard="false" tabindex="-1"
|
<div class="modal fade" id="add_modal" data-backdrop="static" data-keyboard="false" tabindex="-1"
|
||||||
aria-labelledby="staticBackdropLabel" aria-hidden="true">
|
aria-labelledby="staticBackdropLabel" aria-hidden="true">
|
||||||
@ -224,6 +320,8 @@
|
|||||||
</body>
|
</body>
|
||||||
{% include "footer.html" %}
|
{% include "footer.html" %}
|
||||||
<script>
|
<script>
|
||||||
|
let load_timeout;
|
||||||
|
let load_interval = 0;
|
||||||
var conf_name = "{{ conf_data['name'] }}"
|
var conf_name = "{{ conf_data['name'] }}"
|
||||||
$(".sb-"+conf_name+"-url").addClass("active");
|
$(".sb-"+conf_name+"-url").addClass("active");
|
||||||
// Progress Bar
|
// Progress Bar
|
||||||
@ -252,6 +350,7 @@
|
|||||||
}
|
}
|
||||||
function load_data(search){
|
function load_data(search){
|
||||||
startProgressBar()
|
startProgressBar()
|
||||||
|
let result = '';
|
||||||
$.ajax({
|
$.ajax({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/get_config/"+conf_name+"?search="+encodeURIComponent(search),
|
url: "/get_config/"+conf_name+"?search="+encodeURIComponent(search),
|
||||||
@ -259,17 +358,95 @@
|
|||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
},
|
},
|
||||||
success: function (response){
|
success: function (response){
|
||||||
$("#config_body").html(response);
|
{# Check all status #}
|
||||||
|
if (response["listen_port"] === "" && response["status"] === "stopped"){
|
||||||
|
$("config_info_alert").append('<div class="alert alert-warning" role="alert">Peer QR Code and configuration file download required a specified <strong>Listen Port</strong>.</div>')
|
||||||
|
}
|
||||||
|
if (response["conf_address"] === "N/A"){
|
||||||
|
$("config_info_alert").append('<div class="alert alert-warning" role="alert">Configuration <strong>Address</strong> need to be specified to have peers connect to it.</div>')
|
||||||
|
}
|
||||||
|
{# Status Button #}
|
||||||
|
if (response["checked"] === "checked"){
|
||||||
|
$("#conf_status_btn").html('<a href="#" id="'+response["name"]+'" '+response["checked"]+' class="switch text-primary"><i class="bi bi-toggle2-on"></i> ON</a>');
|
||||||
|
}else{
|
||||||
|
$("#conf_status_btn").html('<a href="#" id="'+response["name"]+'" '+response["checked"]+' class="switch text-primary"><i class="bi bi-toggle2-off"></i> OFF</a>');
|
||||||
|
}
|
||||||
|
$("#sort_by_dropdown option").removeAttr("selected");
|
||||||
|
$("#sort_by_dropdown option[value="+response["sort_tag"]+"]").attr("selected", "selected");
|
||||||
|
$(".interval-btn-group button").removeClass("active");
|
||||||
|
$("button[data-refresh-interval="+response["dashboard_refresh_interval"]+"]").addClass("active");
|
||||||
|
$(".display-btn-group button").removeClass("active");
|
||||||
|
$("button[data-display-mode="+response["peer_display_mode"]+"]").addClass("active");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$("#conf_status").html(response["status"]+'<span class="dot dot-'+response["status"]+'"></span>');
|
||||||
|
$("#conf_connected_peers").html(response["running_peer"]);
|
||||||
|
$("#conf_total_data_usage").html(response["total_data_usage"][0]+" GB");
|
||||||
|
$("#conf_total_data_received").html(response["total_data_usage"][1]+" GB");
|
||||||
|
$("#conf_total_data_sent").html(response["total_data_usage"][2]+" GB");
|
||||||
|
$("#conf_public_key").html(response["public_key"]);
|
||||||
|
$("#conf_listen_port").html(response["listen_port"] === "" ? "N/A":response["listen_port"]);
|
||||||
|
$("#conf_address").html(response["conf_address"]);
|
||||||
|
|
||||||
|
if (response["peer_data"].length === 0){
|
||||||
|
$(".peer_list").html('<div class="col-12" style="text-align: center; margin-top: 1.5rem"><h3 class="text-muted">Oops! No peers found ‘︿’</h3></div>');
|
||||||
|
}else{
|
||||||
|
let display_mode = response["peer_display_mode"] === "list" ? "col-12" : "col-sm-6 col-lg-4";
|
||||||
|
response["peer_data"].forEach(function(peer){
|
||||||
|
let spliter = '<div class="w-100"></div>';
|
||||||
|
let peer_name = '<div class="col-sm"><h4>'+ (peer["name"] === "" ? "Untitled" : peer["name"]) +'</h4></div>';
|
||||||
|
let peer_status = '<div class="col-6"><small class="text-muted"><strong>STATUS</strong></small> <h6 style="text-transform: uppercase;" class="mb-2 h6-dot-'+peer["status"]+'"><span class="dot dot-'+peer["status"]+'" style="margin-left: 0 !important;margin-top: 5px"></span></h6></div>'
|
||||||
|
let peer_transfer = '<div class="col-6 peer_data_group" style="text-align: right"> <small class="text-muted"><strong>TRANSFER</strong></small> <p class="text-primary" style="text-transform: uppercase; margin-bottom: 0;"><small><i class="bi bi-arrow-down-right"></i>'+peer["total_receive"]+' GB</small></p> <p class="text-success" style="text-transform: uppercase; margin-bottom: 0"><small><i class="bi bi-arrow-up-right"></i> '+peer["total_sent"]+' 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"> <button type="button" class="btn btn-outline-primary btn-setting-peer btn-control" id="'+peer["id"]+'" data-toggle="modal"><i class="bi bi-gear-fill"></i></button> <button type="button" class="btn btn-outline-danger btn-delete-peer btn-control" id="'+peer["id"]+'" data-toggle="modal"><i class="bi bi-x-circle-fill"></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" img_src="/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>';
|
||||||
|
}
|
||||||
|
peer_control += '</div>';
|
||||||
|
let html = '<div class="'+display_mode+'">' +
|
||||||
|
'<div class="card mb-3 card-'+peer["status"]+'">' +
|
||||||
|
'<div class="card-body">' +
|
||||||
|
'<div class="row">'
|
||||||
|
+ peer_name
|
||||||
|
+ spliter
|
||||||
|
+ peer_status
|
||||||
|
+ peer_transfer
|
||||||
|
+ peer_key
|
||||||
|
+ peer_allowed_ip
|
||||||
|
+ peer_latest_handshake
|
||||||
|
+ spliter
|
||||||
|
+ peer_endpoint
|
||||||
|
+ spliter
|
||||||
|
+ peer_control
|
||||||
|
+ '</div>' +
|
||||||
|
'</div></div>' +
|
||||||
|
'</div></div>';
|
||||||
|
result += html;
|
||||||
|
})
|
||||||
|
$(".peer_list").html(result);
|
||||||
|
if (response["dashboard_refresh_interval"] !== load_interval){
|
||||||
|
load_interval = response["dashboard_refresh_interval"];
|
||||||
|
clearInterval(load_timeout);
|
||||||
|
load_timeout = setInterval(function (){
|
||||||
|
load_data($('#search_peer_textbox').val());
|
||||||
|
},response["dashboard_refresh_interval"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{#$("#config_body").html(response);#}
|
||||||
$("#search_peer_textbox").css("display", "block")
|
$("#search_peer_textbox").css("display", "block")
|
||||||
endProgressBar()
|
endProgressBar()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
$(document).ready(function(){
|
$(function(){
|
||||||
load_data($('#search_peer_textbox').val());
|
load_data($('#search_peer_textbox').val());
|
||||||
setInterval(function(){
|
{#setInterval(function(){#}
|
||||||
load_data($('#search_peer_textbox').val());
|
{# load_data($('#search_peer_textbox').val());#}
|
||||||
}, {{dashboard_refresh_interval}})
|
{#}, {{dashboard_refresh_interval}})#}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<script src="{{ url_for('static',filename='js/configuration.js') }}"></script>
|
<script src="{{ url_for('static',filename='js/configuration.js') }}"></script>
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
<script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||||
<script src="{{ url_for('static',filename='js/bootstrap.bundle.js') }}"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
|
||||||
<script src="{{ url_for('static',filename='js/tools.js') }}"></script>
|
<script src="{{ url_for('static',filename='js/tools.js') }}"></script>
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4 mt-4 mb-4">
|
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4 mt-4 mb-4">
|
||||||
<div class="info mt-4">
|
<div class="info mt-4">
|
||||||
{% if conf_data['listen_port'] == "" and conf_data['status'] == "stopped" %}
|
{% if conf_data['listen_port'] == "" and conf_data['status'] == "stopped" %}
|
||||||
<div class="alert alert-warning" role="alert">
|
<div class="alert alert-warning" role="alert">Peer QR Code and configuration file download required a specified <strong>Listen Port</strong>.</div>
|
||||||
Peer QR Code and configuration file download required a specified <strong>Listen Port</strong>.
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if conf_data['conf_address'] == "N/A" %}
|
{% if conf_data['conf_address'] == "N/A" %}
|
||||||
<div class="alert alert-warning" role="alert">
|
<div class="alert alert-warning" role="alert">
|
||||||
@ -51,7 +49,7 @@
|
|||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
<strong>PUBLIC KEY</strong>
|
<strong>PUBLIC KEY</strong>
|
||||||
<strong style="margin-left: auto!important; opacity: 0; transition: 0.2s ease-in-out" class="text-primary">CLICK TO COPY</strong></small>
|
<strong style="margin-left: auto!important; opacity: 0; transition: 0.2s ease-in-out" class="text-primary">CLICK TO COPY</strong>
|
||||||
</small>
|
</small>
|
||||||
<h6><samp class="key">{{conf_data['public_key']}}</samp></h6>
|
<h6><samp class="key">{{conf_data['public_key']}}</samp></h6>
|
||||||
</div>
|
</div>
|
||||||
@ -63,7 +61,6 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
{{conf_data['listen_port']}}
|
{{conf_data['listen_port']}}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</samp></h6>
|
</samp></h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
@ -114,9 +111,7 @@
|
|||||||
|
|
||||||
<div class="row peer_list">
|
<div class="row peer_list">
|
||||||
{% if conf_data['peer_data']|length == 0 %}
|
{% if conf_data['peer_data']|length == 0 %}
|
||||||
<div class="col-12" style="text-align: center; margin-top: 1.5rem">
|
<div class="col-12" style="text-align: center; margin-top: 1.5rem"><h3 class="text-muted">Oops! No peers found ‘︿’</h3></div>
|
||||||
<h3 class="text-muted">Oops! No peers found ‘︿’</h3>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% for i in conf_data['peer_data']%}
|
{% for i in conf_data['peer_data']%}
|
||||||
@ -126,19 +121,6 @@
|
|||||||
<div class="col-sm-6 col-lg-4">
|
<div class="col-sm-6 col-lg-4">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="card mb-3 card-{{i['status']}}">
|
<div class="card mb-3 card-{{i['status']}}">
|
||||||
{# <div class="card-header">#}
|
|
||||||
{# <div class="row">#}
|
|
||||||
{# <div class="col">#}
|
|
||||||
{# <div class="card-header-body ">#}
|
|
||||||
{# {% if not i['name']%}#}
|
|
||||||
{# {{ "Untitled" }}#}
|
|
||||||
{# {% else %}#}
|
|
||||||
{# {{i['name']}}#}
|
|
||||||
{# {% endif %}#}
|
|
||||||
{# </div>#}
|
|
||||||
{# </div>#}
|
|
||||||
{# </div>#}
|
|
||||||
{# </div>#}
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
@ -170,8 +152,6 @@
|
|||||||
<small class="text-muted"><strong>ALLOWED IP</strong></small>
|
<small class="text-muted"><strong>ALLOWED IP</strong></small>
|
||||||
<h6 style="text-transform: uppercase;">{{i['allowed_ip']}}</h6>
|
<h6 style="text-transform: uppercase;">{{i['allowed_ip']}}</h6>
|
||||||
</div>
|
</div>
|
||||||
{# <div class="w-100"></div>#}
|
|
||||||
|
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<small class="text-muted"><strong>LATEST HANDSHAKE</strong></small>
|
<small class="text-muted"><strong>LATEST HANDSHAKE</strong></small>
|
||||||
<h6 style="text-transform: uppercase;">{{i['latest_handshake']}}</h6>
|
<h6 style="text-transform: uppercase;">{{i['latest_handshake']}}</h6>
|
||||||
@ -182,12 +162,7 @@
|
|||||||
<h6 style="text-transform: uppercase;">{{i['endpoint']}}</h6>
|
<h6 style="text-transform: uppercase;">{{i['endpoint']}}</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-100"></div>
|
<div class="w-100"></div>
|
||||||
<div class="col-sm">
|
<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="{{i['id']}}" data-toggle="modal"><i class="bi bi-gear-fill"></i></button><button type="button" class="btn btn-outline-danger btn-delete-peer btn-control" id="{{i['id']}}" data-toggle="modal"><i class="bi bi-x-circle-fill"></i></button>
|
||||||
<hr>
|
|
||||||
<div class="button-group" style="display:flex">
|
|
||||||
|
|
||||||
<button type="button" class="btn btn-outline-primary btn-setting-peer btn-control" id="{{i['id']}}" data-toggle="modal"><i class="bi bi-gear-fill"></i></button>
|
|
||||||
<button type="button" class="btn btn-outline-danger btn-delete-peer btn-control" id="{{i['id']}}" data-toggle="modal"><i class="bi bi-x-circle-fill"></i></button>
|
|
||||||
{% if i['private_key'] %}
|
{% if i['private_key'] %}
|
||||||
<div class="share_peer_btn_group" style="margin-left: auto !important; display: inline">
|
<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" img_src="/qrcode/{{ conf_data['name'] }}?id={{ i['id']|urlencode }}">
|
<button type="button" class="btn btn-outline-success btn-qrcode-peer btn-control" img_src="/qrcode/{{ conf_data['name'] }}?id={{ i['id']|urlencode }}">
|
||||||
@ -204,11 +179,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{%endfor%}
|
{%endfor%}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<title>WGDashboard | {{ title }}</title>
|
<title>WGDashboard | {{ title }}</title>
|
||||||
<link rel="icon" href="{{ url_for('static',filename='img/logo.png') }}"/>
|
<link rel="icon" href="{{ url_for('static',filename='img/logo.png') }}"/>
|
||||||
<link rel="stylesheet" href="{{ url_for('static',filename='css/bootstrap.min.css') }}">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
|
||||||
<link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='css/dashboard.css') }}">
|
<link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='css/dashboard.css') }}">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css">
|
||||||
</head>
|
</head>
|
@ -46,9 +46,9 @@ install_wgd(){
|
|||||||
then mkdir "log"
|
then mkdir "log"
|
||||||
fi
|
fi
|
||||||
printf "| Upgrading pip |\n"
|
printf "| Upgrading pip |\n"
|
||||||
python3 -m pip install -U pip
|
python3 -m pip install -U pip > /dev/null 2>&1
|
||||||
printf "| Installing latest Python dependencies |\n"
|
printf "| Installing latest Python dependencies |\n"
|
||||||
python3 -m pip install -U -r requirements.txt
|
python3 -m pip install -U -r requirements.txt > /dev/null 2>&1
|
||||||
printf "| WGDashboard installed successfully! |\n"
|
printf "| WGDashboard installed successfully! |\n"
|
||||||
printf "| Enter ./wgd start to start the dashboard |\n"
|
printf "| Enter ./wgd start to start the dashboard |\n"
|
||||||
}
|
}
|
||||||
@ -102,9 +102,9 @@ update_wgd() {
|
|||||||
git pull
|
git pull
|
||||||
# git pull https://github.com/donaldzou/wireguard-dashboard.git $new_ver --force > /dev/null 2>&1
|
# git pull https://github.com/donaldzou/wireguard-dashboard.git $new_ver --force > /dev/null 2>&1
|
||||||
printf "| Upgrading pip |\n"
|
printf "| Upgrading pip |\n"
|
||||||
python3 -m pip install -U pip
|
python3 -m pip install -U pip > /dev/null 2>&1
|
||||||
printf "| Installing latest Python dependencies |\n"
|
printf "| Installing latest Python dependencies |\n"
|
||||||
python3 -m pip install -U -r requirements.txt
|
python3 -m pip install -U -r requirements.txt > /dev/null 2>&1
|
||||||
printf "| Update Successfully! |\n"
|
printf "| Update Successfully! |\n"
|
||||||
printf "%s\n" "$dashes"
|
printf "%s\n" "$dashes"
|
||||||
rm wgd.sh.old
|
rm wgd.sh.old
|
||||||
|
Loading…
Reference in New Issue
Block a user