Compare commits

..

9 Commits

Author SHA1 Message Date
Ariel Rin
577c3e4e67 Merge branch 'translations_7f31a07ccd4e4a66b1dd7b6bc2dbddb5' into 'master'
Updates for project Alliance Auth

See merge request allianceauth/allianceauth!1764
2025-10-11 03:21:06 +00:00
Ariel Rin
92a1bd40a3 Merge branch 'inline-js-overhaul' into 'master'
[CHANGE] InlineJS overhaul

See merge request allianceauth/allianceauth!1766
2025-10-10 09:54:36 +00:00
Ariel Rin
b8bbd0d1c1 Merge branch 'fix-deprecated-utcnow' into 'master'
[FIX] Calls to deprecated `utcnow` method of `datetime.datetime`

See merge request allianceauth/allianceauth!1767
2025-10-10 09:52:45 +00:00
Peter Pfeufer
b7a6c9379a
Merge remote-tracking branch 'origin/fix-deprecated-utcnow' into fix-deprecated-utcnow 2025-09-27 07:51:53 +02:00
Peter Pfeufer
e5f3a67919
[FIX] Calls to deprecated utcnow method of datetime.datetime
datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).

```python
@classmethod
@deprecated("Use timezone-aware objects to represent datetimes in UTC; e.g. by calling .now(datetime.UTC)")
def utcnow(cls) -> Self
```
2025-09-27 07:51:46 +02:00
Peter Pfeufer
466113e6cb
[FIX] Calls to deprecated utcnow method of datetime.datetime
datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).

```python
@classmethod
@deprecated("Use timezone-aware objects to represent datetimes in UTC; e.g. by calling .now(datetime.UTC)")
def utcnow(cls) -> Self
```
2025-09-26 17:20:40 +02:00
Peter Pfeufer
b1f5aad9f9
[CHANGE] Move inline JS into $(document).ready(()) block 2025-09-25 09:38:20 +02:00
Peter Pfeufer
464016ac05
[CHANGE] Move task queue update JS into its own file 2025-09-25 09:14:03 +02:00
Peter Pfeufer
cc76f09a6e
[CHANGE Move sidebar collapse JS into its own file 2025-09-25 08:11:17 +02:00
14 changed files with 441 additions and 409 deletions

View File

@ -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)

View File

@ -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()})

View File

@ -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 %}

View File

@ -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']

View File

@ -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

View File

@ -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

View File

@ -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);
});

View 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));
}
});

View File

@ -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>

View File

@ -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" %}

View File

@ -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 %}

View File

@ -0,0 +1,3 @@
{% load sri %}
{% sri_static 'allianceauth/js/dashboard-update-task-queue.js' %}

View File

@ -0,0 +1,3 @@
{% load sri %}
{% sri_static 'allianceauth/js/sidebar-collapse.js' %}

View File

@ -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 %}