Add active user counter logic; add to admin tools.
Ports in lightly modified logic from the upstream which tracks active sessions to provide counters and listings to understand site traffic in the admin panel.
This commit is contained in:
parent
ecbd8179b9
commit
129d644a3f
8 changed files with 125 additions and 31 deletions
|
@ -88,25 +88,29 @@ mail = Mail(app)
|
|||
|
||||
@app.before_request
|
||||
def before_request():
|
||||
|
||||
with open('site_settings.json', 'r') as f:
|
||||
app.config['SETTINGS'] = json.load(f)
|
||||
|
||||
if request.host != app.config["SERVER_NAME"]: return {"error":"Unauthorized host provided."}, 401
|
||||
if request.headers.get("CF-Worker"): return {"error":"Cloudflare workers are not allowed to access this website."}, 401
|
||||
if request.host != app.config["SERVER_NAME"]:
|
||||
return {"error": "Unauthorized host provided."}, 401
|
||||
|
||||
if not app.config['SETTINGS']['Bots'] and request.headers.get("Authorization"): abort(503)
|
||||
if not app.config['SETTINGS']['Bots'] and request.headers.get("Authorization"):
|
||||
abort(503)
|
||||
|
||||
g.db = db_session()
|
||||
|
||||
ua = request.headers.get("User-Agent","").lower()
|
||||
|
||||
if '; wv) ' in ua: g.webview = True
|
||||
else: g.webview = False
|
||||
|
||||
if 'iphone' in ua or 'ipad' in ua or 'ipod' in ua or 'mac os' in ua or ' firefox/' in ua: g.inferior_browser = True
|
||||
else: g.inferior_browser = False
|
||||
g.agent = request.headers.get("User-Agent")
|
||||
if not g.agent:
|
||||
return 'Please use a "User-Agent" header!', 403
|
||||
|
||||
ua = g.agent.lower()
|
||||
g.webview = ('; wv) ' in ua)
|
||||
g.inferior_browser = (
|
||||
'iphone' in ua or
|
||||
'ipad' in ua or
|
||||
'ipod' in ua or
|
||||
'mac os' in ua or
|
||||
' firefox/' in ua)
|
||||
g.timestamp = int(time.time())
|
||||
|
||||
|
||||
|
|
|
@ -72,6 +72,8 @@ PUSHER_KEY = environ.get("PUSHER_KEY", "").strip()
|
|||
DEFAULT_COLOR = environ.get("DEFAULT_COLOR", "fff").strip()
|
||||
COLORS = {'ff66ac','805ad5','62ca56','38a169','80ffff','2a96f3','eb4963','ff0000','f39731','30409f','3e98a7','e4432d','7b9ae4','ec72de','7f8fa6', 'f8db58','8cdbe6', DEFAULT_COLOR}
|
||||
|
||||
LOGGEDIN_ACTIVE_TIME = 15 * 60
|
||||
|
||||
AWARDS = {
|
||||
"ghost": {
|
||||
"kind": "ghost",
|
||||
|
|
|
@ -3,13 +3,19 @@ from .alerts import *
|
|||
from files.helpers.const import *
|
||||
from files.__main__ import db_session
|
||||
from random import randint
|
||||
import user_agents
|
||||
import time
|
||||
|
||||
def get_logged_in_user():
|
||||
if not (hasattr(g, 'db') and g.db): g.db = db_session()
|
||||
if hasattr(g, 'v'):
|
||||
return g.v
|
||||
|
||||
if not (hasattr(g, 'db') and g.db):
|
||||
g.db = db_session()
|
||||
|
||||
v = None
|
||||
|
||||
token = request.headers.get("Authorization","").strip()
|
||||
token = request.headers.get("Authorization", "").strip()
|
||||
if token:
|
||||
client = g.db.query(ClientAuth).filter(ClientAuth.access_token == token).one_or_none()
|
||||
if client:
|
||||
|
@ -19,7 +25,7 @@ def get_logged_in_user():
|
|||
lo_user = session.get("lo_user")
|
||||
if lo_user:
|
||||
id = int(lo_user)
|
||||
v = g.db.query(User).filter_by(id=id).one_or_none()
|
||||
v = g.db.query(User).get(id)
|
||||
if v:
|
||||
nonce = session.get("login_nonce", 0)
|
||||
if nonce < v.login_nonce or v.id != id: abort(401)
|
||||
|
@ -35,14 +41,40 @@ def get_logged_in_user():
|
|||
|
||||
v.client = None
|
||||
|
||||
|
||||
if request.method.lower() != "get" and app.config['SETTINGS']['Read-only mode'] and not (v and v.admin_level):
|
||||
if request.method.lower() != "get" \
|
||||
and app.config['SETTINGS']['Read-only mode'] \
|
||||
and not (v and v.admin_level):
|
||||
abort(403)
|
||||
|
||||
if v and v.patron:
|
||||
if request.content_length and request.content_length > 16 * 1024 * 1024: abort(413)
|
||||
elif request.content_length and request.content_length > 8 * 1024 * 1024: abort(413)
|
||||
if request.content_length and request.content_length > 8 * 1024 * 1024:
|
||||
abort(413)
|
||||
|
||||
if not session.get("session_id"):
|
||||
session.permanent = True
|
||||
session["session_id"] = secrets.token_hex(49)
|
||||
|
||||
# Active User Counters
|
||||
loggedin = cache.get(f'{SITE}_loggedin') or {}
|
||||
loggedout = cache.get(f'{SITE}_loggedout') or {}
|
||||
|
||||
timestamp = int(time.time())
|
||||
if v:
|
||||
if session["session_id"] in loggedout:
|
||||
del loggedout[session["session_id"]]
|
||||
loggedin[v.id] = timestamp
|
||||
else:
|
||||
ua = str(user_agents.parse(g.agent))
|
||||
if 'spider' not in ua.lower() and 'bot' not in ua.lower():
|
||||
loggedout[session["session_id"]] = (timestamp, ua)
|
||||
|
||||
g.loggedin_counter = len([x for x in loggedin.values() \
|
||||
if (timestamp - x) < LOGGEDIN_ACTIVE_TIME])
|
||||
g.loggedout_counter = len([x for x in loggedout.values() \
|
||||
if (timestamp - x[0]) < LOGGEDIN_ACTIVE_TIME])
|
||||
cache.set(f'{SITE}_loggedin', loggedin)
|
||||
cache.set(f'{SITE}_loggedout', loggedout)
|
||||
|
||||
g.v = v
|
||||
return v
|
||||
|
||||
def check_ban_evade(v):
|
||||
|
@ -53,12 +85,10 @@ def check_ban_evade(v):
|
|||
|
||||
def auth_desired(f):
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
v = get_logged_in_user()
|
||||
|
||||
check_ban_evade(v)
|
||||
|
||||
g.v = v
|
||||
return make_response(f(*args, v=v, **kwargs))
|
||||
|
||||
wrapper.__name__ = f.__name__
|
||||
|
@ -68,13 +98,11 @@ def auth_desired(f):
|
|||
def auth_required(f):
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
v = get_logged_in_user()
|
||||
if not v: abort(401)
|
||||
|
||||
check_ban_evade(v)
|
||||
|
||||
g.v = v
|
||||
return make_response(f(*args, v=v, **kwargs))
|
||||
|
||||
wrapper.__name__ = f.__name__
|
||||
|
@ -84,9 +112,7 @@ def auth_required(f):
|
|||
def is_not_permabanned(f):
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
v = get_logged_in_user()
|
||||
|
||||
if not v: abort(401)
|
||||
|
||||
check_ban_evade(v)
|
||||
|
@ -94,7 +120,6 @@ def is_not_permabanned(f):
|
|||
if v.is_suspended_permanently:
|
||||
return {"error": "Forbidden: you are permabanned."}, 403
|
||||
|
||||
g.v = v
|
||||
return make_response(f(*args, v=v, **kwargs))
|
||||
|
||||
wrapper.__name__ = f.__name__
|
||||
|
@ -106,14 +131,11 @@ def admin_level_required(x):
|
|||
def wrapper_maker(f):
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
v = get_logged_in_user()
|
||||
|
||||
if not v: abort(401)
|
||||
|
||||
if v.admin_level < x: abort(403)
|
||||
|
||||
g.v = v
|
||||
|
||||
return make_response(f(*args, v=v, **kwargs))
|
||||
|
||||
wrapper.__name__ = f.__name__
|
||||
|
|
|
@ -764,6 +764,25 @@ def users_list(v):
|
|||
page=page,
|
||||
)
|
||||
|
||||
|
||||
@app.get('/admin/loggedin')
|
||||
@admin_level_required(2)
|
||||
def loggedin_list(v):
|
||||
ids = [x for x, val in cache.get(f'{SITE}_loggedin').items() \
|
||||
if (time.time() - val) < LOGGEDIN_ACTIVE_TIME]
|
||||
users = g.db.query(User).filter(User.id.in_(ids)) \
|
||||
.order_by(User.admin_level.desc(), User.truecoins.desc()).all()
|
||||
return render_template("admin/loggedin.html", v=v, users=users)
|
||||
|
||||
|
||||
@app.get('/admin/loggedout')
|
||||
@admin_level_required(2)
|
||||
def loggedout_list(v):
|
||||
users = sorted([val[1] for x, val in cache.get(f'{SITE}_loggedout').items() \
|
||||
if (time.time() - val[0]) < LOGGEDIN_ACTIVE_TIME])
|
||||
return render_template("admin/loggedout.html", v=v, users=users)
|
||||
|
||||
|
||||
@app.get("/admin/alt_votes")
|
||||
@admin_level_required(2)
|
||||
def alt_votes_get(v):
|
||||
|
|
|
@ -8,7 +8,13 @@
|
|||
{% block content %}
|
||||
<pre></pre>
|
||||
<pre></pre>
|
||||
<h3> Admin Tools</h3>
|
||||
<h3>Admin Tools</h3>
|
||||
|
||||
<div class="mb-3">
|
||||
<strong>Users Here Now:</strong> {{g.loggedin_counter + g.loggedout_counter}} —
|
||||
<a href="/admin/loggedin">{{g.loggedin_counter}} Logged-In</a> |
|
||||
<a href="/admin/loggedout">{{g.loggedout_counter}} Logged-Out</a>
|
||||
</div>
|
||||
|
||||
<h4>Content</h4>
|
||||
<ul>
|
||||
|
|
21
files/templates/admin/loggedin.html
Normal file
21
files/templates/admin/loggedin.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
{% extends "settings2.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="overflow-x-auto"><table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Name</th>
|
||||
<th>Truescore</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{{loop.index}}</td>
|
||||
<td><a style="color:#{{user.name_color}}" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.name_color}}"{% endif %}>{{user.username}}</span></a></td>
|
||||
<td>{{user.truecoins}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
19
files/templates/admin/loggedout.html
Normal file
19
files/templates/admin/loggedout.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
{% extends "settings2.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="overflow-x-auto"><table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Details</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{{loop.index}}</td>
|
||||
<td>{{user}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
|
@ -21,6 +21,7 @@ qrcode
|
|||
redis
|
||||
requests
|
||||
SQLAlchemy
|
||||
user-agents
|
||||
psycopg2-binary
|
||||
pusher_push_notifications
|
||||
pyenchant
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue