swizzin_dashboard/swizzin.py
raverdave-2k 1f72753744
Added total values for fields on the Network Info panel (#19)
* Added total values for fields on the Network Info panel

* Update top.html

Missed some class change

* Update netinfo.html

Missed some class changes.

* Update swizzin.py

Typo

* Update top.html

Typos

* Changed class on new column to use a theme class rather than the custom one originally added

* Made display of totals column optional via NETWORK_TOTALS config variable and set hidden by default.
2020-11-29 12:40:52 -08:00

391 lines
14 KiB
Python
Executable file

#!/usr/bin/env python
import flask
from core.htpasswd import HtPasswdAuth
from flask_socketio import SocketIO, emit
from threading import Thread, Lock
from packaging import version
import os
import core.config
import requests
import time
from werkzeug.middleware.proxy_fix import ProxyFix
import calendar
import eventlet
#Prep the websockets with eventlet workers
eventlet.monkey_patch()
async_mode = None
#Prep flask
app = flask.Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app)
socketio = SocketIO(app, async_mode=async_mode)
#Config the app
app.config.from_object('core.config.Config')
app.config.from_pyfile('swizzin.cfg', silent=True)
admin_user = app.config['ADMIN_USER']
htpasswd = HtPasswdAuth(app)
#Config rate limiting
def check_authorization():
if flask.request.authorization:
try:
authreq = flask.request.authorization.username
return True
except:
return False
else:
return False
if app.config['RATELIMIT_ENABLED'] == True:
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(
app,
key_func=get_remote_address,
default_limits=[app.config['RATELIMIT_DEFAULT']],
default_limits_exempt_when=check_authorization,
default_limits_per_method=True
)
from core.util import *
#Prepare the background threads
thread = None
thread_lock = Lock()
thread2 = None
thread2_lock = Lock()
#Background thread functions
def current_speed(app):
""" Thread for interface speed """
#Modified for our uses from https://stackoverflow.com/a/26853086
with app.app_context():
#print("Starting current speed for", interface)
interface = get_default_interface()
(tx_prev, rx_prev, total_prev) = (0, 0, 0)
while(True):
tx = get_nic_bytes('tx', interface)
rx = get_nic_bytes('rx', interface)
total = tx + rx
if tx_prev > 0:
tx_speed = tx - tx_prev
#print('TX: ', tx_speed, 'bps')
tx_speed = str(GetHumanReadableB(tx_speed)) + "/s"
if rx_prev > 0:
rx_speed = rx - rx_prev
#print('RX: ', rx_speed, 'bps')
rx_speed = str(GetHumanReadableB(rx_speed)) + "/s"
if total_prev > 0:
total_speed = total - total_prev
#print("TOTAL: ', total_speed, 'bps')
total_speed = str(GetHumanReadableB(total_speed)) + "/s"
emit('speed', {'interface': interface, 'tx': tx_speed, 'rx': rx_speed, 'total': total_speed}, namespace='/websocket', broadcast=True)
time.sleep(1)
tx_prev = tx
rx_prev = rx
total_prev = total
def io_wait(app):
""" Thread for iowait emission """
#https://stackoverflow.com/a/7299268
tick = os.sysconf(os.sysconf_names['SC_CLK_TCK'])
numcpu = os.cpu_count()
interval = 10
with app.app_context():
while(True):
readstats = open('/proc/stat')
procstats = readstats.readlines()[0].split()
user, nice, sys, idle, iowait, irq, sirq = ( float(procstats[1]), float(procstats[2]),
float(procstats[3]), float(procstats[4]),
float(procstats[5]), float(procstats[6]),
float(procstats[7]) )
readstats.close()
time.sleep(interval)
readstats = open('/proc/stat')
procstats = readstats.readlines()[0].split()
userd, niced, sysd, idled, iowaitd, irqd, sirqd = ( float(procstats[1]), float(procstats[2]),
float(procstats[3]), float(procstats[4]),
float(procstats[5]), float(procstats[6]),
float(procstats[7]) )
readstats.close()
iowait = '{0:.1f}'.format(((iowaitd - iowait)* 100 / tick ) / numcpu / interval)
#times = psutil.cpu_times_percent(interval=10)
emit('iowait', {'iowait': iowait}, namespace='/websocket', broadcast=True)
@app.before_request
def reload_htpasswd():
"""
This function will run before every load of the index. It will ensure the htpasswd file is current.
"""
if flask.request.endpoint == 'index':
htpasswd.load_users(app)
#@app.after_request
#def apply_headers(response):
# if flask.request.referrer == "{}login".format(flask.request.host_url):
# response.headers["WWW-Authenticate"] = 'basic realm="{0}"'.format(current_app.config['FLASK_AUTH_REALM'])
# if flask.request.referrer == "{}login/auth".format(flask.request.host_url):
# response.headers["WWW-Authenticate"] = 'basic realm="{0}"'.format(current_app.config['FLASK_AUTH_REALM'])
# if flask.request.referrer == "{}logout".format(flask.request.host_url):
# response.headers["WWW-Authenticate"] = 'basic realm="{0}"'.format(current_app.config['FLASK_AUTH_REALM'])
# return response
@app.errorhandler(401)
def unauthorized(e):
if app.config['FORMS_LOGIN']:
if flask.request.referrer == "{}login".format(flask.request.host_url):
return authenticate()
elif flask.request.referrer == "{}login/auth".format(flask.request.host_url):
return authenticate()
else:
return flask.redirect(flask.url_for('login'))
else:
return authenticate()
def authenticate():
"""
Sends a 401 response that enables basic auth
"""
return flask.Response(
'Could not verify your access level for that URL.\n'
'You have to login with proper credentials',
401,
{'WWW-Authenticate': 'basic realm="{0}"'.format(
current_app.config['FLASK_AUTH_REALM']
)}
)
#Begin routes
@app.route('/')
@htpasswd.required
def index(user):
#global thread
#if thread is None:
# thread = Thread(target=current_speed)
# thread.start()
pages = generate_page_list(user)
mounts = get_mounts()
if os.path.isfile("/install/.quota.lock"):
quota = True
else:
quota = False
return flask.render_template('index.html', title='{user} - swizzin dashboard'.format(user=user), user=user, pages=pages, quota=quota, mounts=mounts, async_mode=socketio.async_mode)
@socketio.on('connect', namespace='/websocket')
def socket_connect():
global thread
global thread2
with thread_lock:
if thread is None:
thread = socketio.start_background_task(current_speed, (flask.current_app._get_current_object()))
with thread2_lock:
if thread2 is None:
thread2 = socketio.start_background_task(io_wait, (flask.current_app._get_current_object()))
emit('my_response', {'data': 'Connected', 'count': 0})
@app.route('/stats')
@app.route('/stats/')
@htpasswd.required
def stats(user):
pages = generate_page_list(user)
return flask.render_template('stats.html', title='Stats', user=user, pages=pages)
@app.route('/stats/netdata/')
@app.route('/stats/netdata/<path:p>',methods=['GET'])
@htpasswd.required
def netdataproxy(user, p = ''):
SITE = 'http://127.0.0.1:19999/{}'.format(p)
if flask.request.method=='GET':
if flask.request.args:
querystring = flask.request.query_string.decode('utf-8')
resp = requests.get("{SITE}?{querystring}".format(SITE=SITE,querystring=querystring))
else:
resp = requests.get("{SITE}".format(SITE=SITE))
excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
headers = [(name, value) for (name, value) in resp.raw.headers.items() if name.lower() not in excluded_headers]
response = flask.Response(resp.content, resp.status_code, headers)
return response
#elif flask.request.method=='POST':
# resp = requests.post(f'{SITE}',json=request.get_json())
# excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
# headers = [(name, value) for (name, value) in resp.raw.headers.items() if name.lower() not in excluded_headers]
# response = flask.Response(resp.content, resp.status_code, headers)
# return response
#elif flask.request.method=='DELETE':
# resp = requests.delete(f'{SITE}').content
# response = flask.Response(resp.content, resp.status_code, headers)
# return response
@app.route('/apps/status')
@htpasswd.required
def app_status(user):
apps = apps_status(user)
return flask.jsonify(apps)
@app.route('/apps/service', methods=['POST'])
@htpasswd.required
def service(user):
if flask.request.method == 'POST':
data = flask.request.get_json()
application = data['application']
try:
profile = str_to_class(application+"_meta")(user)
except:
try:
profile = str_to_class(application+"_meta")
except:
return """Application profile not found."""
try:
multiuser = profile.multiuser
except:
multiuser = False
if multiuser == False and user != admin_user:
return """Access denied"""
try:
application = profile.systemd
except:
pass
if application.endswith("@"):
application = application+user
result = systemctl(data['function'], application)
return str(result)
@app.route('/stats/loadavg')
@htpasswd.required
def loadavg(user):
loadavg = open("/proc/loadavg").readline().split(" ")[:3]
numcpu = os.cpu_count()
perutil = '{0:.2f}'.format((float(loadavg[0]) / numcpu) * 100)
return flask.jsonify({"1m": loadavg[0], "5m": loadavg[1], "15m": loadavg[2], "perutil": perutil})
@app.route('/stats/vnstat')
@htpasswd.required
def vnstat(user):
#stats = []
interface = get_default_interface()
vnstat_info = vnstat_data(interface, "h")
vnstat_jsonversion = vnstat_info["jsonversion"]
if vnstat_jsonversion == "1":
qh = "hours"
qd = "days"
qm = "months"
qt = "tops"
thisday = 0
thismonth = 0
hour = int(time.strftime("%H"))
lasthour = hour - 1
if lasthour == -1:
lasthour = 23
read_unit = GetHumanReadableKB
elif vnstat_jsonversion == "2":
qh = "hour"
qd = "day"
qm = "month"
qt = "top"
thisday = vnstat_data(interface, "d")['interfaces'][0]['traffic']['day'][-1]["id"]
thismonth = vnstat_data(interface, "m")['interfaces'][0]['traffic']['month'][-1]["id"]
hour = vnstat_info['interfaces'][0]['traffic']['hour'][-1]["id"]
lasthour = vnstat_info['interfaces'][0]['traffic']['hour'][-2]["id"]
read_unit = GetHumanReadableB
statsh = vnstat_parse(interface, "h", qh, read_unit, hour)
statslh = vnstat_parse(interface, "h", qh, read_unit, lasthour)
statsd = vnstat_parse(interface, "d", qd, read_unit, thisday)
statsm = vnstat_parse(interface, "m", qm, read_unit, thismonth)
statsa = vnstat_parse(interface, "h", "total", read_unit)
#statsa = vnstat_parse(interface, "m", "total", 0)
tops = vnstat_data(interface, "t")['interfaces'][0]['traffic'][qt]
top = []
for t in tops[:10]:
date = t['date']
year = date['year']
month = calendar.month_abbr[date['month']]
day = date['day']
date = "{month} {day}, {year}".format(year=year, month=month, day=day)
rx = read_unit(t['rx'])
tx =read_unit(t['tx'])
total = read_unit(t['tx'] + t['rx'])
top.append({"date": date, "rx": rx, "tx": tx, "total": total})
columns = {"date", "rx", "tx", "total"}
#stats = []
#stats.extend({"statsh": statsh, "statslh": statslh, "statsd": statsd, "statsm": statsm, "statsa": statsa, "top": top})
#print(stats)
#return flask.jsonify({"statsh": statsh, "statslh": statslh, "statsd": statsd, "statsm": statsm, "statsa": statsa, "top": top})
return flask.render_template('top.html', user=user, top=top, day=statsd, month=statsm, hour=statsh, lasthour=statslh, alltime=statsa, colnames=columns)
@app.route('/stats/disk')
@htpasswd.required
def disk_free(user):
mounts = get_mounts()
data = {}
for mount in mounts:
total, used, free, usage = disk_usage(mount)
data[mount] = {"disktotal": total, "diskused": used, "diskfree": free, "perutil": usage}
return flask.jsonify(data)
@app.route('/stats/quota')
@htpasswd.required
def quota_free(user):
if os.path.isfile("/install/.quota.lock"):
total, used, free, usage = quota_usage(user)
return flask.jsonify({"quota": {"disktotal": total, "diskused": used, "diskfree": free, "perutil": usage}})
else:
return """Quota not installed"""
@app.route('/stats/boot')
@htpasswd.required
def boot_time(user):
return boottimeutc
@app.route('/stats/ram')
@htpasswd.required
def ram_stats(user):
ramstats = dict((i.split()[0].rstrip(':'),int(i.split()[1])) for i in open('/proc/meminfo').readlines())
ramtotal = GetHumanReadableKB(ramstats['MemTotal'])
ramfree = GetHumanReadableKB(ramstats['MemAvailable'])
ramused = GetHumanReadableKB(ramstats['MemTotal'] - ramstats['MemAvailable'])
perutil = '{0:.2f}'.format((ramstats['MemTotal'] - ramstats['MemAvailable']) / ramstats['MemTotal'] * 100)
return flask.jsonify({"ramtotal": ramtotal, "ramfree": ramfree, "ramused": ramused, "perutil": perutil})
@app.route('/stats/network')
@htpasswd.required
def network_quota(user):
if app.config['SHAREDSERVER'] is True:
total, used, free, usage = network_quota_usage(user)
return flask.jsonify({"nettotal": total, "netused": used, "netfree": free, "perutil": usage})
@app.route('/login')
def login():
return flask.render_template('login.html', title='swizzin login')
@app.route('/login/auth')
@htpasswd.required
def auth(user):
return """
<div>You have been logged in. Redirecting to home...</div>
<script>
setTimeout(function () {
window.location.href = "/";
}, 500);
</script>
"""
@app.route('/logout')
@htpasswd.required
def logout(user):
return flask.render_template('logout.html')
if __name__ == '__main__':
socketio.run(app, host=app.config['HOST'], port=app.config['PORT'])
#app.run(debug=True,host='0.0.0.0', port=8333)