Merge pull request 'Clean up the Admin--Users and Monitoring users pages and add sorting' (#390) from h3ndrik/pyfedi:admin_users_cleanup into main

Reviewed-on: https://codeberg.org/rimu/pyfedi/pulls/390
This commit is contained in:
rimu 2024-12-21 00:10:27 +00:00
commit ccad3c30e7
6 changed files with 81 additions and 148 deletions

View file

@ -1147,60 +1147,30 @@ def admin_users():
page = request.args.get('page', 1, type=int) page = request.args.get('page', 1, type=int)
search = request.args.get('search', '') search = request.args.get('search', '')
local_remote = request.args.get('local_remote', '') local_remote = request.args.get('local_remote', '')
sort_by = request.args.get('sort_by', 'last_seen DESC')
last_seen = request.args.get('last_seen', 0, type=int)
sort_by_btn = request.args.get('sort_by_btn', '')
if sort_by_btn:
return redirect(url_for('admin.admin_users', page=page, search=search, local_remote=local_remote, sort_by=sort_by_btn, last_seen=last_seen))
users = User.query.filter_by(deleted=False) users = User.query.filter_by(deleted=False)
if local_remote == 'local': if local_remote == 'local':
users = users.filter_by(ap_id=None) users = users.filter_by(ap_id=None)
if local_remote == 'remote': elif local_remote == 'remote':
users = users.filter(User.ap_id != None) users = users.filter(User.ap_id != None)
if search: if search:
users = users.filter(User.email.ilike(f"%{search}%")) users = users.filter(User.email.ilike(f"%{search}%"))
users = users.order_by(User.user_name).paginate(page=page, per_page=1000, error_out=False) if last_seen > 0:
users = users.filter(User.last_seen > utcnow() - timedelta(days=last_seen))
users = users.order_by(text('"user".' + sort_by))
users = users.paginate(page=page, per_page=1000, error_out=False)
next_url = url_for('admin.admin_users', page=users.next_num) if users.has_next else None next_url = url_for('admin.admin_users', page=users.next_num, search=search, local_remote=local_remote, sort_by=sort_by, last_seen=last_seen) if users.has_next else None
prev_url = url_for('admin.admin_users', page=users.prev_num) if users.has_prev and page != 1 else None prev_url = url_for('admin.admin_users', page=users.prev_num, search=search, local_remote=local_remote, sort_by=sort_by, last_seen=last_seen) if users.has_prev and page != 1 else None
return render_template('admin/users.html', title=_('Users'), next_url=next_url, prev_url=prev_url, users=users, return render_template('admin/users.html', title=_('Users'), next_url=next_url, prev_url=prev_url, users=users,
local_remote=local_remote, search=search, local_remote=local_remote, search=search, sort_by=sort_by, last_seen=last_seen,
moderating_communities=moderating_communities(current_user.get_id()),
joined_communities=joined_communities(current_user.get_id()),
menu_topics=menu_topics(),
site=g.site
)
@bp.route('/users/trash', methods=['GET'])
@login_required
@permission_required('administer all users')
def admin_users_trash():
page = request.args.get('page', 1, type=int)
search = request.args.get('search', '')
local_remote = request.args.get('local_remote', '')
type = request.args.get('type', 'bad_rep')
users = User.query.filter_by(deleted=False)
if local_remote == 'local':
users = users.filter_by(ap_id=None)
if local_remote == 'remote':
users = users.filter(User.ap_id != None)
if search:
users = users.filter(User.email.ilike(f"%{search}%"))
if type == '' or type == 'bad_rep':
users = users.filter(User.last_seen > utcnow() - timedelta(days=7))
users = users.filter(User.reputation < -10)
users = users.order_by(User.reputation).paginate(page=page, per_page=1000, error_out=False)
elif type == 'bad_attitude':
users = users.filter(User.last_seen > utcnow() - timedelta(days=7))
users = users.filter(User.attitude < 0.0).filter(User.reputation < -10)
users = users.order_by(User.attitude).paginate(page=page, per_page=1000, error_out=False)
next_url = url_for('admin.admin_users_trash', page=users.next_num, search=search, local_remote=local_remote, type=type) if users.has_next else None
prev_url = url_for('admin.admin_users_trash', page=users.prev_num, search=search, local_remote=local_remote, type=type) if users.has_prev and page != 1 else None
return render_template('admin/users_trash.html', title=_('Problematic users'), next_url=next_url, prev_url=prev_url, users=users,
local_remote=local_remote, search=search, type=type,
moderating_communities=moderating_communities(current_user.get_id()), moderating_communities=moderating_communities(current_user.get_id()),
joined_communities=joined_communities(current_user.get_id()), joined_communities=joined_communities(current_user.get_id()),
menu_topics=menu_topics(), menu_topics=menu_topics(),

View file

@ -6,7 +6,6 @@
<a href="{{ url_for('admin.admin_communities') }}">{{ _('Communities') }}</a> | <a href="{{ url_for('admin.admin_communities') }}">{{ _('Communities') }}</a> |
<a href="{{ url_for('admin.admin_topics') }}">{{ _('Topics') }}</a> | <a href="{{ url_for('admin.admin_topics') }}">{{ _('Topics') }}</a> |
<a href="{{ url_for('admin.admin_users', local_remote='local') }}">{{ _('Users') }}</a> | <a href="{{ url_for('admin.admin_users', local_remote='local') }}">{{ _('Users') }}</a> |
<a href="{{ url_for('admin.admin_users_trash', local_remote='local') }}">{{ _('Watch') }}</a> |
{% if site.registration_mode == 'RequireApplication' %} {% if site.registration_mode == 'RequireApplication' %}
<a href="{{ url_for('admin.admin_approve_registrations') }}">{{ _('Registration applications') }}</a> | <a href="{{ url_for('admin.admin_approve_registrations') }}">{{ _('Registration applications') }}</a> |
{% endif %} {% endif %}

View file

@ -11,44 +11,84 @@
<div class="col"> <div class="col">
<h1>{{ _('Users') }}</h1> <h1>{{ _('Users') }}</h1>
<a class="btn btn-primary" href="{{ url_for('admin.admin_users_add') }}" style="float: right;">{{ _('Add local user') }}</a> <a class="btn btn-primary" href="{{ url_for('admin.admin_users_add') }}" style="float: right;">{{ _('Add local user') }}</a>
<form method="get"> <form id="searchUsers" method="get">
<input type="search" name="search" value="{{ search }}"> <div>
<input type="radio" name="local_remote" value="local" id="local_remote_local" {{ 'checked' if local_remote == 'local' }}><label for="local_remote_local"> Local</label> <input type="search" name="search" placeholder="{{ _('Search') }}" value="{{ search }}">
<input type="radio" name="local_remote" value="remote" id="local_remote_remote" {{ 'checked' if local_remote == 'remote' }}><label for="local_remote_remote"> Remote</label> <input type="submit" name="submit_search" value="{{ _('Search') }}" class="btn btn-primary">
<input type="submit" name="submit" value="Search" class="btn btn-primary"> </div>
<div style="display:inline;">
<label for="local_remote">Local/Remote: </label>
<select name="local_remote" class="form-control-sm submit_on_change">
<option value="">All</option>
<option value="local" {{ 'selected' if local_remote == 'local' }}>Local</option>
<option value="remote" {{ 'selected' if local_remote == 'remote' }}>Remote</option>
</select>
</div>
<div style="display:inline;">
<label for="last_seen">Active: </label>
<select name="last_seen" class="form-control-sm submit_on_change">
<option value="0">All</option>
<option value="7" {{ 'selected' if last_seen == 7 }}>7 days</option>
<option value="30" {{ 'selected' if last_seen == 30 }}>30 days</option>
</select>
</div>
<input type="hidden" name="sort_by" value="{{ sort_by }}"></button>
</form> </form>
<table class="table table-striped mt-1"> <table class="table table-striped mt-1">
<tr> <tr>
<th title="{{ _('Display name.') }}">{{ _('Name') }}</th> <th>
<th title="{{ _('Last seen.') }}">{{ _('Seen') }}</th> <button form="searchUsers" name="sort_by_btn" value="user_name{{' DESC' if sort_by == 'user_name ASC' else ' ASC' }}" class="btn" title="{{ _('Display name.') }}">
<th title="{{ _('Attitude: Percentage of up votes vs. down votes the account made.') }}">{{ _('Attitude') }}</th> {{ _('Name') }}
<th title="{{ _('Reputation: The Karma of the account. Total up votes minus down votes they got.') }}">{{ _('Reputation') }}</th> <span class="{{ 'fe fe-chevron-up' if sort_by == 'user_name DESC' }}{{ 'fe fe-chevron-down' if sort_by == 'user_name ASC' }}"></span>
</button>
</th>
<th>{{ _('Banned') }}</th> <th>{{ _('Banned') }}</th>
<th title="{{ _('How often a user has been reported.') }}">{{ _('Reports') }}</th> <th>
<th title="{{ _('IP address of last interaction.') }}">{{ _('IP and country code') }}</th> <button form="searchUsers" name="sort_by_btn" value="reports{{' ASC' if sort_by == 'reports DESC' else ' DESC' }}" class="btn" title="{{ _('How often a user has been reported.') }}">
<th title="{{ _('Which website linked to PieFed when the user initially registered.') }}">{{ _('Source') }}</th> {{ _('Reports') }}
<span class="{{ 'fe fe-chevron-up' if sort_by == 'reports ASC' }}{{ 'fe fe-chevron-down' if sort_by == 'reports DESC' }}"></span>
</button>
</th>
<th>
<button form="searchUsers" name="sort_by_btn" value="attitude{{' ASC' if sort_by == 'attitude DESC' else ' DESC' }}" class="btn" title="{{ _('Attitude: Percentage of up votes vs. down votes the account made.') }}">
{{ _('Attitude') }}
<span class="{{ 'fe fe-chevron-up' if sort_by == 'attitude ASC' }}{{ 'fe fe-chevron-down' if sort_by == 'attitude DESC' }}"></span>
</button>
</th>
<th>
<button form="searchUsers" name="sort_by_btn" value="reputation{{' ASC' if sort_by == 'reputation DESC' else ' DESC' }}" class="btn" title="{{ _('Reputation: The Karma of the account. Total up votes minus down votes they got.') }}">
{{ _('Reputation') }}
<span class="{{ 'fe fe-chevron-up' if sort_by == 'reputation ASC' }}{{ 'fe fe-chevron-down' if sort_by == 'reputation DESC' }}"></span>
</button>
</th>
<th>
<button form="searchUsers" name="sort_by_btn" value="last_seen{{' ASC' if sort_by == 'last_seen DESC' else ' DESC' }}" class="btn" title="{{ _('Last seen.') }}">
{{ _('Seen') }}
<span class="{{ 'fe fe-chevron-up' if sort_by == 'last_seen ASC' }}{{ 'fe fe-chevron-down' if sort_by == 'last_seen DESC' }}"></span>
</button>
</th>
<th>{{ _('Actions') }}</th> <th>{{ _('Actions') }}</th>
</tr> </tr>
{% for user in users.items %} {% for user in users.items %}
<tr> <tr>
<td>{{ render_username(user, add_domain=False) }}<br /> <td>{{ render_username(user, add_domain=False) }}<br />
<a href="/u/{{ user.link() }}">{{ user.user_name }}</a>{% if not user.is_local() %}<wbr />@<a href="{{ user.ap_profile_id }}">{{ user.ap_domain }}</a>{% endif %}</td> <a href="/u/{{ user.link() }}">{{ user.user_name }}</a>{% if not user.is_local() %}<wbr />@<a href="{{ user.ap_profile_id }}">{{ user.ap_domain }}</a>{% endif %}</td>
<td>{% if request.args.get('local_remote', '') == 'local' %}
{{ arrow.get(user.last_seen).humanize(locale=locale) }}
{% else %}
{{ user.last_seen }}
{% endif %}
</td>
<td>{% if user.attitude != 1 %}{{ (user.attitude * 100) | round | int }}%{% endif %}</td>
<td>{% if user.reputation %}R {{ user.reputation | round | int }}{% endif %}</td>
<td>{{ '<span class="red">Banned</span>'|safe if user.banned }} <td>{{ '<span class="red">Banned</span>'|safe if user.banned }}
{{ '<span class="red">Banned posts</span>'|safe if user.ban_posts }} {{ '<span class="red">Banned posts</span>'|safe if user.ban_posts }}
{{ '<span class="red">Banned comments</span>'|safe if user.ban_comments }}</td> {{ '<span class="red">Banned comments</span>'|safe if user.ban_comments }}</td>
<td>{{ user.reports if user.reports > 0 }} </td> <td>{{ user.reports if user.reports > 0 }} </td>
<td>{{ user.ip_address if user.ip_address }}<br />{{ user.ip_address_country if user.ip_address_country }}</td> <td>{% if user.attitude != 1 %}{{ (user.attitude * 100) | round | int }}%{% endif %}</td>
<td>{{ user.referrer if user.referrer }} </td> <td>{% if user.reputation %}R {{ user.reputation | round | int }}{% endif %}</td>
<td><a href="{{ url_for('admin.admin_user_edit', user_id=user.id) }}">Edit</a> | <td><span title="{{ user.last_seen }}">{{ arrow.get(user.last_seen).humanize(locale=locale) }}</span></td>
<a href="{{ url_for('admin.admin_user_delete', user_id=user.id) }}" class="confirm_first">Delete</a> <td><a href="{{ url_for('admin.admin_user_edit', user_id=user.id) }}">Edit</a>,
<a href="{{ url_for('admin.admin_user_delete', user_id=user.id) }}" class="confirm_first">Delete</a>,
<br />
{% if user.banned %}
<a href="{{ url_for('user.unban_profile', actor=user.link()) }}" class="confirm_first">Ban</a>,
{% else %}
<a href="{{ url_for('user.ban_profile', actor=user.link()) }}" class="confirm_first">Ban</a>,
{% endif %}
<a href="{{ url_for('user.ban_purge_profile', actor=user.link()) }}" class="confirm_first">Purge</a>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View file

@ -1,77 +0,0 @@
{% if theme() and file_exists('app/templates/themes/' + theme() + '/base.html') %}
{% extends 'themes/' + theme() + '/base.html' %}
{% else %}
{% extends "base.html" %}
{% endif %}
{% from 'bootstrap/form.html' import render_form %}
{% set active_child = 'admin_users_trash' %}
{% block app_content %}
<div class="row">
<div class="col">
<h1>{{ _('Users') }}</h1>
<a class="btn btn-primary" href="{{ url_for('admin.admin_users_add') }}" style="float: right;">{{ _('Add local user') }}</a>
<form method="get">
<input type="search" name="search" value="{{ search }}">
<input type="radio" name="local_remote" value="local" id="local_remote_local" {{ 'checked' if local_remote == 'local' }}><label for="local_remote_local"> Local</label>
<input type="radio" name="local_remote" value="remote" id="local_remote_remote" {{ 'checked' if local_remote == 'remote' }}><label for="local_remote_remote"> Remote</label>
<input type="radio" name="type" value="bad_rep" id="type_bad_rep" {{ 'checked' if type == 'bad_rep' }}><label for="type_bad_rep"> Bad rep</label>
<input type="radio" name="type" value="bad_attitude" id="type_bad_attitude" {{ 'checked' if type == 'bad_attitude' }}><label for="type_bad_attitude"> Bad attitude</label>
<input type="submit" name="submit" value="Search" class="btn btn-primary">
</form>
<table class="table table-striped mt-1">
<tr>
<th>Name</th>
<th>Seen</th>
<th>Attitude</th>
<th>Rep</th>
<th>Banned</th>
<th>Reports</th>
<th>IP</th>
<th>Source</th>
<th>Actions</th>
</tr>
{% for user in users.items %}
<tr>
<td>{{ render_username(user, add_domain=False) }}<br />
<a href="/u/{{ user.link() }}">{{ user.user_name }}</a>{% if not user.is_local() %}<wbr />@<a href="{{ user.ap_profile_id }}">{{ user.ap_domain }}</a>{% endif %}</td>
<td>{% if request.args.get('local_remote', '') == 'local' %}
{{ arrow.get(user.last_seen).humanize(locale=locale) }}
{% else %}
{{ user.last_seen }}
{% endif %}
</td>
<td>{% if user.attitude != 1 %}{{ (user.attitude * 100) | round | int }}%{% endif %}</td>
<td>{% if user.reputation %}R {{ user.reputation | round | int }}{% endif %}</td>
<td>{{ '<span class="red">Banned</span>'|safe if user.banned }} </td>
<td>{{ user.reports if user.reports > 0 }} </td>
<td>{{ user.ip_address if user.ip_address }} </td>
<td>{{ user.referrer if user.referrer }} </td>
<td><a href="{{ url_for('admin.admin_user_edit', user_id=user.id) }}">Edit</a> |
<a href="{{ url_for('admin.admin_user_delete', user_id=user.id) }}" class="confirm_first">Delete</a>
</td>
</tr>
{% endfor %}
</table>
<nav aria-label="Pagination" class="mt-4" role="navigation">
{% if prev_url %}
<a href="{{ prev_url }}" class="btn btn-primary">
<span aria-hidden="true">&larr;</span> {{ _('Previous page') }}
</a>
{% endif %}
{% if next_url %}
<a href="{{ next_url }}" class="btn btn-primary">
{{ _('Next page') }} <span aria-hidden="true">&rarr;</span>
</a>
{% endif %}
</nav>
</div>
</div>
<hr />
<div class="row">
<div class="col">
{% include 'admin/_nav.html' %}
</div>
</div>
<hr />
{% endblock %}

View file

@ -217,7 +217,6 @@
<li><a class="dropdown-item{{ ' active' if active_child == 'admin_communities' }}" href="{{ url_for('admin.admin_communities') }}">{{ _('Communities') }}</a></li> <li><a class="dropdown-item{{ ' active' if active_child == 'admin_communities' }}" href="{{ url_for('admin.admin_communities') }}">{{ _('Communities') }}</a></li>
<li><a class="dropdown-item{{ ' active' if active_child == 'admin_topics' }}" href="{{ url_for('admin.admin_topics') }}">{{ _('Topics') }}</a></li> <li><a class="dropdown-item{{ ' active' if active_child == 'admin_topics' }}" href="{{ url_for('admin.admin_topics') }}">{{ _('Topics') }}</a></li>
<li><a class="dropdown-item{{ ' active' if active_child == 'admin_users' }}" href="{{ url_for('admin.admin_users', local_remote='local') }}">{{ _('Users') }}</a></li> <li><a class="dropdown-item{{ ' active' if active_child == 'admin_users' }}" href="{{ url_for('admin.admin_users', local_remote='local') }}">{{ _('Users') }}</a></li>
<li><a class="dropdown-item{{ ' active' if active_child == 'admin_users_trash' }}" href="{{ url_for('admin.admin_users_trash', local_remote='local') }}">{{ _('Monitoring - users') }}</a></li>
<li><a class="dropdown-item{{ ' active' if active_child == 'admin_content_trash' }}" href="{{ url_for('admin.admin_content_trash') }}">{{ _('Monitoring - content') }}</a></li> <li><a class="dropdown-item{{ ' active' if active_child == 'admin_content_trash' }}" href="{{ url_for('admin.admin_content_trash') }}">{{ _('Monitoring - content') }}</a></li>
<li><a class="dropdown-item{{ ' active' if active_child == 'admin_content_spam' }}" href="{{ url_for('admin.admin_content_spam') }}">{{ _('Monitoring - spammy content') }}</a></li> <li><a class="dropdown-item{{ ' active' if active_child == 'admin_content_spam' }}" href="{{ url_for('admin.admin_content_spam') }}">{{ _('Monitoring - spammy content') }}</a></li>
<li><a class="dropdown-item{{ ' active' if active_child == 'admin_content_deleted' }}" href="{{ url_for('admin.admin_content_deleted') }}">{{ _('Deleted content') }}</a></li> <li><a class="dropdown-item{{ ' active' if active_child == 'admin_content_deleted' }}" href="{{ url_for('admin.admin_content_deleted') }}">{{ _('Deleted content') }}</a></li>

View file

@ -108,6 +108,8 @@
{% if user.is_instance_admin() or (user.is_local() and user.is_admin()) %}<span class="red">({{ _('Admin') }})</span>{% endif %}<br /> {% if user.is_instance_admin() or (user.is_local() and user.is_admin()) %}<span class="red">({{ _('Admin') }})</span>{% endif %}<br />
{% if user.is_admin() or user.is_staff() %}{{ _('Role permissions') }}: {% if user.is_admin() %}{{ _('Admin') }}{% endif %} {% if user.is_staff() %}{{ _('Staff') }}{% endif %}<br />{% endif %} {% if user.is_admin() or user.is_staff() %}{{ _('Role permissions') }}: {% if user.is_admin() %}{{ _('Admin') }}{% endif %} {% if user.is_staff() %}{{ _('Staff') }}{% endif %}<br />{% endif %}
{{ _('Joined') }}: {{ arrow.get(user.created).humanize(locale=locale) }}<br /> {{ _('Joined') }}: {{ arrow.get(user.created).humanize(locale=locale) }}<br />
{% if current_user.is_authenticated and current_user.is_admin() %}{{ _('Referer') }}: <span title="{{ _('Which website linked to PieFed when the user initially registered.') }}">{{ user.referrer if user.referrer }}</span><br />{% endif %}
{% if current_user.is_authenticated and current_user.is_admin() %}{{ _('IP and country code') }}: <span title="{{ _('IP address of last interaction.') }}">{{ user.ip_address if user.ip_address }}{% if user.ip_address_country %} ({{ user.ip_address_country }}){% endif %}</span><br />{% endif %}
{% if current_user.is_authenticated and current_user.is_admin() and user.last_seen %}{{ _('Active') }}: {{ arrow.get(user.last_seen).humanize(locale=locale) }}<br />{% endif %} {% if current_user.is_authenticated and current_user.is_admin() and user.last_seen %}{{ _('Active') }}: {{ arrow.get(user.last_seen).humanize(locale=locale) }}<br />{% endif %}
{% if user.bot %} {% if user.bot %}
{{ _('Bot Account') }}<br /> {{ _('Bot Account') }}<br />