Compare commits

...

28 Commits

Author SHA1 Message Date
Erik Kalkoken
94db5d6e87 Merge branch 'seperate-out-celery-once' into 'master'
Remove dependecies from celery_once customizations

See merge request allianceauth/allianceauth!1491
2025-09-30 22:02:45 +02:00
Ariel Rin
58cc4b84dd Merge branch 'codeowners' into 'master'
[ADD] CODEOWNERS file

See merge request allianceauth/allianceauth!1765
2025-09-26 23:37:55 +00:00
Ariel Rin
331dc7d4d0 Merge branch 'limit-bcrypt-version' into 'master'
[CHANGE] Temporarily limit `bcyrpt` to below v5.0.0

See merge request allianceauth/allianceauth!1768
2025-09-26 23:32:56 +00:00
Peter Pfeufer
01991b78c9
[CHANGE] Temporarily limit bcyrpt to below v5.0.0
See: https://gitlab.com/allianceauth/allianceauth/-/issues/1436
2025-09-26 17:47:50 +02:00
Peter Pfeufer
a7f6a74211
[ADD] CODEOWNERS file
See: https://docs.gitlab.com/user/project/codeowners/
2025-09-24 08:53:28 +02:00
Joel Falknau
8898c665cf
Version Bump 4.10.0 2025-09-21 13:58:11 +10:00
Ariel Rin
313305ab22 Merge branch 'translations_7f31a07ccd4e4a66b1dd7b6bc2dbddb5' into 'master'
Updates for project Alliance Auth

See merge request allianceauth/allianceauth!1757
2025-09-21 03:43:37 +00:00
Ariel Rin
86559fc11f Merge branch 'refresh-task-queue' into 'master'
[CHANGE] Frequently update task queue section on dashboard

See merge request allianceauth/allianceauth!1752
2025-09-21 02:45:39 +00:00
Ariel Rin
1835b04dc8 Merge branch 'mumble-logo-svg' into 'master'
[ADD] Mumble logo to SVG sprite

See merge request allianceauth/allianceauth!1755
2025-09-21 02:45:03 +00:00
Ariel Rin
30180f9fe9 Merge branch 'number-formatt-to-js-framework' into 'master'
[ADD] Number formatter to JS framework

See merge request allianceauth/allianceauth!1756
2025-09-21 02:44:45 +00:00
Ariel Rin
f1eac7b84f Merge branch 'framework-css-position' into 'master'
[CHANGE] Load Framework CSS before theme CSS

See merge request allianceauth/allianceauth!1758
2025-09-21 02:43:56 +00:00
Ariel Rin
793df66f7a Merge branch 'doc-update' into 'master'
Change the installation instructions to match the correct packages

See merge request allianceauth/allianceauth!1759
2025-09-21 02:43:43 +00:00
Ariel Rin
188295daac Merge branch 'optimer-html-fix' into 'master'
[FIX] Optimer table HTML

See merge request allianceauth/allianceauth!1760
2025-09-21 02:43:26 +00:00
Ariel Rin
0447697106 Merge branch 'make-request-available' into 'master'
[CHANGE] Make the `request` object available in theme js/css templates

See merge request allianceauth/allianceauth!1761
2025-09-21 02:43:19 +00:00
Ariel Rin
1608950d43 Merge branch 'user-agent' into 'master'
[CHANGE] User-Agent to our proposed default format

See merge request allianceauth/allianceauth!1762
2025-09-21 02:41:49 +00:00
Ariel Rin
23c283c0bb Merge branch 'mobile-menu-template' into 'master'
[ADD] Mobile menu templates to framework

See merge request allianceauth/allianceauth!1763
2025-09-21 02:41:20 +00:00
Peter Pfeufer
b76fa4282a
[ADD] Mobile menu templates to framework 2025-09-11 12:52:48 +02:00
Peter Pfeufer
d88cb57cf0
[CHANGE] User-Agent to our proposed default format 2025-09-09 11:06:37 +02:00
Peter Pfeufer
787140dd7e
[CHANGE] Make the request object available in theme js/css templates 2025-09-07 09:39:22 +02:00
Peter Pfeufer
735e890de4
[FIX] Don't let Bootstrap override our override 2025-09-06 16:16:20 +02:00
Peter Pfeufer
77caa5543d
[FIX] Optimer table HTML 2025-09-06 15:47:59 +02:00
r0kym
b2f0962527 Change the installation instructions to match the correct packages 2025-09-01 23:10:28 +02:00
Peter Pfeufer
efc0fcf11d
[CHANGE] Load Framework CSS before theme CSS
So it's available when the theme CSS starts loading.
2025-08-31 18:04:56 +02:00
Ariel Rin
1b49ea571e Translate django.po in ru
82% of minimum 50% translated source file: 'django.po'
on 'ru'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format
2025-08-29 13:48:48 +00:00
Peter Pfeufer
3c21a3857a
[ADD] Number formatter 2025-08-27 20:22:24 +02:00
Peter Pfeufer
d0a769f524
[ADD] Mumble logo to SVG sprite
Could be used in at least 2 community apps.
- Mumble Temp Links (https://github.com/Solar-Helix-Independent-Transport/allianceauth-mumble-temp)
- AA Mumble Quick Connect (https://github.com/ppfeufer/aa-mumble-quick-connect)
2025-08-18 16:23:15 +02:00
Peter Pfeufer
295e5a04d8
[CHANGE] Frequently update task queue section on dashboard 2025-08-15 02:16:08 +02:00
Erik Kalkoken
0dd47e72bc Move celery once config into own package 2023-02-28 15:16:51 +01:00
43 changed files with 897 additions and 381 deletions

1
CODEOWNERS Normal file
View File

@ -0,0 +1 @@
* @allianceauth

View File

@ -5,7 +5,7 @@ manage online service access.
# This will make sure the app is always imported when # This will make sure the app is always imported when
# Django starts so that shared_task will use this app. # Django starts so that shared_task will use this app.
__version__ = '4.9.0' __version__ = '4.10.0'
__title__ = 'Alliance Auth' __title__ = 'Alliance Auth'
__title_useragent__ = 'AllianceAuth' __title_useragent__ = 'AllianceAuth'
__url__ = 'https://gitlab.com/allianceauth/allianceauth' __url__ = 'https://gitlab.com/allianceauth/allianceauth'

View File

@ -9,6 +9,7 @@ from allianceauth.tests.auth_utils import AuthUtils
from allianceauth.authentication.constants import ESI_ERROR_MESSAGE_OVERRIDES from allianceauth.authentication.constants import ESI_ERROR_MESSAGE_OVERRIDES
MODULE_PATH = "allianceauth.authentication.views" MODULE_PATH = "allianceauth.authentication.views"
TEMPLATETAGS_PATH = "allianceauth.templatetags.admin_status"
def jsonresponse_to_dict(response) -> dict: def jsonresponse_to_dict(response) -> dict:
@ -17,6 +18,7 @@ def jsonresponse_to_dict(response) -> dict:
@patch(MODULE_PATH + ".queued_tasks_count") @patch(MODULE_PATH + ".queued_tasks_count")
@patch(MODULE_PATH + ".active_tasks_count") @patch(MODULE_PATH + ".active_tasks_count")
@patch(MODULE_PATH + "._celery_stats")
class TestRunningTasksCount(TestCase): class TestRunningTasksCount(TestCase):
@classmethod @classmethod
def setUpClass(cls) -> None: def setUpClass(cls) -> None:
@ -26,36 +28,64 @@ class TestRunningTasksCount(TestCase):
cls.user.is_superuser = True cls.user.is_superuser = True
cls.user.save() cls.user.save()
def test_should_return_data( def test_should_return_data(self, mock_celery_stats, mock_tasks_queued, mock_tasks_running):
self, mock_active_tasks_count, mock_queued_tasks_count
):
# given # given
mock_active_tasks_count.return_value = 2 mock_tasks_running.return_value = 2
mock_queued_tasks_count.return_value = 3 mock_tasks_queued.return_value = 3
mock_celery_stats.return_value = {
"tasks_succeeded": 5,
"tasks_retried": 1,
"tasks_failed": 4,
"tasks_total": 11,
"tasks_hours": 24,
"earliest_task": "2025-08-14T22:47:54.853Z",
}
request = self.factory.get("/") request = self.factory.get("/")
request.user = self.user request.user = self.user
# when # when
response = task_counts(request) response = task_counts(request)
# then # then
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertDictEqual( self.assertDictEqual(
jsonresponse_to_dict(response), { jsonresponse_to_dict(response),
"tasks_running": 2, "tasks_queued": 3} {
"tasks_succeeded": 5,
"tasks_retried": 1,
"tasks_failed": 4,
"tasks_total": 11,
"tasks_hours": 24,
"earliest_task": "2025-08-14T22:47:54.853Z",
"tasks_running": 3,
"tasks_queued": 2,
}
) )
def test_su_only( def test_su_only(self, mock_celery_stats, mock_tasks_queued, mock_tasks_running):
self, mock_active_tasks_count, mock_queued_tasks_count
):
self.user.is_superuser = False self.user.is_superuser = False
self.user.save() self.user.save()
self.user.refresh_from_db() self.user.refresh_from_db()
# given # given
mock_active_tasks_count.return_value = 2 mock_tasks_running.return_value = 2
mock_queued_tasks_count.return_value = 3 mock_tasks_queued.return_value = 3
mock_celery_stats.return_value = {
"tasks_succeeded": 5,
"tasks_retried": 1,
"tasks_failed": 4,
"tasks_total": 11,
"tasks_hours": 24,
"earliest_task": "2025-08-14T22:47:54.853Z",
}
request = self.factory.get("/") request = self.factory.get("/")
request.user = self.user request.user = self.user
# when # when
response = task_counts(request) response = task_counts(request)
# then # then
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)

View File

@ -27,6 +27,7 @@ from allianceauth.hooks import get_hooks
from .constants import ESI_ERROR_MESSAGE_OVERRIDES from .constants import ESI_ERROR_MESSAGE_OVERRIDES
from .core.celery_workers import active_tasks_count, queued_tasks_count from .core.celery_workers import active_tasks_count, queued_tasks_count
from allianceauth.templatetags.admin_status import _celery_stats
from .forms import RegistrationForm from .forms import RegistrationForm
from .models import CharacterOwnership from .models import CharacterOwnership
@ -370,10 +371,10 @@ def registration_closed(request):
@user_passes_test(lambda u: u.is_superuser) @user_passes_test(lambda u: u.is_superuser)
def task_counts(request) -> JsonResponse: def task_counts(request) -> JsonResponse:
"""Return task counts as JSON for an AJAX call.""" """Return task counts as JSON for an AJAX call."""
data = { data = _celery_stats()
"tasks_running": active_tasks_count(), data.update(
"tasks_queued": queued_tasks_count() {"tasks_running": active_tasks_count(), "tasks_queued": queued_tasks_count()}
} )
return JsonResponse(data) return JsonResponse(data)

View File

@ -11,7 +11,7 @@
{% endblock header_nav_brand %} {% endblock header_nav_brand %}
{% block header_nav_collapse_left %} {% block header_nav_collapse_left %}
<li class="nav-item dropdown"> <li class="nav-item dropdown mb-2 mb-lg-0">
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false"> <a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false">
{% translate "Corporations" %} {% translate "Corporations" %}
</a> </a>
@ -26,11 +26,7 @@
{% endfor %} {% endfor %}
{% if perms.corputils.add_corpstats %} {% if perms.corputils.add_corpstats %}
{% if available.count >= 1 %} <li class="mt-3">
<li>&nbsp;</li>
{% endif %}
<li>
<a class="dropdown-item" href="{% url 'corputils:add' %}"> <a class="dropdown-item" href="{% url 'corputils:add' %}">
{% translate "Add corporation" %} {% translate "Add corporation" %}
</a> </a>

View File

@ -31,7 +31,7 @@
------------------------------------------------------------------------------------- */ ------------------------------------------------------------------------------------- */
@media all { @media all {
.table { .table {
--bs-table-bg: transparent; --bs-table-bg: transparent !important;
} }
} }

View File

@ -272,6 +272,49 @@ function objectDeepMerge (target, ...sources) {
return target; return target;
} }
/**
* Formats a number according to the specified locale.
* This function uses the Intl.NumberFormat API to format the number.
*
* @usage
* In your Django template get the current language code:
* ```django
* {% get_current_language as LANGUAGE_CODE %}
* ```
* Then use it in your JavaScript:
* ```javascript
* const userLocale = '{{ LANGUAGE_CODE }}'; // e.g., 'en-US', 'de-DE'
* const number = 1234567.89;
* const formattedNumber = numberFormatter({
* value: number,
* locales: userLocale,
* options: {
* style: 'currency',
* currency: 'ISK'
* }
* });
*
* // Output will vary based on locale
* // e.g., '1,234,567.89' for 'en-US', '1.234.567,89' for 'de-DE'
* console.log(formattedNumber);
* ```
*
* @param {number} value The number to format
* @param {string | string[]} locales The locale(s) to use for formatting (e.g., 'en-US', 'de-DE', ['en-US', 'de-DE']). If not provided, the browser's default locale will be used and any language settings from the user will be ignored.
* @param {Object} [options={}] Additional options for number formatting (see `Intl.NumberFormat` documentation - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat)
* @return {string} The formatted number as a string
*/
const numberFormatter = ({value, locales, options = {}}) => {
console.log('Formatting number:', value, 'for locale(s):', locales, 'with options:', options);
const formatter = new Intl.NumberFormat(locales, {
maximumFractionDigits: 2,
minimumFractionDigits: 0,
...options
});
return formatter.format(value);
};
/** /**
* When the document is ready * When the document is ready
*/ */

