Fixed #376: Emoji admin page

Also did a new table style for admin pages
pull/397/head
Andrew Godwin 2023-01-10 20:31:50 -07:00
rodzic d6f558f89a
commit 113db4ab3a
23 zmienionych plików z 674 dodań i 229 usunięć

Wyświetl plik

@ -125,10 +125,12 @@ class Emoji(StatorModel):
unique_together = ("domain", "shortcode") unique_together = ("domain", "shortcode")
class urls(urlman.Urls): class urls(urlman.Urls):
root = "/admin/emoji/" admin = "/admin/emoji/"
create = "{root}/create/" admin_create = "{admin}create/"
edit = "{root}{self.Emoji}/" admin_edit = "{admin}{self.pk}/"
delete = "{edit}delete/" admin_delete = "{admin}{self.pk}/delete/"
admin_enable = "{admin}{self.pk}/enable/"
admin_disable = "{admin}{self.pk}/disable/"
emoji_regex = re.compile(r"\B:([a-zA-Z0-9(_)-]+):\B") emoji_regex = re.compile(r"\B:([a-zA-Z0-9(_)-]+):\B")
@ -172,8 +174,11 @@ class Emoji(StatorModel):
self.public is None and Config.system.emoji_unreviewed_are_public self.public is None and Config.system.emoji_unreviewed_are_public
) )
def full_url(self) -> RelativeAbsoluteUrl: def full_url_admin(self) -> RelativeAbsoluteUrl:
if self.is_usable: return self.full_url(always_show=True)
def full_url(self, always_show=False) -> RelativeAbsoluteUrl:
if self.is_usable or always_show:
if self.file: if self.file:
return AutoAbsoluteUrl(self.file.url) return AutoAbsoluteUrl(self.file.url)
elif self.remote_url: elif self.remote_url:

Wyświetl plik

@ -117,6 +117,8 @@ class Hashtag(StatorModel):
view = "/tags/{self.hashtag}/" view = "/tags/{self.hashtag}/"
admin = "/admin/hashtags/" admin = "/admin/hashtags/"
admin_edit = "{admin}{self.hashtag}/" admin_edit = "{admin}{self.hashtag}/"
admin_enable = "{admin_edit}enable/"
admin_disable = "{admin_edit}disable/"
timeline = "/tags/{self.hashtag}/" timeline = "/tags/{self.hashtag}/"
hashtag_regex = re.compile(r"\B#([a-zA-Z0-9(_)]+\b)(?!;)") hashtag_regex = re.compile(r"\B#([a-zA-Z0-9(_)]+\b)(?!;)")

Wyświetl plik

@ -1,4 +1,5 @@
import datetime import datetime
from urllib.parse import urlencode
from django import template from django import template
from django.utils import timezone from django.utils import timezone
@ -31,3 +32,18 @@ def timedeltashort(value: datetime.datetime):
years = max(days // 365.25, 1) years = max(days // 365.25, 1)
text = f"{years:0n}y" text = f"{years:0n}y"
return text return text
@register.simple_tag(takes_context=True)
def urlparams(context, **kwargs):
"""
Generates a URL parameter string the same as the current page but with
the given items changed.
"""
params = dict(context["request"].GET.items())
for name, value in kwargs.items():
if value:
params[name] = value
elif name in params:
del params[name]
return urlencode(params)

Wyświetl plik

@ -555,6 +555,92 @@ p.authorization-code {
color: var(--color-text-dull); color: var(--color-text-dull);
} }
/* Item tables */
table.items {
margin: 10px 0;
border: 1px solid var(--color-bg-menu);
border-spacing: 0;
border-radius: 5px;
width: 100%;
}
table.items td {
padding: 10px;
vertical-align: middle;
line-height: 1.1em;
height: 55px;
position: relative;
}
table.items td small {
display: block;
color: var(--color-text-dull);
}
table.items tr:nth-of-type(2n+1) {
background-color: var(--color-bg-box);
}
table.items td.name {
width: 100%;
}
table.items td.name a.overlay,
table.items td.icon a.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
table.items td.icon {
width: 40px;
padding-left: 20px;
}
table.items td.icon img {
width: 32px;
display: block;
}
table.items td .bad {
background: var(--color-delete);
padding: 4px 6px;
border-radius: 3px;
}
table.items td.stat {
white-space: nowrap;
}
table.items td.actions {
text-align: right;
white-space: nowrap;
}
table.items td.actions a {
color: var(--color-text-dull);
padding: 3px 4px;
border-radius: 3px;
text-decoration: none;
border: 3px solid transparent;
margin: 0 0 0 3px;
cursor: pointer;
display: inline-block;
text-align: center;
width: 30px;
}
table.items td.actions a:hover {
color: var(--color-text-main);
background-color: rgba(255, 255, 255, 0.1);
}
table.items td.actions a.danger:hover {
background-color: var(--color-delete);
}
/* Forms */ /* Forms */
@ -1189,13 +1275,14 @@ table.metadata td .emoji {
.view-options { .view-options {
margin: 0 0 10px 0px; margin: 0 0 10px 0px;
display: flex;
} }
.view-options.follows { .view-options.follows {
margin: 0 0 20px 0px; margin: 0 0 20px 0px;
} }
.view-options a { .view-options a:not(.button) {
display: inline-block; display: inline-block;
margin: 0 10px 5px 0; margin: 0 10px 5px 0;
padding: 4px 12px; padding: 4px 12px;
@ -1204,11 +1291,17 @@ table.metadata td .emoji {
border-radius: 3px; border-radius: 3px;
} }
.view-options a:hover { .view-options a.button {
display: inline-block;
margin: 0 0 5px auto;
padding: 1px 8px;
}
.view-options a:not(.button):hover {
color: var(--color-text-dull); color: var(--color-text-dull);
} }
.view-options a.selected { .view-options a:not(.button).selected {
color: var(--color-text-highlight); color: var(--color-text-highlight);
} }
@ -1723,7 +1816,8 @@ form .post {
z-index: 100; z-index: 100;
} }
#image-viewer picture, #image-viewer img { #image-viewer picture,
#image-viewer img {
display: block; display: block;
} }

