1
0
mirror of https://github.com/donaldzou/WGDashboard.git synced 2024-11-22 15:20:09 +01:00
This commit is contained in:
Donald Cheng Hong Zou 2021-04-03 14:06:21 -04:00
parent 7e770b9b5d
commit 0c3960d8d9
3 changed files with 247 additions and 122 deletions

View File

@ -4,16 +4,21 @@ import subprocess
from datetime import datetime, date, time, timedelta from datetime import datetime, date, time, timedelta
from operator import itemgetter from operator import itemgetter
from tinydb import TinyDB, Query from tinydb import TinyDB, Query
import threading
conf_location = "/etc/wireguard" conf_location = "/etc/wireguard"
app = Flask("Wireguard Dashboard") app = Flask("Wireguard Dashboard")
app.config['TEMPLATES_AUTO_RELOAD'] = True app.config['TEMPLATES_AUTO_RELOAD'] = True
css = "" css = ""
conf_data = {} conf_data = {}
def get_conf_peer_key(config_name): def get_conf_peer_key(config_name):
keys = [] keys = []
try: peer_key = subprocess.check_output("wg show "+config_name+" peers", shell=True) try:
except Exception: return "stopped" peer_key = subprocess.check_output("wg show " + config_name + " peers", shell=True)
except Exception:
return "stopped"
peer_key = peer_key.decode("UTF-8").split() peer_key = peer_key.decode("UTF-8").split()
for i in peer_key: keys.append(i) for i in peer_key: keys.append(i)
return keys return keys
@ -21,28 +26,68 @@ def get_conf_peer_key(config_name):
def get_conf_running_peer_number(config_name): def get_conf_running_peer_number(config_name):
running = 0 running = 0
#Get latest handshakes # Get latest handshakes
try: data_usage = subprocess.check_output("wg show "+config_name+" latest-handshakes", shell=True) try:
except Exception: return "stopped" data_usage = subprocess.check_output("wg show " + config_name + " latest-handshakes", shell=True)
except Exception:
return "stopped"
data_usage = data_usage.decode("UTF-8").split() data_usage = data_usage.decode("UTF-8").split()
count = 0 count = 0
now = datetime.now() now = datetime.now()
b = timedelta(minutes=2) b = timedelta(minutes=2)
for i in range(int(len(data_usage)/2)): for i in range(int(len(data_usage) / 2)):
minus = now - datetime.fromtimestamp(int(data_usage[count+1])) minus = now - datetime.fromtimestamp(int(data_usage[count + 1]))
if minus < b: if minus < b:
running += 1 running += 1
count += 2 count += 2
return running return running
def get_conf_peers_data(config_name): def get_conf_peers_data(config_name):
db = TinyDB('db/'+config_name+'.json') db = TinyDB('db/' + config_name + '.json')
peers = Query() peers = Query()
peer_data = {} peer_data = {}
# Read Configuration File Start
conf_location = "/etc/wireguard/wg0.conf"
f = open(conf_location, 'r')
file = f.read().split("\n")
conf_peer_data = {
"Interface": {},
"Peers": []
}
interface = []
peers_start = 0
for i in range(len(file)):
if file[i] == "[Peer]":
peers_start = i
break
else:
if len(file[i]) > 0:
if file[i] != "[Interface]":
tmp = file[i].replace(" ", "").split("=", 1)
conf_peer_data['Interface'][tmp[0]] = tmp[1]
conf_peers = file[peers_start:]
peer = -1
for i in conf_peers:
if i == "[Peer]":
peer += 1
conf_peer_data["Peers"].append({})
else:
if len(i) > 0:
tmp = i.replace(" ", "").split("=", 1)
if len(tmp) == 2:
conf_peer_data["Peers"][peer][tmp[0]] = tmp[1]
# Read Configuration File End
# Get key # Get key
try: peer_key = subprocess.check_output("wg show "+config_name+" peers", shell=True) try:
except Exception: return "stopped" peer_key = subprocess.check_output("wg show " + config_name + " peers", shell=True)
except Exception:
return "stopped"
peer_key = peer_key.decode("UTF-8").split() peer_key = peer_key.decode("UTF-8").split()
now = datetime.now()
current_time = now.strftime("%Y-%m-%d %H:%M:%S")
for i in peer_key: for i in peer_key:
peer_data[i] = {} peer_data[i] = {}
if not db.search(peers.id == i): if not db.search(peers.id == i):
@ -59,47 +104,51 @@ def get_conf_peers_data(config_name):
"traffic": [] "traffic": []
}) })
# Get transfer
try:
data_usage = subprocess.check_output("wg show " + config_name + " transfer", shell=True)
#Get transfer except Exception:
try: data_usage = subprocess.check_output("wg show "+config_name+" transfer", shell=True) return "stopped"
except Exception: return "stopped"
data_usage = data_usage.decode("UTF-8").split() data_usage = data_usage.decode("UTF-8").split()
count = 0 count = 0
upload_total = 0 for i in range(int(len(data_usage) / 3)):
download_total = 0 db.update({"total_receive": round(int(data_usage[count + 1]) / (1024 ** 3), 4),
total = 0 "total_sent": round(int(data_usage[count + 2]) / (1024 ** 3), 4),
for i in range(int(len(data_usage)/3)): "total_data": round((int(data_usage[count + 2]) + int(data_usage[count + 1])) / (1024 ** 3), 4)},
db.update({"total_receive": round(int(data_usage[count+1])/(1024**3),4), peers.id == data_usage[count])
"total_sent": round(int(data_usage[count+2])/(1024**3),4), peer_data[data_usage[count]]['total_receive'] = round(int(data_usage[count + 1]) / (1024 ** 3), 4)
"total_data": round((int(data_usage[count+2])+int(data_usage[count+1]))/(1024**3),4)}, peers.id == data_usage[count]) peer_data[data_usage[count]]['total_sent'] = round(int(data_usage[count + 2]) / (1024 ** 3), 4)
peer_data[data_usage[count]]['total_data'] = round(
peer_data[data_usage[count]]['total_receive'] = round(int(data_usage[count+1])/(1024**3),4) (int(data_usage[count + 2]) + int(data_usage[count + 1])) / (1024 ** 3), 4)
peer_data[data_usage[count]]['total_sent'] = round(int(data_usage[count+2])/(1024**3),4) traffic = db.search(peers.id == data_usage[count])[0]['traffic']
peer_data[data_usage[count]]['total_data'] = round((int(data_usage[count+2])+int(data_usage[count+1]))/(1024**3),4) traffic.append({"time": current_time, "total_receive": round(int(data_usage[count + 1]) / (1024 ** 3), 4),
"total_sent": round(int(data_usage[count + 2]) / (1024 ** 3), 4)})
db.update({"traffic": traffic}, peers.id == data_usage[count])
count += 3 count += 3
# Get endpoint
#Get endpoint try:
try: data_usage = subprocess.check_output("wg show "+config_name+" endpoints", shell=True) data_usage = subprocess.check_output("wg show " + config_name + " endpoints", shell=True)
except Exception: return "stopped" except Exception:
return "stopped"
data_usage = data_usage.decode("UTF-8").split() data_usage = data_usage.decode("UTF-8").split()
count = 0 count = 0
for i in range(int(len(data_usage)/2)): for i in range(int(len(data_usage) / 2)):
db.update({"endpoint": data_usage[count+1]}, peers.id == data_usage[count]) db.update({"endpoint": data_usage[count + 1]}, peers.id == data_usage[count])
peer_data[data_usage[count]]['endpoint'] = data_usage[count+1] peer_data[data_usage[count]]['endpoint'] = data_usage[count + 1]
count += 2 count += 2
#Get latest handshakes # Get latest handshakes
try: data_usage = subprocess.check_output("wg show "+config_name+" latest-handshakes", shell=True) try:
except Exception: return "stopped" data_usage = subprocess.check_output("wg show " + config_name + " latest-handshakes", shell=True)
except Exception:
return "stopped"
data_usage = data_usage.decode("UTF-8").split() data_usage = data_usage.decode("UTF-8").split()
count = 0 count = 0
now = datetime.now() now = datetime.now()
b = timedelta(minutes=2) b = timedelta(minutes=2)
for i in range(int(len(data_usage)/2)): for i in range(int(len(data_usage) / 2)):
minus = now - datetime.fromtimestamp(int(data_usage[count+1])) minus = now - datetime.fromtimestamp(int(data_usage[count + 1]))
status = "" status = ""
if minus < b: if minus < b:
peer_data[data_usage[count]]['status'] = "running" peer_data[data_usage[count]]['status'] = "running"
@ -111,75 +160,96 @@ def get_conf_peers_data(config_name):
db.update({"latest_handshake": str(minus).split(".")[0], "status": status}, peers.id == data_usage[count]) db.update({"latest_handshake": str(minus).split(".")[0], "status": status}, peers.id == data_usage[count])
peer_data[data_usage[count]]['latest_handshake'] = str(minus).split(".")[0] peer_data[data_usage[count]]['latest_handshake'] = str(minus).split(".")[0]
count += 2 count += 2
#Get allowed ip # Get allowed ip
try: data_usage = subprocess.check_output("wg show "+config_name+" allowed-ips", shell=True) for i in conf_peer_data["Peers"]:
except Exception: return "stopped" db.update({"allowed_ip":i['AllowedIPs']}, peers.id == i["PublicKey"])
data_usage = data_usage.decode("UTF-8").split()
count = 0 def getdb(config_name):
for i in range(int(len(data_usage)/2)): get_conf_peers_data(config_name)
db.update({"allowed_ip": data_usage[count+1]}, peers.id == data_usage[count]) db = TinyDB('db/' + config_name + '.json')
peer_data[data_usage[count]]['allowed_ip'] = data_usage[count+1]
count += 2
result = db.all() result = db.all()
result = sorted(result, key=lambda d: d['status']) result = sorted(result, key=lambda d: d['status'])
return result return result
def get_conf_pub_key(config_name): def get_conf_pub_key(config_name):
try: pub_key = subprocess.check_output("wg show "+config_name+" public-key", shell=True, stderr=subprocess.STDOUT) try:
except Exception: return "stopped" pub_key = subprocess.check_output("wg show " + config_name + " public-key", shell=True,
stderr=subprocess.STDOUT)
except Exception:
return "stopped"
return pub_key.decode("UTF-8") return pub_key.decode("UTF-8")
def get_conf_listen_port(config_name): def get_conf_listen_port(config_name):
try: pub_key = subprocess.check_output("wg show "+config_name+" listen-port", shell=True, stderr=subprocess.STDOUT) try:
except Exception: return "stopped" pub_key = subprocess.check_output("wg show " + config_name + " listen-port", shell=True,
stderr=subprocess.STDOUT)
except Exception:
return "stopped"
return pub_key.decode("UTF-8") return pub_key.decode("UTF-8")
def get_conf_total_data(config_name): def get_conf_total_data(config_name):
try: data_usage = subprocess.check_output("wg show "+config_name+" transfer", shell=True, stderr=subprocess.STDOUT) try:
except Exception: return "stopped" data_usage = subprocess.check_output("wg show " + config_name + " transfer", shell=True,
stderr=subprocess.STDOUT)
except Exception:
return "stopped"
data_usage = data_usage.decode("UTF-8").split() data_usage = data_usage.decode("UTF-8").split()
count = 0 count = 0
upload_total = 0 upload_total = 0
download_total = 0 download_total = 0
total = 0 total = 0
for i in range(int(len(data_usage)/3)): for i in range(int(len(data_usage) / 3)):
upload_total += int(data_usage[count+1]) upload_total += int(data_usage[count + 1])
download_total += int(data_usage[count+2]) download_total += int(data_usage[count + 2])
count += 3 count += 3
total = round(((((upload_total+download_total)/1024)/1024)/1024),4) total = round(((((upload_total + download_total) / 1024) / 1024) / 1024), 4)
upload_total = round(((((upload_total)/1024)/1024)/1024),4) upload_total = round(((((upload_total) / 1024) / 1024) / 1024), 4)
download_total = round(((((download_total)/1024)/1024)/1024),4) download_total = round(((((download_total) / 1024) / 1024) / 1024), 4)
return [total, upload_total, download_total] return [total, upload_total, download_total]
def get_conf_status(config_name): def get_conf_status(config_name):
try: status = subprocess.check_output("wg show "+config_name, shell=True, stderr=subprocess.STDOUT) try:
except Exception: return "stopped" status = subprocess.check_output("wg show " + config_name, shell=True, stderr=subprocess.STDOUT)
else: return "running" except Exception:
return "stopped"
else:
return "running"
def get_conf_list(): def get_conf_list():
conf = [] conf = []
for i in os.listdir(conf_location): for i in os.listdir(conf_location):
if ".conf" in i: if ".conf" in i:
i = i.replace('.conf','') i = i.replace('.conf', '')
temp = {"conf":i, "status":get_conf_status(i), "public_key": get_conf_pub_key(i)} temp = {"conf": i, "status": get_conf_status(i), "public_key": get_conf_pub_key(i)}
if temp['status'] == "running": if temp['status'] == "running":
temp['checked'] = 'checked' temp['checked'] = 'checked'
else: temp['checked'] = "" else:
temp['checked'] = ""
conf.append(temp) conf.append(temp)
conf = sorted(conf, key=itemgetter('status')) conf = sorted(conf, key=itemgetter('status'))
return conf return conf
@app.route('/',methods=['GET'])
def get_running_conf_list():
conf = []
for i in os.listdir(conf_location):
if ".conf" in i:
i = i.replace('.conf', '')
if get_conf_status(i) == "running":
conf.append(i)
return conf
@app.route('/', methods=['GET'])
def index(): def index():
return render_template('index.html', conf=get_conf_list()) return render_template('index.html', conf=get_conf_list())
@ -197,9 +267,10 @@ def conf(config_name):
conf_data['checked'] = "checked" conf_data['checked'] = "checked"
return render_template('configuration.html', conf=get_conf_list(), conf_data=conf_data) return render_template('configuration.html', conf=get_conf_list(), conf_data=conf_data)
@app.route('/get_config/<config_name>', methods=['GET']) @app.route('/get_config/<config_name>', methods=['GET'])
def get_conf(config_name): def get_conf(config_name):
db = TinyDB('db/'+config_name+'.json') db = TinyDB('db/' + config_name + '.json')
conf_data = { conf_data = {
"name": config_name, "name": config_name,
@ -207,7 +278,7 @@ def get_conf(config_name):
"total_data_usage": get_conf_total_data(config_name), "total_data_usage": get_conf_total_data(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),
"peer_data":get_conf_peers_data(config_name), "peer_data": getdb(config_name),
"running_peer": get_conf_running_peer_number(config_name), "running_peer": get_conf_running_peer_number(config_name),
} }
if conf_data['status'] == "stopped": if conf_data['status'] == "stopped":
@ -221,11 +292,15 @@ def get_conf(config_name):
def switch(config_name): def switch(config_name):
status = get_conf_status(config_name) status = get_conf_status(config_name)
if status == "running": if status == "running":
try: status = subprocess.check_output("wg-quick down "+config_name, shell=True) try:
except Exception: return redirect('/') status = subprocess.check_output("wg-quick down " + config_name, shell=True)
except Exception:
return redirect('/')
elif status == "stopped": elif status == "stopped":
try: status = subprocess.check_output("wg-quick up "+config_name, shell=True) try:
except Exception: return redirect('/') status = subprocess.check_output("wg-quick up " + config_name, shell=True)
except Exception:
return redirect('/')
return redirect('/') return redirect('/')
@ -239,18 +314,21 @@ def add_peer(config_name):
return "Key already exist." return "Key already exist."
else: else:
status = "" status = ""
try: try:
status = subprocess.check_output("wg set "+config_name+" peer "+public_key+" allowed-ips "+allowed_ips, shell=True, stderr=subprocess.STDOUT) status = subprocess.check_output(
status = subprocess.check_output("wg-quick save "+config_name, shell=True, stderr=subprocess.STDOUT) "wg set " + config_name + " peer " + public_key + " allowed-ips " + allowed_ips, shell=True,
stderr=subprocess.STDOUT)
status = subprocess.check_output("wg-quick save " + config_name, shell=True, stderr=subprocess.STDOUT)
return "true" return "true"
except subprocess.CalledProcessError as exc: except subprocess.CalledProcessError as exc:
return exc.output.strip() return exc.output.strip()
# return redirect('/configuration/'+config_name) # return redirect('/configuration/'+config_name)
@app.route('/remove_peer/<config_name>', methods=['POST']) @app.route('/remove_peer/<config_name>', methods=['POST'])
def remove_peer(config_name): def remove_peer(config_name):
db = TinyDB("db/"+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']
@ -259,37 +337,39 @@ def remove_peer(config_name):
return "This key does not exist" return "This key does not exist"
else: else:
try: try:
status = subprocess.check_output("wg set "+config_name+" peer "+delete_key+" remove", shell=True, stderr=subprocess.STDOUT) status = subprocess.check_output("wg set " + config_name + " peer " + delete_key + " remove", shell=True,
status = subprocess.check_output("wg-quick save "+config_name, shell=True, stderr=subprocess.STDOUT) stderr=subprocess.STDOUT)
status = subprocess.check_output("wg-quick save " + config_name, shell=True, stderr=subprocess.STDOUT)
db.remove(peers.id == delete_key) db.remove(peers.id == delete_key)
return "true" return "true"
except subprocess.CalledProcessError as exc: except subprocess.CalledProcessError as exc:
return exc.output.strip() return exc.output.strip()
@app.route('/save_peer_name/<config_name>', methods=['POST']) @app.route('/save_peer_name/<config_name>', methods=['POST'])
def save_peer_name(config_name): def save_peer_name(config_name):
data = request.get_json() data = request.get_json()
id = data['id'] id = data['id']
name = data['name'] name = data['name']
db = TinyDB("db/"+config_name+".json") db = TinyDB("db/" + config_name + ".json")
peers = Query() peers = Query()
db.update({"name": name}, peers.id == id) db.update({"name": name}, peers.id == id)
return id + " " + name return id + " " + name
@app.route('/get_peer_name/<config_name>', methods=['POST']) @app.route('/get_peer_name/<config_name>', methods=['POST'])
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']
db = TinyDB("db/"+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)
return result[0]['name'] return result[0]['name']
# db.update({"name": name}, peers.id == id) # db.update({"name": name}, peers.id == id)
if __name__ == "__main__":
app.run(host='0.0.0.0', debug=False, port=10086)
# for i in get_running_conf_list():
# p = Process(target=get_conf_peers_data, args=(i,))
# p.start()
app.run(host='0.0.0.0',debug=False, port=10086)

View File

@ -9,6 +9,7 @@
<link href="https://cdn.jsdelivr.net/gh/gitbrent/bootstrap4-toggle@3.6.1/css/bootstrap4-toggle.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/gh/gitbrent/bootstrap4-toggle@3.6.1/css/bootstrap4-toggle.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<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">
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.0.1/dist/chart.min.js"></script>
</head> </head>
<body> <body>
@ -171,7 +172,7 @@
load_data(); load_data();
setInterval(function(){ setInterval(function(){
load_data(); load_data();
}, 10000) }, 15000)
}); });
</script> </script>
<script> <script>