View File

@ -0,0 +1,13 @@
<li class="nav-item">
<a href="{{ url }}" class="nav-link py-lg-0">
<span class="btn btn-{{ btn_modifier|default:'primary' }} d-none d-lg-inline-block">
{% if fa_icon and icon_on_desktop %}<i class="{{ fa_icon }} me-2"></i>{% endif %}
{{ title }}
</span>
<span class="d-inline-block d-lg-none">
{% if fa_icon and icon_on_mobile %}<i class="{{ fa_icon }} fa-fw me-2"></i>{% endif %}
{{ title }}
</span>
</a>
</li>

View File

@ -0,0 +1,12 @@
<li class="nav-item">
<a href="{{ url }}" class="nav-link">
<span class="d-none d-lg-inline-block" title="{{ title }}">
<i class="{{ fa_icon }}"></i>
</span>
<span class="d-inline-block d-lg-none">
{% if icon_on_mobile %}<i class="{{ fa_icon }} me-2 fa-fw"></i>{% endif %}
{{ title }}
</span>
</a>
</li>

View File

@ -15,9 +15,61 @@
<animate attributeName="stroke-dasharray" dur="1.5s" calcMode="spline" values="0 150;42 150;42 150;42 150" keyTimes="0;0.475;0.95;1" keySplines="0.42,0,0.58,1;0.42,0,0.58,1;0.42,0,0.58,1" repeatCount="indefinite" /> <animate attributeName="stroke-dasharray" dur="1.5s" calcMode="spline" values="0 150;42 150;42 150;42 150" keyTimes="0;0.475;0.95;1" keySplines="0.42,0,0.58,1;0.42,0,0.58,1;0.42,0,0.58,1" repeatCount="indefinite" />
<animate attributeName="stroke-dashoffset" dur="1.5s" calcMode="spline" values="0;-16;-59;-59" keyTimes="0;0.475;0.95;1" keySplines="0.42,0,0.58,1;0.42,0,0.58,1;0.42,0,0.58,1" repeatCount="indefinite" /> <animate attributeName="stroke-dashoffset" dur="1.5s" calcMode="spline" values="0;-16;-59;-59" keyTimes="0;0.475;0.95;1" keySplines="0.42,0,0.58,1;0.42,0,0.58,1;0.42,0,0.58,1" repeatCount="indefinite" />
</circle> </circle>
<animateTransform attributeName="transform" type="rotate" dur="2s" values="0 12 12;360 12 12" repeatCount="indefinite" /> <animateTransform attributeName="transform" type="rotate" dur="2s" values="0 12 12;360 12 12" repeatCount="indefinite" />
</g> </g>
</symbol> </symbol>
<!-- Mumble Logo -->
<symbol id="aa-mumble-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400">
<defs>
<radialGradient id="c" cx="206.64" cy="214.43" r="190.25" gradientTransform="matrix(.97267 .016175 -.016656 .97474 9.2188 2.0744)" gradientUnits="userSpaceOnUse">
<stop stop-opacity="0" offset="0" />
<stop stop-opacity=".019608" offset=".81721" />
<stop stop-opacity=".1451" offset=".89931" />
<stop stop-opacity=".20784" offset=".91199" />
<stop stop-opacity=".25098" offset=".95598" />
<stop stop-opacity=".33333" offset="1" />
</radialGradient>
</defs>
<g transform="translate(0 -652.36)">
<path transform="matrix(1.0811 0 0 1.1043 -22.438 617.98)" d="m385.62 214.43c0 96.124-80.133 174.05-178.98 174.05-98.849 0-178.98-77.924-178.98-174.05s80.133-174.05 178.98-174.05c98.849 0 178.98 77.924 178.98 174.05z" fill="#1a1a1a" stroke="#000" stroke-linejoin="round" stroke-width="4.576" />
<path transform="matrix(1.0706 0 0 1.101 -22.082 583.62)" d="m385.62 214.43c0 96.124-80.133 174.05-178.98 174.05-98.849 0-178.98-77.924-178.98-174.05s80.133-174.05 178.98-174.05c98.849 0 178.98 77.924 178.98 174.05z" opacity="0" />
<path transform="matrix(1.0423 0 0 1.0695 -13.736 622.74)" d="m385.62 214.43c0 96.124-80.133 174.05-178.98 174.05-98.849 0-178.98-77.924-178.98-174.05s80.133-174.05 178.98-174.05c98.849 0 178.98 77.924 178.98 174.05z" fill="#fff" opacity=".9" />
<path transform="matrix(1.0641 0 0 1.0787 -20.794 620.64)" d="m385.62 214.43c0 96.124-80.133 174.05-178.98 174.05-98.849 0-178.98-77.924-178.98-174.05s80.133-174.05 178.98-174.05c98.849 0 178.98 77.924 178.98 174.05z" fill="#fff" stroke="#333" stroke-linejoin="round" stroke-width="1.4127" />
<path transform="matrix(1.0857 0 0 1.109 -24.345 616.21)" d="m385.62 214.43c0 96.124-80.133 174.05-178.98 174.05-98.849 0-178.98-77.924-178.98-174.05s80.133-174.05 178.98-174.05c98.849 0 178.98 77.924 178.98 174.05z" fill="none" stroke="#000" stroke-linejoin="round" stroke-width="1.8304" />
</g>
<g>
<path transform="matrix(1.0765 0 0 1.1009 -20.514 -34.696)" d="m385.62 214.43a178.98 174.05 0 1 1-357.96 0 178.98 174.05 0 1 1 357.96 0z" fill="url(#c)" opacity=".75" />
</g>
<g fill-rule="evenodd">
<path transform="matrix(1.05 0 0 1.05 -5.3555 .50955)" d="m152.41 31.61c-24.652-0.61541-49.623 15.705-55.853 40.126-1.4511 5.9204-2.0429 11.533-2.1475 17.251v63.623h25c0.0881-22.382-0.12668-44.644 0.1701-67.072 0.76858-14.243 11.773-29.258 27.049-29.084 0.11203 22.669-0.22918 45.351 0.18004 68.011 1.3028 18.426 18.762 33.676 37.243 32.114 11.546-0.2802 23.178 0.67313 34.648-0.72475 17.466-3.2744 29.553-21.063 27.929-38.449v-60.857c15.888-1.1603 27.938 14.263 28.642 29.084 0.29501 22.427 0.0825 44.692 0.1701 67.072h25v-67.5c-0.81797-7.2761-1.9718-16.18-5.9149-23.198-10.229-20.751-34.153-31.948-56.717-30.261-6.591-0.83713-13.681 3.6197-15.487 9.8666 0.10876 26.739 0.18577 53.486-0.015 80.22-0.75343 11.2-11.79 19.764-22.805 18.342-7.7921 0.33854-16.594 0.0136-21.908-6.6817-7.1623-7.5704-4.7632-18.405-5.1836-27.812 0.0193-21.719-0.0713-43.418 0.1249-65.1-3.2593-6.5913-10.503-9.9936-17.679-8.9119l-1.1877-0.01641-1.2582-0.04042h2.5e-4z" stroke="#fff" />
<path d="m107.27 156.26v177.84c-35.128-3.8535-62.737-42.188-62.737-88.922 0-46.734 27.609-85.068 62.737-88.922z" fill="url(#l)" opacity=".9666" />
</g>
<g fill-rule="evenodd" stroke="#fff">
<path transform="matrix(1.05 0 0 1.05 -5.3555 .50955)" d="m290.42 313.16c-0.69916-7e-3 -3.3105-0.57507-3.9404-0.16697 0 0-1.0356 3.0168-4.6042 5.6725-3.1327 2.3314-6.1076 4.5662-9.2946 6.6625-2.8616 1.8823-5.9328 3.9177-8.8098 5.3024-2.264 1.0896-4.114 1.2482-4.114 1.2482h-32.22c-2.0127 0-3.6618 1.5872-3.6618 3.5625v0.875c0 1.9753 1.649 3.5938 3.6618 3.5938h33.879c0.77968 0 3.5971-0.82022 5.2725-1.5553 4.1768-1.8326 6.899-4.166 11.71-7.0274 5.1144-3.2712 14.573-10.886 14.573-10.886 1.6797-1.0883 2.1278-3.289 1.0189-4.9375l-0.47763-0.75c-0.69304-1.0303-1.8278-1.5824-2.9931-1.5938z" />
<path transform="matrix(1.05 0 0 1.05 -5.3555 .50955)" d="m288.25 148.44v169.38c33.455-3.67 59.75-40.179 59.75-84.688s-26.295-81.018-59.75-84.688z" opacity=".9666" />
<path transform="matrix(1.05 0 0 1.05 -5.3555 .50955)" d="m106.22 149.34v169.38c-33.455-3.67-59.75-40.179-59.75-84.688s26.295-81.018 59.75-84.688z" opacity=".9666" />
<path transform="matrix(1.3048 0 0 1.2146 -20.461 -43.8)" d="m194.74 325.86a22.13 13.831 0 1 1-44.26 0 22.13 13.831 0 1 1 44.26 0z" opacity=".9666" />
<rect transform="matrix(1.0433 0 0 1.05 -4.6563 .094873)" x="274.72" y="146.09" width="13.329" height="171.95" rx="3.8877" ry="3.5401" opacity=".9666" stroke-width="1.0538" />
<rect transform="matrix(1.0433 0 0 1.05 -3.8348 .094873)" x="106.56" y="147.08" width="13.063" height="171.96" rx="3.8101" ry="3.5403" opacity=".9666" stroke-width="1.0432" />
</g>
<g>
<rect x="131.64" y="188.83" width="140.83" height="111.89" fill-rule="evenodd" />
<path transform="matrix(1.1007 0 0 2.0001 -23.812 -190.28)" d="m189.84 226.69c-4e-5 2.3125-0.43754 4.3438-1.3125 6.0938-0.87504 1.75-2.0521 3.1979-3.5312 4.3438-1.75 1.375-3.6719 2.3542-5.7656 2.9375-2.0938 0.58334-4.7552 0.875-7.9844 0.875h-18.625v-46.531h16.438c3.4166 5e-5 6.0052 0.13026 7.7656 0.39063 1.7604 0.26046 3.4114 0.80734 4.9531 1.6406 1.6666 0.89588 2.9114 2.0938 3.7344 3.5938 0.82288 1.5 1.2343 3.2292 1.2344 5.1875-4e-5 2.2709-0.56775 4.2917-1.7031 6.0625-1.1354 1.7709-2.7032 3.073-4.7031 3.9062v0.25c2.875 0.6042 5.177 1.8386 6.9062 3.7031 1.7291 1.8646 2.5937 4.3802 2.5938 7.5469zm-14.969-19.125c-3e-5 -0.74996-0.19274-1.5208-0.57813-2.3125-0.38544-0.79163-0.9844-1.3645-1.7969-1.7188-0.77086-0.33329-1.6823-0.51558-2.7344-0.54687-1.0521-0.0312-2.6198-0.0468-4.7031-0.0469h-0.8125v9.8438h1.4688c2 3e-5 3.401-0.0208 4.2031-0.0625 0.80207-0.0416 1.6302-0.26038 2.4844-0.65625 0.93747-0.43747 1.5833-1.0416 1.9375-1.8125 0.35414-0.7708 0.53122-1.6666 0.53125-2.6875zm2.9375 18.906c-3e-5 -1.4375-0.2917-2.5625-0.875-3.375-0.58336-0.81248-1.4584-1.4271-2.625-1.8438-0.70836-0.27081-1.6823-0.42185-2.9219-0.45312-1.2396-0.0312-2.9011-0.0469-4.9844-0.0469h-2.1562v11.656h0.625c3.0416 1e-5 5.1458-0.0208 6.3125-0.0625 1.1666-0.0416 2.3541-0.3229 3.5625-0.84375 1.0625-0.45832 1.8385-1.1302 2.3281-2.0156 0.48956-0.88541 0.73435-1.8906 0.73438-3.0156z" fill="url(#f)" />
<path transform="matrix(1.1007 0 0 2.0001 -28.291 -190.6)" d="m189.84 226.69c-4e-5 2.3125-0.43754 4.3438-1.3125 6.0938-0.87504 1.75-2.0521 3.1979-3.5312 4.3438-1.75 1.375-3.6719 2.3542-5.7656 2.9375-2.0938 0.58334-4.7552 0.875-7.9844 0.875h-18.625v-46.531h16.438c3.4166 5e-5 6.0052 0.13026 7.7656 0.39063 1.7604 0.26046 3.4114 0.80734 4.9531 1.6406 1.6666 0.89588 2.9114 2.0938 3.7344 3.5938 0.82288 1.5 1.2343 3.2292 1.2344 5.1875-4e-5 2.2709-0.56775 4.2917-1.7031 6.0625-1.1354 1.7709-2.7032 3.073-4.7031 3.9062v0.25c2.875 0.6042 5.177 1.8386 6.9062 3.7031 1.7291 1.8646 2.5937 4.3802 2.5938 7.5469zm-14.969-19.125c-3e-5 -0.74996-0.19274-1.5208-0.57813-2.3125-0.38544-0.79163-0.9844-1.3645-1.7969-1.7188-0.77086-0.33329-1.6823-0.51558-2.7344-0.54687-1.0521-0.0312-2.6198-0.0468-4.7031-0.0469h-0.8125v9.8438h1.4688c2 3e-5 3.401-0.0208 4.2031-0.0625 0.80207-0.0416 1.6302-0.26038 2.4844-0.65625 0.93747-0.43747 1.5833-1.0416 1.9375-1.8125 0.35414-0.7708 0.53122-1.6666 0.53125-2.6875zm2.9375 18.906c-3e-5 -1.4375-0.2917-2.5625-0.875-3.375-0.58336-0.81248-1.4584-1.4271-2.625-1.8438-0.70836-0.27081-1.6823-0.42185-2.9219-0.45312-1.2396-0.0312-2.9011-0.0469-4.9844-0.0469h-2.1562v11.656h0.625c3.0416 1e-5 5.1458-0.0208 6.3125-0.0625 1.1666-0.0416 2.3541-0.3229 3.5625-0.84375 1.0625-0.45832 1.8385-1.1302 2.3281-2.0156 0.48956-0.88541 0.73435-1.8906 0.73438-3.0156z" fill="#fff" />
<path transform="matrix(1.1007 0 0 2.0001 -23.812 -190.28)" d="m227.56 240.94h-31.062v-46.531h11.688v37.656h19.375z" fill="url(#e)" />
<path d="m187.31 197.56v94.531h14.125 0.0625 21.375v-19.25h-21.375v-75.281h-14.188z" fill="#fff" />
<path transform="matrix(1.1007 0 0 2.0001 -23.812 -190.28)" d="m233.12 240.94v-46.531h31.469v8.875h-19.844v8.1562h18.281v8.875h-18.281v11.75h19.844v8.875z" fill="url(#d)" />
<path d="m227.81 197.5v94.531h14.188 21.375v-19.25h-21.375v-22.281h19.656v-18.656h-19.656v-15.094h21.375v-19.25h-21.375-0.0625-14.125z" fill="#fff" />
</g>
<g fill-rule="evenodd">
<path d="m155.37 33.01c-25.884-0.64618-52.104 16.49-58.645 42.133-1.5237 6.2165-2.1451 12.11-2.2549 18.114v66.804h26.25c0.0925-23.501-0.13301-46.876 0.17861-70.426 0.807-14.955 12.362-30.721 28.401-30.539 0.11763 23.802-0.24064 47.619 0.18905 71.412 1.368 19.347 19.7 35.36 39.106 33.72 12.123-0.29421 24.336 0.70678 36.38-0.76099 18.339-3.4381 31.031-22.116 29.325-40.372v-63.9c16.682-1.2183 29.334 14.976 30.074 30.539 0.30976 23.549 0.0866 46.927 0.17861 70.426h26.25v-70.875c-0.85887-7.6399-2.0704-16.989-6.2106-24.358-10.74-21.789-35.861-33.545-59.553-31.774-6.9206-0.87899-14.366 3.8007-16.261 10.36 0.1142 28.075 0.19506 56.161-0.0157 84.231-0.7911 11.76-12.38 20.752-23.945 19.259-8.1817 0.35546-17.423 0.0143-23.004-7.0158-7.5204-7.949-5.0013-19.326-5.4428-29.202 0.0203-22.805-0.0749-45.589 0.13115-68.355-3.4223-6.9208-11.028-10.493-18.563-9.3575l-1.2471-0.01723-1.3211-0.04244h2.6e-4z" fill="url(#i)" />
<path d="m154.53 34.269c-31.655 0-56.169 21.233-59.981 48.759h26.972c3.6448-12.426 13.114-21.517 24.511-22.575 0.74035-0.69976 1.772-1.1484 2.9203-1.1484h26.611l0.0984-15.619s-2.3784-3.7253-4.2984-5.4797c-2.1754-1.9877-5.1118-3.4283-8.0062-3.7406-2.4252-0.26165-5.1996-0.04115-8.8266-0.19688z" fill="url(#j)" />
<path transform="matrix(1.3048 0 0 1.2146 -20.434 -43.907)" d="m194.74 325.86a22.13 13.831 0 1 1-44.26 0 22.13 13.831 0 1 1 44.26 0z" fill="url(#b)" opacity=".9666" />
<path d="m298.66 157.26v177.84c35.128-3.8535 62.738-42.188 62.738-88.922 0-46.734-27.609-85.068-62.738-88.922z" fill="url(#k)" opacity=".9666" />
<path d="m247.97 33.422c31.655 0 56.169 21.233 59.981 48.759h-26.972c-3.6448-12.426-13.114-21.517-24.511-22.575-0.74036-0.69976-1.772-1.1484-2.9203-1.1484h-26.611l-0.0984-15.619s2.3784-3.7253 4.2984-5.4797c2.1754-1.9877 5.1118-3.4283 8.0062-3.7406 2.4252-0.26165 5.1996-0.04115 8.8266-0.19688z" fill="url(#h)" />
<path d="m107.92 156.15v177.84c-35.128-3.8535-62.737-42.188-62.737-88.922 0-46.734 27.609-85.068 62.737-88.922z" fill="url(#g)" opacity=".9666" />
</g>
</symbol>
</defs> </defs>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-07-03 09:07+1000\n" "POT-Creation-Date: 2025-09-21 13:44+1000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -161,14 +161,12 @@ msgstr ""
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:11 #: allianceauth/authentication/templates/authentication/dashboard_characters.html:11
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:12 #: allianceauth/authentication/templates/authentication/dashboard_characters.html:12
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:4 #: allianceauth/templates/allianceauth/top-menu-rh-default.html:4
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:6
msgid "Add Character" msgid "Add Character"
msgstr "" msgstr ""
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:14 #: allianceauth/authentication/templates/authentication/dashboard_characters.html:14
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:15 #: allianceauth/authentication/templates/authentication/dashboard_characters.html:15
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:10 #: allianceauth/templates/allianceauth/top-menu-rh-default.html:8
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:12
msgid "Change Main" msgid "Change Main"
msgstr "" msgstr ""
@ -225,8 +223,8 @@ msgstr ""
#: allianceauth/hrapplications/templates/hrapplications/management.html:168 #: allianceauth/hrapplications/templates/hrapplications/management.html:168
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:35 #: allianceauth/hrapplications/templates/hrapplications/searchview.html:35
#: allianceauth/hrapplications/templates/hrapplications/view.html:94 #: allianceauth/hrapplications/templates/hrapplications/view.html:94
#: allianceauth/srp/templates/srp/data.html:83 #: allianceauth/srp/templates/srp/data.html:81
#: allianceauth/srp/templates/srp/management.html:53 #: allianceauth/srp/templates/srp/management.html:51
msgid "Actions" msgid "Actions"
msgstr "" msgstr ""
@ -277,49 +275,49 @@ msgstr ""
msgid "Invalid or expired activation link." msgid "Invalid or expired activation link."
msgstr "" msgstr ""
#: allianceauth/authentication/views.py:158 #: allianceauth/authentication/views.py:159
#, python-format #, python-format
msgid "" msgid ""
"Cannot change main character to %(char)s: character owned by a different " "Cannot change main character to %(char)s: character owned by a different "
"account." "account."
msgstr "" msgstr ""
#: allianceauth/authentication/views.py:165 #: allianceauth/authentication/views.py:166
#, python-format #, python-format
msgid "Changed main character to %s" msgid "Changed main character to %s"
msgstr "" msgstr ""
#: allianceauth/authentication/views.py:179 #: allianceauth/authentication/views.py:180
#, python-format #, python-format
msgid "Added %(name)s to your account." msgid "Added %(name)s to your account."
msgstr "" msgstr ""
#: allianceauth/authentication/views.py:181 #: allianceauth/authentication/views.py:182
#, python-format #, python-format
msgid "Failed to add %(name)s to your account: they already have an account." msgid "Failed to add %(name)s to your account: they already have an account."
msgstr "" msgstr ""
#: allianceauth/authentication/views.py:226 #: allianceauth/authentication/views.py:227
msgid "" msgid ""
"Unable to authenticate as the selected character. Please log in with the " "Unable to authenticate as the selected character. Please log in with the "
"main character associated with this account." "main character associated with this account."
msgstr "" msgstr ""
#: allianceauth/authentication/views.py:293 #: allianceauth/authentication/views.py:294
msgid "Registration token has expired." msgid "Registration token has expired."
msgstr "" msgstr ""
#: allianceauth/authentication/views.py:354 #: allianceauth/authentication/views.py:355
msgid "" msgid ""
"Sent confirmation email. Please follow the link to confirm your email " "Sent confirmation email. Please follow the link to confirm your email "
"address." "address."
msgstr "" msgstr ""
#: allianceauth/authentication/views.py:360 #: allianceauth/authentication/views.py:361
msgid "Confirmed your email address. Please login to continue." msgid "Confirmed your email address. Please login to continue."
msgstr "" msgstr ""
#: allianceauth/authentication/views.py:366 #: allianceauth/authentication/views.py:367
msgid "Registration of new accounts is not allowed at this time." msgid "Registration of new accounts is not allowed at this time."
msgstr "" msgstr ""
@ -336,11 +334,11 @@ msgstr ""
msgid "Corporations" msgid "Corporations"
msgstr "" msgstr ""
#: allianceauth/corputils/templates/corputils/base.html:35 #: allianceauth/corputils/templates/corputils/base.html:31
msgid "Add corporation" msgid "Add corporation"
msgstr "" msgstr ""
#: allianceauth/corputils/templates/corputils/base.html:51 #: allianceauth/corputils/templates/corputils/base.html:47
msgid "Search all corporations..." msgid "Search all corporations..."
msgstr "" msgstr ""
@ -488,7 +486,7 @@ msgid "Fleet Activity Tracking"
msgstr "" msgstr ""
#: allianceauth/fleetactivitytracking/forms.py:6 allianceauth/srp/form.py:8 #: allianceauth/fleetactivitytracking/forms.py:6 allianceauth/srp/form.py:8
#: allianceauth/srp/templates/srp/management.html:44 #: allianceauth/srp/templates/srp/management.html:42
msgid "Fleet Name" msgid "Fleet Name"
msgstr "" msgstr ""
@ -973,7 +971,7 @@ msgstr ""
#: allianceauth/hrapplications/templates/hrapplications/management.html:123 #: allianceauth/hrapplications/templates/hrapplications/management.html:123
#: allianceauth/hrapplications/templates/hrapplications/management.html:167 #: allianceauth/hrapplications/templates/hrapplications/management.html:167
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:34 #: allianceauth/hrapplications/templates/hrapplications/searchview.html:34
#: allianceauth/srp/templates/srp/data.html:81 #: allianceauth/srp/templates/srp/data.html:79
msgid "Status" msgid "Status"
msgstr "" msgstr ""
@ -986,7 +984,7 @@ msgid "Hidden"
msgstr "" msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:45 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:45
#: allianceauth/templates/allianceauth/admin-status/overview.html:15 #: allianceauth/templates/allianceauth/admin-status/overview.html:53
msgid "Open" msgid "Open"
msgstr "" msgstr ""
@ -1031,17 +1029,9 @@ msgstr ""
msgid "Leave" msgid "Leave"
msgstr "" msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:74 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:73
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:89 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:88
#: allianceauth/hrapplications/templates/hrapplications/management.html:46 msgid "Request pending"
#: allianceauth/hrapplications/templates/hrapplications/management.html:95
#: allianceauth/hrapplications/templates/hrapplications/management.html:138
#: allianceauth/hrapplications/templates/hrapplications/management.html:182
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:46
#: allianceauth/hrapplications/templates/hrapplications/view.html:25
#: allianceauth/srp/templates/srp/data.html:120
#: allianceauth/srp/templates/srp/management.html:87
msgid "Pending"
msgstr "" msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:80 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:80
@ -1052,7 +1042,11 @@ msgstr ""
msgid "Request" msgid "Request"
msgstr "" msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:99 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:93
msgid "Retract"
msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:103
msgid "No groups available." msgid "No groups available."
msgstr "" msgstr ""
@ -1181,6 +1175,19 @@ msgstr ""
msgid "Applied to leave group %(group)s." msgid "Applied to leave group %(group)s."
msgstr "" msgstr ""
#: allianceauth/groupmanagement/views.py:438
msgid "You cannot retract that request"
msgstr ""
#: allianceauth/groupmanagement/views.py:450
#, python-format
msgid "Retracted application to group %(group)s."
msgstr ""
#: allianceauth/groupmanagement/views.py:458
msgid "You have no open request for that group."
msgstr ""
#: allianceauth/hrapplications/apps.py:8 #: allianceauth/hrapplications/apps.py:8
msgid "HR Applications" msgid "HR Applications"
msgstr "" msgstr ""
@ -1251,12 +1258,23 @@ msgstr ""
msgid "Username" msgid "Username"
msgstr "" msgstr ""
#: allianceauth/hrapplications/templates/hrapplications/management.html:46
#: allianceauth/hrapplications/templates/hrapplications/management.html:95
#: allianceauth/hrapplications/templates/hrapplications/management.html:138
#: allianceauth/hrapplications/templates/hrapplications/management.html:182
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:46
#: allianceauth/hrapplications/templates/hrapplications/view.html:25
#: allianceauth/srp/templates/srp/data.html:118
#: allianceauth/srp/templates/srp/management.html:85
msgid "Pending"
msgstr ""
#: allianceauth/hrapplications/templates/hrapplications/management.html:48 #: allianceauth/hrapplications/templates/hrapplications/management.html:48
#: allianceauth/hrapplications/templates/hrapplications/management.html:141 #: allianceauth/hrapplications/templates/hrapplications/management.html:141
#: allianceauth/hrapplications/templates/hrapplications/management.html:185 #: allianceauth/hrapplications/templates/hrapplications/management.html:185
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:48 #: allianceauth/hrapplications/templates/hrapplications/searchview.html:48
#: allianceauth/hrapplications/templates/hrapplications/view.html:21 #: allianceauth/hrapplications/templates/hrapplications/view.html:21
#: allianceauth/srp/templates/srp/data.html:112 #: allianceauth/srp/templates/srp/data.html:110
msgid "Approved" msgid "Approved"
msgstr "" msgstr ""
@ -1264,7 +1282,7 @@ msgstr ""
#: allianceauth/hrapplications/templates/hrapplications/management.html:143 #: allianceauth/hrapplications/templates/hrapplications/management.html:143
#: allianceauth/hrapplications/templates/hrapplications/management.html:187 #: allianceauth/hrapplications/templates/hrapplications/management.html:187
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:50 #: allianceauth/hrapplications/templates/hrapplications/searchview.html:50
#: allianceauth/srp/templates/srp/data.html:116 #: allianceauth/srp/templates/srp/data.html:114
msgid "Rejected" msgid "Rejected"
msgstr "" msgstr ""
@ -1499,8 +1517,8 @@ msgstr ""
#: allianceauth/menu/templates/menu/menu-user.html:155 #: allianceauth/menu/templates/menu/menu-user.html:155
#: allianceauth/menu/templates/menu/menu-user.html:158 #: allianceauth/menu/templates/menu/menu-user.html:158
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:17 #: allianceauth/templates/allianceauth/top-menu-rh-default.html:13
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:18 #: allianceauth/templates/allianceauth/top-menu-rh-default.html:14
msgid "Sign In" msgid "Sign In"
msgstr "" msgstr ""
@ -1528,11 +1546,11 @@ msgstr ""
msgid "Read" msgid "Read"
msgstr "" msgstr ""
#: allianceauth/notifications/templates/notifications/list.html:32 #: allianceauth/notifications/templates/notifications/list.html:31
msgid "Mark all notifications as read" msgid "Mark all notifications as read"
msgstr "" msgstr ""
#: allianceauth/notifications/templates/notifications/list.html:38 #: allianceauth/notifications/templates/notifications/list.html:35
msgid "Delete all read notifications" msgid "Delete all read notifications"
msgstr "" msgstr ""
@ -1597,12 +1615,12 @@ msgid "Operation Type"
msgstr "" msgstr ""
#: allianceauth/optimer/form.py:17 #: allianceauth/optimer/form.py:17
#: allianceauth/srp/templates/srp/management.html:47 #: allianceauth/srp/templates/srp/management.html:45
msgid "Fleet Commander" msgid "Fleet Commander"
msgstr "" msgstr ""
#: allianceauth/optimer/form.py:22 allianceauth/srp/form.py:14 #: allianceauth/optimer/form.py:22 allianceauth/srp/form.py:14
#: allianceauth/srp/templates/srp/data.html:72 #: allianceauth/srp/templates/srp/data.html:70
msgid "Additional Info" msgid "Additional Info"
msgstr "" msgstr ""
@ -1611,7 +1629,7 @@ msgid "(Optional) Describe the operation with a couple of short words."
msgstr "" msgstr ""
#: allianceauth/optimer/templates/optimer/add.html:8 #: allianceauth/optimer/templates/optimer/add.html:8
#: allianceauth/optimer/templates/optimer/management.html:18 #: allianceauth/optimer/templates/optimer/management.html:16
msgid "Create Operation" msgid "Create Operation"
msgstr "" msgstr ""
@ -1660,26 +1678,26 @@ msgstr ""
msgid "Fleet Operation Management" msgid "Fleet Operation Management"
msgstr "" msgstr ""
#: allianceauth/optimer/templates/optimer/management.html:28 #: allianceauth/optimer/templates/optimer/management.html:26
#: allianceauth/timerboard/templates/timerboard/view.html:32 #: allianceauth/timerboard/templates/timerboard/view.html:30
msgid "Current EVE time:" msgid "Current EVE time:"
msgstr "" msgstr ""
#: allianceauth/optimer/templates/optimer/management.html:36 #: allianceauth/optimer/templates/optimer/management.html:34
msgid "Next Fleet Operations" msgid "Next Fleet Operations"
msgstr "" msgstr ""
#: allianceauth/optimer/templates/optimer/management.html:44 #: allianceauth/optimer/templates/optimer/management.html:42
#: allianceauth/timerboard/templates/timerboard/view.html:63 #: allianceauth/timerboard/templates/timerboard/view.html:61
msgid "No upcoming timers." msgid "No upcoming timers."
msgstr "" msgstr ""
#: allianceauth/optimer/templates/optimer/management.html:52 #: allianceauth/optimer/templates/optimer/management.html:50
msgid "Past Fleet Operations" msgid "Past Fleet Operations"
msgstr "" msgstr ""
#: allianceauth/optimer/templates/optimer/management.html:60 #: allianceauth/optimer/templates/optimer/management.html:58
#: allianceauth/timerboard/templates/timerboard/view.html:81 #: allianceauth/timerboard/templates/timerboard/view.html:79
msgid "No past timers." msgid "No past timers."
msgstr "" msgstr ""
@ -2246,7 +2264,7 @@ msgid "Enabled"
msgstr "" msgstr ""
#: allianceauth/services/templates/services/service_status.html:7 #: allianceauth/services/templates/services/service_status.html:7
#: allianceauth/srp/templates/srp/management.html:78 #: allianceauth/srp/templates/srp/management.html:76
msgid "Disabled" msgid "Disabled"
msgstr "" msgstr ""
@ -2283,12 +2301,12 @@ msgstr ""
msgid "Ship Replacement" msgid "Ship Replacement"
msgstr "" msgstr ""
#: allianceauth/srp/form.py:9 allianceauth/srp/templates/srp/management.html:45 #: allianceauth/srp/form.py:9 allianceauth/srp/templates/srp/management.html:43
msgid "Fleet Time" msgid "Fleet Time"
msgstr "" msgstr ""
#: allianceauth/srp/form.py:10 #: allianceauth/srp/form.py:10
#: allianceauth/srp/templates/srp/management.html:46 #: allianceauth/srp/templates/srp/management.html:44
msgid "Fleet Doctrine" msgid "Fleet Doctrine"
msgstr "" msgstr ""
@ -2337,7 +2355,7 @@ msgid "Give this link to the line members."
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/data.html:8 #: allianceauth/srp/templates/srp/data.html:8
#: allianceauth/srp/templates/srp/data.html:39 #: allianceauth/srp/templates/srp/data.html:37
msgid "SRP Fleet Data" msgid "SRP Fleet Data"
msgstr "" msgstr ""
@ -2345,64 +2363,64 @@ msgstr ""
msgid "View Fleets" msgid "View Fleets"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/data.html:26 #: allianceauth/srp/templates/srp/data.html:24
msgid "Mark Incomplete" msgid "Mark Incomplete"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/data.html:30 #: allianceauth/srp/templates/srp/data.html:28
msgid "Mark Completed" msgid "Mark Completed"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/data.html:48 #: allianceauth/srp/templates/srp/data.html:46
#: allianceauth/srp/templates/srp/data.html:142 #: allianceauth/srp/templates/srp/data.html:140
msgid "Total Losses:" msgid "Total Losses:"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/data.html:49 #: allianceauth/srp/templates/srp/data.html:47
#: allianceauth/srp/templates/srp/data.html:143 #: allianceauth/srp/templates/srp/data.html:141
#: allianceauth/srp/templates/srp/management.html:36 #: allianceauth/srp/templates/srp/management.html:34
msgid "Total ISK Cost:" msgid "Total ISK Cost:"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/data.html:60 #: allianceauth/srp/templates/srp/data.html:58
#: allianceauth/srp/templates/srp/data.html:154 #: allianceauth/srp/templates/srp/data.html:152
msgid "Are you sure you want to delete SRP requests?" msgid "Are you sure you want to delete SRP requests?"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/data.html:70 #: allianceauth/srp/templates/srp/data.html:68
msgid "Pilot Name" msgid "Pilot Name"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/data.html:71 #: allianceauth/srp/templates/srp/data.html:69
msgid "Killboard Link" msgid "Killboard Link"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/data.html:73 #: allianceauth/srp/templates/srp/data.html:71
msgid "Ship Type" msgid "Ship Type"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/data.html:74 #: allianceauth/srp/templates/srp/data.html:72
msgid "Killboard Loss Amt" msgid "Killboard Loss Amt"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/data.html:76 #: allianceauth/srp/templates/srp/data.html:74
msgid "SRP ISK Cost" msgid "SRP ISK Cost"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/data.html:77 #: allianceauth/srp/templates/srp/data.html:75
msgid "Click value to edit Enter to save & next ESC to cancel" msgid "Click value to edit Enter to save & next ESC to cancel"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/data.html:80 #: allianceauth/srp/templates/srp/data.html:78
msgid "Post Time" msgid "Post Time"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/data.html:102 #: allianceauth/srp/templates/srp/data.html:100
#: allianceauth/srp/templates/srp/management.html:70 #: allianceauth/srp/templates/srp/management.html:68
msgid "Link" msgid "Link"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/data.html:163 #: allianceauth/srp/templates/srp/data.html:161
msgid "No SRP requests for this fleet." msgid "No SRP requests for this fleet."
msgstr "" msgstr ""
@ -2414,39 +2432,39 @@ msgstr ""
msgid "View All" msgid "View All"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/management.html:27 #: allianceauth/srp/templates/srp/management.html:25
msgid "Add SRP Fleet" msgid "Add SRP Fleet"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/management.html:48 #: allianceauth/srp/templates/srp/management.html:46
msgid "Fleet AAR" msgid "Fleet AAR"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/management.html:49 #: allianceauth/srp/templates/srp/management.html:47
msgid "Fleet SRP Code" msgid "Fleet SRP Code"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/management.html:50 #: allianceauth/srp/templates/srp/management.html:48
msgid "Fleet ISK Cost" msgid "Fleet ISK Cost"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/management.html:51 #: allianceauth/srp/templates/srp/management.html:49
msgid "SRP Status" msgid "SRP Status"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/management.html:52 #: allianceauth/srp/templates/srp/management.html:50
msgid "Pending Requests" msgid "Pending Requests"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/management.html:91 #: allianceauth/srp/templates/srp/management.html:89
msgid "Completed" msgid "Completed"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/management.html:108 #: allianceauth/srp/templates/srp/management.html:106
msgid "Are you sure you want to delete this SRP code and its contents?" msgid "Are you sure you want to delete this SRP code and its contents?"
msgstr "" msgstr ""
#: allianceauth/srp/templates/srp/management.html:129 #: allianceauth/srp/templates/srp/management.html:127
msgid "No SRP fleets created." msgid "No SRP fleets created."
msgstr "" msgstr ""
@ -2582,68 +2600,121 @@ msgstr ""
msgid "Your Server received an ESI error response code of " msgid "Your Server received an ESI error response code of "
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:8 #: allianceauth/templates/allianceauth/admin-status/overview.html:11
msgid "Alliance Auth Notifications" msgid "second"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:20 #: allianceauth/templates/allianceauth/admin-status/overview.html:12
msgid "No notifications at this time" msgid "seconds"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:29 #: allianceauth/templates/allianceauth/admin-status/overview.html:13
msgid "Powered by GitLab" msgid "minute"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:35 #: allianceauth/templates/allianceauth/admin-status/overview.html:14
msgid "Support Discord" msgid "minutes"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:49 #: allianceauth/templates/allianceauth/admin-status/overview.html:15
#: allianceauth/templates/allianceauth/admin-status/overview.html:53 msgid "hour"
msgid "Software Version"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:56 #: allianceauth/templates/allianceauth/admin-status/overview.html:16
msgid "Current" msgid "hours"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:63 #: allianceauth/templates/allianceauth/admin-status/overview.html:17
msgid "Latest Stable" msgid "N/A"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:68 #: allianceauth/templates/allianceauth/admin-status/overview.html:18
msgid "Update available" msgid "ERROR"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:76 #: allianceauth/templates/allianceauth/admin-status/overview.html:19
msgid "Latest Pre-Release"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:81
msgid "Pre-Release available"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:91
msgid "Task Queue"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:96
#, python-format
msgid ""
"\n"
" Status of %(total)s processed tasks • last "
"%(latest)s\n"
" "
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:112
msgid "running" msgid "running"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:113 #: allianceauth/templates/allianceauth/admin-status/overview.html:20
msgid "queued" msgid "queued"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:21
msgid "succeeded"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:22
msgid "retried"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:23
msgid "failed"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:29
msgid "Debug mode"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:34
msgid ""
"Debug mode is currently turned on!<br>Make sure to turn it off as soon as "
"you are finished testing."
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:46
msgid "Alliance Auth Notifications"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:58
msgid "No notifications at this time"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:67
msgid "Powered by GitLab"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:73
msgid "Support Discord"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:87
#: allianceauth/templates/allianceauth/admin-status/overview.html:91
msgid "Software Version"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:94
msgid "Current"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:101
msgid "Latest Stable"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:106
msgid "Update available"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:114
msgid "Latest Pre-Release"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:119
msgid "Pre-Release available"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:129
msgid "Task Queue"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:134
msgid ""
"\n"
" Status of <span id=\"total-task-count\">?</span> "
"processed tasks • last <span id=\"celery-uptime\">?</span>\n"
" "
msgstr ""
#: allianceauth/templates/allianceauth/top-menu-admin.html:19 #: allianceauth/templates/allianceauth/top-menu-admin.html:19
msgid "AA Documentation" msgid "AA Documentation"
msgstr "" msgstr ""
@ -2867,7 +2938,7 @@ msgid "Theft"
msgstr "" msgstr ""
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:7 #: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:7
#: allianceauth/timerboard/templates/timerboard/view.html:54 #: allianceauth/timerboard/templates/timerboard/view.html:52
msgid "Upcoming Timers" msgid "Upcoming Timers"
msgstr "" msgstr ""
@ -2895,7 +2966,7 @@ msgid "Create Timer"
msgstr "" msgstr ""
#: allianceauth/timerboard/templates/timerboard/timer_create_form.html:9 #: allianceauth/timerboard/templates/timerboard/timer_create_form.html:9
#: allianceauth/timerboard/templates/timerboard/view.html:22 #: allianceauth/timerboard/templates/timerboard/view.html:20
msgid "Create Structure Timer" msgid "Create Structure Timer"
msgstr "" msgstr ""
@ -2913,11 +2984,11 @@ msgstr ""
msgid "Structure Timer Management" msgid "Structure Timer Management"
msgstr "" msgstr ""
#: allianceauth/timerboard/templates/timerboard/view.html:41 #: allianceauth/timerboard/templates/timerboard/view.html:39
msgid "Corporation Timers" msgid "Corporation Timers"
msgstr "" msgstr ""
#: allianceauth/timerboard/templates/timerboard/view.html:72 #: allianceauth/timerboard/templates/timerboard/view.html:70
msgid "Past Timers" msgid "Past Timers"
msgstr "" msgstr ""