Wyświetl plik

@ -148,6 +148,23 @@ urlpatterns = [
"admin/hashtags/<hashtag>/", "admin/hashtags/<hashtag>/",
admin.HashtagEdit.as_view(), admin.HashtagEdit.as_view(),
), ),
path("admin/hashtags/<hashtag>/enable/", admin.HashtagEnable.as_view()),
path(
"admin/hashtags/<hashtag>/disable/", admin.HashtagEnable.as_view(enable=False)
),
path(
"admin/emoji/",
admin.EmojiRoot.as_view(),
name="admin_emoji",
),
path(
"admin/emoji/create/",
admin.EmojiCreate.as_view(),
name="admin_emoji_create",
),
path("admin/emoji/<id>/enable/", admin.EmojiEnable.as_view()),
path("admin/emoji/<id>/disable/", admin.EmojiEnable.as_view(enable=False)),
path("admin/emoji/<id>/delete/", admin.EmojiDelete.as_view()),
path( path(
"admin/stator/", "admin/stator/",
admin.Stator.as_view(), admin.Stator.as_view(),

Wyświetl plik

@ -15,14 +15,8 @@
and you will <b>not be able to delete a domain with identities on it</b>. and you will <b>not be able to delete a domain with identities on it</b>.
</p> </p>
<p> <p>
If you will be serving Takahē on the domain you choose, you can leave For more information about domain setup, including what service
the "service domain" field blank. If you would like to let users create domains are, see <a href="https://docs.jointakahe.org/en/latest/domains/">our documentation on domains</a>.
accounts on a domain serving something else, you must pick a unique
"service domain" that pairs up to your chosen domain name, make sure
Takahē is served on that, and add redirects
for <tt>/.well-known/webfinger</tt>, <tt>/.well-known/host-meta</tt>
and <tt>/.well-known/nodeinfo</tt> from the main domain to the
service domain.
</p> </p>
{% csrf_token %} {% csrf_token %}
<fieldset> <fieldset>

Wyświetl plik

@ -3,26 +3,31 @@
{% block subtitle %}Domains{% endblock %} {% block subtitle %}Domains{% endblock %}
{% block content %} {% block content %}
<section class="icon-menu"> <div class="view-options">
<span class="spacer"></span>
<a href="{% url "admin_domains_create" %}" class="button"><i class="fa-solid fa-plus"></i> Add Domain</a>
</div>
<table class="items">
{% for domain in domains %} {% for domain in domains %}
<a class="option" href="{{ domain.urls.edit }}"> <tr>
<i class="fa-solid fa-globe"></i> <td class="icon">
<span class="handle"> <a href="{{ domain.urls.edit }}" class="overlay"></a>
<i class="fa-solid fa-globe"></i>
</td>
<td class="name">
<a href="{{ domain.urls.edit }}" class="overlay"></a>
{{ domain.domain }} {{ domain.domain }}
<small> <small>
{% if domain.public %}Public{% else %}Private{% endif %} {% if domain.service_domain %}{{ domain.service_domain }}{% endif %}
{% if domain.service_domain %}({{ domain.service_domain }}){% endif %}
</small> </small>
</span> </span>
{% if domain.default %} <td class="stat">
<span class="pill">Default</span> {% if domain.public %}Public{% else %}Private{% endif %}
{% endif %} {% if domain.default %}(Default){% endif %}
</a> </td>
</tr>
{% empty %} {% empty %}
<p class="option empty">You have no domains set up.</p> <tr class="empty"><td>You have no domains set up.</td></tr>
{% endfor %} {% endfor %}
<a href="{% url "admin_domains_create" %}" class="option new"> </table>
<i class="fa-solid fa-plus"></i> Add a domain
</a>
</section>
{% endblock %} {% endblock %}

Wyświetl plik

@ -0,0 +1,67 @@
{% extends "settings/base.html" %}
{% load activity_tags %}
{% block subtitle %}Emoji{% endblock %}
{% block content %}
<form action="." class="search">
<input type="search" name="query" value="{{ query }}" placeholder="Search by shortcode or domain">
{% if local_only %}
<input type="hidden" name="local_only" value="true">
{% endif %}
<button><i class="fa-solid fa-search"></i></button>
</form>
<div class="view-options">
{% if local_only %}
<a href=".?{% urlparams local_only=False %}" class="selected"><i class="fa-solid fa-check"></i> Local Only</a>
{% else %}
<a href=".?{% urlparams local_only=True %}"><i class="fa-solid fa-xmark"></i> Local Only</a>
{% endif %}
<a href="{% url "admin_emoji_create" %}" class="button">Add Emoji</a>
</div>
<table class="items">
{% for emoji in page_obj %}
<tr>
<td class="icon">
<img src="{{ emoji.full_url_admin.relative }}" class="icon">
</td>
<td class="name">
{{ emoji.shortcode }}
{% if emoji.domain %}<small>{{ emoji.domain }}</small>{% endif %}
</td>
<td>
</td>
<td class="actions">
{% if not emoji.is_usable %}
<span class="bad">Disabled</span>
<a hx-post="{{ emoji.urls.admin_enable }}" title="Enable"><i class="fa-solid fa-circle-check"></i></a>
{% else %}
<a hx-post="{{ emoji.urls.admin_disable }}" class="danger" title="Disable"><i class="fa-solid fa-circle-xmark"></i></a>
{% endif %}
<a hx-post="{{ emoji.urls.admin_delete }}" hx-confirm="Are you sure you want to delete :{{ emoji.shortcode }}:?" class="danger" title="Delete"><i class="fa-solid fa-trash"></i></a>
</td>
</tr>
{% empty %}
<tr class="empty">
<td>
{% if query %}
No emoji match your query.
{% else %}
There are no emoji yet.
{% endif %}
</td>
</tr>
{% endfor %}
</table>
<div class="pagination">
{% if page_obj.has_previous %}
<a class="button" href=".?{% urlparams page=page_obj.previous_page_number %}">Previous Page</a>
{% endif %}
{% if page_obj.paginator.count %}
<span class="count">{{ page_obj.paginator.count }} emoji</span>
{% endif %}
{% if page_obj.has_next %}
<a class="button" href=".?{% urlparams page=page_obj.next_page_number %}">Next Page</a>
{% endif %}
</div>
{% endblock %}

Wyświetl plik

@ -0,0 +1,18 @@
{% extends "settings/base.html" %}
{% block subtitle %}{{ emoji.shortcode }}{% endblock %}
{% block content %}
<form action="." method="POST" enctype="multipart/form-data">
{% csrf_token %}
<fieldset>
<legend>Emoji Details</legend>
{% include "forms/_field.html" with field=form.shortcode %}
{% include "forms/_field.html" with field=form.image hide_existing=True %}
</fieldset>
<div class="buttons">
<a href="{% url "admin_emoji" %}" class="button secondary left">Back</a>
<button>Save</button>
</div>
</form>
{% endblock %}

Wyświetl plik

@ -1,4 +1,5 @@
{% extends "settings/base.html" %} {% extends "settings/base.html" %}
{% load activity_tags %}
{% block subtitle %}Federation{% endblock %} {% block subtitle %}Federation{% endblock %}
@ -7,33 +8,49 @@
<input type="search" name="query" value="{{ query }}" placeholder="Search by domain"> <input type="search" name="query" value="{{ query }}" placeholder="Search by domain">
<button><i class="fa-solid fa-search"></i></button> <button><i class="fa-solid fa-search"></i></button>
</form> </form>
<section class="icon-menu"> <table class="items">
{% for domain in page_obj %} {% for domain in page_obj %}
<a class="option" href="{{ domain.urls.edit_federation }}"> <tr>
<i class="fa-solid fa-globe"></i> <td class="icon">
<span class="handle"> <a href="{{ domain.urls.edit_federation }}" class="overlay"></a>
<i class="fa-solid fa-globe"></i>
</td>
<td class="name">
<a href="{{ domain.urls.edit_federation }}" class="overlay"></a>
{{ domain.domain }} {{ domain.domain }}
<small> <small>{{ domain.software }}</small>
{{ domain.num_users }} remote identit{{ domain.num_users|pluralize:"y,ies" }} </td>
</small> <td>
</span> {% if domain.blocked %}
{% if domain.blocked %} <span class="bad">Blocked</span>
<span class="pill bad">Blocked</span> {% endif %}
{% endif %} </td>
</a> <td class="stat">
{{ domain.num_users }}
<small>identit{{ domain.num_users|pluralize:"y,ies" }}</small>
</td>
</tr>
{% empty %} {% empty %}
<p class="option empty">There are no federation links yet.</p> <tr class="empty">
<td>
{% if query %}
There are no domains matching your query.
{% else %}
There are no federation links yet.
{% endif %}
</td>
</tr>
{% endfor %} {% endfor %}
<div class="pagination"> </table>
{% if page_obj.has_previous %} <div class="pagination">
<a class="button" href=".?page={{ page_obj.previous_page_number }}">Previous Page</a> {% if page_obj.has_previous %}
{% endif %} <a class="button" href=".?{% urlparams page=page_obj.previous_page_number %}">Previous Page</a>
{% if page_obj.paginator.count %} {% endif %}
<span class="count">{{ page_obj.paginator.count }} domain{{page_obj.paginator.count|pluralize }}</span> {% if page_obj.paginator.count %}
{% endif %} <span class="count">{{ page_obj.paginator.count }} domain{{page_obj.paginator.count|pluralize }}</span>
{% if page_obj.has_next %} {% endif %}
<a class="button" href=".?page={{ page_obj.next_page_number }}">Next Page</a> {% if page_obj.has_next %}
{% endif %} <a class="button" href=".?{% urlparams page=page_obj.next_page_number %}">Next Page</a>
</div> {% endif %}
</section> </div>
{% endblock %} {% endblock %}

Wyświetl plik

@ -11,7 +11,7 @@
{% include "forms/_field.html" with field=form.blocked %} {% include "forms/_field.html" with field=form.blocked %}
</fieldset> </fieldset>
<div class="buttons"> <div class="buttons">
<a href="{{ domain.urls.root }}" class="button secondary left">Back</a> <a href="{{ domain.urls.root_federation }}" class="button secondary left">Back</a>
<a href="{{ domain.urls.delete }}" class="button delete">Delete</a> <a href="{{ domain.urls.delete }}" class="button delete">Delete</a>
<button>Save</button> <button>Save</button>
</div> </div>

Wyświetl plik

@ -3,50 +3,62 @@
{% block subtitle %}Hashtags{% endblock %} {% block subtitle %}Hashtags{% endblock %}
{% block content %} {% block content %}
<section class="icon-menu"> <table class="items">
{% for hashtag in page_obj %} {% for hashtag in page_obj %}
<a class="option hashtags" href="{{ hashtag.urls.admin_edit }}"> <tr>
<div class="tag"> <td class="icon">
<a href="{{ hashtag.urls.admin_edit }}" class="overlay"></a>
<i class="fa-solid fa-hashtag"></i> <i class="fa-solid fa-hashtag"></i>
<span class="handle"> </td>
{{ hashtag.display_name }} <td class="name">
<small> <a href="{{ hashtag.urls.admin_edit }}" class="overlay"></a>
{% if hashtag.public %}Public{% elif hashtag.public is None %}Unreviewed{% else %}Private{% endif %} {{ hashtag.display_name }}
</small> <small>{% if hashtag.public %}Public{% elif hashtag.public is None %}Unreviewed{% else %}Private{% endif %}</small>
</span> </td>
</div> <td class="stat">
{% if hashtag.stats %} {% if hashtag.stats %}
<div class="count"> {{ hashtag.stats.total }}
<span class="handle"> <small>post{{ hashtag.stats.total|pluralize }}</small>
{{ hashtag.stats.total }} {% endif %}
<small>Total</small> </td>
</span> <td class="stat">
</div> {% if hashtag.aliases %}
{% endif %} {% for alias in hashtag.aliases %}
{% if hashtag.aliases %} {{ alias }}{% if not forloop.last %}, {% endif %}
<div class="count"> {% endfor %}
<span class="handle"> <small>Aliases</small>
{% for alias in hashtag.aliases %} {% endif %}
{{ alias }}{% if not forloop.last %}, {% endif %} </td>
{% endfor %} <td class="actions">
<small>Aliases</small> {% if hashtag.public is not True %}
</span> <a hx-post="{{ hashtag.urls.admin_enable }}" title="Make Public"><i class="fa-solid fa-circle-check"></i></a>
</div> {% endif %}
{% endif %} {% if hashtag.public is not False %}
</a> <a hx-post="{{ hashtag.urls.admin_disable }}" title="Make Private"><i class="fa-solid fa-circle-xmark"></i></a>
{% endif %}
</td>
</tr>
{% empty %} {% empty %}
<p class="option empty">There are no hashtags yet.</p> <tr class="empty">
<td>
{% if query %}
No hashtags match your query.
{% else %}
There are no hashtags yet.
{% endif %}
</td>
</tr>
{% endfor %} {% endfor %}
<div class="pagination"> </table>
{% if page_obj.has_previous %} <div class="pagination">
<a class="button" href=".?page={{ page_obj.previous_page_number }}">Previous Page</a> {% if page_obj.has_previous %}
{% endif %} <a class="button" href=".?page={{ page_obj.previous_page_number }}">Previous Page</a>
{% if page_obj.paginator.count %} {% endif %}
<span class="count">{{ page_obj.paginator.count }} hashtag{{page_obj.paginator.count|pluralize }}</span> {% if page_obj.paginator.count %}
{% endif %} <span class="count">{{ page_obj.paginator.count }} hashtag{{page_obj.paginator.count|pluralize }}</span>
{% if page_obj.has_next %} {% endif %}
<a class="button" href=".?page={{ page_obj.next_page_number }}">Next Page</a> {% if page_obj.has_next %}
{% endif %} <a class="button" href=".?page={{ page_obj.next_page_number }}">Next Page</a>
</div> {% endif %}
</section> </div>
{% endblock %} {% endblock %}

Wyświetl plik

@ -1,4 +1,5 @@
{% extends "settings/base.html" %} {% extends "settings/base.html" %}
{% load activity_tags %}
{% block subtitle %}Identities{% endblock %} {% block subtitle %}Identities{% endblock %}
@ -12,42 +13,71 @@
</form> </form>
<div class="view-options"> <div class="view-options">
{% if local_only %} {% if local_only %}
<a href=".?{% if query %}query={{ query }}{% endif %}" class="selected"><i class="fa-solid fa-check"></i> Local Only</a> <a href=".?{% urlparams local_only=False %}" class="selected"><i class="fa-solid fa-check"></i> Local Only</a>
{% else %} {% else %}
<a href=".?local_only=true{% if query %}&amp;query={{ query }}{% endif %}"><i class="fa-solid fa-xmark"></i> Local Only</a> <a href=".?{% urlparams local_only=True %}"><i class="fa-solid fa-xmark"></i> Local Only</a>
{% endif %} {% endif %}
</div> </div>
<section class="icon-menu"> <table class="items">
{% for identity in page_obj %} {% for identity in page_obj %}
<a class="option" href="{{ identity.urls.admin_edit }}"> <tr>
<div class="option-content"> <td class="icon">
{% include "identity/_identity_banner.html" with identity=identity link_avatar=False link_handle=False %} <a href="{{ identity.urls.admin_edit }}" class="overlay"></a>
</div> <img
<div class="option-actions"> src="{{ identity.local_icon_url.relative }}"
{% if identity.banned %} class="icon"
<span class="pill bad">Banned</span> alt="Avatar for {{ identity.name_or_handle }}"
loading="lazy"
data-handle="{{ identity.name_or_handle }}"
_="on error set my.src to generate_avatar(@data-handle)"
>
</td>
<td class="name">
<a href="{{ identity.urls.admin_edit }}" class="overlay"></a>
{{ identity.html_name_or_handle }}
<small>@{{ identity.handle }}</small>
</td>
<td>
{% if identity.restriction == 1 %}
<span class="bad">Limited</span>
{% elif identity.restriction == 2 %}
<span class="bad">Blocked</span>
{% endif %} {% endif %}
</div> </td>
</a> <td class="stat">
{% if identity.local %}
Local
<small>{{ identity.followers_count }} follower{{ identity.followers_count|pluralize }}</small>
{% else %}
Remote
<small>{{ identity.followers_count }} local follower{{ identity.followers_count|pluralize }}</small>
{% endif %}
</td>
<td class="actions">
<a href="{{ identity.urls.admin_edit }}" title="View"><i class="fa-solid fa-eye"></i></a>
</td>
</tr>
{% empty %} {% empty %}
<p class="option empty"> <tr class="empty">
{% if query %} <td>
No identities match your query. {% if query %}
{% else %} No identities match your query.
There are no identities yet. {% else %}
{% endif %} There are no identities yet.
</p> {% endif %}
</td>
</tr>
{% endfor %} {% endfor %}
<div class="pagination"> </table>
{% if page_obj.has_previous %} <div class="pagination">
<a class="button" href=".?page={{ page_obj.previous_page_number }}{% if local_only %}&amp;local_only=true{% endif %}{% if query %}&amp;query={{ query }}{% endif %}">Previous Page</a> {% if page_obj.has_previous %}
{% endif %} <a class="button" href=".?{% urlparams page=page_obj.previous_page_number %}">Previous Page</a>
{% if page_obj.paginator.count %} {% endif %}
<span class="count">{{ page_obj.paginator.count }} identit{{page_obj.paginator.count|pluralize:"y,ies" }}</span> {% if page_obj.paginator.count %}
{% endif %} <span class="count">{{ page_obj.paginator.count }} identit{{page_obj.paginator.count|pluralize:"y,ies" }}</span>
{% if page_obj.has_next %} {% endif %}
<a class="button" href=".?page={{ page_obj.next_page_number }}{% if local_only %}&amp;local_only=true{% endif %}{% if query %}&amp;query={{ query }}{% endif %}">Next Page</a> {% if page_obj.has_next %}
{% endif %} <a class="button" href=".?{% urlparams page=page_obj.next_page_number %}">Next Page</a>
</div> {% endif %}
</section> </div>
{% endblock %} {% endblock %}

Wyświetl plik

@ -4,51 +4,62 @@
{% block subtitle %}Invites{% endblock %} {% block subtitle %}Invites{% endblock %}
{% block content %} {% block content %}
<form> <div class="view-options">
<div class="buttons"> <span class="spacer"></span>
<a href="{% url "admin_invite_create" %}" class="button">Create New</a> <a href="{% url "admin_invite_create" %}" class="button">Create New</a>
</div> </div>
</form> <table class="items">
<section class="icon-menu">
{% for invite in page_obj %} {% for invite in page_obj %}
<a class="option" href="{{ invite.urls.admin_view }}"> <tr>
<i class="fa-solid fa-envelope"></i> <td class="icon">
<span class="handle"> <a href="{{ invite.urls.admin_view }}" class="overlay"></a>
<i class="fa-solid fa-envelope"></i>
</td>
<td class="name">
<a href="{{ invite.urls.admin_view }}" class="overlay"></a>
{{ invite.token }} {{ invite.token }}
<small> <small>
{% if invite.expires %}
Expires in {{ invite.expires|timeuntil }}
{% if invite.note %}|{% endif %}
{% endif %}
{% if invite.note %} {% if invite.note %}
{{ invite.note }} {{ invite.note }}
{% endif %} {% endif %}
</small> </small>
</span> </td>
<time> <td class="stat">
{% if invite.uses %} {% if invite.expires %}
{{ invite.uses }} use{{ invite.uses|pluralize }} left {% if invite.valid %}
{% else %} {{ invite.expires|timeuntil }}
Infinite uses <small>until expiry</small>
{% else %}
<span class="bad">Expired</span>
{% endif %}
{% endif %} {% endif %}
</time> </time>
</a> <td class="stat">
{% if invite.uses %}
{{ invite.uses }}
{% else %}
Infinite
{% endif %}
<small>use{{ invite.uses|pluralize }} left</small>
</time>
</tr>
{% empty %} {% empty %}
<p class="option empty"> <tr class="empty">
There are no unused invites. <td>
</p> There are no invites yet.
</td>
</tr>
{% endfor %} {% endfor %}
<div class="pagination"> </table>
{% if page_obj.has_previous %} <div class="pagination">
<a class="button" href=".?page={{ page_obj.previous_page_number }}">Previous Page</a> {% if page_obj.has_previous %}
{% endif %} <a class="button" href=".?page={{ page_obj.previous_page_number }}">Previous Page</a>
{% if page_obj.paginator.count %} {% endif %}
<span class="count">{{ page_obj.paginator.count }} invite{{page_obj.paginator.count|pluralize }}</span> {% if page_obj.paginator.count %}
{% endif %} <span class="count">{{ page_obj.paginator.count }} invite{{page_obj.paginator.count|pluralize }}</span>
{% if page_obj.has_next %} {% endif %}
<a class="button" href=".?page={{ page_obj.next_page_number }}">Next Page</a> {% if page_obj.has_next %}
{% endif %} <a class="button" href=".?page={{ page_obj.next_page_number }}">Next Page</a>
</div> {% endif %}
</section> </div>
{% endblock %} {% endblock %}

Wyświetl plik

@ -11,36 +11,48 @@
<a href=".?all=true"><i class="fa-solid fa-xmark"></i> Show Resolved</a> <a href=".?all=true"><i class="fa-solid fa-xmark"></i> Show Resolved</a>
{% endif %} {% endif %}
</div> </div>
<section class="icon-menu"> <table class="items">
{% for report in page_obj %} {% for report in page_obj %}
<a class="option" href="{{ report.urls.admin_view }}"> <tr>
<img src="{{ report.subject_identity.local_icon_url.relative }}" class="icon" alt="Avatar for {{ report.subject_identity.name_or_handle }}"> <td class="icon">
<span class="handle"> <a href="{{ report.urls.admin_view }}" class="overlay"></a>
<img src="{{ report.subject_identity.local_icon_url.relative }}" class="icon" alt="Avatar for {{ report.subject_identity.name_or_handle }}">
</td>
<td class="name">
<a href="{{ report.urls.admin_view }}" class="overlay"></a>
{{ report.subject_identity.html_name_or_handle }} {{ report.subject_identity.html_name_or_handle }}
{% if report.subject_post %} {% if report.subject_post %}
(post {{ report.subject_post.pk }}) <small>
Post on {{ report.subject_post.published }}
</small>
{% endif %} {% endif %}
<small> </td>
{{ report.type|title }} <td class="stat">
</small> {{ report.type|title }}
</span> <small>Type</small>
<time>{{ report.created|timedeltashort }} ago</time> </td>
</a> <td class="stat">
{{ report.created|timedeltashort }}
<small>Reported</small>
</td>
</tr>
{% empty %} {% empty %}
<p class="option empty"> <tr class="empty">
There are no {% if all %}reports yet{% else %}unresolved reports{% endif %}. <td>
</p> There are no {% if all %}reports yet{% else %}unresolved reports{% endif %}.
</td>
</tr>
{% endfor %} {% endfor %}
<div class="pagination"> </table>
{% if page_obj.has_previous %} <div class="pagination">
<a class="button" href=".?page={{ page_obj.previous_page_number }}{% if all %}&amp;all=true{% endif %}">Previous Page</a> {% if page_obj.has_previous %}
{% endif %} <a class="button" href=".?page={{ page_obj.previous_page_number }}{% if all %}&amp;all=true{% endif %}">Previous Page</a>
{% if page_obj.paginator.count %} {% endif %}
<span class="count">{{ page_obj.paginator.count }} report{{page_obj.paginator.count|pluralize }}</span> {% if page_obj.paginator.count %}
{% endif %} <span class="count">{{ page_obj.paginator.count }} report{{page_obj.paginator.count|pluralize }}</span>
{% if page_obj.has_next %} {% endif %}
<a class="button" href=".?page={{ page_obj.next_page_number }}{% if all %}&amp;all=true{% endif %}">Next Page</a> {% if page_obj.has_next %}
{% endif %} <a class="button" href=".?page={{ page_obj.next_page_number }}{% if all %}&amp;all=true{% endif %}">Next Page</a>
</div> {% endif %}
</section> </div>
{% endblock %} {% endblock %}

Wyświetl plik

@ -1,4 +1,5 @@
{% extends "settings/base.html" %} {% extends "settings/base.html" %}
{% load activity_tags %}
{% block subtitle %}Users{% endblock %} {% block subtitle %}Users{% endblock %}
@ -7,39 +8,51 @@
<input type="search" name="query" value="{{ query }}" placeholder="Search by email"> <input type="search" name="query" value="{{ query }}" placeholder="Search by email">
<button><i class="fa-solid fa-search"></i></button> <button><i class="fa-solid fa-search"></i></button>
</form> </form>
<section class="icon-menu"> <table class="items">
{% for user in page_obj %} {% for user in page_obj %}
<a class="option" href="{{ user.urls.admin_edit }}"> <tr>
<i class="fa-solid fa-user"></i> <td class="icon">
<span class="handle"> <a href="{{ user.urls.admin_edit }}" class="overlay"></a>
<i class="fa-solid fa-user"></i>
</td>
<td class="name">
<a href="{{ user.urls.admin_edit }}" class="overlay"></a>
{{ user.email }} {{ user.email }}
<small>{% if user.admin %}Admin{% elif user.moderator %}Moderator{% endif %}</small>
</td>
<td class="stat">
{{ user.num_identities }}
<small> <small>
{{ user.num_identities }} identit{{ user.num_identities|pluralize:"y,ies" }} identit{{ user.num_identities|pluralize:"y,ies" }}
</small> </small>
</span> </span>
{% if user.banned %} <td class="actions">
<span class="pill bad">Banned</span> {% if user.banned %}
{% endif %} <span class="bad">Banned</span>
</a> {% endif %}
</td>
</tr>
{% empty %} {% empty %}
<p class="option empty"> <tr class="empty">
{% if query %} <td>
No users match your query. {% if query %}
{% else %} No users match your query.
There are no users yet. {% else %}
{% endif %} There are no users yet.
</p> {% endif %}
</td>
</tr>
{% endfor %} {% endfor %}
<div class="pagination"> </table>
{% if page_obj.has_previous %} <div class="pagination">
<a class="button" href=".?page={{ page_obj.previous_page_number }}">Previous Page</a> {% if page_obj.has_previous %}
{% endif %} <a class="button" href=".?{% urlparams page=page_obj.previous_page_number %}">Previous Page</a>
{% if page_obj.paginator.count %} {% endif %}
<span class="count">{{ page_obj.paginator.count }} user{{page_obj.paginator.count|pluralize }}</span> {% if page_obj.paginator.count %}
{% endif %} <span class="count">{{ page_obj.paginator.count }} user{{page_obj.paginator.count|pluralize }}</span>
{% if page_obj.has_next %} {% endif %}
<a class="button" href=".?page={{ page_obj.next_page_number }}">Next Page</a> {% if page_obj.has_next %}
{% endif %} <a class="button" href=".?{% urlparams page=page_obj.next_page_number %}">Next Page</a>
</div> {% endif %}
</section> </div>
{% endblock %} {% endblock %}

Wyświetl plik

@ -10,14 +10,14 @@
</p> </p>
{% endif %} {% endif %}
{{ field.errors }} {{ field.errors }}
{% if field.field.widget.input_type == "file" and field.value %} {% if field.field.widget.input_type == "file" and field.value and not hide_existing %}
<div class="clear"> <div class="clear">
<input type="checkbox" class="clear" name="{{ field.name }}__clear"> Clear current value</input> <input type="checkbox" class="clear" name="{{ field.name }}__clear"> Clear current value</input>
</div> </div>
{% endif %} {% endif %}
{{ field }} {{ field }}
</div> </div>
{% if field.field.widget.input_type == "file" and field.value %} {% if field.field.widget.input_type == "file" and field.value and not hide_existing %}
<img class="preview" src="{{ field.value }}"> <img class="preview" src="{{ field.value }}">
{% endif %} {% endif %}
</div> </div>

Wyświetl plik

@ -24,6 +24,9 @@
<a href="{% url "admin_hashtags" %}" {% if section == "hashtags" %}class="selected"{% endif %} title="Hashtags"> <a href="{% url "admin_hashtags" %}" {% if section == "hashtags" %}class="selected"{% endif %} title="Hashtags">
<i class="fa-solid fa-hashtag"></i> Hashtags <i class="fa-solid fa-hashtag"></i> Hashtags
</a> </a>
<a href="{% url "admin_emoji" %}" {% if section == "emoji" %}class="selected"{% endif %} title="Emoji">
<i class="fa-solid fa-icons"></i> Emoji
</a>
<a href="{% url "admin_reports" %}" {% if section == "reports" %}class="selected"{% endif %} title="Reports"> <a href="{% url "admin_reports" %}" {% if section == "reports" %}class="selected"{% endif %} title="Reports">
<i class="fa-solid fa-flag"></i> Reports <i class="fa-solid fa-flag"></i> Reports
</a> </a>

Wyświetl plik

@ -203,3 +203,12 @@ class Domain(StatorModel):
f"Client error decoding nodeinfo: domain={self.domain}, error={str(ex)}" f"Client error decoding nodeinfo: domain={self.domain}, error={str(ex)}"
) )
return info return info
@property
def software(self):
if self.nodeinfo:
software = self.nodeinfo.get("software", {})
name = software.get("name", "unknown")
version = software.get("version", "unknown")
return f"{name:.10} - {version:.10}"
return None

