add /admin/users/<id> detail, style changes to adminl board

This commit is contained in:
Yusur 2025-07-17 08:02:07 +02:00
parent 8361890d4a
commit 71042a720c
5 changed files with 77 additions and 6 deletions

View file

@ -23,6 +23,6 @@
<footer class="footer">
<p><a href="/">Back to {{ app_name }}</a>.</p>
</footer>
<script src="/static/lib.js"></script>
<script src="/static/js/lib.js"></script>
</body>
</html>

View file

@ -9,7 +9,6 @@
<ul class="inline">
<li>Reason: <strong>{{ report_reasons[strike.reason_code] }}</strong></li>
<!-- you might not want to see why -->
</ul>
</li>
{% endfor %}
{% if strike_list.has_next %}

View file

@ -0,0 +1,46 @@
{% extends "admin/admin_base.html" %}
{% from "macros/icon.html" import callout with context %}
{% block content %}
<h2><span class="faint">User:</span> {{ u.handle() }}</h2>
<ul>
<li>Age: {{ u.age() }} years old ({{ u.gdpr_birthday.strftime("%B %d, %Y") }})</li>
<li>Registered at: {{ u.joined_at.strftime("%B %d, %Y %H:%M %z") }}</li>
<li>Registered from IP address: {{ u.joined_ip }}</li>
<li>Status: {{ account_status_string(u) }}</li>
<li>Karma: {{ u.karma }}</li>
{% if u.email %}
<li>E-mail: {{ u.email }}</li>
{% endif %}
</ul>
{% if u.banned_at %}
{% call callout('spoiler', 'error') %}
{{ u.handle() }} is suspended
{% if u.banned_until %}until {{ u.banned_until.strftime("%B %d, %Y %H:%M %z") }}{% else %}indefinitely{% endif %}.
{% if u.banned_message %}<br />Ban message: “{{ u.banned_message }}”{% endif %}
{% endcall %}
{% endif %}
<!-- quick actions -->
<form method="POST">
</form>
<h3>Strikes</h3>
{% if strikes %}
<ul>
{% for strike in strikes %}
<li>
<p><strong>#{{ strike.id | to_cb32 }}</strong></p>
<ul class="inline">
<li>Reason: <strong>{{ report_reasons[strike.reason_code] }}</strong></li>
<li><span class="spoiler">{{ strike.text_content }}</span></li>
</li>
{% endfor %}
</ul>
{% else %}
<p class="centered success">{{ u.handle() }} is all good!</p>
{% endif %}
{% endblock %}

View file

@ -5,7 +5,7 @@
<ul>
{% for u in user_list %}
<li>
<p><a href="{{ u.url() }}">{{ u.handle() }}</a> (#{{ u.id | to_b32l }})
<p><a href="/admin/users/{{ u.id | to_b32l }}">{{ u.handle() }}</a> (#{{ u.id | to_b32l }})
{%- if u.is_administrator %}
<span>(Admin)</span>
{% endif -%}
@ -17,7 +17,7 @@
<li>Age: {{ u.age() }} years old ({{ u.gdpr_birthday.strftime("%B %d, %Y") }})</li>
<li>Registered at: {{ u.joined_at.strftime("%B %d, %Y %H:%M %z") }}</li>
<li>Registered from IP address: {{ u.joined_ip }}</li>
<li>Status: {{ account_status_string(u) | faint_paren }}</li>
<li>Status: {{ account_status_string(u) }}</li>
</ul>
</li>
{% endfor %}

View file

@ -6,6 +6,7 @@ from typing import Callable
import warnings
from flask import Blueprint, abort, redirect, render_template, request, url_for
from flask_login import current_user
from markupsafe import Markup
from sqlalchemy import insert, select, update
from suou import additem, not_implemented
@ -36,7 +37,7 @@ def account_status_string(u: User):
elif u.banned_at:
s = 'Suspended'
if u.banned_until:
s += f' until {u.banned_until:%b %d, %Y %H:%M}'
s += f' (until {u.banned_until:%b %d, %Y %H:%M})'
if u.banned_reason in REPORT_REASON_STRINGS:
s += f' ({REPORT_REASON_STRINGS[u.banned_reason]})'
return s
@ -45,6 +46,19 @@ def account_status_string(u: User):
else:
return 'Inactive'
def colorized_account_status_string(u: User):
textc = account_status_string(u)
t1, t2, t3 = textc.partition('(')
if u.is_active:
base = '<span class="success">{0}</span>'
elif u.banned_at:
base = '<span class="error">{0}</span>'
else:
base = '<span class="warning">{0}</span>'
if t2:
base += ' <span class="faint">{1}</span>'
return Markup(base).format(t1, t2 + t3)
def remove_content(target, reason_code: int):
if isinstance(target, Post):
target.removed_at = datetime.datetime.now()
@ -168,4 +182,16 @@ def strikes():
def users():
user_list = db.paginate(select(User).order_by(User.joined_at.desc()))
return render_template('admin/admin_users.html',
user_list=user_list, account_status_string=account_status_string)
user_list=user_list, account_status_string=colorized_account_status_string)
@bp.route('/admin/users/<b32l:id>', methods=['GET', 'POST'])
@admin_required
def user_detail(id: int):
u = db.session.execute(select(User).where(User.id == id)).scalar()
if u is None:
abort(404)
if request.method == 'POST':
abort(501)
strikes = db.session.execute(select(UserStrike).where(UserStrike.user_id == id).order_by(UserStrike.id.desc())).scalars()
return render_template('admin/admin_user_detail.html', u=u,
report_reasons=REPORT_REASON_STRINGS, account_status_string=colorized_account_status_string, strikes=strikes)