View File

@ -4,21 +4,21 @@
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
# Translators: # Translators:
# Андрей Зубков <and.vareba81@gmail.com>, 2023
# Yuriy K <thedjcooltv@gmail.com>, 2023 # Yuriy K <thedjcooltv@gmail.com>, 2023
# Alexander Gess <de.alex.gess@gmail.com>, 2023 # Alexander Gess <de.alex.gess@gmail.com>, 2023
# Filipp Chertiev <f@fzfx.ru>, 2023 # Filipp Chertiev <f@fzfx.ru>, 2023
# Ruslan Virchich, 2024 # Ruslan Virchich, 2024
# Joel Falknau <ozirascal@gmail.com>, 2024 # Joel Falknau <ozirascal@gmail.com>, 2024
# Gnevich <and.vareba81@gmail.com>, 2025
# #
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-06-19 20:23+1000\n" "POT-Creation-Date: 2025-07-03 09:07+1000\n"
"PO-Revision-Date: 2023-11-08 13:50+0000\n" "PO-Revision-Date: 2023-11-08 13:50+0000\n"
"Last-Translator: Joel Falknau <ozirascal@gmail.com>, 2024\n" "Last-Translator: Gnevich <and.vareba81@gmail.com>, 2025\n"
"Language-Team: Russian (https://app.transifex.com/alliance-auth/teams/107430/ru/)\n" "Language-Team: Russian (https://app.transifex.com/alliance-auth/teams/107430/ru/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -28,7 +28,7 @@ msgstr ""
#: allianceauth/analytics/apps.py:8 #: allianceauth/analytics/apps.py:8
msgid "Analytics" msgid "Analytics"
msgstr "" msgstr "Аналитика"
#: allianceauth/analytics/models.py:22 #: allianceauth/analytics/models.py:22
msgid "Google Analytics Universal" msgid "Google Analytics Universal"
@ -40,7 +40,7 @@ msgstr "Google Analytics V4"
#: allianceauth/authentication/apps.py:9 #: allianceauth/authentication/apps.py:9
msgid "Authentication" msgid "Authentication"
msgstr "" msgstr "Авторизация"
#: allianceauth/authentication/constants.py:6 #: allianceauth/authentication/constants.py:6
msgid "" msgid ""
@ -66,68 +66,68 @@ msgid "You are not allowed to add or remove these restricted groups: %s"
msgstr "Вам не разрешено добавлять или удалять эти ограниченные группы: %s" msgstr "Вам не разрешено добавлять или удалять эти ограниченные группы: %s"
#: allianceauth/authentication/models.py:72 #: allianceauth/authentication/models.py:72
#: allianceauth/project_template/project_name/settings/base.py:106 #: allianceauth/project_template/project_name/settings/base.py:104
msgid "English" msgid "English"
msgstr "Английский" msgstr "Английский"
#: allianceauth/authentication/models.py:73 #: allianceauth/authentication/models.py:73
msgid "Czech" msgid "Czech"
msgstr "" msgstr "Чешский"
#: allianceauth/authentication/models.py:74 #: allianceauth/authentication/models.py:74
#: allianceauth/project_template/project_name/settings/base.py:108 #: allianceauth/project_template/project_name/settings/base.py:106
msgid "German" msgid "German"
msgstr "Немецкий" msgstr "Немецкий"
#: allianceauth/authentication/models.py:75 #: allianceauth/authentication/models.py:75
#: allianceauth/project_template/project_name/settings/base.py:109 #: allianceauth/project_template/project_name/settings/base.py:107
msgid "Spanish" msgid "Spanish"
msgstr "Испанский" msgstr "Испанский"
#: allianceauth/authentication/models.py:76 #: allianceauth/authentication/models.py:76
#: allianceauth/project_template/project_name/settings/base.py:110 #: allianceauth/project_template/project_name/settings/base.py:108
msgid "Italian" msgid "Italian"
msgstr "Итальянский" msgstr "Итальянский"
#: allianceauth/authentication/models.py:77 #: allianceauth/authentication/models.py:77
#: allianceauth/project_template/project_name/settings/base.py:111 #: allianceauth/project_template/project_name/settings/base.py:109
msgid "Japanese" msgid "Japanese"
msgstr "Японский" msgstr "Японский"
#: allianceauth/authentication/models.py:78 #: allianceauth/authentication/models.py:78
#: allianceauth/project_template/project_name/settings/base.py:112 #: allianceauth/project_template/project_name/settings/base.py:110
msgid "Korean" msgid "Korean"
msgstr "Корейский" msgstr "Корейский"
#: allianceauth/authentication/models.py:79 #: allianceauth/authentication/models.py:79
#: allianceauth/project_template/project_name/settings/base.py:113 #: allianceauth/project_template/project_name/settings/base.py:111
msgid "French" msgid "French"
msgstr "Французский" msgstr "Французский"
#: allianceauth/authentication/models.py:80 #: allianceauth/authentication/models.py:80
#: allianceauth/project_template/project_name/settings/base.py:116 #: allianceauth/project_template/project_name/settings/base.py:114
msgid "Russian" msgid "Russian"
msgstr "Русский" msgstr "Русский"
#: allianceauth/authentication/models.py:81 #: allianceauth/authentication/models.py:81
#: allianceauth/project_template/project_name/settings/base.py:114 #: allianceauth/project_template/project_name/settings/base.py:112
msgid "Dutch" msgid "Dutch"
msgstr "" msgstr "Голландский"
#: allianceauth/authentication/models.py:82 #: allianceauth/authentication/models.py:82
#: allianceauth/project_template/project_name/settings/base.py:115 #: allianceauth/project_template/project_name/settings/base.py:113
msgid "Polish" msgid "Polish"
msgstr "" msgstr "Польский"
#: allianceauth/authentication/models.py:83 #: allianceauth/authentication/models.py:83
#: allianceauth/project_template/project_name/settings/base.py:117 #: allianceauth/project_template/project_name/settings/base.py:115
msgid "Ukrainian" msgid "Ukrainian"
msgstr "Украинский" msgstr "Украинский"
#: allianceauth/authentication/models.py:84 #: allianceauth/authentication/models.py:84
#: allianceauth/project_template/project_name/settings/base.py:118 #: allianceauth/project_template/project_name/settings/base.py:116
msgid "Simplified Chinese" msgid "Simplified Chinese"
msgstr "" msgstr "Упрощенный китайский"
#: allianceauth/authentication/models.py:100 #: allianceauth/authentication/models.py:100
#: allianceauth/menu/templates/menu/menu-user.html:67 #: allianceauth/menu/templates/menu/menu-user.html:67
@ -142,7 +142,7 @@ msgstr "Ночной режим"
#: allianceauth/authentication/models.py:109 #: allianceauth/authentication/models.py:109
#: allianceauth/theme/templates/theme/theme_select.html:4 #: allianceauth/theme/templates/theme/theme_select.html:4
msgid "Theme" msgid "Theme"
msgstr "" msgstr "Тема"
#: allianceauth/authentication/models.py:126 #: allianceauth/authentication/models.py:126
#, python-format #, python-format
@ -481,7 +481,7 @@ msgstr ""
#: allianceauth/eveonline/apps.py:8 #: allianceauth/eveonline/apps.py:8
msgid "EVE Online" msgid "EVE Online"
msgstr "" msgstr "EVE Online"
#: allianceauth/eveonline/autogroups/apps.py:8 #: allianceauth/eveonline/autogroups/apps.py:8
msgid "EVE Online Autogroups" msgid "EVE Online Autogroups"
@ -1439,15 +1439,15 @@ msgstr ""
#: allianceauth/menu/admin.py:100 #: allianceauth/menu/admin.py:100
msgid "visible" msgid "visible"
msgstr "" msgstr "видимый"
#: allianceauth/menu/apps.py:16 #: allianceauth/menu/apps.py:16
msgid "Menu" msgid "Menu"
msgstr "" msgstr "Меню"
#: allianceauth/menu/constants.py:16 #: allianceauth/menu/constants.py:16
msgid "app" msgid "app"
msgstr "" msgstr "приложение"
#: allianceauth/menu/constants.py:17 allianceauth/menu/models.py:38 #: allianceauth/menu/constants.py:17 allianceauth/menu/models.py:38
msgid "folder" msgid "folder"
@ -1479,7 +1479,7 @@ msgstr ""
#: allianceauth/menu/models.py:43 #: allianceauth/menu/models.py:43
msgid "is hidden" msgid "is hidden"
msgstr "" msgstr "скрыто"
#: allianceauth/menu/models.py:45 #: allianceauth/menu/models.py:45
msgid "" msgid ""
@ -1499,7 +1499,7 @@ msgstr ""
#: allianceauth/menu/models.py:68 #: allianceauth/menu/models.py:68
msgid "url" msgid "url"
msgstr "" msgstr "ссылка"
#: allianceauth/menu/models.py:69 #: allianceauth/menu/models.py:69
msgid "External URL this menu items will link to" msgid "External URL this menu items will link to"
@ -1507,7 +1507,7 @@ msgstr ""
#: allianceauth/menu/templates/admin/menu/menuitem/change_list.html:10 #: allianceauth/menu/templates/admin/menu/menuitem/change_list.html:10
msgid "Add folder" msgid "Add folder"
msgstr "" msgstr "Добавить папку"
#: allianceauth/menu/templates/menu/menu-notification-block.html:12 #: allianceauth/menu/templates/menu/menu-notification-block.html:12
#: allianceauth/notifications/apps.py:8 #: allianceauth/notifications/apps.py:8
@ -1880,7 +1880,7 @@ msgstr "Пароль должен быть не менее 8 символов."
#: allianceauth/services/modules/discord/apps.py:8 #: allianceauth/services/modules/discord/apps.py:8
msgid "Discord Service" msgid "Discord Service"
msgstr "" msgstr "Discord сервис"
#: allianceauth/services/modules/discord/models.py:187 #: allianceauth/services/modules/discord/models.py:187
msgid "Discord Account Disabled" msgid "Discord Account Disabled"
@ -2014,7 +2014,7 @@ msgstr ""
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:7 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:7
msgid "Mumble" msgid "Mumble"
msgstr "" msgstr "Mumble"
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:11 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:11
msgid "Mumble History" msgid "Mumble History"
@ -2026,24 +2026,24 @@ msgstr ""
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:32 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:32
msgid "Displayed Name" msgid "Displayed Name"
msgstr "" msgstr "Отображаемое имя"
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:33 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:33
msgid "Release" msgid "Release"
msgstr "" msgstr "Релиз"
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:34 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:34
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:68 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:68
msgid "Version" msgid "Version"
msgstr "" msgstr "Версия"
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:35 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:35
msgid "Last Connect" msgid "Last Connect"
msgstr "" msgstr "Последние подключение"
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:36 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:36
msgid "Last Disconnect" msgid "Last Disconnect"
msgstr "" msgstr "Последние отключение"
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:48 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:48
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:60 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:60
@ -2052,7 +2052,7 @@ msgstr ""
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:69 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:69
msgid "Number" msgid "Number"
msgstr "" msgstr "Число"
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:28 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:28
#: allianceauth/services/templates/services/service_password.html:26 #: allianceauth/services/templates/services/service_password.html:26
@ -2191,7 +2191,7 @@ msgstr ""
#: allianceauth/services/modules/teamspeak3/templates/services/teamspeak3/teamspeak3_service_ctrl.html:6 #: allianceauth/services/modules/teamspeak3/templates/services/teamspeak3/teamspeak3_service_ctrl.html:6
msgid "Teamspeak 3" msgid "Teamspeak 3"
msgstr "" msgstr "Teamspeak 3"
#: allianceauth/services/modules/teamspeak3/templates/services/teamspeak3/teamspeakjoin.html:6 #: allianceauth/services/modules/teamspeak3/templates/services/teamspeak3/teamspeakjoin.html:6
msgid "Verify TeamSpeak3" msgid "Verify TeamSpeak3"
@ -2263,7 +2263,7 @@ msgstr "ФлитФорматер"
#: allianceauth/services/templates/services/fleetformattertool.html:18 #: allianceauth/services/templates/services/fleetformattertool.html:18
msgid "Fleet Details" msgid "Fleet Details"
msgstr "" msgstr "Детали флота"
#: allianceauth/services/templates/services/fleetformattertool.html:37 #: allianceauth/services/templates/services/fleetformattertool.html:37
msgid "Format" msgid "Format"
@ -2306,7 +2306,7 @@ msgstr "Установить %(service_name)s Пароль"
#: allianceauth/services/templates/services/service_status.html:5 #: allianceauth/services/templates/services/service_status.html:5
msgid "Enabled" msgid "Enabled"
msgstr "" msgstr "Включено"
#: allianceauth/services/templates/services/service_status.html:7 #: allianceauth/services/templates/services/service_status.html:7
#: allianceauth/srp/templates/srp/management.html:78 #: allianceauth/srp/templates/srp/management.html:78
@ -2319,7 +2319,7 @@ msgstr "Управление Сервисами"
#: allianceauth/services/templates/services/services.html:20 #: allianceauth/services/templates/services/services.html:20
msgid "Legend" msgid "Legend"
msgstr "" msgstr "Легенда"
#: allianceauth/services/templates/services/services.html:27 #: allianceauth/services/templates/services/services.html:27
msgid "Click to activate the service for your user." msgid "Click to activate the service for your user."
@ -2840,51 +2840,51 @@ msgstr ""
#: allianceauth/timerboard/models.py:31 #: allianceauth/timerboard/models.py:31
msgid "Astrahus" msgid "Astrahus"
msgstr "" msgstr "Astrahus"
#: allianceauth/timerboard/models.py:32 #: allianceauth/timerboard/models.py:32
msgid "Fortizar" msgid "Fortizar"
msgstr "" msgstr "Fortizar"
#: allianceauth/timerboard/models.py:33 #: allianceauth/timerboard/models.py:33
msgid "Keepstar" msgid "Keepstar"
msgstr "" msgstr "Keepstar"
#: allianceauth/timerboard/models.py:34 #: allianceauth/timerboard/models.py:34
msgid "Raitaru" msgid "Raitaru"
msgstr "" msgstr "Raitaru"
#: allianceauth/timerboard/models.py:35 #: allianceauth/timerboard/models.py:35
msgid "Azbel" msgid "Azbel"
msgstr "" msgstr "Raitaru"
#: allianceauth/timerboard/models.py:36 #: allianceauth/timerboard/models.py:36
msgid "Sotiyo" msgid "Sotiyo"
msgstr "" msgstr "Sotiyo"
#: allianceauth/timerboard/models.py:37 #: allianceauth/timerboard/models.py:37
msgid "Athanor" msgid "Athanor"
msgstr "" msgstr "Athanor"
#: allianceauth/timerboard/models.py:38 #: allianceauth/timerboard/models.py:38
msgid "Tatara" msgid "Tatara"
msgstr "" msgstr "Tatara"
#: allianceauth/timerboard/models.py:39 #: allianceauth/timerboard/models.py:39
msgid "Cyno Beacon" msgid "Cyno Beacon"
msgstr "" msgstr "Cyno Beacon"
#: allianceauth/timerboard/models.py:40 #: allianceauth/timerboard/models.py:40
msgid "Cyno Jammer" msgid "Cyno Jammer"
msgstr "" msgstr "Cyno Jammer"
#: allianceauth/timerboard/models.py:41 #: allianceauth/timerboard/models.py:41
msgid "Ansiblex Jump Gate" msgid "Ansiblex Jump Gate"
msgstr "" msgstr "Ansiblex Jump Gate"
#: allianceauth/timerboard/models.py:42 #: allianceauth/timerboard/models.py:42
msgid "Mercenary Den" msgid "Mercenary Den"
msgstr "" msgstr "Mercenary Den"
#: allianceauth/timerboard/models.py:43 #: allianceauth/timerboard/models.py:43
msgid "Moon Mining Cycle" msgid "Moon Mining Cycle"
@ -2892,7 +2892,7 @@ msgstr ""
#: allianceauth/timerboard/models.py:44 #: allianceauth/timerboard/models.py:44
msgid "Metenox Moon Drill" msgid "Metenox Moon Drill"
msgstr "" msgstr "Metenox Moon Drill"
#: allianceauth/timerboard/models.py:45 #: allianceauth/timerboard/models.py:45
msgid "Other" msgid "Other"
@ -2928,7 +2928,7 @@ msgstr "Снятие с якоря"
#: allianceauth/timerboard/models.py:59 #: allianceauth/timerboard/models.py:59
msgid "Abandoned" msgid "Abandoned"
msgstr "" msgstr "Заброшенная "
#: allianceauth/timerboard/models.py:60 #: allianceauth/timerboard/models.py:60
msgid "Theft" msgid "Theft"
@ -3000,7 +3000,7 @@ msgstr "Изменения таймера сохранены."
#: allianceauth/views.py:55 #: allianceauth/views.py:55
msgid "Bad Request" msgid "Bad Request"
msgstr "" msgstr "Bad Request"
#: allianceauth/views.py:57 allianceauth/views.py:87 #: allianceauth/views.py:57 allianceauth/views.py:87
msgid "" msgid ""
@ -3010,7 +3010,7 @@ msgstr ""
#: allianceauth/views.py:65 #: allianceauth/views.py:65
msgid "Permission Denied" msgid "Permission Denied"
msgstr "" msgstr "Доступ запрещен"
#: allianceauth/views.py:67 #: allianceauth/views.py:67
msgid "" msgid ""
@ -3020,7 +3020,7 @@ msgstr ""
#: allianceauth/views.py:75 #: allianceauth/views.py:75
msgid "Page Not Found" msgid "Page Not Found"
msgstr "" msgstr "Страница не найдена"
#: allianceauth/views.py:77 #: allianceauth/views.py:77
msgid "" msgid ""