Wyświetl plik

@ -8,8 +8,14 @@ from users.views.admin.domains import ( # noqa
DomainEdit, DomainEdit,
Domains, Domains,
) )
from users.views.admin.emoji import ( # noqa
EmojiCreate,
EmojiDelete,
EmojiEnable,
EmojiRoot,
)
from users.views.admin.federation import FederationEdit, FederationRoot # noqa from users.views.admin.federation import FederationEdit, FederationRoot # noqa
from users.views.admin.hashtags import HashtagEdit, Hashtags # noqa from users.views.admin.hashtags import HashtagEdit, HashtagEnable, Hashtags # noqa
from users.views.admin.identities import IdentitiesRoot, IdentityEdit # noqa from users.views.admin.identities import IdentitiesRoot, IdentityEdit # noqa
from users.views.admin.invites import InviteCreate, InvitesRoot, InviteView # noqa from users.views.admin.invites import InviteCreate, InvitesRoot, InviteView # noqa
from users.views.admin.reports import ReportsRoot, ReportView # noqa from users.views.admin.reports import ReportsRoot, ReportView # noqa

Wyświetl plik

@ -0,0 +1,96 @@
from django import forms
from django.conf import settings
from django.db import models
from django.shortcuts import get_object_or_404, redirect
from django.utils.decorators import method_decorator
from django.views.generic import FormView, ListView, View
from django_htmx.http import HttpResponseClientRefresh
from activities.models import Emoji
from users.decorators import moderator_required
@method_decorator(moderator_required, name="dispatch")
class EmojiRoot(ListView):
template_name = "admin/emoji.html"
paginate_by = 50
def get(self, request, *args, **kwargs):
self.query = request.GET.get("query")
self.local_only = request.GET.get("local_only")
self.extra_context = {
"section": "emoji",
"query": self.query or "",
"local_only": self.local_only,
}
return super().get(request, *args, **kwargs)
def get_queryset(self):
queryset = Emoji.objects.filter().order_by("shortcode", "domain_id")
if self.local_only:
queryset = queryset.filter(local=True)
if self.query:
query = self.query.lower().strip().lstrip("@")
queryset = queryset.filter(
models.Q(shortcode__icontains=query) | models.Q(domain_id=query)
)
return queryset
@method_decorator(moderator_required, name="dispatch")
class EmojiCreate(FormView):
template_name = "admin/emoji_create.html"
extra_context = {"section": "emoji"}
class form_class(forms.Form):
shortcode = forms.SlugField(
help_text="What users type to use the emoji :likethis:",
)
image = forms.ImageField(
help_text="The emoji image\nShould be at least 40 x 40 pixels, and under 50kb",
)
def clean_image(self):
data = self.cleaned_data["image"]
if data.size > settings.SETUP.EMOJI_MAX_IMAGE_FILESIZE_KB:
raise forms.ValidationError("Image filesize is too large")
return data
def form_valid(self, form):
Emoji.objects.create(
shortcode=form.cleaned_data["shortcode"],
file=form.cleaned_data["image"],
mimetype=form.cleaned_data["image"].image.get_format_mimetype(),
local=True,
public=True,
)
return redirect(Emoji.urls.admin)
@method_decorator(moderator_required, name="dispatch")
class EmojiDelete(View):
"""
Deletes an emoji
"""
def post(self, request, id):
self.emoji = get_object_or_404(Emoji, pk=id)
self.emoji.delete()
return HttpResponseClientRefresh()
@method_decorator(moderator_required, name="dispatch")
class EmojiEnable(View):
"""
Sets an emoji to be enabled (or not!)
"""
enable = True
def post(self, request, id):
self.emoji = get_object_or_404(Emoji, pk=id)
self.emoji.public = self.enable
self.emoji.save()
return HttpResponseClientRefresh()

