mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2025-12-06 12:51:41 +01:00
Compare commits
10 Commits
94db5d6e87
...
1606ea7c7d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1606ea7c7d | ||
|
|
92a1bd40a3 | ||
|
|
b8bbd0d1c1 | ||
|
|
b7a6c9379a | ||
|
|
e5f3a67919 | ||
|
|
466113e6cb | ||
|
|
b1f5aad9f9 | ||
|
|
464016ac05 | ||
|
|
cc76f09a6e | ||
|
|
0dd47e72bc |
@ -27,7 +27,7 @@ def dashboard_results(hours: int) -> _TaskCounts:
|
||||
my_earliest = events.first_event(earliest=earliest)
|
||||
return [my_earliest] if my_earliest else []
|
||||
|
||||
earliest = dt.datetime.utcnow() - dt.timedelta(hours=hours)
|
||||
earliest = dt.datetime.now(dt.timezone.utc) - dt.timedelta(hours=hours)
|
||||
earliest_events = []
|
||||
succeeded_count = succeeded_tasks.count(earliest=earliest)
|
||||
earliest_events += earliest_if_exists(succeeded_tasks, earliest)
|
||||
|
||||
@ -42,7 +42,7 @@ class EventSeries:
|
||||
- event_time: timestamp of event. Will use current time if not specified.
|
||||
"""
|
||||
if not event_time:
|
||||
event_time = dt.datetime.utcnow()
|
||||
event_time = dt.datetime.now(dt.timezone.utc)
|
||||
my_id = self._redis.incr(self._key_counter)
|
||||
self._redis.zadd(self._key_sorted_set, {my_id: event_time.timestamp()})
|
||||
|
||||
|
||||
@ -65,6 +65,7 @@
|
||||
{% include 'bundles/timers-js.html' %}
|
||||
|
||||
<script>
|
||||
$(document).ready(() => {
|
||||
// Data
|
||||
const timers = [
|
||||
{% for op in optimer %}
|
||||
@ -143,5 +144,6 @@
|
||||
|
||||
// Start timed updates
|
||||
setInterval(timedUpdate, 1000);
|
||||
});
|
||||
</script>
|
||||
{% endblock content %}
|
||||
|
||||
@ -26,7 +26,7 @@ app.conf.task_default_priority = 5 # anything called with the task.delay() will
|
||||
app.conf.worker_prefetch_multiplier = 1 # only prefetch single tasks at a time on the workers so that prio tasks happen
|
||||
|
||||
app.conf.ONCE = {
|
||||
'backend': 'allianceauth.services.tasks.DjangoBackend',
|
||||
'backend': 'allianceauth.services.celery_once.backends.DjangoBackend',
|
||||
'settings': {}
|
||||
}
|
||||
|
||||
|
||||
0
allianceauth/services/celery_once/__init__.py
Normal file
0
allianceauth/services/celery_once/__init__.py
Normal file
19
allianceauth/services/celery_once/backends.py
Normal file
19
allianceauth/services/celery_once/backends.py
Normal file
@ -0,0 +1,19 @@
|
||||
from celery_once import AlreadyQueued
|
||||
from django.core.cache import cache
|
||||
|
||||
|
||||
class DjangoBackend:
|
||||
"""Locking backend for celery once."""
|
||||
|
||||
def __init__(self, settings):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def raise_or_lock(key, timeout):
|
||||
acquired = cache.add(key=key, value="lock", timeout=timeout)
|
||||
if not acquired:
|
||||
raise AlreadyQueued(int(cache.ttl(key)))
|
||||
|
||||
@staticmethod
|
||||
def clear_lock(key):
|
||||
return cache.delete(key)
|
||||
8
allianceauth/services/celery_once/tasks.py
Normal file
8
allianceauth/services/celery_once/tasks.py
Normal file
@ -0,0 +1,8 @@
|
||||
from celery_once import QueueOnce as BaseTask
|
||||
|
||||
|
||||
class QueueOnce(BaseTask):
|
||||
"""QueueOnce class with custom defaults."""
|
||||
|
||||
once = BaseTask.once
|
||||
once["graceful"] = True
|
||||
47
allianceauth/services/celery_once/tests.py
Normal file
47
allianceauth/services/celery_once/tests.py
Normal file
@ -0,0 +1,47 @@
|
||||
from celery_once import AlreadyQueued
|
||||
from django.core.cache import cache
|
||||
from django.test import TestCase
|
||||
|
||||
from allianceauth.services.celery_once.backends import DjangoBackend
|
||||
|
||||
|
||||
class TestDjangoBackend(TestCase):
|
||||
TEST_KEY = "my-django-backend-test-key"
|
||||
TIMEOUT = 1800
|
||||
|
||||
def setUp(self) -> None:
|
||||
cache.delete(self.TEST_KEY)
|
||||
self.backend = DjangoBackend(dict())
|
||||
|
||||
def test_can_get_lock(self):
|
||||
"""
|
||||
when lock can be acquired
|
||||
then set it with timeout
|
||||
"""
|
||||
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||
self.assertIsNotNone(cache.get(self.TEST_KEY))
|
||||
self.assertAlmostEqual(cache.ttl(self.TEST_KEY), self.TIMEOUT, delta=2)
|
||||
|
||||
def test_when_cant_get_lock_raise_exception(self):
|
||||
"""
|
||||
when lock can bot be acquired
|
||||
then raise AlreadyQueued exception with countdown
|
||||
"""
|
||||
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||
|
||||
try:
|
||||
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||
except Exception as ex:
|
||||
self.assertIsInstance(ex, AlreadyQueued)
|
||||
self.assertAlmostEqual(ex.countdown, self.TIMEOUT, delta=2)
|
||||
|
||||
def test_can_clear_lock(self):
|
||||
"""
|
||||
when a lock exists
|
||||
then can get a new lock after clearing it
|
||||
"""
|
||||
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||
|
||||
self.backend.clear_lock(self.TEST_KEY)
|
||||
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||
self.assertIsNotNone(cache.get(self.TEST_KEY))
|
||||
@ -100,13 +100,13 @@ def jabber_broadcast_view(request):
|
||||
if main_char is not None:
|
||||
message_to_send = form.cleaned_data['message'] + "\n##### SENT BY: " + "[" + main_char.corporation_ticker + "]" + \
|
||||
main_char.character_name + " TO: " + \
|
||||
form.cleaned_data['group'] + " WHEN: " + datetime.datetime.utcnow().strftime(
|
||||
form.cleaned_data['group'] + " WHEN: " + datetime.datetime.now(datetime.timezone.utc).strftime(
|
||||
"%Y-%m-%d %H:%M:%S") + " #####\n##### Replies are NOT monitored #####\n"
|
||||
group_to_send = form.cleaned_data['group']
|
||||
|
||||
else:
|
||||
message_to_send = form.cleaned_data['message'] + "\n##### SENT BY: " + "No character but can send pings?" + " TO: " + \
|
||||
form.cleaned_data['group'] + " WHEN: " + datetime.datetime.utcnow().strftime(
|
||||
form.cleaned_data['group'] + " WHEN: " + datetime.datetime.now(datetime.timezone.utc).strftime(
|
||||
"%Y-%m-%d %H:%M:%S") + " #####\n##### Replies are NOT monitored #####\n"
|
||||
group_to_send = form.cleaned_data['group']
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import random
|
||||
import string
|
||||
import calendar
|
||||
import re
|
||||
from datetime import datetime
|
||||
import datetime as dt
|
||||
|
||||
from passlib.apps import phpbb3_context
|
||||
from django.db import connections
|
||||
@ -128,7 +128,7 @@ class Phpbb3Manager:
|
||||
|
||||
@staticmethod
|
||||
def __get_current_utc_date():
|
||||
d = datetime.utcnow()
|
||||
d = dt.datetime.now(dt.timezone.utc)
|
||||
unixtime = calendar.timegm(d.utctimetuple())
|
||||
return unixtime
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import random
|
||||
import string
|
||||
import calendar
|
||||
from datetime import datetime
|
||||
import datetime as dt
|
||||
import hashlib
|
||||
import logging
|
||||
import re
|
||||
@ -105,7 +105,7 @@ class SmfManager:
|
||||
|
||||
@staticmethod
|
||||
def get_current_utc_date():
|
||||
d = datetime.utcnow()
|
||||
d = dt.datetime.now(dt.timezone.utc)
|
||||
unixtime = calendar.timegm(d.utctimetuple())
|
||||
return unixtime
|
||||
|
||||
|
||||
@ -3,33 +3,11 @@ import logging
|
||||
from celery import shared_task
|
||||
from django.contrib.auth.models import User
|
||||
from .hooks import ServicesHook
|
||||
from celery_once import QueueOnce as BaseTask, AlreadyQueued
|
||||
from django.core.cache import cache
|
||||
|
||||
from .celery_once.tasks import QueueOnce # noqa: F401 - for backwards compatibility
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class QueueOnce(BaseTask):
|
||||
once = BaseTask.once
|
||||
once['graceful'] = True
|
||||
|
||||
|
||||
class DjangoBackend:
|
||||
def __init__(self, settings):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def raise_or_lock(key, timeout):
|
||||
acquired = cache.add(key=key, value="lock", timeout=timeout)
|
||||
if not acquired:
|
||||
raise AlreadyQueued(int(cache.ttl(key)))
|
||||
|
||||
@staticmethod
|
||||
def clear_lock(key):
|
||||
return cache.delete(key)
|
||||
|
||||
|
||||
@shared_task(bind=True)
|
||||
def validate_services(self, pk):
|
||||
user = User.objects.get(pk=pk)
|
||||
@ -38,7 +16,7 @@ def validate_services(self, pk):
|
||||
for svc in ServicesHook.get_services():
|
||||
try:
|
||||
svc.validate_user(user)
|
||||
except:
|
||||
except Exception:
|
||||
logger.exception(f'Exception running validate_user for services module {svc} on user {user}')
|
||||
|
||||
|
||||
|
||||
@ -1,15 +1,10 @@
|
||||
from unittest import mock
|
||||
|
||||
from celery_once import AlreadyQueued
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.test import override_settings, TestCase
|
||||
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
from allianceauth.services.tasks import validate_services, update_groups_for_user
|
||||
|
||||
from ..tasks import DjangoBackend
|
||||
|
||||
|
||||
@override_settings(CELERY_ALWAYS_EAGER=True, CELERY_EAGER_PROPAGATES_EXCEPTIONS=True)
|
||||
class ServicesTasksTestCase(TestCase):
|
||||
@ -46,46 +41,3 @@ class ServicesTasksTestCase(TestCase):
|
||||
self.assertTrue(svc.update_groups.called)
|
||||
args, _ = svc.update_groups.call_args
|
||||
self.assertEqual(self.member, args[0]) # Assert correct user
|
||||
|
||||
|
||||
class TestDjangoBackend(TestCase):
|
||||
|
||||
TEST_KEY = "my-django-backend-test-key"
|
||||
TIMEOUT = 1800
|
||||
|
||||
def setUp(self) -> None:
|
||||
cache.delete(self.TEST_KEY)
|
||||
self.backend = DjangoBackend(dict())
|
||||
|
||||
def test_can_get_lock(self):
|
||||
"""
|
||||
when lock can be acquired
|
||||
then set it with timetout
|
||||
"""
|
||||
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||
self.assertIsNotNone(cache.get(self.TEST_KEY))
|
||||
self.assertAlmostEqual(cache.ttl(self.TEST_KEY), self.TIMEOUT, delta=2)
|
||||
|
||||
def test_when_cant_get_lock_raise_exception(self):
|
||||
"""
|
||||
when lock can bot be acquired
|
||||
then raise AlreadyQueued exception with countdown
|
||||
"""
|
||||
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||
|
||||
try:
|
||||
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||
except Exception as ex:
|
||||
self.assertIsInstance(ex, AlreadyQueued)
|
||||
self.assertAlmostEqual(ex.countdown, self.TIMEOUT, delta=2)
|
||||
|
||||
def test_can_clear_lock(self):
|
||||
"""
|
||||
when a lock exists
|
||||
then can get a new lock after clearing it
|
||||
"""
|
||||
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||
|
||||
self.backend.clear_lock(self.TEST_KEY)
|
||||
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||
self.assertIsNotNone(cache.get(self.TEST_KEY))
|
||||
|
||||
@ -0,0 +1,207 @@
|
||||
/* global fetchGet, numberFormatter, taskQueueSettings */
|
||||
|
||||
$(document).ready(() => {
|
||||
'use strict';
|
||||
|
||||
const elements = {
|
||||
total: document.getElementById('total-task-count'),
|
||||
uptime: document.getElementById('celery-uptime'),
|
||||
running: document.getElementById('running-task-count'),
|
||||
queued: document.getElementById('queued-tasks-count'),
|
||||
succeeded: document.getElementById('succeeded-tasks-count'),
|
||||
retried: document.getElementById('retried-tasks-count'),
|
||||
failed: document.getElementById('failed-tasks-count')
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches the task queue status and updates the UI elements accordingly.
|
||||
* It retrieves the total number of tasks, running tasks, queued tasks,
|
||||
* succeeded tasks, retried tasks, and failed tasks, and updates the
|
||||
* corresponding HTML elements with the fetched data.
|
||||
* It also updates the progress bars for succeeded, retried, and failed tasks.
|
||||
* The function is called immediately and then every 30 seconds to keep the
|
||||
* task queue status up to date.
|
||||
*/
|
||||
const updateTaskCount = () => {
|
||||
fetchGet({url: taskQueueSettings.url})
|
||||
.then((data) => {
|
||||
const elemProgressBar = document.getElementById('celery-tasks-progress-bar');
|
||||
const progressElements = {
|
||||
succeeded: {
|
||||
bar: document.getElementById('celery-progress-bar-succeeded'),
|
||||
text: document.getElementById('celery-progress-bar-succeeded-progress')
|
||||
},
|
||||
retried: {
|
||||
bar: document.getElementById('celery-progress-bar-retried'),
|
||||
text: document.getElementById('celery-progress-bar-retried-progress')
|
||||
},
|
||||
failed: {
|
||||
bar: document.getElementById('celery-progress-bar-failed'),
|
||||
text: document.getElementById('celery-progress-bar-failed-progress')
|
||||
}
|
||||
};
|
||||
|
||||
// Assign progress data from the fetched data to variables
|
||||
const {
|
||||
earliest_task: earliestTask,
|
||||
tasks_total: tasksTotal,
|
||||
tasks_running: tasksRunning,
|
||||
tasks_queued: tasksQueued,
|
||||
tasks_succeeded: tasksSucceeded,
|
||||
tasks_retried: tasksRetried,
|
||||
tasks_failed: tasksFailed
|
||||
} = data;
|
||||
|
||||
/**
|
||||
* Updates the text content of the specified HTML element with the given value.
|
||||
* If the value is null, it sets the text to 'N/A'.
|
||||
* Otherwise, it formats the number using the locale-specific format.
|
||||
*
|
||||
* @param {HTMLElement} element The HTML element to update.
|
||||
* @param {number|null} value The value to set in the element.
|
||||
*/
|
||||
const updateTaskCount = (element, value) => {
|
||||
element.textContent = value === null ? taskQueueSettings.l10n.na : numberFormatter({value: value, locales: taskQueueSettings.l10n.language});
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the time since the given timestamp and returns a formatted string.
|
||||
* If the timestamp is null or undefined, it returns 'N/A'.
|
||||
* The returned string is in the format of "X hours, Y minutes" or "X minutes, Y seconds".
|
||||
*
|
||||
* @param {string|null} timestamp The timestamp to calculate the time since.
|
||||
* @returns {string} A formatted string representing the time since the timestamp.
|
||||
*/
|
||||
const timeSince = (timestamp) => {
|
||||
if (!timestamp) {
|
||||
return taskQueueSettings.l10n.na;
|
||||
}
|
||||
|
||||
const diffSecs = Math.floor((Date.now() - new Date(timestamp)) / 1000);
|
||||
|
||||
if (diffSecs >= 3600) {
|
||||
const hours = Math.floor(diffSecs / 3600);
|
||||
const minutes = Math.floor((diffSecs % 3600) / 60);
|
||||
|
||||
if (minutes > 0) {
|
||||
const hourText = hours === 1 ? taskQueueSettings.l10n.hour_singular : taskQueueSettings.l10n.hour_plural;
|
||||
const minuteText = minutes === 1 ? taskQueueSettings.l10n.minute_singular : taskQueueSettings.l10n.minute_plural;
|
||||
|
||||
return `${hours} ${hourText}, ${minutes} ${minuteText}`;
|
||||
}
|
||||
|
||||
const hourText = hours === 1 ? taskQueueSettings.l10n.hour_singular : taskQueueSettings.l10n.hour_plural;
|
||||
|
||||
return `${hours} ${hourText}`;
|
||||
}
|
||||
|
||||
const units = [
|
||||
[
|
||||
60,
|
||||
taskQueueSettings.l10n.minute_singular,
|
||||
taskQueueSettings.l10n.minute_plural
|
||||
],
|
||||
[
|
||||
1,
|
||||
taskQueueSettings.l10n.second_singular,
|
||||
taskQueueSettings.l10n.second_plural
|
||||
]
|
||||
];
|
||||
|
||||
for (const [seconds, singular, plural] of units) {
|
||||
const value = Math.floor(diffSecs / seconds);
|
||||
|
||||
if (value > 0) {
|
||||
return `${value} ${value > 1 ? plural : singular}`;
|
||||
}
|
||||
}
|
||||
|
||||
return `0 ${taskQueueSettings.l10n.second_plural}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the progress bar element and its text content based on the given value and total.
|
||||
* It calculates the percentage of completion and updates the aria attributes and styles accordingly.
|
||||
*
|
||||
* @param {HTMLElement} element The progress bar element to update.
|
||||
* @param {HTMLElement} textElement The text element to update with the percentage.
|
||||
* @param {number} value The current value to set in the progress bar.
|
||||
* @param {number} total The total value for calculating the percentage.
|
||||
*/
|
||||
const updateProgressBar = (element, textElement, value, total) => {
|
||||
const percentage = total ? (value / total) * 100 : 0;
|
||||
|
||||
element.setAttribute('aria-valuenow', percentage.toString());
|
||||
textElement.textContent = `${numberFormatter({value: percentage.toFixed(0), locales: taskQueueSettings.l10n.language})}%`;
|
||||
element.style.width = `${percentage}%`;
|
||||
};
|
||||
|
||||
// Update task counts
|
||||
[
|
||||
[elements.total, tasksTotal],
|
||||
[elements.running, tasksRunning],
|
||||
[elements.queued, tasksQueued],
|
||||
[elements.succeeded, tasksSucceeded],
|
||||
[elements.retried, tasksRetried],
|
||||
[elements.failed, tasksFailed]
|
||||
].forEach(([element, value]) => {
|
||||
updateTaskCount(element, value);
|
||||
});
|
||||
|
||||
// Update uptime
|
||||
elements.uptime.textContent = timeSince(earliestTask);
|
||||
|
||||
// Update progress bar title
|
||||
const [
|
||||
titleTextSucceeded,
|
||||
titleTextRetried,
|
||||
titleTextFailed
|
||||
] = [
|
||||
[tasksSucceeded, taskQueueSettings.l10n.succeeded],
|
||||
[tasksRetried, taskQueueSettings.l10n.retried],
|
||||
[tasksFailed, taskQueueSettings.l10n.failed]
|
||||
].map(([count, label]) => {
|
||||
return `${numberFormatter({value: count, locales: taskQueueSettings.l10n.language})} ${label}`;
|
||||
});
|
||||
|
||||
// Set the title attribute for the progress bar
|
||||
elemProgressBar.setAttribute(
|
||||
'title',
|
||||
`${titleTextSucceeded}, ${titleTextRetried}, ${titleTextFailed}`
|
||||
);
|
||||
|
||||
// Update progress bars
|
||||
[
|
||||
tasksSucceeded,
|
||||
tasksRetried,
|
||||
tasksFailed
|
||||
].forEach((count, index) => {
|
||||
const type = ['succeeded', 'retried', 'failed'][index];
|
||||
|
||||
updateProgressBar(
|
||||
progressElements[type].bar,
|
||||
progressElements[type].text,
|
||||
count,
|
||||
tasksTotal
|
||||
);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error fetching task queue:', error);
|
||||
|
||||
// If there is an error fetching the task queue, set all elements to 'ERROR'
|
||||
[
|
||||
elements.running,
|
||||
elements.queued,
|
||||
elements.succeeded,
|
||||
elements.retried,
|
||||
elements.failed
|
||||
].forEach((elem) => {
|
||||
elem.textContent = taskQueueSettings.l10n.error;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
updateTaskCount();
|
||||
setInterval(updateTaskCount, 30000);
|
||||
});
|
||||
30
allianceauth/static/allianceauth/js/sidebar-collapse.js
Normal file
30
allianceauth/static/allianceauth/js/sidebar-collapse.js
Normal file
@ -0,0 +1,30 @@
|
||||
$(document).ready(() => {
|
||||
'use strict';
|
||||
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const sidebarKey = `sidebar_${sidebar.id}`;
|
||||
|
||||
sidebar.addEventListener('shown.bs.collapse', (event) => {
|
||||
if (event.target.id === sidebar.id) {
|
||||
localStorage.removeItem(sidebarKey);
|
||||
}
|
||||
});
|
||||
|
||||
sidebar.addEventListener('hidden.bs.collapse', (event) => {
|
||||
if (event.target.id === sidebar.id) {
|
||||
localStorage.setItem(sidebarKey, 'closed');
|
||||
}
|
||||
});
|
||||
|
||||
sidebar.classList.toggle('show', localStorage.getItem(sidebarKey) !== 'closed');
|
||||
|
||||
const activeChildMenuItem = document.querySelector('ul#sidebar-menu ul.collapse a.active');
|
||||
|
||||
if (activeChildMenuItem) {
|
||||
const activeChildMenuUl = activeChildMenuItem.closest('ul');
|
||||
activeChildMenuUl.classList.add('show');
|
||||
|
||||
document.querySelectorAll(`[data-bs-target^="#${activeChildMenuUl.id}"]`)
|
||||
.forEach(element => element.setAttribute('aria-expanded', true));
|
||||
}
|
||||
});
|
||||
@ -1,29 +1,32 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div id="esi-alert" class="col-12 collapse">
|
||||
<div class="alert alert-warning">
|
||||
<p class="text-center ">{% translate 'Your Server received an ESI error response code of ' %}<b id="esi-code">?</b></p>
|
||||
<hr>
|
||||
<pre id="esi-data" class="text-center text-wrap"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const elemCard = document.getElementById('esi-alert');
|
||||
const elemMessage = document.getElementById('esi-data');
|
||||
const elemCode = document.getElementById('esi-code');
|
||||
$(document).ready(() => {
|
||||
const elements = {
|
||||
card: document.getElementById('esi-alert'),
|
||||
message: document.getElementById('esi-data'),
|
||||
code: document.getElementById('esi-code')
|
||||
};
|
||||
|
||||
fetchGet({url: '{% url "authentication:esi_check" %}'})
|
||||
.then((data) => {
|
||||
console.log('ESI Check: ', JSON.stringify(data, null, 2));
|
||||
.then(({status, data}) => {
|
||||
console.log('ESI Check:', JSON.stringify({status, data}, null, 2));
|
||||
|
||||
if (data.status !== 200) {
|
||||
elemCode.textContent = data.status;
|
||||
elemMessage.textContent = data.data.error;
|
||||
if (status !== 200) {
|
||||
elements.code.textContent = status;
|
||||
elements.message.textContent = data.error;
|
||||
|
||||
new bootstrap.Collapse(elemCard, {toggle: true});
|
||||
new bootstrap.Collapse(elements.card, {toggle: true});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error fetching ESI check:', error);
|
||||
.catch((error) => console.error('Error fetching ESI check:', error));
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
@ -161,206 +161,24 @@ the escapejs filter without having to redefine them later.
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const elements = {
|
||||
total: document.getElementById('total-task-count'),
|
||||
uptime: document.getElementById('celery-uptime'),
|
||||
running: document.getElementById('running-task-count'),
|
||||
queued: document.getElementById('queued-tasks-count'),
|
||||
succeeded: document.getElementById('succeeded-tasks-count'),
|
||||
retried: document.getElementById('retried-tasks-count'),
|
||||
failed: document.getElementById('failed-tasks-count')
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches the task queue status and updates the UI elements accordingly.
|
||||
* It retrieves the total number of tasks, running tasks, queued tasks,
|
||||
* succeeded tasks, retried tasks, and failed tasks, and updates the
|
||||
* corresponding HTML elements with the fetched data.
|
||||
* It also updates the progress bars for succeeded, retried, and failed tasks.
|
||||
* The function is called immediately and then every 30 seconds to keep the
|
||||
* task queue status up to date.
|
||||
*/
|
||||
const updateTaskCount = () => {
|
||||
fetchGet({url: '{% url "authentication:task_counts" %}'})
|
||||
.then((data) => {
|
||||
const numberL10nFormat = new Intl.NumberFormat('{{ LANGUAGE_CODE }}');
|
||||
const elemProgressBar = document.getElementById('celery-tasks-progress-bar');
|
||||
const progressElements = {
|
||||
succeeded: {
|
||||
bar: document.getElementById('celery-progress-bar-succeeded'),
|
||||
text: document.getElementById('celery-progress-bar-succeeded-progress')
|
||||
},
|
||||
retried: {
|
||||
bar: document.getElementById('celery-progress-bar-retried'),
|
||||
text: document.getElementById('celery-progress-bar-retried-progress')
|
||||
},
|
||||
failed: {
|
||||
bar: document.getElementById('celery-progress-bar-failed'),
|
||||
text: document.getElementById('celery-progress-bar-failed-progress')
|
||||
const taskQueueSettings = {
|
||||
url: '{% url "authentication:task_counts" %}',
|
||||
l10n: {
|
||||
language: '{{ LANGUAGE_CODE }}',
|
||||
second_singular: '{{ l10nSecondSingular|escapejs }}',
|
||||
second_plural: '{{ l10nSecondPlural|escapejs }}',
|
||||
minute_singular: '{{ l10nMinuteSingular|escapejs }}',
|
||||
minute_plural: '{{ l10nMinutePlural|escapejs }}',
|
||||
hour_singular: '{{ l10nHourSingular|escapejs }}',
|
||||
hour_plural: '{{ l10nHourPlural|escapejs }}',
|
||||
na: '{{ l10nNA|escapejs }}',
|
||||
error: '{{ l10nError|escapejs }}',
|
||||
running: '{{ l10nRunning|escapejs }}',
|
||||
queued: '{{ l10nQueued|escapejs }}',
|
||||
succeeded: '{{ l10nSucceeded|escapejs }}',
|
||||
retried: '{{ l10nRetried|escapejs }}',
|
||||
failed: '{{ l10nFailed|escapejs }}'
|
||||
}
|
||||
};
|
||||
|
||||
// Assign progress data from the fetched data to variables
|
||||
const {
|
||||
earliest_task: earliestTask,
|
||||
tasks_total: tasksTotal,
|
||||
tasks_running: tasksRunning,
|
||||
tasks_queued: tasksQueued,
|
||||
tasks_succeeded: tasksSucceeded,
|
||||
tasks_retried: tasksRetried,
|
||||
tasks_failed: tasksFailed
|
||||
} = data;
|
||||
|
||||
/**
|
||||
* Updates the text content of the specified HTML element with the given value.
|
||||
* If the value is null, it sets the text to 'N/A'.
|
||||
* Otherwise, it formats the number using the locale-specific format.
|
||||
*
|
||||
* @param {HTMLElement} element The HTML element to update.
|
||||
* @param {number|null} value The value to set in the element.
|
||||
*/
|
||||
const updateTaskCount = (element, value) => {
|
||||
element.textContent = value == null ? '{{ l10nNA|escapejs }}' : numberL10nFormat.format(value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the time since the given timestamp and returns a formatted string.
|
||||
* If the timestamp is null or undefined, it returns 'N/A'.
|
||||
* The returned string is in the format of "X hours, Y minutes" or "X minutes, Y seconds".
|
||||
*
|
||||
* @param {string|null} timestamp The timestamp to calculate the time since.
|
||||
* @returns {string} A formatted string representing the time since the timestamp.
|
||||
*/
|
||||
const timeSince = (timestamp) => {
|
||||
if (!timestamp) {
|
||||
return '{{ l10nNA|escapejs }}';
|
||||
}
|
||||
|
||||
const diffSecs = Math.floor((Date.now() - new Date(timestamp)) / 1000);
|
||||
|
||||
if (diffSecs >= 3600) {
|
||||
const hours = Math.floor(diffSecs / 3600);
|
||||
const minutes = Math.floor((diffSecs % 3600) / 60);
|
||||
|
||||
if (minutes > 0) {
|
||||
const hourText = hours === 1 ? '{{ l10nHourSingular|escapejs }}' : '{{ l10nHourPlural|escapejs }}';
|
||||
const minuteText = minutes === 1 ? '{{ l10nMinuteSingular|escapejs }}' : '{{ l10nMinutePlural|escapejs }}';
|
||||
|
||||
return `${hours} ${hourText}, ${minutes} ${minuteText}`;
|
||||
}
|
||||
|
||||
const hourText = hours === 1 ? '{{ l10nHourSingular|escapejs }}' : '{{ l10nHourPlural|escapejs }}';
|
||||
|
||||
return `${hours} ${hourText}`;
|
||||
}
|
||||
|
||||
const units = [
|
||||
[
|
||||
60,
|
||||
'{{ l10nMinuteSingular|escapejs }}',
|
||||
'{{ l10nMinutePlural|escapejs }}'
|
||||
],
|
||||
[
|
||||
1,
|
||||
'{{ l10nSecondSingular|escapejs }}',
|
||||
'{{ l10nSecondPlural|escapejs }}'
|
||||
]
|
||||
];
|
||||
|
||||
for (const [seconds, singular, plural] of units) {
|
||||
const value = Math.floor(diffSecs / seconds);
|
||||
|
||||
if (value > 0) {
|
||||
return `${value} ${value > 1 ? plural : singular}`;
|
||||
}
|
||||
}
|
||||
|
||||
return '0 {{ l10nSecondPlural|escapejs }}';
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the progress bar element and its text content based on the given value and total.
|
||||
* It calculates the percentage of completion and updates the aria attributes and styles accordingly.
|
||||
*
|
||||
* @param {HTMLElement} element The progress bar element to update.
|
||||
* @param {HTMLElement} textElement The text element to update with the percentage.
|
||||
* @param {number} value The current value to set in the progress bar.
|
||||
* @param {number} total The total value for calculating the percentage.
|
||||
*/
|
||||
const updateProgressBar = (element, textElement, value, total) => {
|
||||
const percentage = total ? (value / total) * 100 : 0;
|
||||
|
||||
element.setAttribute('aria-valuenow', percentage.toString());
|
||||
textElement.textContent = `${numberL10nFormat.format(percentage.toFixed(0))}%`;
|
||||
element.style.width = `${percentage}%`;
|
||||
};
|
||||
|
||||
// Update task counts
|
||||
[
|
||||
[elements.total, tasksTotal],
|
||||
[elements.running, tasksRunning],
|
||||
[elements.queued, tasksQueued],
|
||||
[elements.succeeded, tasksSucceeded],
|
||||
[elements.retried, tasksRetried],
|
||||
[elements.failed, tasksFailed]
|
||||
].forEach(([element, value]) => {
|
||||
updateTaskCount(element, value);
|
||||
});
|
||||
|
||||
// Update uptime
|
||||
elements.uptime.textContent = timeSince(earliestTask);
|
||||
|
||||
// Update progress bar title
|
||||
const [
|
||||
titleTextSucceeded,
|
||||
titleTextRetried,
|
||||
titleTextFailed
|
||||
] = [
|
||||
[tasksSucceeded, '{{ l10nSucceeded|escapejs }}'],
|
||||
[tasksRetried, '{{ l10nRetried|escapejs }}'],
|
||||
[tasksFailed, '{{ l10nFailed|escapejs }}']
|
||||
].map(([count, label]) => {
|
||||
return `${numberL10nFormat.format(count)} ${label}`;
|
||||
});
|
||||
|
||||
// Set the title attribute for the progress bar
|
||||
elemProgressBar.setAttribute(
|
||||
'title',
|
||||
`${titleTextSucceeded}, ${titleTextRetried}, ${titleTextFailed}`
|
||||
);
|
||||
|
||||
// Update progress bars
|
||||
[
|
||||
tasksSucceeded,
|
||||
tasksRetried,
|
||||
tasksFailed
|
||||
].forEach((count, index) => {
|
||||
const type = ['succeeded', 'retried', 'failed'][index];
|
||||
|
||||
updateProgressBar(
|
||||
progressElements[type].bar,
|
||||
progressElements[type].text,
|
||||
count,
|
||||
tasksTotal
|
||||
);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error fetching task queue:', error);
|
||||
|
||||
// If there is an error fetching the task queue, set all elements to 'ERROR'
|
||||
[
|
||||
elements.running,
|
||||
elements.queued,
|
||||
elements.succeeded,
|
||||
elements.retried,
|
||||
elements.failed
|
||||
].forEach((elem) => {
|
||||
elem.textContent = '{{ l10nError|escapejs }}';
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
updateTaskCount();
|
||||
setInterval(updateTaskCount, 30000);
|
||||
</script>
|
||||
{% include "bundles/auth-dashboard-task-queue-js.html" %}
|
||||
|
||||
@ -102,44 +102,7 @@
|
||||
</main>
|
||||
<!-- End Body -->
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
// TODO Move to own JS file
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const sidebarKey = `sidebar_${sidebar.id}`;
|
||||
|
||||
sidebar.addEventListener('shown.bs.collapse', (event) => {
|
||||
if (event.target.id === sidebar.id) {
|
||||
localStorage.removeItem(sidebarKey);
|
||||
}
|
||||
});
|
||||
|
||||
sidebar.addEventListener('hidden.bs.collapse', (event) => {
|
||||
if (event.target.id === sidebar.id) {
|
||||
localStorage.setItem(sidebarKey, 'closed');
|
||||
}
|
||||
});
|
||||
|
||||
if (localStorage.getItem(sidebarKey) === 'closed') {
|
||||
sidebar.classList.remove('show');
|
||||
} else {
|
||||
sidebar.classList.add('show');
|
||||
}
|
||||
|
||||
const activeChildMenuItem = document.querySelector('#sidebar-menu li ul li a.active');
|
||||
|
||||
if (activeChildMenuItem) {
|
||||
const activeChildMenuUl = activeChildMenuItem.parentElement.parentElement;
|
||||
const elementsToToggle = document.querySelectorAll(`[data-bs-target^="#${activeChildMenuUl.id}"]`);
|
||||
|
||||
activeChildMenuUl.classList.add('show');
|
||||
|
||||
elementsToToggle.forEach((element) => {
|
||||
element.setAttribute('aria-expanded', true);
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{% include "bundles/auth-sidebar-collapse-js.html" %}
|
||||
|
||||
{% theme_js %}
|
||||
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
{% load sri %}
|
||||
|
||||
{% sri_static 'allianceauth/js/dashboard-update-task-queue.js' %}
|
||||
@ -0,0 +1,3 @@
|
||||
{% load sri %}
|
||||
|
||||
{% sri_static 'allianceauth/js/sidebar-collapse.js' %}
|
||||
@ -96,6 +96,7 @@
|
||||
{% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
|
||||
|
||||
<script>
|
||||
$(document).ready(() => {
|
||||
const timers = [
|
||||
{% for timer in timers %}
|
||||
{
|
||||
@ -175,7 +176,6 @@
|
||||
// Start timed updates
|
||||
setInterval(timedUpdate, 1000);
|
||||
|
||||
$(document).ready(() => {
|
||||
const dtOptions = {
|
||||
language: {url: '{{ DT_LANG_PATH }}'},
|
||||
order: [
|
||||
@ -185,7 +185,10 @@
|
||||
|
||||
{% if perms.auth.timer_management %}
|
||||
dtOptions['columnDefs'] = [
|
||||
{ "orderable": false, "targets": 7 }
|
||||
{
|
||||
"orderable": false,
|
||||
"targets": 7
|
||||
}
|
||||
];
|
||||
{% endif %}
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ app.conf.task_default_priority = 5 # anything called with the task.delay() will
|
||||
app.conf.worker_prefetch_multiplier = 1 # only prefetch single tasks at a time on the workers so that prio tasks happen
|
||||
|
||||
app.conf.ONCE = {
|
||||
'backend': 'allianceauth.services.tasks.DjangoBackend',
|
||||
'backend': 'allianceauth.services.celery_once.backends.DjangoBackend',
|
||||
'settings': {}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user