View File

@ -5,10 +5,10 @@
<li class="nav-item" id="menu_item_notifications"> <li class="nav-item" id="menu_item_notifications">
<a class="nav-link {% navactive request 'notifications:' %}" href="{% url 'notifications:list' %}"> <a class="nav-link {% navactive request 'notifications:' %}" href="{% url 'notifications:list' %}">
{% with unread_count=request.user|user_unread_notification_count %} {% with unread_count=request.user|user_unread_notification_count %}
<i class="fa-solid fa-bell{% if unread_count %} text-danger{% endif %}"></i> <i class="fa-solid fa-bell me-2 fa-fw{% if unread_count %} text-danger{% endif %}"></i>
{% endwith %} {% endwith %}
<span class="d-lg-none d-md-inline m-2"> <span class="d-lg-none d-md-inline">
{% translate "Notifications" %} {% translate "Notifications" %}
</span> </span>
</a> </a>

View File

@ -28,17 +28,13 @@
{% endblock %} {% endblock %}
{% block header_nav_collapse_right %} {% block header_nav_collapse_right %}
<li class="nav-item"> {% translate "Mark all notifications as read" as nav_item_title %}
<a href="{% url 'notifications:mark_all_read' %}" class="nav-link" title="{% translate 'Mark all notifications as read' %}"> {% url "notifications:mark_all_read" as nav_item_link %}
<i class="fa-solid fa-check-double"></i> {% include "framework/header/nav-collapse-icon.html" with fa_icon="fa-solid fa-check-double" url=nav_item_link title=nav_item_title icon_on_mobile=True %}
</a>
</li>
<li class="nav-item"> {% translate "Delete all read notifications" as nav_item_title %}
<a href="{% url 'notifications:delete_all_read' %}" class="nav-link" title="{% translate 'Delete all read notifications' %}"> {% url "notifications:mark_all_read" as nav_item_link %}
<i class="fa-solid fa-trash-can"></i> {% include "framework/header/nav-collapse-icon.html" with fa_icon="fa-solid fa-trash-can" url=nav_item_link title=nav_item_title icon_on_mobile=True %}
</a>
</li>
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@ -22,8 +22,8 @@
</tr> </tr>
</thead> </thead>
{% for ops in timers %} <tbody>
<tbody> {% for ops in timers %}
<tr> <tr>
<td> <td>
{{ ops.operation_name }} {{ ops.operation_name }}
@ -55,8 +55,8 @@
</td> </td>
{% endif %} {% endif %}
</tr> </tr>
</tbody> {% endfor %}
{% endfor %} </tbody>
</table> </table>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -13,11 +13,9 @@
{% block header_nav_collapse_right %} {% block header_nav_collapse_right %}
{% if perms.auth.optimer_management %} {% if perms.auth.optimer_management %}
<li class="nav-item"> {% translate "Create Operation" as nav_item_title %}
<a class="btn btn-success" href="{% url 'optimer:add' %}"> {% url "optimer:add" as nav_item_link %}
{% translate "Create Operation" %} {% include "framework/header/nav-collapse-button.html" with btn_modifier="success" url=nav_item_link title=nav_item_title fa_icon="fa-solid fa-plus" icon_on_mobile=True %}
</a>
</li>
{% endif %} {% endif %}
{% endblock header_nav_collapse_right %} {% endblock header_nav_collapse_right %}

View File

@ -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.worker_prefetch_multiplier = 1 # only prefetch single tasks at a time on the workers so that prio tasks happen
app.conf.ONCE = { app.conf.ONCE = {
'backend': 'allianceauth.services.tasks.DjangoBackend', 'backend': 'allianceauth.services.celery_once.backends.DjangoBackend',
'settings': {} 'settings': {}
} }

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

View 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

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

View File

@ -16,8 +16,7 @@ from redis import Redis
from allianceauth.utils.cache import get_redis_client from allianceauth.utils.cache import get_redis_client
from allianceauth import __title__ as AUTH_TITLE from allianceauth import __title_useragent__, __url__, __version__
from allianceauth import __url__, __version__
from .. import __title__ from .. import __title__
from ..utils import LoggerAddTag from ..utils import LoggerAddTag
@ -647,7 +646,7 @@ class DiscordClient:
if self.is_rate_limited: if self.is_rate_limited:
self._ensure_rate_limed_not_exhausted(uid) self._ensure_rate_limed_not_exhausted(uid)
headers = { headers = {
'User-Agent': f'{AUTH_TITLE} ({__url__}, {__version__})', 'User-Agent': f'{__title_useragent__}/{__version__} (+{__url__})',
'accept': 'application/json', 'accept': 'application/json',
'X-RateLimit-Precision': 'millisecond', 'X-RateLimit-Precision': 'millisecond',
'authorization': str(authorization) 'authorization': str(authorization)

View File

@ -7,8 +7,7 @@ import requests_mock
from redis import Redis from redis import Redis
from requests.exceptions import HTTPError from requests.exceptions import HTTPError
from allianceauth import __title__ as AUTH_TITLE from allianceauth import __title_useragent__, __url__, __version__
from allianceauth import __url__, __version__
from allianceauth.utils.testing import NoSocketsTestCase from allianceauth.utils.testing import NoSocketsTestCase
from ...utils import set_logger_to_file from ...utils import set_logger_to_file
@ -46,7 +45,7 @@ API_BASE_URL = 'https://discord.com/api/'
TEST_RETRY_AFTER = 3000 TEST_RETRY_AFTER = 3000
DEFAULT_REQUEST_HEADERS = { DEFAULT_REQUEST_HEADERS = {
'User-Agent': f'{AUTH_TITLE} ({__url__}, {__version__})', 'User-Agent': f'{__title_useragent__}/{__version__} (+{__url__})',
'accept': 'application/json', 'accept': 'application/json',
'authorization': 'Bot ' + TEST_BOT_TOKEN 'authorization': 'Bot ' + TEST_BOT_TOKEN
} }

View File

@ -3,33 +3,11 @@ import logging
from celery import shared_task from celery import shared_task
from django.contrib.auth.models import User from django.contrib.auth.models import User
from .hooks import ServicesHook from .hooks import ServicesHook
from celery_once import QueueOnce as BaseTask, AlreadyQueued from .celery_once.tasks import QueueOnce # noqa: F401 - for backwards compatibility
from django.core.cache import cache
logger = logging.getLogger(__name__) 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) @shared_task(bind=True)
def validate_services(self, pk): def validate_services(self, pk):
user = User.objects.get(pk=pk) user = User.objects.get(pk=pk)
@ -38,7 +16,7 @@ def validate_services(self, pk):
for svc in ServicesHook.get_services(): for svc in ServicesHook.get_services():
try: try:
svc.validate_user(user) svc.validate_user(user)
except: except Exception:
logger.exception(f'Exception running validate_user for services module {svc} on user {user}') logger.exception(f'Exception running validate_user for services module {svc} on user {user}')

View File

@ -1,15 +1,10 @@
from unittest import mock from unittest import mock
from celery_once import AlreadyQueued
from django.core.cache import cache
from django.test import override_settings, TestCase from django.test import override_settings, TestCase
from allianceauth.tests.auth_utils import AuthUtils from allianceauth.tests.auth_utils import AuthUtils
from allianceauth.services.tasks import validate_services, update_groups_for_user 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) @override_settings(CELERY_ALWAYS_EAGER=True, CELERY_EAGER_PROPAGATES_EXCEPTIONS=True)
class ServicesTasksTestCase(TestCase): class ServicesTasksTestCase(TestCase):
@ -46,46 +41,3 @@ class ServicesTasksTestCase(TestCase):
self.assertTrue(svc.update_groups.called) self.assertTrue(svc.update_groups.called)
args, _ = svc.update_groups.call_args args, _ = svc.update_groups.call_args
self.assertEqual(self.member, args[0]) # Assert correct user 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))

View File

@ -4,7 +4,7 @@ import requests
from django.contrib.auth.models import User from django.contrib.auth.models import User
from allianceauth import NAME from allianceauth import __title_useragent__, __url__, __version__
from allianceauth.srp.providers import esi from allianceauth.srp.providers import esi
from .models import SrpUserRequest from .models import SrpUserRequest
@ -24,7 +24,7 @@ class SRPManager:
def get_kill_data(kill_id): def get_kill_data(kill_id):
url = ("https://zkillboard.com/api/killID/%s/" % kill_id) url = ("https://zkillboard.com/api/killID/%s/" % kill_id)
headers = { headers = {
'User-Agent': NAME, 'User-Agent': f'{__title_useragent__}/{__version__} (+{__url__})',
'Content-Type': 'application/json', 'Content-Type': 'application/json',
} }
r = requests.get(url, headers=headers) r = requests.get(url, headers=headers)

View File

@ -20,17 +20,15 @@
{% block header_nav_collapse_right %} {% block header_nav_collapse_right %}
{% if perms.auth.srp_management %} {% if perms.auth.srp_management %}
<li class="nav-item"> {% if fleet_status == "Completed" %}
{% if fleet_status == "Completed" %} {% translate "Mark Incomplete" as nav_item_title %}
<a class="btn btn-warning" href="{% url 'srp:mark_uncompleted' fleet_id %}"> {% url "srp:mark_uncompleted" fleet_id as nav_item_link %}
{% translate "Mark Incomplete" %} {% include "framework/header/nav-collapse-button.html" with btn_modifier="warning" url=nav_item_link title=nav_item_title %}
</a> {% else %}
{% else %} {% translate "Mark Completed" as nav_item_title %}
<a class="btn btn-success" href="{% url 'srp:mark_completed' fleet_id %}"> {% url "srp:mark_completed" fleet_id as nav_item_link %}
{% translate "Mark Completed" %} {% include "framework/header/nav-collapse-button.html" with btn_modifier="warning" url=nav_item_link title=nav_item_title %}
</a> {% endif %}
{% endif %}
</li>
{% endif %} {% endif %}
{% endblock header_nav_collapse_right %} {% endblock header_nav_collapse_right %}

View File

@ -22,11 +22,9 @@
{% endblock header_nav_collapse_left %} {% endblock header_nav_collapse_left %}
{% block header_nav_collapse_right %} {% block header_nav_collapse_right %}
{% if perms.srp.add_srpfleetmain or perms.auth.srp_management %} {% if perms.srp.add_srpfleetmain or perms.auth.srp_management %}
<li class="nav-item"> {% translate "Add SRP Fleet" as nav_item_title %}
<a class="btn btn-success" href="{% url 'srp:add' %}"> {% url "srp:add" as nav_item_link %}
{% translate "Add SRP Fleet" %} {% include "framework/header/nav-collapse-button.html" with btn_modifier="success" url=nav_item_link title=nav_item_title fa_icon="fa-solid fa-plus" icon_on_mobile=True %}
</a>
</li>
{% endif %} {% endif %}
{% endblock header_nav_collapse_right %} {% endblock header_nav_collapse_right %}
{% block content %} {% block content %}

View File

@ -2,6 +2,7 @@
{% load admin_status %} {% load admin_status %}
<div <div
id="celery-progress-bar-{{ label }}"
class="progress-bar text-bg-{{ level }} task-status-progress-bar" class="progress-bar text-bg-{{ level }} task-status-progress-bar"
role="progressbar" role="progressbar"
aria-valuenow="{% decimal_widthratio tasks_count tasks_total 100 %}" aria-valuenow="{% decimal_widthratio tasks_count tasks_total 100 %}"
@ -9,5 +10,5 @@
aria-valuemax="100" aria-valuemax="100"
style="width: {% decimal_widthratio tasks_count tasks_total 100 %}%;" style="width: {% decimal_widthratio tasks_count tasks_total 100 %}%;"
> >
<span>{% widthratio tasks_count tasks_total 100 %}%</span> <span id="celery-progress-bar-{{ label }}-progress">{% widthratio tasks_count tasks_total 100 %}%</span>
</div> </div>

View File

