mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-23 10:01:04 +00:00
Merge pull request #2298 from bookwyrm-social/import-review-null-state
Adds celery status view
This commit is contained in:
commit
d5c095ce6e
7 changed files with 247 additions and 0 deletions
109
bookwyrm/templates/settings/celery.html
Normal file
109
bookwyrm/templates/settings/celery.html
Normal file
|
@ -0,0 +1,109 @@
|
|||
{% extends 'settings/layout.html' %}
|
||||
{% load humanize %}
|
||||
{% load i18n %}
|
||||
{% load celery_tags %}
|
||||
|
||||
{% block title %}{% trans "Celery Status" %}{% endblock %}
|
||||
|
||||
{% block header %}{% trans "Celery Status" %}{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
|
||||
{% if queues %}
|
||||
<section class="block content">
|
||||
<h2>{% trans "Queues" %}</h2>
|
||||
<div class="columns has-text-centered">
|
||||
<div class="column is-4">
|
||||
<div class="notification">
|
||||
<p class="header">{% trans "Low priority" %}</p>
|
||||
<p class="title is-5">{{ queues.low_priority|intcomma }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<div class="notification">
|
||||
<p class="header">{% trans "Medium priority" %}</p>
|
||||
<p class="title is-5">{{ queues.medium_priority|intcomma }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<div class="notification">
|
||||
<p class="header">{% trans "High priority" %}</p>
|
||||
<p class="title is-5">{{ queues.high_priority|intcomma }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% else %}
|
||||
<div class="notification is-danger is-flex is-align-items-start">
|
||||
<span class="icon icon-warning is-size-4 pr-3" aria-hidden="true"></span>
|
||||
<span>
|
||||
{% trans "Could not connect to Redis broker" %}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% if stats %}
|
||||
<section class="block content">
|
||||
<h2>{% trans "Active Tasks" %}</h2>
|
||||
{% for worker in active_tasks.values %}
|
||||
<div class="table-container">
|
||||
<table class="table is-striped is-fullwidth">
|
||||
<tr>
|
||||
<th>{% trans "ID" %}</th>
|
||||
<th>{% trans "Task name" %}</th>
|
||||
<th>{% trans "Run time" %}</th>
|
||||
<th>{% trans "Priority" %}</th>
|
||||
</tr>
|
||||
{% if not worker %}
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
<em>{% trans "No active tasks" %}</em>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% for task in worker %}
|
||||
<tr>
|
||||
<td>{{ task.id }}</td>
|
||||
<td>{{ task.name|shortname }}</td>
|
||||
<td>{{ task.time_start|runtime }}</td>
|
||||
<td>{{ task.delivery_info.routing_key }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</section>
|
||||
|
||||
<section class="block content">
|
||||
<h2>{% trans "Workers" %}</h2>
|
||||
|
||||
{% for worker_name, worker in stats.items %}
|
||||
<div class="notification">
|
||||
<h3>{{ worker_name }}</h3>
|
||||
{% trans "Uptime:" %} {{ worker.uptime|uptime }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</section>
|
||||
|
||||
{% else %}
|
||||
|
||||
<div class="notification is-danger is-flex is-align-items-start">
|
||||
<span class="icon icon-warning is-size-4 pr-3" aria-hidden="true"></span>
|
||||
<span>
|
||||
{% trans "Could not connect to Celery" %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if errors %}
|
||||
<div class="block content">
|
||||
<h2>{% trans "Errors" %}</h2>
|
||||
{% for error in errors %}
|
||||
<pre>{{ error }}</pre>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
|
@ -74,6 +74,15 @@
|
|||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if perms.edit_instance_settings %}
|
||||
<h2 class="menu-label">{% trans "System" %}</h2>
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
{% url 'settings-celery' as url %}
|
||||
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Celery status" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if perms.bookwyrm.edit_instance_settings %}
|
||||
<h2 class="menu-label">{% trans "Instance Settings" %}</h2>
|
||||
<ul class="menu-list">
|
||||
|
|
24
bookwyrm/templatetags/celery_tags.py
Normal file
24
bookwyrm/templatetags/celery_tags.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
""" template filters for really common utilities """
|
||||
import datetime
|
||||
from django import template
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter(name="uptime")
|
||||
def uptime(seconds):
|
||||
"""Seconds uptime to a readable format"""
|
||||
return str(datetime.timedelta(seconds=seconds))
|
||||
|
||||
|
||||
@register.filter(name="runtime")
|
||||
def runtime(timestamp):
|
||||
"""How long has it been?"""
|
||||
return datetime.datetime.now() - datetime.datetime.fromtimestamp(timestamp)
|
||||
|
||||
|
||||
@register.filter(name="shortname")
|
||||
def shortname(name):
|
||||
"""removes bookwyrm.celery..."""
|
||||
return ".".join(name.split(".")[-2:])
|
45
bookwyrm/tests/views/admin/test_celery.py
Normal file
45
bookwyrm/tests/views/admin/test_celery.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
""" test for app action functionality """
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.contrib.auth.models import Group
|
||||
from django.template.response import TemplateResponse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from bookwyrm import models, views
|
||||
from bookwyrm.management.commands import initdb
|
||||
from bookwyrm.tests.validate_html import validate_html
|
||||
|
||||
|
||||
class CeleryStatusViews(TestCase):
|
||||
"""every response to a get request, html or json"""
|
||||
|
||||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||
"bookwyrm.activitystreams.populate_stream_task.delay"
|
||||
), patch("bookwyrm.lists_stream.populate_lists_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.mouse",
|
||||
"password",
|
||||
local=True,
|
||||
localname="mouse",
|
||||
)
|
||||
initdb.init_groups()
|
||||
initdb.init_permissions()
|
||||
group = Group.objects.get(name="admin")
|
||||
self.local_user.groups.set([group])
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_celery_status_get(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.CeleryStatus.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
||||
result = view(request)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
|
@ -291,6 +291,9 @@ urlpatterns = [
|
|||
views.Report.as_view(),
|
||||
name="report-link",
|
||||
),
|
||||
re_path(
|
||||
r"^settings/celery/?$", views.CeleryStatus.as_view(), name="settings-celery"
|
||||
),
|
||||
# landing pages
|
||||
re_path(r"^about/?$", views.about, name="about"),
|
||||
re_path(r"^privacy/?$", views.privacy, name="privacy"),
|
||||
|
|
|
@ -4,6 +4,7 @@ from .admin.announcements import Announcements, Announcement
|
|||
from .admin.announcements import EditAnnouncement, delete_announcement
|
||||
from .admin.automod import AutoMod, automod_delete, run_automod
|
||||
from .admin.automod import schedule_automod_task, unschedule_automod_task
|
||||
from .admin.celery_status import CeleryStatus
|
||||
from .admin.dashboard import Dashboard
|
||||
from .admin.federation import Federation, FederatedServer
|
||||
from .admin.federation import AddFederatedServer, ImportServerBlocklist
|
||||
|
|
56
bookwyrm/views/admin/celery_status.py
Normal file
56
bookwyrm/views/admin/celery_status.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
""" celery status """
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
import redis
|
||||
|
||||
from celerywyrm import settings
|
||||
from bookwyrm.tasks import app as celery
|
||||
|
||||
r = redis.Redis(
|
||||
host=settings.REDIS_BROKER_HOST,
|
||||
port=settings.REDIS_BROKER_PORT,
|
||||
password=settings.REDIS_BROKER_PASSWORD,
|
||||
db=settings.REDIS_BROKER_DB_INDEX,
|
||||
)
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
@method_decorator(
|
||||
permission_required("bookwyrm.edit_instance_settings", raise_exception=True),
|
||||
name="dispatch",
|
||||
)
|
||||
class CeleryStatus(View):
|
||||
"""Are your tasks running? Well you'd better go catch them"""
|
||||
|
||||
def get(self, request):
|
||||
"""See workers and active tasks"""
|
||||
errors = []
|
||||
try:
|
||||
inspect = celery.control.inspect()
|
||||
stats = inspect.stats()
|
||||
active_tasks = inspect.active()
|
||||
# pylint: disable=broad-except
|
||||
except Exception as err:
|
||||
stats = active_tasks = None
|
||||
errors.append(err)
|
||||
|
||||
try:
|
||||
queues = {
|
||||
"low_priority": r.llen("low_priority"),
|
||||
"medium_priority": r.llen("medium_priority"),
|
||||
"high_priority": r.llen("high_priority"),
|
||||
}
|
||||
# pylint: disable=broad-except
|
||||
except Exception as err:
|
||||
queues = None
|
||||
errors.append(err)
|
||||
|
||||
data = {
|
||||
"stats": stats,
|
||||
"active_tasks": active_tasks,
|
||||
"queues": queues,
|
||||
"errors": errors,
|
||||
}
|
||||
return TemplateResponse(request, "settings/celery.html", data)
|
Loading…
Reference in a new issue