View File

@ -52,29 +52,42 @@
{% for i in conf_data['peer_data']%} {% for i in conf_data['peer_data']%}
<div class="card mb-3"> <div class="card mb-3">
<div class="card-header"> <div class="card-header">
{% if not i['name']%} <div class="row">
{{ "Untitled Peer" }} <div class="col">
{% else %} <div class="card-header-body ">
{{i['name']}} {% if not i['name']%}
{% endif %} {{ "Untitled Peer" }}
{% else %}
{{i['name']}}
{% endif %}
<span class="dot dot-{{i['status']}}"></span>
</div>
</div>
<div class="col" style="text-align: right">
<p class="text-primary" style="text-transform: uppercase; display: inline-block; margin-bottom: 0; margin-right: 1rem"><i class="bi bi-arrow-down-right"></i> {{i['total_receive']}} GB</p>
<p class="text-success" style="text-transform: uppercase; display: inline-block; margin-bottom: 0"><i class="bi bi-arrow-up-right"></i> {{i['total_sent']}} GB</p>
</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">#}
<small class="text-muted"><strong>STATUS</strong></small> {# <small class="text-muted"><strong>STATUS</strong></small>#}
<h6 style="text-transform: uppercase;">{{i['status']}}<span class="dot dot-{{i['status']}}"></span></h6> {# <h6 style="text-transform: uppercase;">{{i['status']}}<span class="dot dot-{{i['status']}}"></span></h6>#}
</div> {# </div>#}
<div class="col-sm"> <div class="col-sm">
<small class="text-muted"><strong>PEER</strong></small> <small class="text-muted"><strong>PEER</strong></small>
<h6><samp>{{i['id']}}</samp></h6> <h6><samp class="ml-auto">{{i['id']}}</samp></h6>
</div> </div>
<div class="w-100"></div>
<div class="col-sm"> <div class="col-sm">
<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>
@ -83,20 +96,51 @@
<small class="text-muted"><strong>END POINT</strong></small> <small class="text-muted"><strong>END POINT</strong></small>
<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">#}
{# <small class="text-muted"><strong>TOTAL DATA USAGE</strong></small>#} {# <canvas id="{{ i['id'] }}_chart" style="width: 100%" height="200"></canvas>#}
{# <h6 style="text-transform: uppercase;">{{i['total_data']}} GB</h6>#} {# </div>#}
{# </div>#} {# <script>#}
<div class="col-sm"> {# var data = "{{ i['traffic'] }}";#}
<small class="text-muted"><strong>TOTAL RECEIVED</strong></small> {# data = data.replaceAll("&#39;", "\"")#}
<h6 style="text-transform: uppercase;"><i class="bi bi-arrow-down-right"></i> {{i['total_receive']}} GB</h6> {# data = JSON.parse(data)#}
</div> {# y_label = [];#}
<div class="col-sm"> {# data_point = [];#}
<small class="text-muted"><strong>TOTAL SENT</strong></small> {# for (var i = 1; i < data.length; i++){#}
<h6 style="text-transform: uppercase;"><i class="bi bi-arrow-up-right"></i> {{i['total_sent']}} GB</h6> {# y_label.push(data[i]['time'])#}
</div> {# data_point.push(data[i]['total_sent'] - data[i-1]['total_sent']);#}
<div class="w-100"></div> {# }#}
{# var ctx = document.getElementById('{{ i['id'] }}_chart');#}
{# var plot_data =#}
{# {#}
{# labels: y_label,#}
{# datasets:[{#}
{# label: "Traffic",#}
{# data: data_point,#}
{# fill: false,#}
{# borderColor: 'rgb(75, 192, 192)',#}
{# tension: 0.1#}
{# }],#}
{# options: {#}
{# scales: {#}
{# xAxes: [{#}
{# type: 'time',#}
{# ticks: {#}
{# autoSkip: true,#}
{# maxTicksLimit: 12#}
{# }#}
{# }]#}
{# },#}
{# responsive: true#}
{# }#}
{# }#}
{##}
{# var myLineChart = new Chart(ctx, {#}
{# type: 'line',#}
{# data: plot_data#}
{# });#}
{# </script>#}
<div class="w-100"></div>
<div class="col-sm"> <div class="col-sm">
<!-- <small class="text-muted"><strong>ACTION</strong></small> --> <!-- <small class="text-muted"><strong>ACTION</strong></small> -->
<div class="button-group"> <div class="button-group">