@ -1,6 +1,27 @@
{% load i18n %} {% load i18n %}
{% load humanize %} {% load humanize %}
{% get_current_language as LANGUAGE_CODE %}
{% comment %}
Some translations used in the HTML and JavaScript code below.
We define them here so that they can be used in the JavaScript code as well with
the escapejs filter without having to redefine them later.
{% endcomment %}
{% translate "second" as l10nSecondSingular %}
{% translate "seconds" as l10nSecondPlural %}
{% translate "minute" as l10nMinuteSingular %}
{% translate "minutes" as l10nMinutePlural %}
{% translate "hour" as l10nHourSingular %}
{% translate "hours" as l10nHourPlural %}
{% translate "N/A" as l10nNA %}
{% translate "ERROR" as l10nError %}
{% translate "running" as l10nRunning %}
{% translate "queued" as l10nQueued %}
{% translate "succeeded" as l10nSucceeded %}
{% translate "retried" as l10nRetried %}
{% translate "failed" as l10nFailed %}
{% if debug %} {% if debug %}
<div id="aa-dashboard-panel-debug" class="col-12 mb-3"> <div id="aa-dashboard-panel-debug" class="col-12 mb-3">
<div class="card text-bg-warning"> <div class="card text-bg-warning">
@ -111,23 +132,27 @@
<div> <div>
<p> <p>
{% blocktranslate with total=tasks_total|intcomma latest=earliest_task|timesince|default:"?" %} {% blocktranslate with total=tasks_total|intcomma latest=earliest_task|timesince|default:"?" %}
Status of {{ total }} processed tasks • last {{ latest }} Status of <span id="total-task-count">?</span> processed tasks • last <span id="celery-uptime">?</span>
{% endblocktranslate %} {% endblocktranslate %}
</p> </p>
<div <div
class="progress" id="celery-tasks-progress-bar"
class="progress mb-2"
style="height: 21px;" style="height: 21px;"
title="{{ tasks_succeeded|intcomma }} succeeded, {{ tasks_retried|intcomma }} retried, {{ tasks_failed|intcomma }} failed" title="? {{ l10nSucceeded }}, ? {{ l10nRetried }}, ? {{ l10nFailed }}"
> >
{% include "allianceauth/admin-status/celery_bar_partial.html" with label="suceeded" level="success" tasks_count=tasks_succeeded %} {% include "allianceauth/admin-status/celery_bar_partial.html" with label="succeeded" level="success" tasks_count=0 %}
{% include "allianceauth/admin-status/celery_bar_partial.html" with label="retried" level="info" tasks_count=tasks_retried %} {% include "allianceauth/admin-status/celery_bar_partial.html" with label="retried" level="info" tasks_count=0 %}
{% include "allianceauth/admin-status/celery_bar_partial.html" with label="failed" level="danger" tasks_count=tasks_failed %} {% include "allianceauth/admin-status/celery_bar_partial.html" with label="failed" level="danger" tasks_count=0 %}
</div> </div>
<p> <p>
<span id="task-counts">?</span> {% translate 'running' %} | <span id="running-task-count">?</span> {{ l10nRunning }} |
<span id="queued-tasks-count">?</span> {% translate 'queued' %} <span id="queued-tasks-count">?</span> {{ l10nQueued }} |
<span id="succeeded-tasks-count">?</span> {{ l10nSucceeded }} |
<span id="retried-tasks-count">?</span> {{ l10nRetried }} |
<span id="failed-tasks-count">?</span> {{ l10nFailed }}
</p> </p>
</div> </div>
</div> </div>
@ -136,24 +161,206 @@
</div> </div>
<script> <script>
const elemRunning = document.getElementById('task-counts'); const elements = {
const elemQueued = document.getElementById('queued-tasks-count'); 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')
};
fetchGet({url: '{% url "authentication:task_counts" %}'}) /**
.then((data) => { * Fetches the task queue status and updates the UI elements accordingly.
const running = data.tasks_running; * It retrieves the total number of tasks, running tasks, queued tasks,
const queued = data.tasks_queued; * 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 updateTaskCount = (element, value) => { // Assign progress data from the fetched data to variables
element.textContent = value == null ? 'N/A' : value.toLocaleString(); const {
}; earliest_task: earliestTask,
tasks_total: tasksTotal,
tasks_running: tasksRunning,
tasks_queued: tasksQueued,
tasks_succeeded: tasksSucceeded,
tasks_retried: tasksRetried,
tasks_failed: tasksFailed
} = data;
updateTaskCount(elemRunning, running); /**
updateTaskCount(elemQueued, queued); * 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'.
.catch((error) => { * Otherwise, it formats the number using the locale-specific format.
console.error('Error fetching task queue:', error); *
* @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);
};
[elemRunning, elemQueued].forEach(elem => elem.textContent = 'ERROR'); /**
}); * 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> </script>

View File

@ -18,10 +18,11 @@
<title>{% block title %}{% block page_title %}{% endblock page_title %} - {{ SITE_NAME }}{% endblock title %}</title> <title>{% block title %}{% block page_title %}{% endblock page_title %} - {{ SITE_NAME }}{% endblock title %}</title>
{% include 'bundles/auth-framework-css.html' %}
{% theme_css %} {% theme_css %}
{% include 'bundles/fontawesome.html' %} {% include 'bundles/fontawesome.html' %}
{% include 'bundles/auth-framework-css.html' %}
{% include 'bundles/jquery-js.html' %} {% include 'bundles/jquery-js.html' %}
{% include 'bundles/auth-framework-js.html' %} {% include 'bundles/auth-framework-js.html' %}

View File

@ -1,21 +1,17 @@
{% load i18n %} {% load i18n %}
{% if user.is_authenticated %} {% if user.is_authenticated %}
<li class="nav-item"> {% translate "Add Character" as nav_item_title %}
<a href="{% url 'authentication:add_character' %}" class="nav-link" title="{% translate 'Add Character' %}"> {% url "authentication:add_character" as nav_item_link %}
<i class="fa-solid fa-user-plus"></i> {% include "framework/header/nav-collapse-icon.html" with fa_icon="fa-solid fa-user-plus" url=nav_item_link title=nav_item_title icon_on_mobile=True %}
<span class="d-lg-none d-md-inline m-2">{% translate "Add Character" %}</span>
</a> {% translate "Change Main" as nav_item_title %}
</li> {% url "authentication:change_main_character" as nav_item_link %}
<li class="nav-item"> {% include "framework/header/nav-collapse-icon.html" with fa_icon="fa-solid fa-shuffle" url=nav_item_link title=nav_item_title icon_on_mobile=True %}
<a href="{% url 'authentication:change_main_character' %}" class="nav-link" title="{% translate 'Change Main' %}">
<i class="fa-solid fa-shuffle"></i>
<span class="d-lg-none d-md-inline m-2">{% translate "Change Main" %}</span>
</a>
</li>
{% else %} {% else %}
<li class="nav-item"> <li class="nav-item">
<a href="{% url 'authentication:login' %}" class="nav-link" title="{% translate 'Sign In' %}"> <a href="{% url 'authentication:login' %}" class="nav-link" title="{% translate 'Sign In' %}">
<i class="fa-solid fa-right-to-bracket fa-fw "></i> {% translate "Sign In" %} <i class="fa-solid fa-right-to-bracket fa-fw me-2"></i> {% translate "Sign In" %}
</a> </a>
</li> </li>
{% endif %} {% endif %}

View File

@ -37,7 +37,8 @@ def get_theme(request):
def get_theme_context(request): def get_theme_context(request):
return { return {
'theme': get_theme(request) 'theme': get_theme(request),
'request': request
} }

View File

@ -17,11 +17,9 @@
{% block header_nav_collapse_right %} {% block header_nav_collapse_right %}
{% if perms.auth.timer_management %} {% if perms.auth.timer_management %}
<li class="nav-item"> {% translate "Create Structure Timer" as nav_item_title %}
<a class="btn btn-success" href="{% url 'timerboard:add' %}"> {% url "timerboard:add" as nav_item_link %}
{% translate "Create Structure Timer" %} {% include "framework/header/nav-collapse-button.html" with btn_modifier="success" url=nav_item_link title=nav_item_title fa_icon="fa-solid fa-plus" icon_on_mobile=True %}
</a>
</li>
{% endif %} {% endif %}
{% endblock header_nav_collapse_right %} {% endblock header_nav_collapse_right %}

View File

@ -1,7 +1,7 @@
PROTOCOL=https:// PROTOCOL=https://
AUTH_SUBDOMAIN=%AUTH_SUBDOMAIN% AUTH_SUBDOMAIN=%AUTH_SUBDOMAIN%
DOMAIN=%DOMAIN% DOMAIN=%DOMAIN%
AA_DOCKER_TAG=registry.gitlab.com/allianceauth/allianceauth/auth:v4.9.0 AA_DOCKER_TAG=registry.gitlab.com/allianceauth/allianceauth/auth:v4.10.0
# Nginx Proxy Manager # Nginx Proxy Manager
PROXY_HTTP_PORT=80 PROXY_HTTP_PORT=80

View File

@ -1,5 +1,5 @@
FROM python:3.11-slim FROM python:3.11-slim
ARG AUTH_VERSION=v4.9.0 ARG AUTH_VERSION=v4.10.0
ARG AUTH_PACKAGE=allianceauth==${AUTH_VERSION} ARG AUTH_PACKAGE=allianceauth==${AUTH_VERSION}
ENV AUTH_USER=allianceauth ENV AUTH_USER=allianceauth
ENV AUTH_GROUP=allianceauth ENV AUTH_GROUP=allianceauth

View File

@ -122,3 +122,44 @@ const merged = objectDeepMerge(target, source1, source2);
console.log(merged); // {a: 5, b: {c: 6, d: 3}, e: 4} console.log(merged); // {a: 5, b: {c: 6, d: 3}, e: 4}
``` ```
### numberFormatter()
Formats a number according to the specified locale.
Usage:
In your template get the current language code:
```django
{% get_current_language as LANGUAGE_CODE %}
```
Then use it in your JavaScript code:
```javascript
/* global numberFormatter */
const userLocale = '{{ LANGUAGE_CODE }}'; // e.g., 'en-US', 'de-DE'
const number = 1234567.89;
const formattedNumber = numberFormatter({
value: number,
locales: userLocale,
options: {
style: 'currency',
currency: 'ISK'
}
});
console.log(formattedNumber); // e.g. "ISK 1,234,567.89" or "1.234.567,89 ISK" depending on locale
```
#### numberFormatter() Parameters
- `value`: The number to format.
- `locales`: The locale(s) to use for formatting (e.g., `en-US`, `de-DE`, `['en-US',
'de-DE']`). If not provided, the browser's default locale will be used and any
language settings from the user will be ignored.
- `options`: Additional options for number formatting (see [`Intl.NumberFormat` documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat))
- `minimumFractionDigits` is set to 0 by default if not provided.
- `maximumFractionDigits` is set to 2 by default if not provided.

View File

@ -48,3 +48,13 @@ The loading spinner can be used with the following code:
<use href="#aa-loading-spinner"></use> <use href="#aa-loading-spinner"></use>
</svg> </svg>
``` ```
### Mumble Logo
The Mumble logo can be used with the following code:
```html
<svg>
<use href="#aa-mumble-logo"></use>
</svg>
```

View File

@ -66,3 +66,53 @@ To use it, you can use the following code in your template:
</div> </div>
{% endblock %} {% endblock %}
``` ```
### Top Navigation Buttons
To ensure a unified style for top navigation buttons, we provide a template partial for this.
To use it, you can use the following code in your template:
```django
{% block header_nav_collapse_right %}
{% translate "My Awesome Link" as nav_item_title %}
{% url "my_app:my_function" as nav_item_link %}
{% include "framework/header/nav-collapse-button.html" with btn_modifier="success" url=nav_item_link title=nav_item_title fa_icon="fa-solid fa-plus" icon_on_mobile=True icon_on_desktop=True %}
{% endblock header_nav_collapse_right %}
```
This template takes care of the mobile responsiveness and the styling. In mobile view,
the button will be changed to a text link. The icon will be placed in front of the text
link if `icon_on_mobile` is set to `True`.
#### Button Parameters
- `btn_modifier`: (Optional) The button modifier class, e.g. `primary`, `secondary`, `success`, `danger`, `warning`, `info`, `light`, `dark`, `link`. Default is `primary`.
- `url`: The URL the button should point to.
- `title`: The title of the button.
- `fa_icon`: (Optional) The FontAwesome icon class to use, e.g. `fa-solid fa-plus`.
- `icon_on_mobile`: (Optional) Boolean to indicate if the icon should be shown on mobile devices in front of the text link. Default is `False`.
- `icon_on_desktop`: (Optional) Boolean to indicate if the icon should be shown on desktop devices in front of the button text. Default is `False`.
### Top Navigation FontAwesome Icons
To ensure a unified style for top navigation FontAwesome icons, we provide a template partial for this.
To use it, you can use the following code in your template:
```django
{% block header_nav_collapse_right %}
{% translate "My Awesome Link as FontAwesome Icon" as nav_item_title %}
{% url "my_app:my_function" as nav_item_link %}
{% include "framework/header/nav-collapse-icon.html" with fa_icon="fa-solid fa-check-double" url=nav_item_link title=nav_item_title icon_on_mobile=True %}
{% endblock header_nav_collapse_right %}
```
This template takes care of the mobile responsiveness and the styling. In mobile view,
the icon will be changed to a text link. The icon will be placed in front of the text
link if `icon_on_mobile` is set to `True`.
#### Icon Parameters
- `fa_icon`: The FontAwesome icon class to use, e.g. `fa-solid fa-check-double`.
- `url`: The URL the icon should point to.
- `title`: The title of the icon (used as tooltip).
- `icon_on_mobile`: (Optional) Boolean to indicate if the icon should be shown on mobile devices in front of the text link. Default is `False`.

View File

@ -69,7 +69,7 @@ Next, we need to install Python and related development tools.
Use the following command to install Python 3 with all required libraries with the default version: Use the following command to install Python 3 with all required libraries with the default version:
```shell ```shell
sudo apt-get install python3 python3-dev python3-venv python3-setuptools python3-pip python-pip sudo apt-get install python3 python3-dev python3-venv python3-setuptools python3-pip
``` ```
### Install redis and other tools ### Install redis and other tools
@ -89,7 +89,7 @@ sudo redis-server --daemonize yes
Install MySQL and required libraries with the following command: Install MySQL and required libraries with the following command:
```shell ```shell
sudo apt-get install mariadb-server mariadb-client libmariadbclient-dev sudo apt-get install mariadb-server mariadb-client libmariadb-dev-compat libmariadb-dev
``` ```
Start the mysql server Start the mysql server

View File

@ -40,7 +40,7 @@ dynamic = [
"version", "version",
] ]
dependencies = [ dependencies = [
"bcrypt", "bcrypt<5",
"beautifulsoup4", "beautifulsoup4",
"celery>=5.2,<6", "celery>=5.2,<6",
"celery-once>=3.0.1", "celery-once>=3.0.1",

View File

@ -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.worker_prefetch_multiplier = 1 # only prefetch single tasks at a time on the workers so that prio tasks happen
app.conf.ONCE = { app.conf.ONCE = {
'backend': 'allianceauth.services.tasks.DjangoBackend', 'backend': 'allianceauth.services.celery_once.backends.DjangoBackend',
'settings': {} 'settings': {}
} }