Wyświetl plik

@ -1,7 +1,8 @@
from django import forms from django import forms
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.generic import FormView, ListView from django.views.generic import FormView, ListView, View
from django_htmx.http import HttpResponseClientRefresh
from activities.models import Hashtag, HashtagStates from activities.models import Hashtag, HashtagStates
from users.decorators import moderator_required from users.decorators import moderator_required
@ -78,3 +79,18 @@ class HashtagEdit(FormView):
"name_override": self.hashtag.name_override, "name_override": self.hashtag.name_override,
"public": self.hashtag.public, "public": self.hashtag.public,
} }
@method_decorator(moderator_required, name="dispatch")
class HashtagEnable(View):
"""
Sets a hashtag to be enabled (or not!)
"""
enable = True
def post(self, request, hashtag):
self.hashtag = get_object_or_404(Hashtag, hashtag=hashtag)
self.hashtag.public = self.enable
self.hashtag.save()
return HttpResponseClientRefresh()

Wyświetl plik

@ -25,9 +25,11 @@ class IdentitiesRoot(ListView):
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
def get_queryset(self): def get_queryset(self):
identities = Identity.objects.annotate( identities = (
num_users=models.Count("users") Identity.objects.annotate(num_users=models.Count("users"))
).order_by("created") .annotate(followers_count=models.Count("inbound_follows"))
.order_by("created")
)
if self.local_only: if self.local_only:
identities = identities.filter(local=True) identities = identities.filter(local=True)
if self.query: if self.query: