mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2026-02-04 14:16:21 +01:00
Compare commits
236 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
055077fa77 | ||
|
|
f342ccbc6a | ||
|
|
37ffd0a1ac | ||
|
|
a1f705381e | ||
|
|
c0970ad4fa | ||
|
|
3818d0c6d1 | ||
|
|
95411c79cb | ||
|
|
eeccbbacfc | ||
|
|
f6c4180502 | ||
|
|
f4d3d6c0b1 | ||
|
|
e9d2d11297 | ||
|
|
96204b29e8 | ||
|
|
47842c1243 | ||
|
|
9b494106bc | ||
|
|
d51e730a7f | ||
|
|
363909c0c4 | ||
|
|
82273f68fe | ||
|
|
12fa38b446 | ||
|
|
c26af593ff | ||
|
|
8e9a53c494 | ||
|
|
5559ce5fbb | ||
|
|
faa529a55b | ||
|
|
4ccfe20c14 | ||
|
|
960c9625fe | ||
|
|
7b92d103d6 | ||
|
|
c1e2449084 | ||
|
|
3acb651650 | ||
|
|
2de57b334b | ||
|
|
0498f5bb1b | ||
|
|
929485a8f9 | ||
|
|
28cb62f373 | ||
|
|
02214b74d0 | ||
|
|
f497c18e5b | ||
|
|
cb57d922e6 | ||
|
|
805d138b09 | ||
|
|
09a583fb1d | ||
|
|
146c4c8d94 | ||
|
|
c2ae680f72 | ||
|
|
b5ad1c8a1a | ||
|
|
8be2760fc4 | ||
|
|
f047943eb7 | ||
|
|
43906f41b3 | ||
|
|
a18ec98877 | ||
|
|
14163d2c0c | ||
|
|
81d9c41cf6 | ||
|
|
58f5a5b41d | ||
|
|
6363bb706a | ||
|
|
baf3be4cb2 | ||
|
|
e69444fe79 | ||
|
|
7483fcb876 | ||
|
|
a57d55504d | ||
|
|
affb30e9f4 | ||
|
|
588cb1b7ca | ||
|
|
a609faa91b | ||
|
|
856e939c21 | ||
|
|
1b6cf98885 | ||
|
|
92c2af9975 | ||
|
|
5ef70bb031 | ||
|
|
60998bffc2 | ||
|
|
a5971314f5 | ||
|
|
a03c766840 | ||
|
|
ad47ff2c54 | ||
|
|
3efdb8f12b | ||
|
|
823fc82d19 | ||
|
|
a93e510895 | ||
|
|
d99f5858d8 | ||
|
|
4578ecf21d | ||
|
|
b737504d52 | ||
|
|
c6b6443901 | ||
|
|
f51523dc07 | ||
|
|
bd4dd60c98 | ||
|
|
a4ea48e14e | ||
|
|
646d3f5408 | ||
|
|
0f057ffa84 | ||
|
|
7033406ba6 | ||
|
|
6b395ca1d4 | ||
|
|
795a7e006f | ||
|
|
2a894cd62c | ||
|
|
9ada26e849 | ||
|
|
7120b3956c | ||
|
|
4da67cfaf6 | ||
|
|
0a940810bd | ||
|
|
a868438492 | ||
|
|
dc1ed8c570 | ||
|
|
8489f204dd | ||
|
|
1478588016 | ||
|
|
a16eb4b7f7 | ||
|
|
292fb7b29d | ||
|
|
c6890dd2c6 | ||
|
|
702564d15e | ||
|
|
cef2e86ea1 | ||
|
|
50681b023b | ||
|
|
2822775fb8 | ||
|
|
ef7c8be7b5 | ||
|
|
d639617eba | ||
|
|
2125192f72 | ||
|
|
8d63801b00 | ||
|
|
e053fb7d96 | ||
|
|
ae7ed5c297 | ||
|
|
d624ba4427 | ||
|
|
164cd4fbb2 | ||
|
|
94b52c850e | ||
|
|
4d19ceb388 | ||
|
|
9a64728311 | ||
|
|
dcc0c78daf | ||
|
|
5507c30af4 | ||
|
|
66b97835d4 | ||
|
|
fd66a7cb20 | ||
|
|
c0f0f8db73 | ||
|
|
4210b2eabc | ||
|
|
225e68647e | ||
|
|
7e2f864ebf | ||
|
|
f2384ba45b | ||
|
|
c5918b9b3c | ||
|
|
ffedc4103d | ||
|
|
0467b23a1a | ||
|
|
cda5ce739f | ||
|
|
e5c8426ea3 | ||
|
|
b2bd489ddc | ||
|
|
6397cf358a | ||
|
|
90fcc4a811 | ||
|
|
ec7472fe22 | ||
|
|
5148b1914d | ||
|
|
2e0716f5ae | ||
|
|
1fb091acb2 | ||
|
|
07c62ed32a | ||
|
|
660fe79d08 | ||
|
|
42ee06470c | ||
|
|
69aaa9652f | ||
|
|
1ccfff50e5 | ||
|
|
57a39557fd | ||
|
|
c36dea08e3 | ||
|
|
d3acd821b7 | ||
|
|
0a17427169 | ||
|
|
ce8935e621 | ||
|
|
efff946a56 | ||
|
|
1dea92ed76 | ||
|
|
3f54d49d8b | ||
|
|
77da6928b2 | ||
|
|
bd17b95cac | ||
|
|
4ed1c5b7c4 | ||
|
|
271fd8e8c4 | ||
|
|
9b4321281a | ||
|
|
052c35c8e5 | ||
|
|
0fcb517b0b | ||
|
|
dcfddf0add | ||
|
|
4a4258d0e6 | ||
|
|
dd15a221aa | ||
|
|
737e02293a | ||
|
|
c34efebacf | ||
|
|
4602097399 | ||
|
|
7051e06564 | ||
|
|
9767ce79d8 | ||
|
|
0c090f1486 | ||
|
|
618ee81f9b | ||
|
|
98efb9f887 | ||
|
|
cbe6c821cc | ||
|
|
de9d2b39a6 | ||
|
|
0d5f22288b | ||
|
|
e0d76dc268 | ||
|
|
ecc9e68330 | ||
|
|
710149ec21 | ||
|
|
3c2c137dad | ||
|
|
a8271c4189 | ||
|
|
3315ae7778 | ||
|
|
d2f048f8fe | ||
|
|
0fe2855faa | ||
|
|
79a1fa3d7c | ||
|
|
96fe88d5c7 | ||
|
|
59391ad3c5 | ||
|
|
94e9c08422 | ||
|
|
acff3695bc | ||
|
|
43ec8514aa | ||
|
|
4c629b193f | ||
|
|
c651da4011 | ||
|
|
da382cffd1 | ||
|
|
4ecfc3afd8 | ||
|
|
4eb7dbbe62 | ||
|
|
c96ba65296 | ||
|
|
ff2f60f7f3 | ||
|
|
3000545c98 | ||
|
|
f3ad092ef2 | ||
|
|
a012e7df2f | ||
|
|
1fa77412c0 | ||
|
|
e56caeb22b | ||
|
|
ceb07ebc67 | ||
|
|
237075d45c | ||
|
|
7099b1946d | ||
|
|
e416ab8ff2 | ||
|
|
2802ed03a5 | ||
|
|
4af73c76fe | ||
|
|
b6149979aa | ||
|
|
cb20288427 | ||
|
|
db6f4c91dc | ||
|
|
57ac7a5277 | ||
|
|
136438f9c2 | ||
|
|
e2be8b3440 | ||
|
|
04f3473ef3 | ||
|
|
255cb0da8d | ||
|
|
069352fb0f | ||
|
|
66e8ddb684 | ||
|
|
179c26975c | ||
|
|
e17f6e799b | ||
|
|
7cd8294104 | ||
|
|
ede5540335 | ||
|
|
747279b773 | ||
|
|
44f8b1c477 | ||
|
|
7c6ebd9bf6 | ||
|
|
430469b708 | ||
|
|
efbb3cee31 | ||
|
|
21094ed4dd | ||
|
|
5f326efc7e | ||
|
|
b6e34ace35 | ||
|
|
fe4a8965e3 | ||
|
|
23371c233d | ||
|
|
7a3bbf0d7f | ||
|
|
89a1bec9c1 | ||
|
|
1c1e70619a | ||
|
|
634e7357be | ||
|
|
08dc88da1a | ||
|
|
3d206e445c | ||
|
|
64686cdad1 | ||
|
|
d7fe09bdf1 | ||
|
|
6da50da92f | ||
|
|
51e4dd986f | ||
|
|
bee6522182 | ||
|
|
1711a9dd33 | ||
|
|
3914626379 | ||
|
|
df276cb32d | ||
|
|
daad7d8b10 | ||
|
|
3bf5bc0fe3 | ||
|
|
96abae553a | ||
|
|
f9cbfb1562 | ||
|
|
8eaa94e179 | ||
|
|
4f876b648b | ||
|
|
cd738137c0 |
@@ -25,7 +25,7 @@ before_script:
|
||||
pre-commit-check:
|
||||
<<: *only-default
|
||||
stage: pre-commit
|
||||
image: python:3.11-bullseye
|
||||
image: python:3.11-bookworm
|
||||
# variables:
|
||||
# PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
|
||||
# cache:
|
||||
@@ -53,7 +53,7 @@ secret_detection:
|
||||
|
||||
test-3.8-core:
|
||||
<<: *only-default
|
||||
image: python:3.8-bullseye
|
||||
image: python:3.8-bookworm
|
||||
script:
|
||||
- tox -e py38-core
|
||||
artifacts:
|
||||
@@ -65,7 +65,7 @@ test-3.8-core:
|
||||
|
||||
test-3.9-core:
|
||||
<<: *only-default
|
||||
image: python:3.9-bullseye
|
||||
image: python:3.9-bookworm
|
||||
script:
|
||||
- tox -e py39-core
|
||||
artifacts:
|
||||
@@ -77,7 +77,7 @@ test-3.9-core:
|
||||
|
||||
test-3.10-core:
|
||||
<<: *only-default
|
||||
image: python:3.10-bullseye
|
||||
image: python:3.10-bookworm
|
||||
script:
|
||||
- tox -e py310-core
|
||||
artifacts:
|
||||
@@ -89,7 +89,7 @@ test-3.10-core:
|
||||
|
||||
test-3.11-core:
|
||||
<<: *only-default
|
||||
image: python:3.11-bullseye
|
||||
image: python:3.11-bookworm
|
||||
script:
|
||||
- tox -e py311-core
|
||||
artifacts:
|
||||
@@ -101,7 +101,7 @@ test-3.11-core:
|
||||
|
||||
test-3.12-core:
|
||||
<<: *only-default
|
||||
image: python:3.12-rc-bullseye
|
||||
image: python:3.12-bookworm
|
||||
script:
|
||||
- tox -e py312-core
|
||||
artifacts:
|
||||
@@ -113,7 +113,7 @@ test-3.12-core:
|
||||
|
||||
test-3.8-all:
|
||||
<<: *only-default
|
||||
image: python:3.8-bullseye
|
||||
image: python:3.8-bookworm
|
||||
script:
|
||||
- tox -e py38-all
|
||||
artifacts:
|
||||
@@ -125,7 +125,7 @@ test-3.8-all:
|
||||
|
||||
test-3.9-all:
|
||||
<<: *only-default
|
||||
image: python:3.9-bullseye
|
||||
image: python:3.9-bookworm
|
||||
script:
|
||||
- tox -e py39-all
|
||||
artifacts:
|
||||
@@ -137,7 +137,7 @@ test-3.9-all:
|
||||
|
||||
test-3.10-all:
|
||||
<<: *only-default
|
||||
image: python:3.10-bullseye
|
||||
image: python:3.10-bookworm
|
||||
script:
|
||||
- tox -e py310-all
|
||||
artifacts:
|
||||
@@ -149,7 +149,7 @@ test-3.10-all:
|
||||
|
||||
test-3.11-all:
|
||||
<<: *only-default
|
||||
image: python:3.11-bullseye
|
||||
image: python:3.11-bookworm
|
||||
script:
|
||||
- tox -e py311-all
|
||||
artifacts:
|
||||
@@ -162,7 +162,7 @@ test-3.11-all:
|
||||
|
||||
test-3.12-all:
|
||||
<<: *only-default
|
||||
image: python:3.12-rc-bullseye
|
||||
image: python:3.12-bookworm
|
||||
script:
|
||||
- tox -e py312-all
|
||||
artifacts:
|
||||
@@ -174,7 +174,7 @@ test-3.12-all:
|
||||
|
||||
build-test:
|
||||
stage: test
|
||||
image: python:3.11-bullseye
|
||||
image: python:3.11-bookworm
|
||||
|
||||
before_script:
|
||||
- python -m pip install --upgrade pip
|
||||
@@ -193,13 +193,13 @@ build-test:
|
||||
|
||||
test-docs:
|
||||
<<: *only-default
|
||||
image: python:3.11-bullseye
|
||||
image: python:3.11-bookworm
|
||||
script:
|
||||
- tox -e docs
|
||||
|
||||
deploy_production:
|
||||
stage: deploy
|
||||
image: python:3.11-bullseye
|
||||
image: python:3.11-bookworm
|
||||
|
||||
before_script:
|
||||
- python -m pip install --upgrade pip
|
||||
|
||||
@@ -3,22 +3,41 @@
|
||||
# Update this file:
|
||||
# pre-commit autoupdate
|
||||
|
||||
# Set the default language versions for the hooks
|
||||
default_language_version:
|
||||
python: python3 # Force all Python hooks to use Python 3
|
||||
node: 22.12.0 # Force all Node hooks to use Node 22.12.0
|
||||
|
||||
# Globally exclude files
|
||||
# https://pre-commit.com/#top_level-exclude
|
||||
exclude: |
|
||||
(?x)(
|
||||
LICENSE|
|
||||
allianceauth\/static\/allianceauth\/css\/themes\/bootstrap-locals.less|
|
||||
\.min\.css|
|
||||
\.min\.js|
|
||||
\.po|
|
||||
\.mo|
|
||||
swagger\.json|
|
||||
static/(.*)/libs/
|
||||
)
|
||||
|
||||
repos:
|
||||
# Code Upgrades
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.15.2
|
||||
rev: v3.19.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py38-plus]
|
||||
- repo: https://github.com/adamchainz/django-upgrade
|
||||
rev: 1.17.0
|
||||
rev: 1.22.2
|
||||
hooks:
|
||||
- id: django-upgrade
|
||||
args: [--target-version=4.2]
|
||||
|
||||
# Formatting
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.6.0
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
# Identify invalid files
|
||||
- id: check-ast
|
||||
@@ -33,9 +52,9 @@ repos:
|
||||
- id: detect-private-key
|
||||
- id: check-case-conflict
|
||||
# Python checks
|
||||
# - id: check-docstring-first
|
||||
# - id: check-docstring-first
|
||||
- id: debug-statements
|
||||
# - id: requirements-txt-fixer
|
||||
# - id: requirements-txt-fixer
|
||||
- id: fix-encoding-pragma
|
||||
args: [--remove]
|
||||
- id: fix-byte-order-marker
|
||||
@@ -44,40 +63,22 @@ repos:
|
||||
args: [--fix=lf]
|
||||
- id: trailing-whitespace
|
||||
args: [--markdown-linebreak-ext=md]
|
||||
exclude: |
|
||||
(?x)(
|
||||
\.min\.css|
|
||||
\.min\.js|
|
||||
\.po|
|
||||
\.mo|
|
||||
swagger\.json
|
||||
)
|
||||
- id: check-executables-have-shebangs
|
||||
- id: end-of-file-fixer
|
||||
exclude: |
|
||||
(?x)(
|
||||
\.min\.css|
|
||||
\.min\.js|
|
||||
\.po|
|
||||
\.mo|
|
||||
swagger\.json
|
||||
)
|
||||
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
|
||||
rev: 2.7.3
|
||||
rev: 3.2.0
|
||||
hooks:
|
||||
- id: editorconfig-checker
|
||||
exclude: |
|
||||
(?x)(
|
||||
LICENSE|
|
||||
allianceauth\/static\/allianceauth\/css\/themes\/bootstrap-locals.less|
|
||||
\.po|
|
||||
\.mo|
|
||||
swagger\.json
|
||||
)
|
||||
|
||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||
rev: v0.44.0
|
||||
hooks:
|
||||
- id: markdownlint
|
||||
language: node
|
||||
args:
|
||||
- --disable=MD013
|
||||
# Infrastructure
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: 2.0.3
|
||||
rev: v2.5.0
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
name: pyproject.toml formatter
|
||||
@@ -85,9 +86,9 @@ repos:
|
||||
args:
|
||||
- --indent=4
|
||||
additional_dependencies:
|
||||
- tox==4.15.0 # https://github.com/tox-dev/tox/releases/latest
|
||||
- tox==4.24.1 # https://github.com/tox-dev/tox/releases/latest
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.17
|
||||
rev: v0.23
|
||||
hooks:
|
||||
- id: validate-pyproject
|
||||
name: Validate pyproject.toml
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
[](https://pypi.org/project/allianceauth/)
|
||||
[](https://pypi.org/project/allianceauth/)
|
||||
[](https://gitlab.com/allianceauth/allianceauth/commits/master)
|
||||
[](http://allianceauth.readthedocs.io/?badge=latest)
|
||||
[](https://allianceauth.readthedocs.io/?badge=latest)
|
||||
[](https://gitlab.com/allianceauth/allianceauth/commits/master)
|
||||
[](https://discord.gg/fjnHAmk)
|
||||
|
||||
@@ -14,7 +14,7 @@ An auth system for EVE Online to help in-game organizations manage online servic
|
||||
## Content
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Documentation](http://allianceauth.rtfd.io)
|
||||
- [Documentation](https://allianceauth.rtfd.io)
|
||||
- [Support](#support)
|
||||
- [Release Notes](https://gitlab.com/allianceauth/allianceauth/-/releases)
|
||||
- [Developer Team](#development-team)
|
||||
@@ -38,7 +38,7 @@ Main features:
|
||||
|
||||
- English :flag_gb:, Chinese :flag_cn:, German :flag_de:, Spanish :flag_es:, Korean :flag_kr:, Russian :flag_ru:, Italian :flag_it:, French :flag_fr:, Japanese :flag_jp: and Ukrainian :flag_ua: Localization
|
||||
|
||||
For further details about AA - including an installation guide and a full list of included services and plugin apps - please see the [official documentation](http://allianceauth.rtfd.io).
|
||||
For further details about AA - including an installation guide and a full list of included services and plugin apps - please see the [official documentation](https://allianceauth.rtfd.io).
|
||||
|
||||
## Screenshot
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ manage online service access.
|
||||
# This will make sure the app is always imported when
|
||||
# Django starts so that shared_task will use this app.
|
||||
|
||||
__version__ = '4.0.2'
|
||||
__version__ = '4.6.4'
|
||||
__title__ = 'Alliance Auth'
|
||||
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
|
||||
NAME = f'{__title__} v{__version__}'
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import AnalyticsIdentifier, AnalyticsTokens
|
||||
from solo.admin import SingletonModelAdmin
|
||||
|
||||
|
||||
@admin.register(AnalyticsIdentifier)
|
||||
class AnalyticsIdentifierAdmin(admin.ModelAdmin):
|
||||
class AnalyticsIdentifierAdmin(SingletonModelAdmin):
|
||||
search_fields = ['identifier', ]
|
||||
list_display = ('identifier',)
|
||||
list_display = ['identifier', ]
|
||||
|
||||
|
||||
@admin.register(AnalyticsTokens)
|
||||
class AnalyticsTokensAdmin(admin.ModelAdmin):
|
||||
search_fields = ['name', ]
|
||||
list_display = ('name', 'type',)
|
||||
list_display = ['name', 'type', ]
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 4.2.16 on 2024-12-11 02:17
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('analytics', '0009_remove_analyticstokens_ignore_paths_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='analyticsidentifier',
|
||||
options={'verbose_name': 'Analytics Identifier'},
|
||||
),
|
||||
]
|
||||
@@ -1,23 +1,19 @@
|
||||
from typing import Literal
|
||||
from django.db import models
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from solo.models import SingletonModel
|
||||
from uuid import uuid4
|
||||
|
||||
|
||||
class AnalyticsIdentifier(models.Model):
|
||||
class AnalyticsIdentifier(SingletonModel):
|
||||
|
||||
identifier = models.UUIDField(
|
||||
default=uuid4,
|
||||
editable=False)
|
||||
identifier = models.UUIDField(default=uuid4, editable=False)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.pk and AnalyticsIdentifier.objects.exists():
|
||||
# Force a single object
|
||||
raise ValidationError('There is can be only one \
|
||||
AnalyticsIdentifier instance')
|
||||
self.pk = self.id = 1 # If this happens to be deleted and recreated, force it to be 1
|
||||
return super().save(*args, **kwargs)
|
||||
def __str__(self) -> Literal['Analytics Identifier']:
|
||||
return "Analytics Identifier"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Analytics Identifier"
|
||||
|
||||
|
||||
class AnalyticsTokens(models.Model):
|
||||
|
||||
@@ -5,6 +5,7 @@ from django.apps import apps
|
||||
from celery import shared_task
|
||||
from .models import AnalyticsTokens, AnalyticsIdentifier
|
||||
from .utils import (
|
||||
existence_baremetal_or_docker,
|
||||
install_stat_addons,
|
||||
install_stat_tokens,
|
||||
install_stat_users)
|
||||
@@ -67,8 +68,8 @@ def analytics_event(namespace: str,
|
||||
value=value).apply_async(priority=9)
|
||||
|
||||
|
||||
@shared_task()
|
||||
def analytics_daily_stats():
|
||||
@shared_task
|
||||
def analytics_daily_stats() -> None:
|
||||
"""Celery Task: Do not call directly
|
||||
|
||||
Gathers a series of daily statistics
|
||||
@@ -77,6 +78,7 @@ def analytics_daily_stats():
|
||||
users = install_stat_users()
|
||||
tokens = install_stat_tokens()
|
||||
addons = install_stat_addons()
|
||||
existence_type = existence_baremetal_or_docker()
|
||||
logger.debug("Running Daily Analytics Upload")
|
||||
|
||||
analytics_event(namespace='allianceauth.analytics',
|
||||
@@ -84,6 +86,11 @@ def analytics_daily_stats():
|
||||
label='existence',
|
||||
value=1,
|
||||
event_type='Stats')
|
||||
analytics_event(namespace='allianceauth.analytics',
|
||||
task='send_install_stats',
|
||||
label=existence_type,
|
||||
value=1,
|
||||
event_type='Stats')
|
||||
analytics_event(namespace='allianceauth.analytics',
|
||||
task='send_install_stats',
|
||||
label='users',
|
||||
@@ -99,16 +106,42 @@ def analytics_daily_stats():
|
||||
label='addons',
|
||||
value=addons,
|
||||
event_type='Stats')
|
||||
|
||||
for appconfig in apps.get_app_configs():
|
||||
analytics_event(namespace='allianceauth.analytics',
|
||||
task='send_extension_stats',
|
||||
label=appconfig.label,
|
||||
value=1,
|
||||
event_type='Stats')
|
||||
if appconfig.label in [
|
||||
"django_celery_beat",
|
||||
"bootstrapform",
|
||||
"messages",
|
||||
"sessions",
|
||||
"auth",
|
||||
"staticfiles",
|
||||
"users",
|
||||
"addons",
|
||||
"admin",
|
||||
"humanize",
|
||||
"contenttypes",
|
||||
"sortedm2m",
|
||||
"django_bootstrap5",
|
||||
"tokens",
|
||||
"authentication",
|
||||
"services",
|
||||
"framework",
|
||||
"notifications"
|
||||
"eveonline",
|
||||
"navhelper",
|
||||
"analytics",
|
||||
"menu",
|
||||
"theme"
|
||||
]:
|
||||
pass
|
||||
else:
|
||||
analytics_event(namespace='allianceauth.analytics',
|
||||
task='send_extension_stats',
|
||||
label=appconfig.label,
|
||||
value=1,
|
||||
event_type='Stats')
|
||||
|
||||
|
||||
@shared_task()
|
||||
@shared_task
|
||||
def send_ga_tracking_celery_event(
|
||||
measurement_id: str,
|
||||
secret: str,
|
||||
@@ -138,7 +171,7 @@ def send_ga_tracking_celery_event(
|
||||
}
|
||||
|
||||
payload = {
|
||||
'client_id': AnalyticsIdentifier.objects.get(id=1).identifier.hex,
|
||||
'client_id': AnalyticsIdentifier.get_solo().identifier.hex,
|
||||
"user_properties": {
|
||||
"allianceauth_version": {
|
||||
"value": __version__
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
from allianceauth.analytics.models import AnalyticsIdentifier
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from django.test.testcases import TestCase
|
||||
|
||||
from uuid import UUID, uuid4
|
||||
from uuid import uuid4
|
||||
|
||||
|
||||
# Identifiers
|
||||
@@ -14,14 +13,4 @@ uuid_2 = "7aa6bd70701f44729af5e3095ff4b55c"
|
||||
class TestAnalyticsIdentifier(TestCase):
|
||||
|
||||
def test_identifier_random(self):
|
||||
self.assertNotEqual(AnalyticsIdentifier.objects.get(), uuid4)
|
||||
|
||||
def test_identifier_singular(self):
|
||||
AnalyticsIdentifier.objects.all().delete()
|
||||
AnalyticsIdentifier.objects.create(identifier=uuid_1)
|
||||
# Yeah i have multiple asserts here, they all do the same thing
|
||||
with self.assertRaises(ValidationError):
|
||||
AnalyticsIdentifier.objects.create(identifier=uuid_2)
|
||||
self.assertEqual(AnalyticsIdentifier.objects.count(), 1)
|
||||
self.assertEqual(AnalyticsIdentifier.objects.get(
|
||||
pk=1).identifier, UUID(uuid_1))
|
||||
self.assertNotEqual(AnalyticsIdentifier.get_solo(), uuid4)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import os
|
||||
from django.apps import apps
|
||||
from allianceauth.authentication.models import User
|
||||
from esi.models import Token
|
||||
@@ -34,3 +35,16 @@ def install_stat_addons() -> int:
|
||||
The Number of Installed Apps"""
|
||||
addons = len(list(apps.get_app_configs()))
|
||||
return addons
|
||||
|
||||
|
||||
def existence_baremetal_or_docker() -> str:
|
||||
"""Checks the Installation Type of an install
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
existence_baremetal or existence_docker"""
|
||||
docker_tag = os.getenv('AA_DOCKER_TAG')
|
||||
if docker_tag:
|
||||
return "existence_docker"
|
||||
return "existence_baremetal"
|
||||
|
||||
@@ -1,30 +1,8 @@
|
||||
from django.apps import AppConfig
|
||||
from django.core.checks import Warning, Error, register
|
||||
|
||||
|
||||
class AllianceAuthConfig(AppConfig):
|
||||
name = 'allianceauth'
|
||||
|
||||
|
||||
@register()
|
||||
def check_settings(app_configs, **kwargs):
|
||||
from django.conf import settings
|
||||
|
||||
errors = []
|
||||
if hasattr(settings, "SITE_URL"):
|
||||
if settings.SITE_URL[-1] == "/":
|
||||
errors.append(Warning(
|
||||
"'SITE_URL' Has a trailing slash. This may lead to incorrect links being generated by Auth."))
|
||||
else:
|
||||
errors.append(Error(
|
||||
"No 'SITE_URL' found is settings. This may lead to incorrect links being generated by Auth or Errors in 3rd party modules."))
|
||||
if hasattr(settings, "CSRF_TRUSTED_ORIGINS"):
|
||||
if hasattr(settings, "SITE_URL"):
|
||||
if settings.SITE_URL not in settings.CSRF_TRUSTED_ORIGINS:
|
||||
errors.append(Warning(
|
||||
"'SITE_URL' not found in 'CSRF_TRUSTED_ORIGINS'. Auth may not load pages correctly until this is rectified."))
|
||||
else:
|
||||
errors.append(Error(
|
||||
"No 'CSRF_TRUSTED_ORIGINS' found is settings, Auth may not load pages correctly until this is rectified"))
|
||||
|
||||
return errors
|
||||
def ready(self) -> None:
|
||||
import allianceauth.checks # noqa
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2 on 2024-09-13 09:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentication', '0023_alter_userprofile_language'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='userprofile',
|
||||
name='language',
|
||||
field=models.CharField(blank=True, choices=[('en', 'English'), ('cs-cz', 'Czech'), ('de', 'German'), ('es', 'Spanish'), ('it-it', 'Italian'), ('ja', 'Japanese'), ('ko-kr', 'Korean'), ('fr-fr', 'French'), ('ru', 'Russian'), ('nl-nl', 'Dutch'), ('pl-pl', 'Polish'), ('uk', 'Ukrainian'), ('zh-hans', 'Simplified Chinese')], default='', max_length=10, verbose_name='Language'),
|
||||
),
|
||||
]
|
||||
@@ -67,18 +67,20 @@ class UserProfile(models.Model):
|
||||
"""
|
||||
Choices for UserProfile.language
|
||||
"""
|
||||
|
||||
# Sorted by Language Code alphabetical order + English at top
|
||||
ENGLISH = 'en', _('English')
|
||||
CZECH = 'cs-cz', _("Czech") # Not yet at 50% translated
|
||||
GERMAN = 'de', _('German')
|
||||
SPANISH = 'es', _('Spanish')
|
||||
CHINESE = 'zh-hans', _('Chinese Simplified')
|
||||
RUSSIAN = 'ru', _('Russian')
|
||||
KOREAN = 'ko', _('Korean')
|
||||
FRENCH = 'fr', _('French')
|
||||
ITALIAN = 'it-it', _('Italian')
|
||||
JAPANESE = 'ja', _('Japanese')
|
||||
ITALIAN = 'it', _('Italian')
|
||||
KOREAN = 'ko-kr', _('Korean')
|
||||
FRENCH = 'fr-fr', _('French')
|
||||
RUSSIAN = 'ru', _('Russian')
|
||||
DUTCH = 'nl-nl', _("Dutch")
|
||||
POLISH = 'pl-pl', _("Polish")
|
||||
UKRAINIAN = 'uk', _('Ukrainian')
|
||||
POLISH = 'pl', _("Polish")
|
||||
CHINESE = 'zh-hans', _('Simplified Chinese')
|
||||
|
||||
user = models.OneToOneField(
|
||||
User,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
{% translate "Dashboard" %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-around align-self-center flex-wrap">
|
||||
<div class="row">
|
||||
{% for dash in views %}
|
||||
{{ dash | safe }}
|
||||
{% endfor %}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
{% load i18n %}
|
||||
<div id="aa-dashboard-panel-characters" class="col-12 col-xl-8 align-self-stretch p-2 ps-0 pe-0 ps-xl-0 pe-xl-2">
|
||||
<div class="card">
|
||||
<div id="aa-dashboard-panel-characters" class="col-12 col-xl-8 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<h4 class="ms-auto me-auto">
|
||||
{% translate "Characters" %}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% translate "Characters" as widget_title %}
|
||||
{% include "framework/dashboard/widget-title.html" with title=widget_title %}
|
||||
|
||||
<div>
|
||||
<div style="height: 300px; overflow-y:auto;">
|
||||
<div class="d-flex">
|
||||
<a href="{% url 'authentication:add_character' %}" class="btn btn-primary flex-fill m-1" title="{% translate 'Add Character' %}">
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
{% load i18n %}
|
||||
<div id="aa-dashboard-panel-membership" class="col-12 col-xl-4 align-self-stretch py-2 ps-xl-2">
|
||||
<div id="aa-dashboard-panel-membership" class="col-12 col-xl-4 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title text-center">{% translate "Membership" %}</h4>
|
||||
<div class="card-body">
|
||||
{% translate "Membership" as widget_title %}
|
||||
{% include "framework/dashboard/widget-title.html" with title=widget_title %}
|
||||
|
||||
<div>
|
||||
<div style="height: 300px; overflow-y:auto;">
|
||||
<h5 class="text-center">{% translate "State:" %} {{ request.user.profile.state }}</h5>
|
||||
<table class="table">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
|
||||
{% load aa_i18n %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}
|
||||
@@ -13,7 +14,7 @@
|
||||
{% block content %}
|
||||
<div>
|
||||
<p class="mb-3">
|
||||
{% translate "This page is a best attempt, but backups or database logs can still contain your tokens. Always revoke tokens on https://community.eveonline.com/support/third-party-applications/ where possible."|urlize %}
|
||||
{% translate "This page is a best attempt, but backups or database logs can still contain your tokens. Always revoke tokens on https://developers.eveonline.com/authorized-apps where possible."|urlize %}
|
||||
</p>
|
||||
|
||||
<table class="table w-100" id="table_tokens">
|
||||
@@ -50,20 +51,23 @@
|
||||
{% block extra_javascript %}
|
||||
{% include "bundles/datatables-js-bs5.html" %}
|
||||
|
||||
{% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
|
||||
|
||||
<script>
|
||||
$(document).ready(() => {
|
||||
let grp = 2;
|
||||
|
||||
const table = $('#table_tokens').DataTable({
|
||||
$('#table_tokens').DataTable({
|
||||
"language": {"url": '{{ DT_LANG_PATH }}'},
|
||||
'columnDefs': [{orderable: false, targets: [0, 1]}, {
|
||||
'visible': false,
|
||||
'targets': grp
|
||||
}],
|
||||
'order': [[grp, 'asc']],
|
||||
'drawCallback': function (settings) {
|
||||
var api = this.api();
|
||||
var rows = api.rows({page: 'current'}).nodes();
|
||||
var last = null;
|
||||
const api = this.api();
|
||||
const rows = api.rows({page: 'current'}).nodes();
|
||||
let last = null;
|
||||
api.column(grp, {page: 'current'})
|
||||
.data()
|
||||
.each((group, i) => {
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
{% csrf_token %}
|
||||
|
||||
<select class="form-select" onchange="this.form.submit()" class="form-control" id="lang-select" name="language">
|
||||
{% get_language_info_list for LANGUAGES as languages %}
|
||||
{% get_available_languages as LANGUAGES %}
|
||||
|
||||
{% for language in languages %}
|
||||
<option lang="{{ language.code }}" value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
|
||||
{{ language.name_local|capfirst }} ({{ language.code }})
|
||||
{% for lang_code, lang_name in LANGUAGES %}
|
||||
<option lang="{{ lang_code }}" value="{{ lang_code }}"{% if lang_code == LANGUAGE_CODE %} selected{% endif %}>
|
||||
{{ lang_code|language_name_local|capfirst }} ({{ lang_code }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</p>
|
||||
|
||||
<p class="text-center">
|
||||
<a class="text-reset" href="https://community.eveonline.com/support/third-party-applications/" target="_blank" rel="noopener noreferrer">
|
||||
<a class="text-reset" href="https://developers.eveonline.com/authorized-apps" target="_blank" rel="noopener noreferrer">
|
||||
{% translate "Manage ESI Applications" %}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
562
allianceauth/checks.py
Normal file
562
allianceauth/checks.py
Normal file
@@ -0,0 +1,562 @@
|
||||
"""
|
||||
Django system checks for Alliance Auth
|
||||
"""
|
||||
|
||||
from typing import List
|
||||
from django import db
|
||||
from django.core.checks import CheckMessage, Error, register, Warning
|
||||
from allianceauth.utils.cache import get_redis_client
|
||||
from django.utils import timezone
|
||||
from packaging.version import InvalidVersion, Version as Pep440Version
|
||||
from celery import current_app
|
||||
from django.conf import settings
|
||||
from sqlite3.dbapi2 import sqlite_version_info
|
||||
|
||||
"""
|
||||
A = System Packages
|
||||
B = Configuration
|
||||
"""
|
||||
|
||||
|
||||
@register()
|
||||
def django_settings(app_configs, **kwargs) -> List[CheckMessage]:
|
||||
"""
|
||||
Check that Django settings are correctly configured
|
||||
|
||||
:param app_configs:
|
||||
:type app_configs:
|
||||
:param kwargs:
|
||||
:type kwargs:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
errors: List[CheckMessage] = []
|
||||
|
||||
# Check for SITE_URL
|
||||
if hasattr(settings, "SITE_URL"):
|
||||
# Check if SITE_URL is empty
|
||||
if settings.SITE_URL == "":
|
||||
errors.append(
|
||||
Error(
|
||||
msg="'SITE_URL' is empty.",
|
||||
hint="Make sure to set 'SITE_URL' to the URL of your Auth instance. (Without trailing slash)",
|
||||
id="allianceauth.checks.B011",
|
||||
)
|
||||
)
|
||||
# Check if SITE_URL has a trailing slash
|
||||
elif settings.SITE_URL[-1] == "/":
|
||||
errors.append(
|
||||
Warning(
|
||||
msg="'SITE_URL' has a trailing slash. This may lead to incorrect links being generated by Auth.",
|
||||
hint="",
|
||||
id="allianceauth.checks.B005",
|
||||
)
|
||||
)
|
||||
# SITE_URL not found
|
||||
else:
|
||||
errors.append(
|
||||
Error(
|
||||
msg="No 'SITE_URL' found is settings. This may lead to incorrect links being generated by Auth or Errors in 3rd party modules.",
|
||||
hint="",
|
||||
id="allianceauth.checks.B006",
|
||||
)
|
||||
)
|
||||
|
||||
# Check for CSRF_TRUSTED_ORIGINS
|
||||
if hasattr(settings, "CSRF_TRUSTED_ORIGINS") and hasattr(settings, "SITE_URL"):
|
||||
# Check if SITE_URL is not in CSRF_TRUSTED_ORIGINS
|
||||
if settings.SITE_URL not in settings.CSRF_TRUSTED_ORIGINS:
|
||||
errors.append(
|
||||
Warning(
|
||||
msg="'SITE_URL' not found in 'CSRF_TRUSTED_ORIGINS'. Auth may not load pages correctly until this is rectified.",
|
||||
hint="",
|
||||
id="allianceauth.checks.B007",
|
||||
)
|
||||
)
|
||||
# CSRF_TRUSTED_ORIGINS not found
|
||||
else:
|
||||
errors.append(
|
||||
Error(
|
||||
msg="No 'CSRF_TRUSTED_ORIGINS' found is settings, Auth may not load pages correctly until this is rectified",
|
||||
hint="",
|
||||
id="allianceauth.checks.B008",
|
||||
)
|
||||
)
|
||||
|
||||
# Check for ESI_USER_CONTACT_EMAIL
|
||||
if hasattr(settings, "ESI_USER_CONTACT_EMAIL"):
|
||||
# Check if ESI_USER_CONTACT_EMAIL is empty
|
||||
if settings.ESI_USER_CONTACT_EMAIL == "":
|
||||
errors.append(
|
||||
Error(
|
||||
msg="'ESI_USER_CONTACT_EMAIL' is empty. A valid email is required as maintainer contact for CCP.",
|
||||
hint="",
|
||||
id="allianceauth.checks.B009",
|
||||
)
|
||||
)
|
||||
# ESI_USER_CONTACT_EMAIL not found
|
||||
else:
|
||||
errors.append(
|
||||
Error(
|
||||
msg="No 'ESI_USER_CONTACT_EMAIL' found is settings. A valid email is required as maintainer contact for CCP.",
|
||||
hint="",
|
||||
id="allianceauth.checks.B010",
|
||||
)
|
||||
)
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@register()
|
||||
def system_package_redis(app_configs, **kwargs) -> List[CheckMessage]:
|
||||
"""
|
||||
Check that Redis is a supported version
|
||||
|
||||
:param app_configs:
|
||||
:type app_configs:
|
||||
:param kwargs:
|
||||
:type kwargs:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
allianceauth_redis_install_link = "https://allianceauth.readthedocs.io/en/latest/installation/allianceauth.html#redis-and-other-tools"
|
||||
|
||||
errors: List[CheckMessage] = []
|
||||
|
||||
try:
|
||||
redis_version = Pep440Version(get_redis_client().info()["redis_version"])
|
||||
except InvalidVersion:
|
||||
errors.append(Warning("Unable to confirm Redis Version"))
|
||||
|
||||
return errors
|
||||
|
||||
if (
|
||||
redis_version.major == 7
|
||||
and redis_version.minor == 2
|
||||
and timezone.now()
|
||||
> timezone.datetime(year=2025, month=8, day=31, tzinfo=timezone.utc)
|
||||
):
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"Redis {redis_version.public} in Security Support only, Updating Suggested",
|
||||
hint=allianceauth_redis_install_link,
|
||||
id="allianceauth.checks.A001",
|
||||
)
|
||||
)
|
||||
elif redis_version.major == 7 and redis_version.minor == 0:
|
||||
errors.append(
|
||||
Warning(
|
||||
msg=f"Redis {redis_version.public} in Security Support only, Updating Suggested",
|
||||
hint=allianceauth_redis_install_link,
|
||||
id="allianceauth.checks.A002",
|
||||
)
|
||||
)
|
||||
elif redis_version.major == 6 and redis_version.minor == 2:
|
||||
errors.append(
|
||||
Warning(
|
||||
msg=f"Redis {redis_version.public} in Security Support only, Updating Suggested",
|
||||
hint=allianceauth_redis_install_link,
|
||||
id="allianceauth.checks.A018",
|
||||
)
|
||||
)
|
||||
elif redis_version.major in [6, 5]:
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"Redis {redis_version.public} EOL",
|
||||
hint=allianceauth_redis_install_link,
|
||||
id="allianceauth.checks.A003",
|
||||
)
|
||||
)
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@register()
|
||||
def system_package_mysql(app_configs, **kwargs) -> List[CheckMessage]:
|
||||
"""
|
||||
Check that MySQL is a supported version
|
||||
|
||||
:param app_configs:
|
||||
:type app_configs:
|
||||
:param kwargs:
|
||||
:type kwargs:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
mysql_quick_guide_link = "https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/"
|
||||
|
||||
errors: List[CheckMessage] = []
|
||||
|
||||
for connection in db.connections.all():
|
||||
if connection.vendor == "mysql":
|
||||
try:
|
||||
mysql_version = Pep440Version(
|
||||
".".join(str(i) for i in connection.mysql_version)
|
||||
)
|
||||
except InvalidVersion:
|
||||
errors.append(Warning("Unable to confirm MySQL Version"))
|
||||
|
||||
return errors
|
||||
|
||||
# MySQL 8
|
||||
if mysql_version.major == 8:
|
||||
if mysql_version.minor == 4 and timezone.now() > timezone.datetime(
|
||||
year=2032, month=4, day=30, tzinfo=timezone.utc
|
||||
):
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MySQL {mysql_version.public} EOL",
|
||||
hint=mysql_quick_guide_link,
|
||||
id="allianceauth.checks.A004",
|
||||
)
|
||||
)
|
||||
elif mysql_version.minor == 3:
|
||||
errors.append(
|
||||
Warning(
|
||||
msg=f"MySQL {mysql_version.public} Non LTS",
|
||||
hint=mysql_quick_guide_link,
|
||||
id="allianceauth.checks.A005",
|
||||
)
|
||||
)
|
||||
elif mysql_version.minor == 2:
|
||||
errors.append(
|
||||
Warning(
|
||||
msg=f"MySQL {mysql_version.public} Non LTS",
|
||||
hint=mysql_quick_guide_link,
|
||||
id="allianceauth.checks.A006",
|
||||
)
|
||||
)
|
||||
elif mysql_version.minor == 1:
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MySQL {mysql_version.public} EOL",
|
||||
hint=mysql_quick_guide_link,
|
||||
id="allianceauth.checks.A007",
|
||||
)
|
||||
)
|
||||
elif mysql_version.minor == 0 and timezone.now() > timezone.datetime(
|
||||
year=2026, month=4, day=30, tzinfo=timezone.utc
|
||||
):
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MySQL {mysql_version.public} EOL",
|
||||
hint=mysql_quick_guide_link,
|
||||
id="allianceauth.checks.A008",
|
||||
)
|
||||
)
|
||||
|
||||
# MySQL below 8
|
||||
# This will also catch Mariadb 5.x
|
||||
elif mysql_version.major < 8:
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MySQL or MariaDB {mysql_version.public} EOL",
|
||||
hint=mysql_quick_guide_link,
|
||||
id="allianceauth.checks.A009",
|
||||
)
|
||||
)
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@register()
|
||||
def system_package_mariadb(app_configs, **kwargs) -> List[CheckMessage]:
|
||||
"""
|
||||
Check that MariaDB is a supported version
|
||||
|
||||
:param app_configs:
|
||||
:type app_configs:
|
||||
:param kwargs:
|
||||
:type kwargs:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
mariadb_download_link = "https://mariadb.org/download/?t=repo-config"
|
||||
|
||||
errors: List[CheckMessage] = []
|
||||
|
||||
for connection in db.connections.all():
|
||||
# TODO: Find a way to determine MySQL vs. MariaDB
|
||||
if connection.vendor == "mysql":
|
||||
try:
|
||||
mariadb_version = Pep440Version(
|
||||
".".join(str(i) for i in connection.mysql_version)
|
||||
)
|
||||
except InvalidVersion:
|
||||
errors.append(Warning("Unable to confirm MariaDB Version"))
|
||||
|
||||
return errors
|
||||
|
||||
# MariaDB 11
|
||||
if mariadb_version.major == 11:
|
||||
if mariadb_version.minor == 4 and timezone.now() > timezone.datetime(
|
||||
year=2029, month=5, day=19, tzinfo=timezone.utc
|
||||
):
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MariaDB {mariadb_version.public} EOL",
|
||||
hint=mariadb_download_link,
|
||||
id="allianceauth.checks.A010",
|
||||
)
|
||||
)
|
||||
elif mariadb_version.minor == 2:
|
||||
errors.append(
|
||||
Warning(
|
||||
msg=f"MariaDB {mariadb_version.public} Non LTS",
|
||||
hint=mariadb_download_link,
|
||||
id="allianceauth.checks.A018",
|
||||
)
|
||||
)
|
||||
|
||||
if timezone.now() > timezone.datetime(
|
||||
year=2024, month=11, day=21, tzinfo=timezone.utc
|
||||
):
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MariaDB {mariadb_version.public} EOL",
|
||||
hint=mariadb_download_link,
|
||||
id="allianceauth.checks.A011",
|
||||
)
|
||||
)
|
||||
elif mariadb_version.minor == 1:
|
||||
errors.append(
|
||||
Warning(
|
||||
msg=f"MariaDB {mariadb_version.public} Non LTS",
|
||||
hint=mariadb_download_link,
|
||||
id="allianceauth.checks.A019",
|
||||
)
|
||||
)
|
||||
|
||||
if timezone.now() > timezone.datetime(
|
||||
year=2024, month=8, day=21, tzinfo=timezone.utc
|
||||
):
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MariaDB {mariadb_version.public} EOL",
|
||||
hint=mariadb_download_link,
|
||||
id="allianceauth.checks.A012",
|
||||
)
|
||||
)
|
||||
# Demote versions down here once EOL
|
||||
elif mariadb_version.minor in [0, 3]:
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MariaDB {mariadb_version.public} EOL",
|
||||
hint=mariadb_download_link,
|
||||
id="allianceauth.checks.A013",
|
||||
)
|
||||
)
|
||||
|
||||
# MariaDB 10
|
||||
elif mariadb_version.major == 10:
|
||||
if mariadb_version.minor == 11 and timezone.now() > timezone.datetime(
|
||||
year=2028, month=2, day=10, tzinfo=timezone.utc
|
||||
):
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MariaDB {mariadb_version.public} EOL",
|
||||
hint=mariadb_download_link,
|
||||
id="allianceauth.checks.A014",
|
||||
)
|
||||
)
|
||||
elif mariadb_version.minor == 6 and timezone.now() > timezone.datetime(
|
||||
year=2026, month=7, day=6, tzinfo=timezone.utc
|
||||
):
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MariaDB {mariadb_version.public} EOL",
|
||||
hint=mariadb_download_link,
|
||||
id="allianceauth.checks.A0015",
|
||||
)
|
||||
)
|
||||
elif mariadb_version.minor == 5 and timezone.now() > timezone.datetime(
|
||||
year=2025, month=6, day=24, tzinfo=timezone.utc
|
||||
):
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MariaDB {mariadb_version.public} EOL",
|
||||
hint=mariadb_download_link,
|
||||
id="allianceauth.checks.A016",
|
||||
)
|
||||
)
|
||||
# Demote versions down here once EOL
|
||||
elif mariadb_version.minor in [0, 1, 2, 3, 4, 7, 9, 10]:
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MariaDB {mariadb_version.public} EOL",
|
||||
hint=mariadb_download_link,
|
||||
id="allianceauth.checks.A017",
|
||||
)
|
||||
)
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@register()
|
||||
def system_package_sqlite(app_configs, **kwargs) -> List[CheckMessage]:
|
||||
"""
|
||||
Check that SQLite is a supported version
|
||||
|
||||
:param app_configs:
|
||||
:type app_configs:
|
||||
:param kwargs:
|
||||
:type kwargs:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
errors: List[CheckMessage] = []
|
||||
|
||||
for connection in db.connections.all():
|
||||
if connection.vendor == "sqlite":
|
||||
try:
|
||||
sqlite_version = Pep440Version(
|
||||
".".join(str(i) for i in sqlite_version_info)
|
||||
)
|
||||
except InvalidVersion:
|
||||
errors.append(Warning("Unable to confirm SQLite Version"))
|
||||
|
||||
return errors
|
||||
if sqlite_version.major == 3 and sqlite_version.minor < 27:
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"SQLite {sqlite_version.public} Unsupported by Django",
|
||||
hint="https://pkgs.org/download/sqlite3",
|
||||
id="allianceauth.checks.A020",
|
||||
)
|
||||
)
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@register()
|
||||
def sql_settings(app_configs, **kwargs) -> List[CheckMessage]:
|
||||
"""
|
||||
Check that SQL settings are correctly configured
|
||||
|
||||
:param app_configs:
|
||||
:type app_configs:
|
||||
:param kwargs:
|
||||
:type kwargs:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
errors: List[CheckMessage] = []
|
||||
|
||||
for connection in db.connections.all():
|
||||
if connection.vendor == "mysql":
|
||||
try:
|
||||
if connection.settings_dict["OPTIONS"]["charset"] != "utf8mb4":
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"SQL Charset is not set to utf8mb4 DB: {connection.alias}",
|
||||
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8",
|
||||
id="allianceauth.checks.B001",
|
||||
)
|
||||
)
|
||||
except KeyError:
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"SQL Charset is not set to utf8mb4 DB: {connection.alias}",
|
||||
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8",
|
||||
id="allianceauth.checks.B001",
|
||||
)
|
||||
)
|
||||
|
||||
# This hasn't actually been set on AA yet
|
||||
# try:
|
||||
# if (
|
||||
# connection.settings_dict["OPTIONS"]["collation"]
|
||||
# != "utf8mb4_unicode_ci"
|
||||
# ):
|
||||
# errors.append(
|
||||
# Error(
|
||||
# msg=f"SQL Collation is not set to utf8mb4_unicode_ci DB:{connection.alias}",
|
||||
# hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8",
|
||||
# id="allianceauth.checks.B001",
|
||||
# )
|
||||
# )
|
||||
# except KeyError:
|
||||
# errors.append(
|
||||
# Error(
|
||||
# msg=f"SQL Collation is not set to utf8mb4_unicode_ci DB:{connection.alias}",
|
||||
# hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8",
|
||||
# id="allianceauth.checks.B001",
|
||||
# )
|
||||
# )
|
||||
|
||||
# if connection.vendor == "sqlite":
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@register()
|
||||
def celery_settings(app_configs, **kwargs) -> List[CheckMessage]:
|
||||
"""
|
||||
Check that Celery settings are correctly configured
|
||||
|
||||
:param app_configs:
|
||||
:type app_configs:
|
||||
:param kwargs:
|
||||
:type kwargs:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
errors: List[CheckMessage] = []
|
||||
|
||||
try:
|
||||
if current_app.conf.broker_transport_options != {
|
||||
"priority_steps": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||
"queue_order_strategy": "priority",
|
||||
}:
|
||||
errors.append(
|
||||
Error(
|
||||
msg="Celery Priorities are not set correctly",
|
||||
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/8861ec0a61790eca0261f1adc1cc04ca5f243cbc",
|
||||
id="allianceauth.checks.B003",
|
||||
)
|
||||
)
|
||||
except KeyError:
|
||||
errors.append(
|
||||
Error(
|
||||
msg="Celery Priorities are not set",
|
||||
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/8861ec0a61790eca0261f1adc1cc04ca5f243cbc",
|
||||
id="allianceauth.checks.B003",
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
if not current_app.conf.broker_connection_retry_on_startup:
|
||||
errors.append(
|
||||
Error(
|
||||
msg="Celery broker_connection_retry_on_startup not set correctly",
|
||||
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/380c41400b535447839e5552df2410af35a75280",
|
||||
id="allianceauth.checks.B004",
|
||||
)
|
||||
)
|
||||
except KeyError:
|
||||
errors.append(
|
||||
Error(
|
||||
msg="Celery broker_connection_retry_on_startup not set",
|
||||
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/380c41400b535447839e5552df2410af35a75280",
|
||||
id="allianceauth.checks.B004",
|
||||
)
|
||||
)
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
# IDEAS
|
||||
|
||||
# Any other celery things weve manually changed over the years
|
||||
# I'd be happy to add Community App checks, old versions the owners dont want to support etc.
|
||||
|
||||
|
||||
# Check Default Collation on DB
|
||||
# Check Charset Collation on all tables
|
||||
@@ -1,5 +1,6 @@
|
||||
{% extends 'corputils/base.html' %}
|
||||
|
||||
{% load aa_i18n %}
|
||||
{% load i18n %}
|
||||
{% load humanize %}
|
||||
|
||||
@@ -238,9 +239,12 @@
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/datatables-js-bs5.html' %}
|
||||
|
||||
{% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
|
||||
|
||||
<script>
|
||||
$(document).ready(() => {
|
||||
$('#table-mains').DataTable({
|
||||
"language": {"url": '{{ DT_LANG_PATH }}'},
|
||||
"columnDefs": [
|
||||
{ "sortable": false, "targets": [1] },
|
||||
],
|
||||
@@ -249,6 +253,7 @@
|
||||
});
|
||||
|
||||
$('#table-members').DataTable({
|
||||
"language": {"url": '{{ DT_LANG_PATH }}'},
|
||||
"columnDefs": [
|
||||
{ "searchable": false, "targets": [0, 2] },
|
||||
{ "sortable": false, "targets": [0, 2] },
|
||||
@@ -259,6 +264,7 @@
|
||||
});
|
||||
|
||||
$('#table-unregistered').DataTable({
|
||||
"language": {"url": '{{ DT_LANG_PATH }}'},
|
||||
"columnDefs": [
|
||||
{ "searchable": false, "targets": [0, 2] },
|
||||
{ "sortable": false, "targets": [0, 2] },
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% extends "corputils/base.html" %}
|
||||
|
||||
{% load aa_i18n %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block member_data %}
|
||||
@@ -42,9 +43,12 @@
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/datatables-js-bs5.html' %}
|
||||
|
||||
{% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
|
||||
|
||||
<script>
|
||||
$(document).ready(() => {
|
||||
$('#table-search').DataTable({
|
||||
"language": {"url": '{{ DT_LANG_PATH }}'},
|
||||
"stateSave": true,
|
||||
"stateDuration": 0
|
||||
});
|
||||
|
||||
3
allianceauth/crontab/__init__.py
Normal file
3
allianceauth/crontab/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Alliance Auth Crontab Utilities
|
||||
"""
|
||||
14
allianceauth/crontab/apps.py
Normal file
14
allianceauth/crontab/apps.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
Crontab App Config
|
||||
"""
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CrontabConfig(AppConfig):
|
||||
"""
|
||||
Crontab App Config
|
||||
"""
|
||||
|
||||
name = "allianceauth.crontab"
|
||||
label = "crontab"
|
||||
29
allianceauth/crontab/migrations/0001_initial.py
Normal file
29
allianceauth/crontab/migrations/0001_initial.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 4.2.16 on 2025-01-20 06:16
|
||||
|
||||
import allianceauth.crontab.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CronOffset',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('minute', models.FloatField(default=allianceauth.crontab.models.random_default, verbose_name='Minute Offset')),
|
||||
('hour', models.FloatField(default=allianceauth.crontab.models.random_default, verbose_name='Hour Offset')),
|
||||
('day_of_month', models.FloatField(default=allianceauth.crontab.models.random_default, verbose_name='Day of Month Offset')),
|
||||
('month_of_year', models.FloatField(default=allianceauth.crontab.models.random_default, verbose_name='Month of Year Offset')),
|
||||
('day_of_week', models.FloatField(default=allianceauth.crontab.models.random_default, verbose_name='Day of Week Offset')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Cron Offsets',
|
||||
},
|
||||
),
|
||||
]
|
||||
0
allianceauth/crontab/migrations/__init__.py
Normal file
0
allianceauth/crontab/migrations/__init__.py
Normal file
23
allianceauth/crontab/models.py
Normal file
23
allianceauth/crontab/models.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from random import random
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from solo.models import SingletonModel
|
||||
|
||||
|
||||
def random_default() -> float:
|
||||
return random()
|
||||
|
||||
|
||||
class CronOffset(SingletonModel):
|
||||
|
||||
minute = models.FloatField(_("Minute Offset"), default=random_default)
|
||||
hour = models.FloatField(_("Hour Offset"), default=random_default)
|
||||
day_of_month = models.FloatField(_("Day of Month Offset"), default=random_default)
|
||||
month_of_year = models.FloatField(_("Month of Year Offset"), default=random_default)
|
||||
day_of_week = models.FloatField(_("Day of Week Offset"), default=random_default)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "Cron Offsets"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Cron Offsets"
|
||||
70
allianceauth/crontab/schedulers.py
Normal file
70
allianceauth/crontab/schedulers.py
Normal file
@@ -0,0 +1,70 @@
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django_celery_beat.schedulers import (
|
||||
DatabaseScheduler
|
||||
)
|
||||
from django_celery_beat.models import CrontabSchedule
|
||||
from django.db.utils import OperationalError, ProgrammingError
|
||||
|
||||
from celery import schedules
|
||||
from celery.utils.log import get_logger
|
||||
|
||||
from allianceauth.crontab.models import CronOffset
|
||||
from allianceauth.crontab.utils import offset_cron
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class OffsetDatabaseScheduler(DatabaseScheduler):
|
||||
"""
|
||||
Customization of Django Celery Beat, Database Scheduler
|
||||
Takes the Celery Schedule from local.py and applies our AA Framework Cron Offset, if apply_offset is true
|
||||
Otherwise it passes it through as normal
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def update_from_dict(self, mapping):
|
||||
s = {}
|
||||
|
||||
try:
|
||||
cron_offset = CronOffset.get_solo()
|
||||
except (OperationalError, ProgrammingError, ObjectDoesNotExist) as exc:
|
||||
# This is just incase we haven't migrated yet or something
|
||||
logger.warning(
|
||||
"OffsetDatabaseScheduler: Could not fetch CronOffset (%r). "
|
||||
"Defering to DatabaseScheduler",
|
||||
exc
|
||||
)
|
||||
return super().update_from_dict(mapping)
|
||||
|
||||
for name, entry_fields in mapping.items():
|
||||
try:
|
||||
apply_offset = entry_fields.pop("apply_offset", False) # Ensure this pops before django tries to save to ORM
|
||||
entry = self.Entry.from_entry(name, app=self.app, **entry_fields)
|
||||
|
||||
if apply_offset:
|
||||
entry_fields.update({"apply_offset": apply_offset}) # Reapply this as its gets pulled from config inconsistently.
|
||||
schedule_obj = entry.schedule
|
||||
if isinstance(schedule_obj, schedules.crontab):
|
||||
offset_cs = CrontabSchedule.from_schedule(offset_cron(schedule_obj))
|
||||
offset_cs, created = CrontabSchedule.objects.get_or_create(
|
||||
minute=offset_cs.minute,
|
||||
hour=offset_cs.hour,
|
||||
day_of_month=offset_cs.day_of_month,
|
||||
month_of_year=offset_cs.month_of_year,
|
||||
day_of_week=offset_cs.day_of_week,
|
||||
timezone=offset_cs.timezone,
|
||||
)
|
||||
entry.schedule = offset_cron(schedule_obj) # This gets passed into Celery Beats Memory, important to keep it in sync with the model/DB
|
||||
entry.model.crontab = offset_cs
|
||||
entry.model.save()
|
||||
logger.debug(f"Offset applied for '{name}' due to 'apply_offset' = True.")
|
||||
|
||||
if entry.model.enabled:
|
||||
s[name] = entry
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("Error updating schedule for %s: %r", name, e)
|
||||
|
||||
self.schedule.update(s)
|
||||
0
allianceauth/crontab/tests/__init__.py
Normal file
0
allianceauth/crontab/tests/__init__.py
Normal file
63
allianceauth/crontab/tests/test_models.py
Normal file
63
allianceauth/crontab/tests/test_models.py
Normal file
@@ -0,0 +1,63 @@
|
||||
from unittest.mock import patch
|
||||
from django.test import TestCase
|
||||
|
||||
from allianceauth.crontab.models import CronOffset
|
||||
|
||||
|
||||
class CronOffsetModelTest(TestCase):
|
||||
def test_cron_offset_is_singleton(self):
|
||||
"""
|
||||
Test that CronOffset is indeed a singleton and that
|
||||
multiple calls to get_solo() return the same instance.
|
||||
"""
|
||||
offset1 = CronOffset.get_solo()
|
||||
offset2 = CronOffset.get_solo()
|
||||
|
||||
# They should be the exact same object in memory
|
||||
self.assertEqual(offset1.pk, offset2.pk)
|
||||
|
||||
def test_default_values_random(self):
|
||||
"""
|
||||
Test that the default values are set via random_default() when
|
||||
no explicit value is provided. We'll patch 'random.random' to
|
||||
produce predictable output.
|
||||
"""
|
||||
with patch('allianceauth.crontab.models.random', return_value=0.1234):
|
||||
# Force creation of a new CronOffset by clearing the existing one
|
||||
CronOffset.objects.all().delete()
|
||||
|
||||
offset = CronOffset.get_solo() # This triggers creation
|
||||
|
||||
# All fields should be 0.1234, because we patched random()
|
||||
self.assertAlmostEqual(offset.minute, 0.1234)
|
||||
self.assertAlmostEqual(offset.hour, 0.1234)
|
||||
self.assertAlmostEqual(offset.day_of_month, 0.1234)
|
||||
self.assertAlmostEqual(offset.month_of_year, 0.1234)
|
||||
self.assertAlmostEqual(offset.day_of_week, 0.1234)
|
||||
|
||||
def test_update_offset_values(self):
|
||||
"""
|
||||
Test that we can update the offsets and retrieve them.
|
||||
"""
|
||||
offset = CronOffset.get_solo()
|
||||
offset.minute = 0.5
|
||||
offset.hour = 0.25
|
||||
offset.day_of_month = 0.75
|
||||
offset.month_of_year = 0.99
|
||||
offset.day_of_week = 0.33
|
||||
offset.save()
|
||||
|
||||
# Retrieve again to ensure changes persist
|
||||
saved_offset = CronOffset.get_solo()
|
||||
self.assertEqual(saved_offset.minute, 0.5)
|
||||
self.assertEqual(saved_offset.hour, 0.25)
|
||||
self.assertEqual(saved_offset.day_of_month, 0.75)
|
||||
self.assertEqual(saved_offset.month_of_year, 0.99)
|
||||
self.assertEqual(saved_offset.day_of_week, 0.33)
|
||||
|
||||
def test_str_representation(self):
|
||||
"""
|
||||
Verify the __str__ method returns 'Cron Offsets'.
|
||||
"""
|
||||
offset = CronOffset.get_solo()
|
||||
self.assertEqual(str(offset), "Cron Offsets")
|
||||
80
allianceauth/crontab/tests/test_utils.py
Normal file
80
allianceauth/crontab/tests/test_utils.py
Normal file
@@ -0,0 +1,80 @@
|
||||
# myapp/tests/test_tasks.py
|
||||
|
||||
import logging
|
||||
from unittest.mock import patch
|
||||
from django.test import TestCase
|
||||
from django.db import ProgrammingError
|
||||
from celery.schedules import crontab
|
||||
|
||||
from allianceauth.crontab.utils import offset_cron
|
||||
from allianceauth.crontab.models import CronOffset
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TestOffsetCron(TestCase):
|
||||
|
||||
def test_offset_cron_normal(self):
|
||||
"""
|
||||
Test that offset_cron modifies the minute/hour fields
|
||||
based on the CronOffset values when everything is normal.
|
||||
"""
|
||||
# We'll create a mock CronOffset instance
|
||||
mock_offset = CronOffset(minute=0.5, hour=0.5)
|
||||
|
||||
# Our initial crontab schedule
|
||||
original_schedule = crontab(
|
||||
minute=[0, 5, 55],
|
||||
hour=[0, 3, 23],
|
||||
day_of_month='*',
|
||||
month_of_year='*',
|
||||
day_of_week='*'
|
||||
)
|
||||
|
||||
# Patch CronOffset.get_solo to return our mock offset
|
||||
with patch('allianceauth.crontab.models.CronOffset.get_solo', return_value=mock_offset):
|
||||
new_schedule = offset_cron(original_schedule)
|
||||
|
||||
# Check the new minute/hour
|
||||
# minute 0 -> 0 + round(60 * 0.5) = 30 % 60 = 30
|
||||
# minute 5 -> 5 + 30 = 35 % 60 = 35
|
||||
# minute 55 -> 55 + 30 = 85 % 60 = 25 --> sorted => 25,30,35
|
||||
self.assertEqual(new_schedule._orig_minute, '25,30,35')
|
||||
|
||||
# hour 0 -> 0 + round(24 * 0.5) = 12 % 24 = 12
|
||||
# hour 3 -> 3 + 12 = 15 % 24 = 15
|
||||
# hour 23 -> 23 + 12 = 35 % 24 = 11 --> sorted => 11,12,15
|
||||
self.assertEqual(new_schedule._orig_hour, '11,12,15')
|
||||
|
||||
# Check that other fields are unchanged
|
||||
self.assertEqual(new_schedule._orig_day_of_month, '*')
|
||||
self.assertEqual(new_schedule._orig_month_of_year, '*')
|
||||
self.assertEqual(new_schedule._orig_day_of_week, '*')
|
||||
|
||||
def test_offset_cron_programming_error(self):
|
||||
"""
|
||||
Test that if a ProgrammingError is raised (e.g. before migrations),
|
||||
offset_cron just returns the original schedule.
|
||||
"""
|
||||
original_schedule = crontab(minute=[0, 15, 30], hour=[1, 2, 3])
|
||||
|
||||
# Force get_solo to raise ProgrammingError
|
||||
with patch('allianceauth.crontab.models.CronOffset.get_solo', side_effect=ProgrammingError()):
|
||||
new_schedule = offset_cron(original_schedule)
|
||||
|
||||
# Should return the original schedule unchanged
|
||||
self.assertEqual(new_schedule, original_schedule)
|
||||
|
||||
def test_offset_cron_unexpected_exception(self):
|
||||
"""
|
||||
Test that if any other exception is raised, offset_cron
|
||||
also returns the original schedule, and logs the error.
|
||||
"""
|
||||
original_schedule = crontab(minute='0', hour='0')
|
||||
|
||||
# Force get_solo to raise a generic Exception
|
||||
with patch('allianceauth.crontab.models.CronOffset.get_solo', side_effect=Exception("Something bad")):
|
||||
new_schedule = offset_cron(original_schedule)
|
||||
|
||||
# Should return the original schedule unchanged
|
||||
self.assertEqual(new_schedule, original_schedule)
|
||||
50
allianceauth/crontab/utils.py
Normal file
50
allianceauth/crontab/utils.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from celery.schedules import crontab
|
||||
import logging
|
||||
from allianceauth.crontab.models import CronOffset
|
||||
from django.db import ProgrammingError
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def offset_cron(schedule: crontab) -> crontab:
|
||||
"""Take a crontab and apply a series of precalculated offsets to spread out tasks execution on remote resources
|
||||
|
||||
Args:
|
||||
schedule (crontab): celery.schedules.crontab()
|
||||
|
||||
Returns:
|
||||
crontab: A crontab with offsetted Minute and Hour fields
|
||||
"""
|
||||
|
||||
try:
|
||||
cron_offset = CronOffset.get_solo()
|
||||
|
||||
# Stops this shit from happening 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23
|
||||
# It is only cosmetic, but still annoying
|
||||
if schedule._orig_minute == '*':
|
||||
new_minute = '*'
|
||||
else:
|
||||
new_minute = [(m + (round(60 * cron_offset.minute))) % 60 for m in schedule.minute]
|
||||
if schedule._orig_hour == '*':
|
||||
new_hour = '*'
|
||||
else:
|
||||
new_hour = [(m + (round(24 * cron_offset.hour))) % 24 for m in schedule.hour]
|
||||
|
||||
return crontab(
|
||||
minute=",".join(str(m) for m in sorted(new_minute)),
|
||||
hour=",".join(str(h) for h in sorted(new_hour)),
|
||||
day_of_month=schedule._orig_day_of_month,
|
||||
month_of_year=schedule._orig_month_of_year,
|
||||
day_of_week=schedule._orig_day_of_week)
|
||||
|
||||
except ProgrammingError as e:
|
||||
# If this is called before migrations are run hand back the default schedule
|
||||
# These offsets are stored in a Singleton Model,
|
||||
logger.error(e)
|
||||
return schedule
|
||||
|
||||
except Exception as e:
|
||||
# We absolutely cant fail to hand back a schedule
|
||||
logger.error(e)
|
||||
return schedule
|
||||
3
allianceauth/custom_css/__init__.py
Normal file
3
allianceauth/custom_css/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Initializes the custom_css module.
|
||||
"""
|
||||
25
allianceauth/custom_css/admin.py
Normal file
25
allianceauth/custom_css/admin.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
Admin classes for custom_css app
|
||||
"""
|
||||
|
||||
# Django
|
||||
from django.contrib import admin
|
||||
|
||||
# Django Solos
|
||||
from solo.admin import SingletonModelAdmin
|
||||
|
||||
# Alliance Auth Custom CSS
|
||||
from allianceauth.custom_css.models import CustomCSS
|
||||
from allianceauth.custom_css.forms import CustomCSSAdminForm
|
||||
|
||||
|
||||
@admin.register(CustomCSS)
|
||||
class CustomCSSAdmin(SingletonModelAdmin):
|
||||
"""
|
||||
Custom CSS Admin
|
||||
"""
|
||||
|
||||
form = CustomCSSAdminForm
|
||||
|
||||
# Leave this here for when we decide to add syntax highlighting to the CSS editor
|
||||
# change_form_template = 'custom_css/admin/change_form.html'
|
||||
13
allianceauth/custom_css/apps.py
Normal file
13
allianceauth/custom_css/apps.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""
|
||||
Django app configuration for custom_css
|
||||
"""
|
||||
|
||||
# Django
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class CustomCSSConfig(AppConfig):
|
||||
name = "allianceauth.custom_css"
|
||||
label = "custom_css"
|
||||
verbose_name = _("Custom CSS")
|
||||
29
allianceauth/custom_css/forms.py
Normal file
29
allianceauth/custom_css/forms.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""
|
||||
Forms for custom_css app
|
||||
"""
|
||||
|
||||
# Alliance Auth Custom CSS
|
||||
from allianceauth.custom_css.models import CustomCSS
|
||||
from allianceauth.custom_css.widgets import CssEditorWidget
|
||||
|
||||
# Django
|
||||
from django import forms
|
||||
|
||||
|
||||
class CustomCSSAdminForm(forms.ModelForm):
|
||||
"""
|
||||
Form for editing custom CSS
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = CustomCSS
|
||||
fields = ("css",)
|
||||
widgets = {
|
||||
"css": CssEditorWidget(
|
||||
attrs={
|
||||
"style": "width: 90%; height: 100%;",
|
||||
"data-editor": "code-highlight",
|
||||
"data-language": "css",
|
||||
}
|
||||
)
|
||||
}
|
||||
42
allianceauth/custom_css/migrations/0001_initial.py
Normal file
42
allianceauth/custom_css/migrations/0001_initial.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# Generated by Django 4.2.15 on 2024-08-14 11:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="CustomCSS",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"css",
|
||||
models.TextField(
|
||||
blank=True,
|
||||
help_text="This CSS will be added to the site after the default CSS.",
|
||||
null=True,
|
||||
verbose_name="Your custom CSS",
|
||||
),
|
||||
),
|
||||
("timestamp", models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Custom CSS",
|
||||
"verbose_name_plural": "Custom CSS",
|
||||
"default_permissions": (),
|
||||
},
|
||||
),
|
||||
]
|
||||
0
allianceauth/custom_css/migrations/__init__.py
Normal file
0
allianceauth/custom_css/migrations/__init__.py
Normal file
143
allianceauth/custom_css/models.py
Normal file
143
allianceauth/custom_css/models.py
Normal file
@@ -0,0 +1,143 @@
|
||||
"""
|
||||
Models for the custom_css app
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
# Django Solo
|
||||
from solo.models import SingletonModel
|
||||
|
||||
# Django
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class CustomCSS(SingletonModel):
|
||||
"""
|
||||
Model for storing custom CSS for the site
|
||||
"""
|
||||
|
||||
css = models.TextField(
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_("Your custom CSS"),
|
||||
help_text=_("This CSS will be added to the site after the default CSS."),
|
||||
)
|
||||
timestamp = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Meta for CustomCSS
|
||||
"""
|
||||
|
||||
default_permissions = ()
|
||||
verbose_name = _("Custom CSS")
|
||||
verbose_name_plural = _("Custom CSS")
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
String representation of CustomCSS
|
||||
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
return str(_("Custom CSS"))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
Save method for CustomCSS
|
||||
|
||||
:param args:
|
||||
:type args:
|
||||
:param kwargs:
|
||||
:type kwargs:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
self.pk = 1
|
||||
|
||||
if self.css and len(self.css.replace(" ", "")) > 0:
|
||||
# Write the custom CSS to a file
|
||||
custom_css_file = open(
|
||||
f"{settings.STATIC_ROOT}allianceauth/custom-styles.css", "w+"
|
||||
)
|
||||
custom_css_file.write(self.compress_css())
|
||||
custom_css_file.close()
|
||||
else:
|
||||
# Remove the custom CSS file
|
||||
try:
|
||||
os.remove(f"{settings.STATIC_ROOT}allianceauth/custom-styles.css")
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def compress_css(self) -> str:
|
||||
"""
|
||||
Compress CSS
|
||||
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
css = self.css
|
||||
new_css = ""
|
||||
|
||||
# Remove comments
|
||||
css = re.sub(pattern=r"\s*/\*\s*\*/", repl="$$HACK1$$", string=css)
|
||||
css = re.sub(pattern=r"/\*[\s\S]*?\*/", repl="", string=css)
|
||||
css = css.replace("$$HACK1$$", "/**/")
|
||||
|
||||
# url() doesn't need quotes
|
||||
css = re.sub(pattern=r'url\((["\'])([^)]*)\1\)', repl=r"url(\2)", string=css)
|
||||
|
||||
# Spaces may be safely collapsed as generated content will collapse them anyway.
|
||||
css = re.sub(pattern=r"\s+", repl=" ", string=css)
|
||||
|
||||
# Shorten collapsable colors: #aabbcc to #abc
|
||||
css = re.sub(
|
||||
pattern=r"#([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3(\s|;)",
|
||||
repl=r"#\1\2\3\4",
|
||||
string=css,
|
||||
)
|
||||
|
||||
# Fragment values can loose zeros
|
||||
css = re.sub(
|
||||
pattern=r":\s*0(\.\d+([cm]m|e[mx]|in|p[ctx]))\s*;", repl=r":\1;", string=css
|
||||
)
|
||||
|
||||
for rule in re.findall(pattern=r"([^{]+){([^}]*)}", string=css):
|
||||
# We don't need spaces around operators
|
||||
selectors = [
|
||||
re.sub(
|
||||
pattern=r"(?<=[\[\(>+=])\s+|\s+(?=[=~^$*|>+\]\)])",
|
||||
repl=r"",
|
||||
string=selector.strip(),
|
||||
)
|
||||
for selector in rule[0].split(",")
|
||||
]
|
||||
|
||||
# Order is important, but we still want to discard repetitions
|
||||
properties = {}
|
||||
porder = []
|
||||
|
||||
for prop in re.findall(pattern="(.*?):(.*?)(;|$)", string=rule[1]):
|
||||
key = prop[0].strip().lower()
|
||||
|
||||
if key not in porder:
|
||||
porder.append(key)
|
||||
|
||||
properties[key] = prop[1].strip()
|
||||
|
||||
# output rule if it contains any declarations
|
||||
if properties:
|
||||
new_css += "{}{{{}}}".format(
|
||||
",".join(selectors),
|
||||
"".join([f"{key}:{properties[key]};" for key in porder])[:-1],
|
||||
)
|
||||
|
||||
return new_css
|
||||
@@ -0,0 +1,48 @@
|
||||
{% extends "admin/change_form.html" %}
|
||||
|
||||
{% block field_sets %}
|
||||
{% for fieldset in adminform %}
|
||||
<fieldset class="module aligned {{ fieldset.classes }}">
|
||||
{% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
|
||||
|
||||
{% if fieldset.description %}
|
||||
<div class="description">{{ fieldset.description|safe }}</div>
|
||||
{% endif %}
|
||||
|
||||
{% for line in fieldset %}
|
||||
<div class="form-row{% if line.fields|length == 1 and line.errors %} errors{% endif %}{% if not line.has_visible_field %} hidden{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
|
||||
{% if line.fields|length == 1 %}{{ line.errors }}{% else %}<div class="flex-container form-multiline">{% endif %}
|
||||
|
||||
{% for field in line %}
|
||||
<div>
|
||||
{% if not line.fields|length == 1 and not field.is_readonly %}{{ field.errors }}{% endif %}
|
||||
|
||||
<div class="flex-container{% if not line.fields|length == 1 %} fieldBox{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if not field.is_readonly and field.errors %} errors{% endif %}{% if field.field.is_hidden %} hidden{% endif %}{% elif field.is_checkbox %} checkbox-row{% endif %}">
|
||||
{% if field.is_checkbox %}
|
||||
{{ field.field }}{{ field.label_tag }}
|
||||
{% else %}
|
||||
{{ field.label_tag }}
|
||||
{% if field.is_readonly %}
|
||||
<div class="readonly">{{ field.contents }}</div>
|
||||
{% else %}
|
||||
{{ field.field }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if field.field.help_text %}
|
||||
<div class="help"{% if field.field.id_for_label %} id="{{ field.field.id_for_label }}_helptext"{% endif %}>
|
||||
<div>{{ field.field.help_text|safe }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% if not line.fields|length == 1 %}</div>{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block after_field_sets %}{% endblock %}
|
||||
@@ -0,0 +1,3 @@
|
||||
{% load custom_css %}
|
||||
|
||||
{% custom_css_static 'allianceauth/custom-styles.css' %}
|
||||
3
allianceauth/custom_css/templatetags/__init__.py
Normal file
3
allianceauth/custom_css/templatetags/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Init file for custom_css templatetags
|
||||
"""
|
||||
48
allianceauth/custom_css/templatetags/custom_css.py
Normal file
48
allianceauth/custom_css/templatetags/custom_css.py
Normal file
@@ -0,0 +1,48 @@
|
||||
"""
|
||||
Custom template tags for custom_css app
|
||||
"""
|
||||
|
||||
# Alliance Auth Custom CSS
|
||||
from allianceauth.custom_css.models import CustomCSS
|
||||
|
||||
# Django
|
||||
from django.conf import settings
|
||||
from django.template.defaulttags import register
|
||||
from django.templatetags.static import static
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def custom_css_static(path: str) -> str:
|
||||
"""
|
||||
Versioned static URL
|
||||
This is to make sure to break the browser cache on CSS updates.
|
||||
|
||||
Example: /static/allianceauth/custom-styles.css?v=1234567890
|
||||
|
||||
:param path:
|
||||
:type path:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
try:
|
||||
Path(f"{settings.STATIC_ROOT}{path}").resolve(strict=True)
|
||||
except FileNotFoundError:
|
||||
return ""
|
||||
else:
|
||||
try:
|
||||
custom_css = CustomCSS.objects.get(pk=1)
|
||||
except CustomCSS.DoesNotExist:
|
||||
return ""
|
||||
else:
|
||||
custom_css_changed = custom_css.timestamp.timestamp()
|
||||
custom_css_version = (
|
||||
str(custom_css_changed).replace(" ", "").replace(":", "").replace("-", "")
|
||||
) # remove spaces, colons, and dashes
|
||||
static_url = static(path)
|
||||
versioned_url = static_url + "?v=" + custom_css_version
|
||||
|
||||
return mark_safe(f'<link rel="stylesheet" href="{versioned_url}">')
|
||||
38
allianceauth/custom_css/widgets.py
Normal file
38
allianceauth/custom_css/widgets.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""
|
||||
Form widgets for custom_css app
|
||||
"""
|
||||
|
||||
# Django
|
||||
from django import forms
|
||||
|
||||
# Alliance Auth
|
||||
from allianceauth.custom_css.models import CustomCSS
|
||||
|
||||
|
||||
class CssEditorWidget(forms.Textarea):
|
||||
"""
|
||||
Widget for editing CSS
|
||||
"""
|
||||
|
||||
def __init__(self, attrs=None):
|
||||
default_attrs = {"class": "custom-css-editor"}
|
||||
|
||||
if attrs:
|
||||
default_attrs.update(attrs)
|
||||
|
||||
super().__init__(default_attrs)
|
||||
|
||||
# For when we want to add some sort of syntax highlight to it, which is not that
|
||||
# easy to do on a textarea field though.
|
||||
# `highlight.js` is just used as an example here, and doesn't work on a textarea field.
|
||||
# class Media:
|
||||
# css = {
|
||||
# "all": (
|
||||
# "/static/custom_css/libs/highlight.js/11.10.0/styles/github.min.css",
|
||||
# )
|
||||
# }
|
||||
# js = (
|
||||
# "/static/custom_css/libs/highlight.js/11.10.0/highlight.min.js",
|
||||
# "/static/custom_css/libs/highlight.js/11.10.0/languages/css.min.js",
|
||||
# "/static/custom_css/javascript/custom-css.min.js",
|
||||
# )
|
||||
@@ -10,7 +10,7 @@ from . import (
|
||||
)
|
||||
|
||||
|
||||
_BASE_URL = 'http://evemaps.dotlan.net'
|
||||
_BASE_URL = 'https://evemaps.dotlan.net'
|
||||
|
||||
|
||||
def _build_url(category: str, name: str) -> str:
|
||||
|
||||
@@ -31,29 +31,29 @@ class TestDotlan(TestCase):
|
||||
def test_alliance_url(self):
|
||||
self.assertEqual(
|
||||
dotlan.alliance_url('Wayne Enterprices'),
|
||||
'http://evemaps.dotlan.net/alliance/Wayne_Enterprices'
|
||||
'https://evemaps.dotlan.net/alliance/Wayne_Enterprices'
|
||||
)
|
||||
|
||||
def test_corporation_url(self):
|
||||
self.assertEqual(
|
||||
dotlan.corporation_url('Wayne Technology'),
|
||||
'http://evemaps.dotlan.net/corp/Wayne_Technology'
|
||||
'https://evemaps.dotlan.net/corp/Wayne_Technology'
|
||||
)
|
||||
self.assertEqual(
|
||||
dotlan.corporation_url('Crédit Agricole'),
|
||||
'http://evemaps.dotlan.net/corp/Cr%C3%A9dit_Agricole'
|
||||
'https://evemaps.dotlan.net/corp/Cr%C3%A9dit_Agricole'
|
||||
)
|
||||
|
||||
def test_region_url(self):
|
||||
self.assertEqual(
|
||||
dotlan.region_url('Black Rise'),
|
||||
'http://evemaps.dotlan.net/map/Black_Rise'
|
||||
'https://evemaps.dotlan.net/map/Black_Rise'
|
||||
)
|
||||
|
||||
def test_solar_system_url(self):
|
||||
self.assertEqual(
|
||||
dotlan.solar_system_url('Jita'),
|
||||
'http://evemaps.dotlan.net/system/Jita'
|
||||
'https://evemaps.dotlan.net/system/Jita'
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
from random import randint
|
||||
|
||||
from celery import shared_task
|
||||
|
||||
@@ -9,7 +10,8 @@ from . import providers
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TASK_PRIORITY = 7
|
||||
CHUNK_SIZE = 500
|
||||
CHARACTER_AFFILIATION_CHUNK_SIZE = 500
|
||||
EVEONLINE_TASK_JITTER = 600
|
||||
|
||||
|
||||
def chunks(lst, n):
|
||||
@@ -19,13 +21,13 @@ def chunks(lst, n):
|
||||
|
||||
|
||||
@shared_task
|
||||
def update_corp(corp_id):
|
||||
def update_corp(corp_id: int) -> None:
|
||||
"""Update given corporation from ESI"""
|
||||
EveCorporationInfo.objects.update_corporation(corp_id)
|
||||
|
||||
|
||||
@shared_task
|
||||
def update_alliance(alliance_id):
|
||||
def update_alliance(alliance_id: int) -> None:
|
||||
"""Update given alliance from ESI"""
|
||||
EveAllianceInfo.objects.update_alliance(alliance_id).populate_alliance()
|
||||
|
||||
@@ -37,23 +39,30 @@ def update_character(character_id: int) -> None:
|
||||
|
||||
|
||||
@shared_task
|
||||
def run_model_update():
|
||||
def run_model_update() -> None:
|
||||
"""Update all alliances, corporations and characters from ESI"""
|
||||
|
||||
#update existing corp models
|
||||
# Queue update tasks for Known Corporation Models
|
||||
for corp in EveCorporationInfo.objects.all().values('corporation_id'):
|
||||
update_corp.apply_async(args=[corp['corporation_id']], priority=TASK_PRIORITY)
|
||||
update_corp.apply_async(
|
||||
args=[corp['corporation_id']],
|
||||
priority=TASK_PRIORITY,
|
||||
countdown=randint(1, EVEONLINE_TASK_JITTER))
|
||||
|
||||
# update existing alliance models
|
||||
# Queue update tasks for Known Alliance Models
|
||||
for alliance in EveAllianceInfo.objects.all().values('alliance_id'):
|
||||
update_alliance.apply_async(args=[alliance['alliance_id']], priority=TASK_PRIORITY)
|
||||
update_alliance.apply_async(
|
||||
args=[alliance['alliance_id']],
|
||||
priority=TASK_PRIORITY,
|
||||
countdown=randint(1, EVEONLINE_TASK_JITTER))
|
||||
|
||||
# update existing character models
|
||||
# Queue update tasks for Known Character Models
|
||||
character_ids = EveCharacter.objects.all().values_list('character_id', flat=True)
|
||||
for character_ids_chunk in chunks(character_ids, CHUNK_SIZE):
|
||||
for character_ids_chunk in chunks(character_ids, CHARACTER_AFFILIATION_CHUNK_SIZE):
|
||||
update_character_chunk.apply_async(
|
||||
args=[character_ids_chunk], priority=TASK_PRIORITY
|
||||
)
|
||||
args=[character_ids_chunk],
|
||||
priority=TASK_PRIORITY,
|
||||
countdown=randint(1, EVEONLINE_TASK_JITTER))
|
||||
|
||||
|
||||
@shared_task
|
||||
@@ -68,8 +77,9 @@ def update_character_chunk(character_ids_chunk: list):
|
||||
logger.info("Failed to bulk update characters. Attempting single updates")
|
||||
for character_id in character_ids_chunk:
|
||||
update_character.apply_async(
|
||||
args=[character_id], priority=TASK_PRIORITY
|
||||
)
|
||||
args=[character_id],
|
||||
priority=TASK_PRIORITY,
|
||||
countdown=randint(1, EVEONLINE_TASK_JITTER))
|
||||
return
|
||||
|
||||
affiliations = {
|
||||
@@ -107,5 +117,5 @@ def update_character_chunk(character_ids_chunk: list):
|
||||
|
||||
if corp_changed or alliance_changed or name_changed:
|
||||
update_character.apply_async(
|
||||
args=[character.get('character_id')], priority=TASK_PRIORITY
|
||||
)
|
||||
args=[character.get('character_id')],
|
||||
priority=TASK_PRIORITY)
|
||||
|
||||
@@ -723,5 +723,5 @@ class TestEveSwaggerProvider(TestCase):
|
||||
my_client = my_provider.client
|
||||
operation = my_client.Universe.get_universe_factions()
|
||||
self.assertEqual(
|
||||
operation.future.request.headers['User-Agent'], 'allianceauth v1.0.0'
|
||||
operation.future.request.headers['User-Agent'], 'allianceauth v1.0.0 dummy@example.net'
|
||||
)
|
||||
|
||||
@@ -84,7 +84,7 @@ class TestUpdateTasks(TestCase):
|
||||
@override_settings(CELERY_ALWAYS_EAGER=True)
|
||||
@patch('allianceauth.eveonline.providers.esi_client_factory')
|
||||
@patch('allianceauth.eveonline.tasks.providers')
|
||||
@patch('allianceauth.eveonline.tasks.CHUNK_SIZE', 2)
|
||||
@patch('allianceauth.eveonline.tasks.CHARACTER_AFFILIATION_CHUNK_SIZE', 2)
|
||||
class TestRunModelUpdate(TransactionTestCase):
|
||||
def test_should_run_updates(self, mock_providers, mock_esi_client_factory):
|
||||
# given
|
||||
@@ -139,7 +139,7 @@ class TestRunModelUpdate(TransactionTestCase):
|
||||
@patch('allianceauth.eveonline.tasks.update_character', wraps=update_character)
|
||||
@patch('allianceauth.eveonline.providers.esi_client_factory')
|
||||
@patch('allianceauth.eveonline.tasks.providers')
|
||||
@patch('allianceauth.eveonline.tasks.CHUNK_SIZE', 2)
|
||||
@patch('allianceauth.eveonline.tasks.CHARACTER_AFFILIATION_CHUNK_SIZE', 2)
|
||||
class TestUpdateCharacterChunk(TestCase):
|
||||
@staticmethod
|
||||
def _updated_character_ids(spy_update_character) -> set:
|
||||
|
||||
@@ -13,6 +13,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Image overflow fix
|
||||
------------------------------------------------------------------------------------- */
|
||||
@media all {
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* Side Navigation
|
||||
------------------------------------------------------------------------------------- */
|
||||
@media all {
|
||||
@@ -40,14 +49,14 @@
|
||||
}
|
||||
|
||||
/* Chevron icons */
|
||||
#sidebar-menu [data-bs-toggle="collapse"] > i.fa-chevron-down,
|
||||
#sidebar-menu [data-bs-toggle="collapse"].collapsed > i.fa-chevron-right {
|
||||
#sidebar-menu span[data-bs-toggle="collapse"][aria-expanded="true"] > i.fa-chevron-down,
|
||||
#sidebar-menu span[data-bs-toggle="collapse"][aria-expanded="false"] > i.fa-chevron-right {
|
||||
display: block;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
#sidebar-menu [data-bs-toggle="collapse"] > i.fa-chevron-right,
|
||||
#sidebar-menu [data-bs-toggle="collapse"].collapsed > i.fa-chevron-down {
|
||||
#sidebar-menu span[data-bs-toggle="collapse"][aria-expanded="true"] > i.fa-chevron-right,
|
||||
#sidebar-menu span[data-bs-toggle="collapse"][aria-expanded="false"] > i.fa-chevron-down {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{#Usage:#}
|
||||
{# {% include "framework/dashboard/widget-title.html" with title="Foobar" %}#}
|
||||
|
||||
<div class="text-center">
|
||||
<h4 class="ms-auto me-auto mb-3">
|
||||
{{ title }}
|
||||
</h4>
|
||||
</div>
|
||||
@@ -49,7 +49,7 @@ class GroupsMenuItem(MenuItemHook):
|
||||
MenuItemHook.__init__(
|
||||
self,
|
||||
text=_("Groups"),
|
||||
classes="fa-solid fa-user",
|
||||
classes="fa-solid fa-users",
|
||||
url_name="groupmanagement:groups",
|
||||
order=25,
|
||||
navactive=[
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
|
||||
{% load aa_i18n %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load navactive %}
|
||||
@@ -73,6 +74,8 @@
|
||||
{% include 'bundles/moment-js.html' with locale=True %}
|
||||
{% include 'bundles/filterdropdown-js.html' %}
|
||||
|
||||
{% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
|
||||
|
||||
<script>
|
||||
$.fn.dataTable.moment = (format, locale) => {
|
||||
const types = $.fn.dataTable.ext.type;
|
||||
@@ -94,6 +97,7 @@
|
||||
$.fn.dataTable.moment('YYYY-MMM-D, HH:mm');
|
||||
|
||||
$('#log-entries').DataTable({
|
||||
language: {url: '{{ DT_LANG_PATH }}'},
|
||||
order: [[0, 'desc'], [1, 'asc']],
|
||||
filterDropDown:
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
|
||||
{% load static %}
|
||||
{% load aa_i18n %}
|
||||
{% load i18n %}
|
||||
{% load evelinks %}
|
||||
{% load navactive %}
|
||||
@@ -86,9 +87,12 @@
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/datatables-js-bs5.html' %}
|
||||
|
||||
{% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
|
||||
|
||||
<script>
|
||||
$(document).ready(() => {
|
||||
$('#tab_group_members').DataTable({
|
||||
language: {url: '{{ DT_LANG_PATH }}'},
|
||||
order: [[0, "asc"]],
|
||||
columnDefs: [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
|
||||
{% load static %}
|
||||
{% load aa_i18n %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}
|
||||
@@ -56,7 +57,7 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if g.group.authgroup.group_leaders.all.count %}
|
||||
{% if g.group.authgroup.group_leader_groups.all.count %}
|
||||
{% for group in g.group.authgroup.group_leader_groups.all %}
|
||||
<span class="my-1 me-1 badge bg-secondary">{{group.name}}</span>
|
||||
{% endfor %}
|
||||
@@ -103,9 +104,13 @@
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/datatables-js-bs5.html' %}
|
||||
|
||||
{% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
|
||||
|
||||
<script>
|
||||
$(document).ready(() => {
|
||||
$('#groupsTable').DataTable();
|
||||
$('#groupsTable').DataTable({
|
||||
language: {url: '{{ DT_LANG_PATH }}'},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Binary file not shown.
BIN
allianceauth/locale/cs_CZ/LC_MESSAGES/django.mo
Normal file
BIN
allianceauth/locale/cs_CZ/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
@@ -11,14 +11,14 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-05-12 19:15+1000\n"
|
||||
"POT-Creation-Date: 2024-09-13 19:57+1000\n"
|
||||
"PO-Revision-Date: 2023-11-08 13:50+0000\n"
|
||||
"Last-Translator: Tomas Skarecky <t.skarecky@gmail.com>, 2024\n"
|
||||
"Language-Team: Czech (https://app.transifex.com/alliance-auth/teams/107430/cs/)\n"
|
||||
"Language-Team: Czech (Czech Republic) (https://app.transifex.com/alliance-auth/teams/107430/cs_CZ/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: cs\n"
|
||||
"Language: cs_CZ\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
|
||||
|
||||
#: allianceauth/analytics/models.py:26
|
||||
@@ -54,66 +54,90 @@ msgstr ""
|
||||
"přístupem:%s"
|
||||
|
||||
#: allianceauth/authentication/models.py:71
|
||||
#: allianceauth/project_template/project_name/settings/base.py:99
|
||||
msgid "English"
|
||||
msgstr "Angličtina"
|
||||
|
||||
#: allianceauth/authentication/models.py:72
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:73
|
||||
#: allianceauth/project_template/project_name/settings/base.py:101
|
||||
msgid "German"
|
||||
msgstr "Němčina"
|
||||
|
||||
#: allianceauth/authentication/models.py:73
|
||||
#: allianceauth/authentication/models.py:74
|
||||
#: allianceauth/project_template/project_name/settings/base.py:102
|
||||
msgid "Spanish"
|
||||
msgstr "Španělština"
|
||||
|
||||
#: allianceauth/authentication/models.py:74
|
||||
msgid "Chinese Simplified"
|
||||
msgstr "Zjednodušená čínština"
|
||||
|
||||
#: allianceauth/authentication/models.py:75
|
||||
msgid "Russian"
|
||||
msgstr "Ruština"
|
||||
|
||||
#: allianceauth/authentication/models.py:76
|
||||
msgid "Korean"
|
||||
msgstr "Korejština"
|
||||
|
||||
#: allianceauth/authentication/models.py:77
|
||||
msgid "French"
|
||||
msgstr "Francouzština"
|
||||
|
||||
#: allianceauth/authentication/models.py:78
|
||||
msgid "Japanese"
|
||||
msgstr "Japonština"
|
||||
|
||||
#: allianceauth/authentication/models.py:79
|
||||
#: allianceauth/project_template/project_name/settings/base.py:103
|
||||
msgid "Italian"
|
||||
msgstr "Italština"
|
||||
|
||||
#: allianceauth/authentication/models.py:76
|
||||
#: allianceauth/project_template/project_name/settings/base.py:104
|
||||
msgid "Japanese"
|
||||
msgstr "Japonština"
|
||||
|
||||
#: allianceauth/authentication/models.py:77
|
||||
#: allianceauth/project_template/project_name/settings/base.py:105
|
||||
msgid "Korean"
|
||||
msgstr "Korejština"
|
||||
|
||||
#: allianceauth/authentication/models.py:78
|
||||
#: allianceauth/project_template/project_name/settings/base.py:106
|
||||
msgid "French"
|
||||
msgstr "Francouzština"
|
||||
|
||||
#: allianceauth/authentication/models.py:79
|
||||
#: allianceauth/project_template/project_name/settings/base.py:109
|
||||
msgid "Russian"
|
||||
msgstr "Ruština"
|
||||
|
||||
#: allianceauth/authentication/models.py:80
|
||||
#: allianceauth/project_template/project_name/settings/base.py:107
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:81
|
||||
#: allianceauth/project_template/project_name/settings/base.py:108
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:82
|
||||
#: allianceauth/project_template/project_name/settings/base.py:110
|
||||
msgid "Ukrainian"
|
||||
msgstr "Ukrajinština"
|
||||
|
||||
#: allianceauth/authentication/models.py:96
|
||||
#: allianceauth/authentication/models.py:83
|
||||
#: allianceauth/project_template/project_name/settings/base.py:111
|
||||
msgid "Simplified Chinese"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:99
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:42
|
||||
msgid "Language"
|
||||
msgstr "Jazyk"
|
||||
|
||||
#: allianceauth/authentication/models.py:101
|
||||
#: allianceauth/authentication/models.py:104
|
||||
#: allianceauth/templates/allianceauth/night-toggle.html:6
|
||||
msgid "Night Mode"
|
||||
msgstr "Noční režim"
|
||||
|
||||
#: allianceauth/authentication/models.py:105
|
||||
#: allianceauth/authentication/models.py:108
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:46
|
||||
msgid "Theme"
|
||||
msgstr "Motiv"
|
||||
|
||||
#: allianceauth/authentication/models.py:122
|
||||
#: allianceauth/authentication/models.py:125
|
||||
#, python-format
|
||||
msgid "State changed to: %s"
|
||||
msgstr "Status změněn na: %s"
|
||||
|
||||
#: allianceauth/authentication/models.py:123
|
||||
#: allianceauth/authentication/models.py:126
|
||||
#, python-format
|
||||
msgid "Your user's state is now: %(state)s"
|
||||
msgstr "Váš uživatelský status je nyní: %(state)s"
|
||||
@@ -125,27 +149,27 @@ msgstr "Váš uživatelský status je nyní: %(state)s"
|
||||
msgid "Dashboard"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:7
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:5
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:33
|
||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:54
|
||||
msgid "Characters"
|
||||
msgstr "Postavy"
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:13
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:14
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:11
|
||||
#: 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:6
|
||||
msgid "Add Character"
|
||||
msgstr "Přidat postavu"
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:16
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:17
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:14
|
||||
#: 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:12
|
||||
msgid "Change Main"
|
||||
msgstr "Změnit postavu"
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:24
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:22
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:89
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:23
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:31
|
||||
@@ -154,12 +178,12 @@ msgstr "Změnit postavu"
|
||||
msgid "Name"
|
||||
msgstr "Jméno"
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:25
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:23
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:33
|
||||
msgid "Corp"
|
||||
msgstr "Korporace"
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:26
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:24
|
||||
#: allianceauth/corputils/templates/corputils/corpstats.html:125
|
||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:63
|
||||
msgid "Alliance"
|
||||
@@ -169,7 +193,7 @@ msgstr "Aliance"
|
||||
msgid "Membership"
|
||||
msgstr "Členství"
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_groups.html:8
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_groups.html:10
|
||||
msgid "State:"
|
||||
msgstr "Stav:"
|
||||
|
||||
@@ -409,6 +433,19 @@ msgstr "Vybraná korporace již má statistický modul"
|
||||
msgid "Failed to gather corporation statistics with selected token."
|
||||
msgstr "Nepodařilo se získat statistiky korporace pomocí vybraného tokenu."
|
||||
|
||||
#: allianceauth/custom_css/apps.py:13 allianceauth/custom_css/models.py:36
|
||||
#: allianceauth/custom_css/models.py:37 allianceauth/custom_css/models.py:47
|
||||
msgid "Custom CSS"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/custom_css/models.py:25
|
||||
msgid "Your custom CSS"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/custom_css/models.py:26
|
||||
msgid "This CSS will be added to the site after the default CSS."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/fleetactivitytracking/auth_hooks.py:10
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/characternotexisting.html:10
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkcreate.html:11
|
||||
@@ -501,8 +538,8 @@ msgstr "Uživatel"
|
||||
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:37
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:42
|
||||
#: allianceauth/optimer/form.py:13 allianceauth/timerboard/form.py:59
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:16
|
||||
#: allianceauth/optimer/form.py:13 allianceauth/timerboard/form.py:37
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:17
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:9
|
||||
msgid "System"
|
||||
msgstr "Systém"
|
||||
@@ -826,7 +863,7 @@ msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31
|
||||
#: allianceauth/optimer/templates/optimer/dashboard.ops.html:15
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:15
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:16
|
||||
msgid "Type"
|
||||
msgstr ""
|
||||
|
||||
@@ -918,7 +955,7 @@ msgid "Hidden"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:45
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:19
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:16
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
|
||||
@@ -1394,16 +1431,16 @@ msgstr ""
|
||||
msgid "Super User"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:68
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:70
|
||||
#: allianceauth/templates/allianceauth/top-menu-admin.html:9
|
||||
msgid "Admin"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:80
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:82
|
||||
msgid "Sign Out"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:84
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:86
|
||||
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:17
|
||||
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:18
|
||||
msgid "Sign In"
|
||||
@@ -1553,7 +1590,7 @@ msgid "Form Up System"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/optimer/templates/optimer/dashboard.ops.html:17
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:17
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:18
|
||||
msgid "EVE Time"
|
||||
msgstr ""
|
||||
|
||||
@@ -1748,17 +1785,17 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:26
|
||||
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:20
|
||||
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:22
|
||||
msgid "Activate"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:32
|
||||
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:32
|
||||
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:34
|
||||
msgid "Reset Password"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:38
|
||||
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:38
|
||||
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:40
|
||||
msgid "Deactivate"
|
||||
msgstr ""
|
||||
|
||||
@@ -1839,12 +1876,12 @@ msgstr ""
|
||||
msgid "Deactivated IPSuite4 account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:26
|
||||
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:28
|
||||
#: allianceauth/services/templates/services/service_password.html:26
|
||||
msgid "Set Password"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:44
|
||||
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:46
|
||||
msgid "Connect"
|
||||
msgstr ""
|
||||
|
||||
@@ -2407,56 +2444,56 @@ msgstr ""
|
||||
msgid "Your Server received an ESI error response code of "
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:11
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:8
|
||||
msgid "Alliance Auth Notifications"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:21
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:18
|
||||
msgid "Closed"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:27
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:24
|
||||
msgid "No notifications at this time"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:36
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:33
|
||||
msgid "Powered by GitLab"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:42
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:39
|
||||
msgid "Support Discord"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:59
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:63
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:53
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:57
|
||||
msgid "Software Version"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:66
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:60
|
||||
msgid "Current"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:73
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:67
|
||||
msgid "Latest Stable"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:78
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:72
|
||||
msgid "Update available"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:86
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:80
|
||||
msgid "Latest Pre-Release"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:91
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:85
|
||||
msgid "Pre-Release available"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:102
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:95
|
||||
msgid "Task Queue"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:107
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:100
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
@@ -2464,11 +2501,11 @@ msgid ""
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:123
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:116
|
||||
msgid "running"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:124
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:117
|
||||
msgid "queued"
|
||||
msgstr ""
|
||||
|
||||
@@ -2497,114 +2534,216 @@ msgstr ""
|
||||
msgid "Select Theme"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/form.py:53
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:172
|
||||
msgid "Other"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/form.py:54
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:38
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:44
|
||||
msgid "Friendly"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/form.py:55
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:33
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:38
|
||||
msgid "Hostile"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/form.py:56
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:43
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:50
|
||||
msgid "Neutral"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/form.py:58
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:13
|
||||
#: allianceauth/timerboard/form.py:36
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:14
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:7
|
||||
msgid "Details"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/form.py:60
|
||||
#: allianceauth/timerboard/form.py:38
|
||||
msgid "Planet/Moon"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/form.py:61
|
||||
#: allianceauth/timerboard/form.py:39
|
||||
msgid "Structure Type"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/form.py:62
|
||||
#: allianceauth/timerboard/form.py:40
|
||||
msgid "Timer Type"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/form.py:63
|
||||
#: allianceauth/timerboard/form.py:41
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:8
|
||||
msgid "Objective"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/form.py:64
|
||||
#: allianceauth/timerboard/form.py:42
|
||||
msgid "Absolute Timer"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/form.py:65
|
||||
#: allianceauth/timerboard/form.py:43
|
||||
msgid "Date and Time"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/form.py:66
|
||||
#: allianceauth/timerboard/form.py:44
|
||||
msgid "Days Remaining"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/form.py:67
|
||||
#: allianceauth/timerboard/form.py:45
|
||||
msgid "Hours Remaining"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/form.py:69
|
||||
#: allianceauth/timerboard/form.py:47
|
||||
msgid "Minutes Remaining"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/form.py:71
|
||||
#: allianceauth/timerboard/form.py:48
|
||||
msgid "Important"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/form.py:72
|
||||
#: allianceauth/timerboard/form.py:49
|
||||
msgid "Corp-Restricted"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:14
|
||||
msgid "Not Specified"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:15
|
||||
msgid "Shield"
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:39
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:36
|
||||
msgid "Friendly"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:16
|
||||
msgid "Armor"
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:34
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:34
|
||||
msgid "Hostile"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:17
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:44
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:38
|
||||
msgid "Neutral"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:24
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:48
|
||||
msgid "POCO"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:25
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:50
|
||||
msgid "Orbital Skyhook"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:26
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:52
|
||||
msgid "I-HUB"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:27
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:55
|
||||
msgid "TCU"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:28
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:57
|
||||
msgid "POS [S]"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:29
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:59
|
||||
msgid "POS [M]"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:30
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:61
|
||||
msgid "POS [L]"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:31
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:63
|
||||
msgid "Astrahus"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:32
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:65
|
||||
msgid "Fortizar"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:33
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:67
|
||||
msgid "Keepstar"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:34
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:69
|
||||
msgid "Raitaru"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:35
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:71
|
||||
msgid "Azbel"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:36
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:73
|
||||
msgid "Sotiyo"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:37
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:75
|
||||
msgid "Athanor"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:38
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:77
|
||||
msgid "Tatara"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:39
|
||||
msgid "Pharolux Cyno Beacon"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:40
|
||||
msgid "Tenebrex Cyno Jammer"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:41
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:83
|
||||
msgid "Ansiblex Jump Gate"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:42
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:85
|
||||
msgid "Moon Mining Cycle"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:43
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:87
|
||||
msgid "Metenox Moon Drill"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:44
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:89
|
||||
msgid "Other"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:51
|
||||
msgid "Not Specified"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:52
|
||||
msgid "Shield"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:53
|
||||
msgid "Armor"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:54
|
||||
msgid "Hull"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:18
|
||||
#: allianceauth/timerboard/models.py:55
|
||||
msgid "Final"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:19
|
||||
#: allianceauth/timerboard/models.py:56
|
||||
msgid "Anchoring"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:20
|
||||
#: allianceauth/timerboard/models.py:57
|
||||
msgid "Unanchoring"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:58
|
||||
msgid "Abandoned"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:7
|
||||
#: allianceauth/timerboard/templates/timerboard/view.html:53
|
||||
msgid "Upcoming Timers"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:14
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:15
|
||||
msgid "Timer"
|
||||
msgstr ""
|
||||
|
||||
@@ -2648,78 +2787,14 @@ msgstr ""
|
||||
msgid "Structure"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:64
|
||||
msgid "POCO"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:70
|
||||
msgid "I-HUB"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:76
|
||||
msgid "TCU"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:82
|
||||
msgid "POS [S]"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:88
|
||||
msgid "POS [M]"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:94
|
||||
msgid "POS [L]"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:100
|
||||
msgid "Astrahus"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:106
|
||||
msgid "Fortizar"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:112
|
||||
msgid "Keepstar"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:118
|
||||
msgid "Raitaru"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:124
|
||||
msgid "Azbel"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:130
|
||||
msgid "Sotiyo"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:136
|
||||
msgid "Athanor"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:142
|
||||
msgid "Tatara"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:148
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:79
|
||||
msgid "Cyno Beacon"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:154
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:81
|
||||
msgid "Cyno Jammer"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:160
|
||||
msgid "Ansiblex Jump Gate"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:166
|
||||
msgid "Moon Mining Cycle"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/view.html:9
|
||||
msgid "Structure Timer Management"
|
||||
msgstr ""
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
BIN
allianceauth/locale/nl_NL/LC_MESSAGES/django.mo
Normal file
BIN
allianceauth/locale/nl_NL/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
@@ -12,14 +12,14 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-05-12 19:15+1000\n"
|
||||
"POT-Creation-Date: 2024-09-13 19:57+1000\n"
|
||||
"PO-Revision-Date: 2023-11-08 13:50+0000\n"
|
||||
"Last-Translator: Agent Fuse, 2024\n"
|
||||
"Language-Team: Dutch (https://app.transifex.com/alliance-auth/teams/107430/nl/)\n"
|
||||
"Language-Team: Dutch (Netherlands) (https://app.transifex.com/alliance-auth/teams/107430/nl_NL/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: nl\n"
|
||||
"Language: nl_NL\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: allianceauth/analytics/models.py:26
|
||||
@@ -62,66 +62,90 @@ msgstr ""
|
||||
"Je bent niet gemachtigd om de volgende beperkte groepen te verwijderen: %s"
|
||||
|
||||
#: allianceauth/authentication/models.py:71
|
||||
#: allianceauth/project_template/project_name/settings/base.py:99
|
||||
msgid "English"
|
||||
msgstr "Engels"
|
||||
|
||||
#: allianceauth/authentication/models.py:72
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:73
|
||||
#: allianceauth/project_template/project_name/settings/base.py:101
|
||||
msgid "German"
|
||||
msgstr "Duits"
|
||||
|
||||
#: allianceauth/authentication/models.py:73
|
||||
#: allianceauth/authentication/models.py:74
|
||||
#: allianceauth/project_template/project_name/settings/base.py:102
|
||||
msgid "Spanish"
|
||||
msgstr "Spaans"
|
||||
|
||||
#: allianceauth/authentication/models.py:74
|
||||
msgid "Chinese Simplified"
|
||||
msgstr "Vereenvoudigd Chinees"
|
||||
|
||||
#: allianceauth/authentication/models.py:75
|
||||
msgid "Russian"
|
||||
msgstr "Russisch"
|
||||
|
||||
#: allianceauth/authentication/models.py:76
|
||||
msgid "Korean"
|
||||
msgstr "Koreaans"
|
||||
|
||||
#: allianceauth/authentication/models.py:77
|
||||
msgid "French"
|
||||
msgstr "Frans"
|
||||
|
||||
#: allianceauth/authentication/models.py:78
|
||||
msgid "Japanese"
|
||||
msgstr "Japans"
|
||||
|
||||
#: allianceauth/authentication/models.py:79
|
||||
#: allianceauth/project_template/project_name/settings/base.py:103
|
||||
msgid "Italian"
|
||||
msgstr "Italiaans"
|
||||
|
||||
#: allianceauth/authentication/models.py:76
|
||||
#: allianceauth/project_template/project_name/settings/base.py:104
|
||||
msgid "Japanese"
|
||||
msgstr "Japans"
|
||||
|
||||
#: allianceauth/authentication/models.py:77
|
||||
#: allianceauth/project_template/project_name/settings/base.py:105
|
||||
msgid "Korean"
|
||||
msgstr "Koreaans"
|
||||
|
||||
#: allianceauth/authentication/models.py:78
|
||||
#: allianceauth/project_template/project_name/settings/base.py:106
|
||||
msgid "French"
|
||||
msgstr "Frans"
|
||||
|
||||
#: allianceauth/authentication/models.py:79
|
||||
#: allianceauth/project_template/project_name/settings/base.py:109
|
||||
msgid "Russian"
|
||||
msgstr "Russisch"
|
||||
|
||||
#: allianceauth/authentication/models.py:80
|
||||
#: allianceauth/project_template/project_name/settings/base.py:107
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:81
|
||||
#: allianceauth/project_template/project_name/settings/base.py:108
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:82
|
||||
#: allianceauth/project_template/project_name/settings/base.py:110
|
||||
msgid "Ukrainian"
|
||||
msgstr "Oekraïens"
|
||||
|
||||
#: allianceauth/authentication/models.py:96
|
||||
#: allianceauth/authentication/models.py:83
|
||||
#: allianceauth/project_template/project_name/settings/base.py:111
|
||||
msgid "Simplified Chinese"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:99
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:42
|
||||
msgid "Language"
|
||||
msgstr "Taal"
|
||||
|
||||
#: allianceauth/authentication/models.py:101
|
||||
#: allianceauth/authentication/models.py:104
|
||||
#: allianceauth/templates/allianceauth/night-toggle.html:6
|
||||
msgid "Night Mode"
|
||||
msgstr "Nachtstand"
|
||||
|
||||
#: allianceauth/authentication/models.py:105
|
||||
#: allianceauth/authentication/models.py:108
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:46
|
||||
msgid "Theme"
|
||||
msgstr "Thema"
|
||||
|
||||
#: allianceauth/authentication/models.py:122
|
||||
#: allianceauth/authentication/models.py:125
|
||||
#, python-format
|
||||
msgid "State changed to: %s"
|
||||
msgstr "State gewijzigd naar: %s"
|
||||
|
||||
#: allianceauth/authentication/models.py:123
|
||||
#: allianceauth/authentication/models.py:126
|
||||
#, python-format
|
||||
msgid "Your user's state is now: %(state)s"
|
||||
msgstr "De gebruikers staat is nu: %(state)s"
|
||||
@@ -133,27 +157,27 @@ msgstr "De gebruikers staat is nu: %(state)s"
|
||||
msgid "Dashboard"
|
||||
msgstr "Dashboard"
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:7
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:5
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:33
|
||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:54
|
||||
msgid "Characters"
|
||||
msgstr "Karakter"
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:13
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:14
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:11
|
||||
#: 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:6
|
||||
msgid "Add Character"
|
||||
msgstr "Personages toevoegen"
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:16
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:17
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:14
|
||||
#: 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:12
|
||||
msgid "Change Main"
|
||||
msgstr "Verander Main"
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:24
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:22
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:89
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:23
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:31
|
||||
@@ -162,12 +186,12 @@ msgstr "Verander Main"
|
||||
msgid "Name"
|
||||
msgstr "Naam"
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:25
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:23
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:33
|
||||
msgid "Corp"
|
||||
msgstr "Corp"
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:26
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:24
|
||||
#: allianceauth/corputils/templates/corputils/corpstats.html:125
|
||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:63
|
||||
msgid "Alliance"
|
||||
@@ -177,7 +201,7 @@ msgstr "Alliantie"
|
||||
msgid "Membership"
|
||||
msgstr "Lidmaatschap"
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_groups.html:8
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_groups.html:10
|
||||
msgid "State:"
|
||||
msgstr "Status:"
|
||||
|
||||
@@ -422,6 +446,19 @@ msgstr ""
|
||||
"Het is niet gelukt om bedrijfsstatistieken te verzamelen met het "
|
||||
"geselecteerde token."
|
||||
|
||||
#: allianceauth/custom_css/apps.py:13 allianceauth/custom_css/models.py:36
|
||||
#: allianceauth/custom_css/models.py:37 allianceauth/custom_css/models.py:47
|
||||
msgid "Custom CSS"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/custom_css/models.py:25
|
||||
msgid "Your custom CSS"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/custom_css/models.py:26
|
||||
msgid "This CSS will be added to the site after the default CSS."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/fleetactivitytracking/auth_hooks.py:10
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/characternotexisting.html:10
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkcreate.html:11
|
||||
@@ -514,8 +551,8 @@ msgstr "Gebruiker"
|
||||
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:37
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:42
|
||||
#: allianceauth/optimer/form.py:13 allianceauth/timerboard/form.py:59
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:16
|
||||
#: allianceauth/optimer/form.py:13 allianceauth/timerboard/form.py:37
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:17
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:9
|
||||
msgid "System"
|
||||
msgstr "Systeem"
|
||||
@@ -837,7 +874,7 @@ msgstr "Aanvrager"
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31
|
||||
#: allianceauth/optimer/templates/optimer/dashboard.ops.html:15
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:15
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:16
|
||||
msgid "Type"
|
||||
msgstr "Type"
|
||||
|
||||
@@ -929,7 +966,7 @@ msgid "Hidden"
|
||||
msgstr "verborgen"
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:45
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:19
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:16
|
||||
msgid "Open"
|
||||
msgstr "Open"
|
||||
|
||||
@@ -1405,16 +1442,16 @@ msgstr "Notificatie"
|
||||
msgid "Super User"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:68
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:70
|
||||
#: allianceauth/templates/allianceauth/top-menu-admin.html:9
|
||||
msgid "Admin"
|
||||
msgstr "Administrator"
|
||||
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:80
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:82
|
||||
msgid "Sign Out"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:84
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:86
|
||||
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:17
|
||||
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:18
|
||||
msgid "Sign In"
|
||||
@@ -1564,7 +1601,7 @@ msgid "Form Up System"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/optimer/templates/optimer/dashboard.ops.html:17
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:17
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:18
|
||||
msgid "EVE Time"
|
||||
msgstr ""
|
||||
|
||||
@@ -1759,17 +1796,17 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:26
|
||||
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:20
|
||||
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:22
|
||||
msgid "Activate"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:32
|
||||
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:32
|
||||
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:34
|
||||
msgid "Reset Password"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:38
|
||||
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:38
|
||||
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:40
|
||||
msgid "Deactivate"
|
||||
msgstr ""
|
||||
|
||||
@@ -1850,12 +1887,12 @@ msgstr ""
|
||||
msgid "Deactivated IPSuite4 account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:26
|
||||
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:28
|
||||
#: allianceauth/services/templates/services/service_password.html:26
|
||||
msgid "Set Password"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:44
|
||||
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:46
|
||||
msgid "Connect"
|
||||
msgstr ""
|
||||
|
||||
@@ -2418,56 +2455,56 @@ msgstr ""
|
||||
msgid "Your Server received an ESI error response code of "
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:11
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:8
|
||||
msgid "Alliance Auth Notifications"
|
||||
msgstr "Alliantie Authenticatie Notificaties"
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:21
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:18
|
||||
msgid "Closed"
|
||||
msgstr "Gesloten"
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:27
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:24
|
||||
msgid "No notifications at this time"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:36
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:33
|
||||
msgid "Powered by GitLab"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:42
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:39
|
||||
msgid "Support Discord"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:59
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:63
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:53
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:57
|
||||
msgid "Software Version"
|
||||
msgstr "Software Versie"
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:66
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:60
|
||||
msgid "Current"
|
||||
msgstr "Huidige"
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:73
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:67
|
||||
msgid "Latest Stable"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:78
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:72
|
||||
msgid "Update available"
|
||||
msgstr "Update Beschikbaar"
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:86
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:80
|
||||
msgid "Latest Pre-Release"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:91
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:85
|
||||
msgid "Pre-Release available"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:102
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:95
|
||||
msgid "Task Queue"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:107
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:100
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
@@ -2475,11 +2512,11 @@ msgid ""
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:123
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:116
|
||||
msgid "running"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:124
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:117
|
||||
msgid "queued"
|
||||
msgstr ""
|
||||
|
||||
@@ -2508,114 +2545,216 @@ msgstr ""
|
||||
msgid "Select Theme"
|
||||
msgstr "Selecteer Thema"
|
||||
|
||||
#: allianceauth/timerboard/form.py:53
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:172
|
||||
msgid "Other"
|
||||
msgstr "Andere"
|
||||
|
||||
#: allianceauth/timerboard/form.py:54
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:38
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:44
|
||||
msgid "Friendly"
|
||||
msgstr "Vriendelijk"
|
||||
|
||||
#: allianceauth/timerboard/form.py:55
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:33
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:38
|
||||
msgid "Hostile"
|
||||
msgstr "Vijandig"
|
||||
|
||||
#: allianceauth/timerboard/form.py:56
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:43
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:50
|
||||
msgid "Neutral"
|
||||
msgstr "Neutraal"
|
||||
|
||||
#: allianceauth/timerboard/form.py:58
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:13
|
||||
#: allianceauth/timerboard/form.py:36
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:14
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:7
|
||||
msgid "Details"
|
||||
msgstr "Details"
|
||||
|
||||
#: allianceauth/timerboard/form.py:60
|
||||
#: allianceauth/timerboard/form.py:38
|
||||
msgid "Planet/Moon"
|
||||
msgstr "Planeet/Maan"
|
||||
|
||||
#: allianceauth/timerboard/form.py:61
|
||||
#: allianceauth/timerboard/form.py:39
|
||||
msgid "Structure Type"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/form.py:62
|
||||
#: allianceauth/timerboard/form.py:40
|
||||
msgid "Timer Type"
|
||||
msgstr "Timer Type"
|
||||
|
||||
#: allianceauth/timerboard/form.py:63
|
||||
#: allianceauth/timerboard/form.py:41
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:8
|
||||
msgid "Objective"
|
||||
msgstr "Doel"
|
||||
|
||||
#: allianceauth/timerboard/form.py:64
|
||||
#: allianceauth/timerboard/form.py:42
|
||||
msgid "Absolute Timer"
|
||||
msgstr "Absolute Timer"
|
||||
|
||||
#: allianceauth/timerboard/form.py:65
|
||||
#: allianceauth/timerboard/form.py:43
|
||||
msgid "Date and Time"
|
||||
msgstr "Datum en Tijd"
|
||||
|
||||
#: allianceauth/timerboard/form.py:66
|
||||
#: allianceauth/timerboard/form.py:44
|
||||
msgid "Days Remaining"
|
||||
msgstr "Resterende Dagen"
|
||||
|
||||
#: allianceauth/timerboard/form.py:67
|
||||
#: allianceauth/timerboard/form.py:45
|
||||
msgid "Hours Remaining"
|
||||
msgstr "Resterende Uren"
|
||||
|
||||
#: allianceauth/timerboard/form.py:69
|
||||
#: allianceauth/timerboard/form.py:47
|
||||
msgid "Minutes Remaining"
|
||||
msgstr "Resterende Minuten"
|
||||
|
||||
#: allianceauth/timerboard/form.py:71
|
||||
#: allianceauth/timerboard/form.py:48
|
||||
msgid "Important"
|
||||
msgstr "Belangrijk"
|
||||
|
||||
#: allianceauth/timerboard/form.py:72
|
||||
#: allianceauth/timerboard/form.py:49
|
||||
msgid "Corp-Restricted"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:14
|
||||
#: allianceauth/timerboard/models.py:15
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:39
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:36
|
||||
msgid "Friendly"
|
||||
msgstr "Vriendelijk"
|
||||
|
||||
#: allianceauth/timerboard/models.py:16
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:34
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:34
|
||||
msgid "Hostile"
|
||||
msgstr "Vijandig"
|
||||
|
||||
#: allianceauth/timerboard/models.py:17
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:44
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:38
|
||||
msgid "Neutral"
|
||||
msgstr "Neutraal"
|
||||
|
||||
#: allianceauth/timerboard/models.py:24
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:48
|
||||
msgid "POCO"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:25
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:50
|
||||
msgid "Orbital Skyhook"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:26
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:52
|
||||
msgid "I-HUB"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:27
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:55
|
||||
msgid "TCU"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:28
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:57
|
||||
msgid "POS [S]"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:29
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:59
|
||||
msgid "POS [M]"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:30
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:61
|
||||
msgid "POS [L]"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:31
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:63
|
||||
msgid "Astrahus"
|
||||
msgstr "Astrahus"
|
||||
|
||||
#: allianceauth/timerboard/models.py:32
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:65
|
||||
msgid "Fortizar"
|
||||
msgstr "Fortizar"
|
||||
|
||||
#: allianceauth/timerboard/models.py:33
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:67
|
||||
msgid "Keepstar"
|
||||
msgstr "Keepstar"
|
||||
|
||||
#: allianceauth/timerboard/models.py:34
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:69
|
||||
msgid "Raitaru"
|
||||
msgstr "Raitaru"
|
||||
|
||||
#: allianceauth/timerboard/models.py:35
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:71
|
||||
msgid "Azbel"
|
||||
msgstr "Sotiyo"
|
||||
|
||||
#: allianceauth/timerboard/models.py:36
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:73
|
||||
msgid "Sotiyo"
|
||||
msgstr "Sotiyo"
|
||||
|
||||
#: allianceauth/timerboard/models.py:37
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:75
|
||||
msgid "Athanor"
|
||||
msgstr "Athanor"
|
||||
|
||||
#: allianceauth/timerboard/models.py:38
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:77
|
||||
msgid "Tatara"
|
||||
msgstr "Tatara"
|
||||
|
||||
#: allianceauth/timerboard/models.py:39
|
||||
msgid "Pharolux Cyno Beacon"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:40
|
||||
msgid "Tenebrex Cyno Jammer"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:41
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:83
|
||||
msgid "Ansiblex Jump Gate"
|
||||
msgstr "Ansiblex Jump Gate"
|
||||
|
||||
#: allianceauth/timerboard/models.py:42
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:85
|
||||
msgid "Moon Mining Cycle"
|
||||
msgstr "Maan mijn Cyclus"
|
||||
|
||||
#: allianceauth/timerboard/models.py:43
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:87
|
||||
msgid "Metenox Moon Drill"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:44
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:89
|
||||
msgid "Other"
|
||||
msgstr "Andere"
|
||||
|
||||
#: allianceauth/timerboard/models.py:51
|
||||
msgid "Not Specified"
|
||||
msgstr "Niet gespecifieerd"
|
||||
|
||||
#: allianceauth/timerboard/models.py:15
|
||||
#: allianceauth/timerboard/models.py:52
|
||||
msgid "Shield"
|
||||
msgstr "Schild"
|
||||
|
||||
#: allianceauth/timerboard/models.py:16
|
||||
#: allianceauth/timerboard/models.py:53
|
||||
msgid "Armor"
|
||||
msgstr "Pantser"
|
||||
|
||||
#: allianceauth/timerboard/models.py:17
|
||||
#: allianceauth/timerboard/models.py:54
|
||||
msgid "Hull"
|
||||
msgstr "Romp"
|
||||
|
||||
#: allianceauth/timerboard/models.py:18
|
||||
#: allianceauth/timerboard/models.py:55
|
||||
msgid "Final"
|
||||
msgstr "Laatste"
|
||||
|
||||
#: allianceauth/timerboard/models.py:19
|
||||
#: allianceauth/timerboard/models.py:56
|
||||
msgid "Anchoring"
|
||||
msgstr "Ankeren"
|
||||
|
||||
#: allianceauth/timerboard/models.py:20
|
||||
#: allianceauth/timerboard/models.py:57
|
||||
msgid "Unanchoring"
|
||||
msgstr "ontankeren"
|
||||
|
||||
#: allianceauth/timerboard/models.py:58
|
||||
msgid "Abandoned"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:7
|
||||
#: allianceauth/timerboard/templates/timerboard/view.html:53
|
||||
msgid "Upcoming Timers"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:14
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:15
|
||||
msgid "Timer"
|
||||
msgstr "Timer"
|
||||
|
||||
@@ -2659,78 +2798,14 @@ msgstr ""
|
||||
msgid "Structure"
|
||||
msgstr "Constructie"
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:64
|
||||
msgid "POCO"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:70
|
||||
msgid "I-HUB"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:76
|
||||
msgid "TCU"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:82
|
||||
msgid "POS [S]"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:88
|
||||
msgid "POS [M]"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:94
|
||||
msgid "POS [L]"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:100
|
||||
msgid "Astrahus"
|
||||
msgstr "Astrahus"
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:106
|
||||
msgid "Fortizar"
|
||||
msgstr "Fortizar"
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:112
|
||||
msgid "Keepstar"
|
||||
msgstr "Keepstar"
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:118
|
||||
msgid "Raitaru"
|
||||
msgstr "Raitaru"
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:124
|
||||
msgid "Azbel"
|
||||
msgstr "Sotiyo"
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:130
|
||||
msgid "Sotiyo"
|
||||
msgstr "Sotiyo"
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:136
|
||||
msgid "Athanor"
|
||||
msgstr "Athanor"
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:142
|
||||
msgid "Tatara"
|
||||
msgstr "Tatara"
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:148
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:79
|
||||
msgid "Cyno Beacon"
|
||||
msgstr "Cyno Beacon"
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:154
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:81
|
||||
msgid "Cyno Jammer"
|
||||
msgstr "Cyno Jammer"
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:160
|
||||
msgid "Ansiblex Jump Gate"
|
||||
msgstr "Ansiblex Jump Gate"
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:166
|
||||
msgid "Moon Mining Cycle"
|
||||
msgstr "Maan mijn Cyclus"
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/view.html:9
|
||||
msgid "Structure Timer Management"
|
||||
msgstr ""
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -11,8 +11,8 @@
|
||||
data-bs-target="#{{ item.html_id }}"
|
||||
aria-expanded="false"
|
||||
aria-controls=""
|
||||
{% endif %}>
|
||||
</i>
|
||||
{% endif %}
|
||||
></i>
|
||||
<a
|
||||
class="nav-link flex-fill align-self-center me-auto {% if item.navactive %}{% navactive request item.navactive|join:' ' %}{% endif %}"
|
||||
{% if item.is_folder %}
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
{% if item.is_folder %}
|
||||
<span
|
||||
class="pill m-2 align-self-center collapsed"
|
||||
class="pill m-2 align-self-center"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#{{ item.html_id }}"
|
||||
|
||||
@@ -60,15 +60,17 @@
|
||||
<li>
|
||||
<a class="dropdown-item" href="https://discord.gg/fjnHAmk" title="Alliance Auth Discord"><i class="fa-brands fa-discord fa-fw"></i> Alliance Auth Discord</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="https://gitlab.com/allianceauth/allianceauth" title="Alliance Auth Git"><i class="fa-brands fa-gitlab fa-fw"></i> Alliance Auth Git</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="{% url 'admin:index' %}">
|
||||
<i class="fa-solid fa-gear fa-fw"></i> {% translate "Admin" %}
|
||||
</a>
|
||||
<a class="dropdown-item" href="https://gitlab.com/allianceauth/allianceauth" title="Alliance Auth Git"><i class="fa-brands fa-gitlab fa-fw"></i> Alliance Auth Git</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if user.is_staff %}
|
||||
<li>
|
||||
<a class="dropdown-item" href="{% url 'admin:index' %}">
|
||||
<i class="fa-solid fa-gear fa-fw"></i> {% translate "Admin" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
{% if user.is_authenticated %}
|
||||
<li>
|
||||
|
||||
@@ -1 +1,21 @@
|
||||
"""
|
||||
Example
|
||||
=======
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from allianceauth.notifications.models import Notification
|
||||
|
||||
|
||||
def notify_user_view(request):
|
||||
'''Simple view sending a notification to the user'''
|
||||
|
||||
Notification.objects.notify_user(
|
||||
user=request.user,
|
||||
title="Some title",
|
||||
message="Some message",
|
||||
level=Notification.Level.INFO,
|
||||
)
|
||||
|
||||
"""
|
||||
from .core import notify # noqa: F401
|
||||
|
||||
@@ -15,7 +15,7 @@ class OpForm(forms.Form):
|
||||
operation_name = forms.CharField(max_length=254, required=True, label=_("Operation Name"))
|
||||
type = forms.CharField(required=False, label=_("Operation Type"))
|
||||
fc = forms.CharField(max_length=254, required=True, label=_("Fleet Commander"))
|
||||
duration = forms.CharField(max_length=254, required=True, label=_("Duration"))
|
||||
duration = forms.CharField(max_length=25, required=True, label=_("Duration"))
|
||||
description = forms.CharField(
|
||||
widget=forms.Textarea(attrs={"rows": 10, "cols": 20, "input_type": "textarea"}),
|
||||
required=False,
|
||||
|
||||
@@ -1,41 +1,40 @@
|
||||
{% load i18n %}
|
||||
{% load evelinks %}
|
||||
|
||||
<div class="col-12 align-self-stretch py-2">
|
||||
<div class="col-12 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title text-center">{% translate "Upcoming Fleets" %}</h4>
|
||||
{% translate "Upcoming Fleets" as widget_title %}
|
||||
{% include "framework/dashboard/widget-title.html" with title=widget_title %}
|
||||
|
||||
<div class="card-body">
|
||||
<div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">{% translate "Operation" %}</th>
|
||||
<th class="text-center">{% translate "Type" %}</th>
|
||||
<th class="text-center">{% translate "Form Up System" %}</th>
|
||||
<th class="text-center">{% translate "EVE Time" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for ops in timers %}
|
||||
<tr>
|
||||
<th class="text-center">{% translate "Operation" %}</th>
|
||||
<th class="text-center">{% translate "Type" %}</th>
|
||||
<th class="text-center">{% translate "Form Up System" %}</th>
|
||||
<th class="text-center">{% translate "EVE Time" %}</th>
|
||||
<td class="text-center">
|
||||
{{ ops.operation_name }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{{ ops.type }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="{{ ops.system|dotlan_solar_system_url }}">{{ ops.system }}</a>
|
||||
</td>
|
||||
<td class="text-center" nowrap>{{ ops.start | date:"Y-m-d H:i" }}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for ops in timers %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
{{ ops.operation_name }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{{ ops.type }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="{{ ops.system|dotlan_solar_system_url }}">{{ ops.system }}</a>
|
||||
</td>
|
||||
<td class="text-center" nowrap>{{ ops.start | date:"Y-m-d H:i" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
|
||||
{% load aa_i18n %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}
|
||||
@@ -57,11 +58,14 @@
|
||||
{% include "bundles/datatables-js-bs5.html" %}
|
||||
{% include "bundles/filterdropdown-js.html" %}
|
||||
|
||||
{% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
|
||||
|
||||
<script>
|
||||
$(document).ready(() => {
|
||||
const groupColumn = 0;
|
||||
|
||||
$('#tab_permissions_audit').DataTable({
|
||||
language: {url: '{{ DT_LANG_PATH }}'},
|
||||
columnDefs: [{
|
||||
"visible": false,
|
||||
"targets": groupColumn
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
|
||||
{% load aa_i18n %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}
|
||||
@@ -62,11 +63,14 @@
|
||||
{% include "bundles/datatables-js-bs5.html" %}
|
||||
{% include "bundles/filterdropdown-js.html" %}
|
||||
|
||||
{% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
|
||||
|
||||
<script>
|
||||
$(document).ready(() => {
|
||||
const groupColumn = 0;
|
||||
|
||||
$('#tab_permissions_overview').DataTable({
|
||||
language: {url: '{{ DT_LANG_PATH }}'},
|
||||
columnDefs: [{
|
||||
"visible": false,
|
||||
"targets": groupColumn
|
||||
|
||||
@@ -12,6 +12,8 @@ from celery.schedules import crontab
|
||||
|
||||
from django.contrib import messages
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'allianceauth', # needs to be on top of this list to support favicons in Django admin (see https://gitlab.com/allianceauth/allianceauth/-/issues/1301)
|
||||
'django.contrib.admin',
|
||||
@@ -22,6 +24,7 @@ INSTALLED_APPS = [
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.humanize',
|
||||
'django_celery_beat',
|
||||
'solo',
|
||||
'bootstrapform',
|
||||
'django_bootstrap5', # https://github.com/zostera/django-bootstrap5
|
||||
'sortedm2m',
|
||||
@@ -39,13 +42,17 @@ INSTALLED_APPS = [
|
||||
'allianceauth.theme.darkly',
|
||||
'allianceauth.theme.flatly',
|
||||
'allianceauth.theme.materia',
|
||||
"allianceauth.custom_css",
|
||||
'allianceauth.crontab',
|
||||
'sri',
|
||||
]
|
||||
|
||||
SRI_ALGORITHM = "sha512"
|
||||
SECRET_KEY = "wow I'm a really bad default secret key"
|
||||
|
||||
# Celery configuration
|
||||
BROKER_URL = 'redis://localhost:6379/0'
|
||||
CELERYBEAT_SCHEDULER = "django_celery_beat.schedulers.DatabaseScheduler"
|
||||
CELERYBEAT_SCHEDULER = "allianceauth.crontab.schedulers.OffsetDatabaseScheduler"
|
||||
CELERYBEAT_SCHEDULE = {
|
||||
'esi_cleanup_callbackredirect': {
|
||||
'task': 'esi.tasks.cleanup_callbackredirect',
|
||||
@@ -58,10 +65,12 @@ CELERYBEAT_SCHEDULE = {
|
||||
'run_model_update': {
|
||||
'task': 'allianceauth.eveonline.tasks.run_model_update',
|
||||
'schedule': crontab(minute='0', hour="*/6"),
|
||||
'apply_offset': True
|
||||
},
|
||||
'check_all_character_ownership': {
|
||||
'task': 'allianceauth.authentication.tasks.check_all_character_ownership',
|
||||
'schedule': crontab(minute='0', hour='*/4'),
|
||||
'apply_offset': True
|
||||
},
|
||||
'analytics_daily_stats': {
|
||||
'task': 'allianceauth.analytics.tasks.analytics_daily_stats',
|
||||
@@ -69,6 +78,7 @@ CELERYBEAT_SCHEDULE = {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
BASE_DIR = os.path.dirname(PROJECT_DIR)
|
||||
@@ -91,20 +101,62 @@ LOCALE_PATHS = (
|
||||
os.path.join(BASE_DIR, 'locale/'),
|
||||
)
|
||||
|
||||
LANGUAGES = (
|
||||
("en", "English"),
|
||||
("de", "German"),
|
||||
("es", "Spanish"),
|
||||
("zh-hans", "Chinese Simplified"),
|
||||
("ru", "Russian"),
|
||||
("ko", "Korean"),
|
||||
("fr", "French"),
|
||||
("ja", "Japanese"),
|
||||
("it", "Italian"),
|
||||
("uk", "Ukrainian"),
|
||||
("pl", "Polish"),
|
||||
LANGUAGES = ( # Sorted by Language Code alphabetical order + English at top
|
||||
("en", _("English")),
|
||||
# ("cs-cz", _("Czech")), #Not yet at 50% translated
|
||||
("de", _("German")),
|
||||
("es", _("Spanish")),
|
||||
("it-it", _("Italian")),
|
||||
("ja", _("Japanese")),
|
||||
("ko-kr", _("Korean")),
|
||||
("fr-fr", _("French")),
|
||||
("nl-nl", _("Dutch")),
|
||||
("pl-pl", _("Polish")),
|
||||
("ru", _("Russian")),
|
||||
("uk", _("Ukrainian")),
|
||||
("zh-hans", _("Simplified Chinese")),
|
||||
)
|
||||
|
||||
# Django's language codes are different from some of the libraries we use,
|
||||
# so we need to map them.
|
||||
# When adding a new language, please remember to add it to the mapping
|
||||
# and add the language files to their respective directories under `allianceauth/static/allianceauth/libs/`.
|
||||
LANGUAGE_MAPPING = {
|
||||
# See https://github.com/DataTables/Plugins/tree/master/i18n for available languages
|
||||
# (We use the JSON files)
|
||||
# `allianceauth/static/allianceauth/libs/DataTables/Plugins/{version}/i18n/` for the files
|
||||
"DataTables": {
|
||||
"cs-cz": "cs",
|
||||
"de": "de-DE",
|
||||
"es": "es-ES",
|
||||
"fr-fr": "fr-FR",
|
||||
"it-it": "it-IT",
|
||||
"ja": "ja",
|
||||
"ko-kr": "ko",
|
||||
"nl-nl": "nl-NL",
|
||||
"pl-pl": "pl",
|
||||
"ru": "ru",
|
||||
"uk": "uk",
|
||||
"zh-hans": "zh-HANT",
|
||||
},
|
||||
# See https://github.com/moment/moment/tree/master/locale for available languages
|
||||
# `allianceauth/static/allianceauth/libs/moment.js/{version}/locale/` for the files
|
||||
"MomentJS": {
|
||||
"cs-cz": "cs",
|
||||
"de": "de",
|
||||
"es": "es",
|
||||
"fr-fr": "fr",
|
||||
"it-it": "it",
|
||||
"ja": "ja",
|
||||
"ko-kr": "ko",
|
||||
"nl-nl": "nl",
|
||||
"pl-pl": "pl",
|
||||
"ru": "ru",
|
||||
"uk": "uk",
|
||||
"zh-hans": "zh-cn",
|
||||
},
|
||||
}
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# Generated by Django 4.2.16 on 2024-10-07 13:39
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("mumble", "0012_mumble_client_info"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="mumbleuser",
|
||||
options={
|
||||
"permissions": (
|
||||
("access_mumble", "Can access the Mumble service"),
|
||||
(
|
||||
"view_connection_history",
|
||||
"Can access the connection history of the Mumble service",
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="mumbleuser",
|
||||
name="release",
|
||||
field=models.TextField(
|
||||
blank=True,
|
||||
editable=False,
|
||||
help_text="Client release. For official releases, this equals the version. For snapshots and git compiles, this will be something else.",
|
||||
max_length=254,
|
||||
null=True,
|
||||
verbose_name="Mumble Release",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -90,7 +90,7 @@ class MumbleUser(AbstractServiceModel):
|
||||
blank=True,
|
||||
null=True,
|
||||
editable=False,
|
||||
help_text="The Mumble Release the user last authenticated with"
|
||||
help_text="Client release. For official releases, this equals the version. For snapshots and git compiles, this will be something else."
|
||||
)
|
||||
version = models.IntegerField(
|
||||
verbose_name="Mumble Version",
|
||||
@@ -159,4 +159,5 @@ class MumbleUser(AbstractServiceModel):
|
||||
class Meta:
|
||||
permissions = (
|
||||
("access_mumble", "Can access the Mumble service"),
|
||||
("view_connection_history", "Can access the connection history of the Mumble service"),
|
||||
)
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
|
||||
{% load aa_i18n %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}
|
||||
{% translate "Mumble" %}
|
||||
{% endblock page_title %}
|
||||
|
||||
{% block header_nav_brand %}
|
||||
{% trans "Mumble History" %} - {{ mumble_url }}
|
||||
{% endblock header_nav_brand %}
|
||||
|
||||
{% block header_nav_collapse_left %}
|
||||
{% endblock header_nav_collapse_left %}
|
||||
|
||||
{% block header_nav_collapse_right %}
|
||||
{% endblock header_nav_collapse_right %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card col-lg-12 mb-3">
|
||||
<div class="card-header">
|
||||
<span class="card-title">{% translate "Server Connection History" %}</span>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table w-100" id="table-mumble-connection-history">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-start">{% translate "User" %}</th>
|
||||
<th class="text-start">{% translate "Displayed Name" %}</th>
|
||||
<th class="text-start">{% translate "Release" %}</th>
|
||||
<th class="text-start">{% translate "Version" %}</th>
|
||||
<th class="text-end">{% translate "Last Connect" %}</th>
|
||||
<th class="text-end">{% translate "Last Disconnect" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-title">{% translate "Server Connection Breakdown" %}</span>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<canvas id="pieChart"></canvas> <!-- Canvas element for the pie chart -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-title">{% translate "Server Connection Breakdown" %}</span>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table w-100" id="table-mumble-connection-stats">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-start">{% translate "Version" %}</th>
|
||||
<th class="text-end">{% translate "Number" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
{% block extra_javascript %}
|
||||
{% include "bundles/datatables-js-bs5.html" %}
|
||||
{% include "bundles/filterdropdown-js.html" %}
|
||||
{% include "bundles/chart-js.html" %}
|
||||
{% include "bundles/moment-js.html" with locale=True %}
|
||||
|
||||
{% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
const MUMBLESTATS_DATETIME_FORMAT = 'LLL';
|
||||
|
||||
'use strict';
|
||||
|
||||
$("#table-mumble-connection-history").DataTable({
|
||||
language: {url: '{{ DT_LANG_PATH }}'},
|
||||
ajax: {
|
||||
url: '{% url "mumble:connection_history_data" %}',
|
||||
dataSrc: 'connection_history_data',
|
||||
},
|
||||
columns: [
|
||||
{ data: 'user' },
|
||||
{ data: 'display_name' },
|
||||
{ data: 'release' },
|
||||
{ data: 'version' },
|
||||
{
|
||||
data: 'last_connect',
|
||||
render: {
|
||||
_: (data) => {
|
||||
return data === null
|
||||
? ''
|
||||
: moment(data)
|
||||
.utc()
|
||||
.format(MUMBLESTATS_DATETIME_FORMAT);
|
||||
},
|
||||
sort: (data) => {
|
||||
return data === null ? '' : data;
|
||||
}
|
||||
},
|
||||
className: 'text-end',
|
||||
},
|
||||
{
|
||||
data: 'last_disconnect',
|
||||
render: {
|
||||
_: (data) => {
|
||||
return data === null
|
||||
? ''
|
||||
: moment(data)
|
||||
.utc()
|
||||
.format(MUMBLESTATS_DATETIME_FORMAT);
|
||||
},
|
||||
sort: (data) => {
|
||||
return data === null ? '' : data;
|
||||
}
|
||||
},
|
||||
className: 'text-end',
|
||||
},
|
||||
],
|
||||
order: [[4, 'desc']],
|
||||
processing: true,
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
filterDropDown: {
|
||||
columns: [
|
||||
{
|
||||
idx: 2,
|
||||
},
|
||||
{
|
||||
idx: 3,
|
||||
},
|
||||
],
|
||||
bootstrap: true,
|
||||
bootstrap_version: 5,
|
||||
},
|
||||
});
|
||||
|
||||
$("#table-mumble-connection-stats").DataTable({
|
||||
language: {url: '{{ DT_LANG_PATH }}'},
|
||||
ajax: {
|
||||
url: '{% url "mumble:release_counts_data" %}',
|
||||
dataSrc: 'release_counts_data',
|
||||
},
|
||||
columns: [
|
||||
{ data: 'release' },
|
||||
{ data: 'user_count', className: 'text-end' },
|
||||
],
|
||||
order: [[1, 'desc']],
|
||||
processing: true,
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
});
|
||||
|
||||
// Initialize empty Pie chart
|
||||
const elementBody = document.querySelector('body');
|
||||
const elementBodyCss = getComputedStyle(elementBody);
|
||||
const ctx = document.getElementById('pieChart').getContext('2d');
|
||||
const pieChart = new Chart(ctx, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: [], // Initially empty
|
||||
datasets: [
|
||||
{
|
||||
label: 'Server Connection Breakdown',
|
||||
data: [], // Initially empty
|
||||
backgroundColor: [
|
||||
'rgba(255, 99, 132, 0.2)',
|
||||
'rgba(54, 162, 235, 0.2)',
|
||||
'rgba(255, 206, 86, 0.2)'
|
||||
],
|
||||
borderColor: [
|
||||
'rgba(255, 99, 132, 1)',
|
||||
'rgba(54, 162, 235, 1)',
|
||||
'rgba(255, 206, 86, 1)'
|
||||
],
|
||||
borderWidth: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Chart.defaults.color = elementBodyCss.color;
|
||||
|
||||
// AJAX call to dynamically update the chart
|
||||
$.ajax({
|
||||
url: '{% url "mumble:release_pie_chart_data" %}', // Your Django view URL that returns chart data
|
||||
method: "GET",
|
||||
success: (data) => {
|
||||
// Replace chart data with the data from the AJAX response
|
||||
pieChart.data.labels = data.labels; // Set the new labels
|
||||
pieChart.data.datasets[0].data = data.values; // Set the new values
|
||||
|
||||
// Update the chart to reflect the new data
|
||||
pieChart.update();
|
||||
},
|
||||
error: (xhr, status, error) => {
|
||||
console.error('Error fetching pie chart data:', status, error);
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock extra_javascript %}
|
||||
|
||||
{% block extra_css %}
|
||||
{% include "bundles/datatables-css-bs5.html" %}
|
||||
{% endblock extra_css %}
|
||||
@@ -7,7 +7,9 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block url %}
|
||||
<a href="{{ service_url }}">{{ service_url }}</a>
|
||||
{% if username != '' %}
|
||||
<a href="mumble://{{ connect_url }}">{{ service_url }}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block user %}
|
||||
@@ -46,4 +48,9 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if request.user.is_superuser %}
|
||||
<a class="btn btn-primary" type="button" id="btnMumbleConnectionHistory" href="{% url 'mumble:connection_history' %}" title="{% translate 'Mumble Connection History' %}">
|
||||
<i class="fa-solid fa-clock-rotate-left"></i> History
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -10,6 +10,10 @@ module_urls = [
|
||||
path('deactivate/', views.DeleteMumbleView.as_view(), name='deactivate'),
|
||||
path('reset_password/', views.ResetPasswordMumbleView.as_view(), name='reset_password'),
|
||||
path('set_password/', views.SetPasswordMumbleView.as_view(), name='set_password'),
|
||||
path('connection_history/', views.connection_history, name="connection_history"),
|
||||
path('ajax/connection_history_data', views.connection_history_data, name="connection_history_data"),
|
||||
path('ajax/release_counts_data', views.release_counts_data, name="release_counts_data"),
|
||||
path('ajax/release_pie_chart_data', views.release_pie_chart_data, name="release_pie_chart_data"),
|
||||
]
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
@@ -3,6 +3,11 @@ import logging
|
||||
from allianceauth.services.forms import ServicePasswordModelForm
|
||||
from allianceauth.services.abstract import BaseCreatePasswordServiceAccountView, BaseDeactivateServiceAccountView, \
|
||||
BaseResetPasswordServiceAccountView, BaseSetPasswordServiceAccountView
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.db.models import Count
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.shortcuts import render
|
||||
|
||||
from .models import MumbleUser
|
||||
|
||||
@@ -35,3 +40,51 @@ class ResetPasswordMumbleView(MumbleViewMixin, BaseResetPasswordServiceAccountVi
|
||||
|
||||
class SetPasswordMumbleView(MumbleViewMixin, BaseSetPasswordServiceAccountView):
|
||||
form_class = MumblePasswordForm
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('mumble.view_connection_history')
|
||||
def connection_history(request) -> HttpResponse:
|
||||
|
||||
context = {
|
||||
"mumble_url": settings.MUMBLE_URL,
|
||||
}
|
||||
|
||||
return render(request, 'services/mumble/mumble_connection_history.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("mumble.view_connection_history")
|
||||
def connection_history_data(request) -> JsonResponse:
|
||||
connection_history_data = MumbleUser.objects.all(
|
||||
).values(
|
||||
'user',
|
||||
'display_name',
|
||||
'release',
|
||||
'version',
|
||||
'last_connect',
|
||||
'last_disconnect',
|
||||
)
|
||||
|
||||
return JsonResponse({"connection_history_data": list(connection_history_data)})
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("mumble.view_connection_history")
|
||||
def release_counts_data(request) -> JsonResponse:
|
||||
release_counts_data = MumbleUser.objects.values('release').annotate(user_count=Count('user_id')).order_by('release')
|
||||
|
||||
return JsonResponse({
|
||||
"release_counts_data": list(release_counts_data),
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("mumble.view_connection_history")
|
||||
def release_pie_chart_data(request) -> JsonResponse:
|
||||
release_counts = MumbleUser.objects.values('release').annotate(user_count=Count('user_id')).order_by('release')
|
||||
|
||||
return JsonResponse({
|
||||
"labels": list(release_counts.values_list("release", flat=True)),
|
||||
"values": list(release_counts.values_list("user_count", flat=True)),
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load aa_i18n %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load humanize %}
|
||||
@@ -168,6 +169,8 @@ ESC to cancel{% endblocktranslate %}" id="blah"></i></th>
|
||||
{% include "bundles/moment-js.html" %}
|
||||
{% include "bundles/clipboard-js.html" %}
|
||||
|
||||
{% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
|
||||
|
||||
<script>
|
||||
const clipboard = new ClipboardJS('.copy-text-fa-icon');
|
||||
|
||||
@@ -237,6 +240,7 @@ ESC to cancel{% endblocktranslate %}" id="blah"></i></th>
|
||||
});
|
||||
|
||||
elementTableSrpList.DataTable({
|
||||
language: {url: '{{ DT_LANG_PATH }}'},
|
||||
"order": [[ 6, "asc" ]],
|
||||
"paging": false,
|
||||
"columnDefs": [
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
{
|
||||
"emptyTable": "Tabulka neobsahuje žádná data",
|
||||
"info": "Zobrazuji _START_ až _END_ z celkem _TOTAL_ záznamů",
|
||||
"infoEmpty": "Zobrazuji 0 až 0 z 0 záznamů",
|
||||
"infoFiltered": "(filtrováno z celkem _MAX_ záznamů)",
|
||||
"loadingRecords": "Načítám...",
|
||||
"zeroRecords": "Žádné záznamy nebyly nalezeny",
|
||||
"paginate": {
|
||||
"first": "První",
|
||||
"last": "Poslední",
|
||||
"next": "Další",
|
||||
"previous": "Předchozí"
|
||||
},
|
||||
"searchBuilder": {
|
||||
"add": "Přidat podmínku",
|
||||
"clearAll": "Smazat vše",
|
||||
"condition": "Podmínka",
|
||||
"conditions": {
|
||||
"date": {
|
||||
"after": "po",
|
||||
"before": "před",
|
||||
"between": "mezi",
|
||||
"empty": "prázdné",
|
||||
"equals": "rovno",
|
||||
"not": "není",
|
||||
"notBetween": "není mezi",
|
||||
"notEmpty": "není prázdné"
|
||||
},
|
||||
"number": {
|
||||
"between": "mezi",
|
||||
"empty": "prázdné",
|
||||
"equals": "rovno",
|
||||
"gt": "větší",
|
||||
"gte": "rovno a větší",
|
||||
"lt": "menší",
|
||||
"lte": "rovno a menší",
|
||||
"not": "není",
|
||||
"notBetween": "není mezi",
|
||||
"notEmpty": "není prázdné"
|
||||
},
|
||||
"string": {
|
||||
"contains": "obsahuje",
|
||||
"empty": "prázdné",
|
||||
"endsWith": "končí na",
|
||||
"equals": "rovno",
|
||||
"not": "není",
|
||||
"notEmpty": "není prázdné",
|
||||
"startsWith": "začíná na",
|
||||
"notContains": "Podmínka",
|
||||
"notStartsWith": "Nezačíná",
|
||||
"notEndsWith": "Nekončí"
|
||||
},
|
||||
"array": {
|
||||
"equals": "rovno",
|
||||
"empty": "prázdné",
|
||||
"contains": "obsahuje",
|
||||
"not": "není",
|
||||
"notEmpty": "není prázdné",
|
||||
"without": "neobsahuje"
|
||||
}
|
||||
},
|
||||
"data": "Sloupec",
|
||||
"logicAnd": "A",
|
||||
"logicOr": "NEBO",
|
||||
"title": {
|
||||
"0": "Rozšířený filtr",
|
||||
"_": "Rozšířený filtr (%d)"
|
||||
},
|
||||
"value": "Hodnota",
|
||||
"button": {
|
||||
"0": "Rozšířený filtr",
|
||||
"_": "Rozšířený filtr (%d)"
|
||||
},
|
||||
"deleteTitle": "Smazat filtrovací pravidlo",
|
||||
"leftTitle": "Zrušení odsazení podmínky",
|
||||
"rightTitle": "Odsazení podmínky"
|
||||
},
|
||||
"autoFill": {
|
||||
"cancel": "Zrušit",
|
||||
"fill": "Vyplň všechny buňky textem <i>%d<i><\/i><\/i>",
|
||||
"fillHorizontal": "Vyplň všechny buňky horizontálně",
|
||||
"fillVertical": "Vyplň všechny buňky vertikálně",
|
||||
"info": "Příklad automatického vyplňování"
|
||||
},
|
||||
"buttons": {
|
||||
"collection": "Kolekce <span class=\"ui-button-icon-primary ui-icon ui-icon-triangle-1-s\"><\/span>",
|
||||
"copy": "Kopírovat",
|
||||
"copyTitle": "Kopírovat do schránky",
|
||||
"csv": "CSV",
|
||||
"excel": "Excel",
|
||||
"pageLength": {
|
||||
"-1": "Zobrazit všechny řádky",
|
||||
"_": "Zobrazit %d řádků",
|
||||
"1": "Zobraz 1 řádek"
|
||||
},
|
||||
"pdf": "PDF",
|
||||
"print": "Tisknout",
|
||||
"colvis": "Viditelnost sloupců",
|
||||
"colvisRestore": "Resetovat sloupce",
|
||||
"copyKeys": "Zmáčkněte ctrl or u2318 + C pro zkopírování dat. Pro zrušení klikněte na tuto zprávu nebo zmáčkněte esc..",
|
||||
"copySuccess": {
|
||||
"1": "Zkopírován 1 řádek do schránky",
|
||||
"_": "Zkopírováno %d řádků do schránky"
|
||||
},
|
||||
"createState": "Vytvořit Stav",
|
||||
"removeAllStates": "Vymazat všechny Stavy",
|
||||
"removeState": "Odstranit",
|
||||
"renameState": "Odstranit",
|
||||
"savedStates": "Uložit Stavy",
|
||||
"stateRestore": "Stav %d",
|
||||
"updateState": "Aktualizovat"
|
||||
},
|
||||
"searchPanes": {
|
||||
"clearMessage": "Smazat vše",
|
||||
"collapse": {
|
||||
"0": "Vyhledávací Panely",
|
||||
"_": "Vyhledávací Panely (%d)"
|
||||
},
|
||||
"count": "{total}",
|
||||
"countFiltered": "{shown} ({total})",
|
||||
"emptyPanes": "Žádné Vyhledávací Panely",
|
||||
"loadMessage": "Načítám Vyhledávací Panely",
|
||||
"title": "Aktivních filtrů - %d",
|
||||
"showMessage": "Zobrazit Vše",
|
||||
"collapseMessage": "Sbalit Vše"
|
||||
},
|
||||
"select": {
|
||||
"cells": {
|
||||
"1": "Vybrán 1 záznam",
|
||||
"_": "Vybráno %d záznamů"
|
||||
},
|
||||
"columns": {
|
||||
"1": "Vybrán 1 sloupec",
|
||||
"_": "Vybráno %d sloupců"
|
||||
},
|
||||
"rows": {
|
||||
"1": "Vybrán 1 řádek",
|
||||
"_": "Vybráno %d řádků"
|
||||
}
|
||||
},
|
||||
"aria": {
|
||||
"sortAscending": "Aktivujte pro seřazení vzestupně",
|
||||
"sortDescending": "Aktivujte pro seřazení sestupně"
|
||||
},
|
||||
"lengthMenu": "Zobrazit _MENU_ výsledků",
|
||||
"processing": "Zpracovávání...",
|
||||
"search": "Vyhledávání:",
|
||||
"datetime": {
|
||||
"previous": "Předchozí",
|
||||
"next": "Další",
|
||||
"hours": "Hodiny",
|
||||
"minutes": "Minuty",
|
||||
"seconds": "Vteřiny",
|
||||
"unknown": "-",
|
||||
"amPm": [
|
||||
"Dopoledne",
|
||||
"Odpoledne"
|
||||
],
|
||||
"months": [
|
||||
"Leden",
|
||||
"Únor",
|
||||
"Březen",
|
||||
"Duben",
|
||||
"Květen",
|
||||
"Červen",
|
||||
"Červenec",
|
||||
"Srpen",
|
||||
"Září",
|
||||
"Říjen",
|
||||
"Listopad",
|
||||
"Prosinec"
|
||||
],
|
||||
"weekdays": [
|
||||
"Ne",
|
||||
"Po",
|
||||
"Út",
|
||||
"St",
|
||||
"Čt",
|
||||
"Pá",
|
||||
"So"
|
||||
]
|
||||
},
|
||||
"editor": {
|
||||
"close": "Zavřít",
|
||||
"create": {
|
||||
"button": "Nový",
|
||||
"title": "Nový záznam",
|
||||
"submit": "Vytvořit"
|
||||
},
|
||||
"edit": {
|
||||
"button": "Změnit",
|
||||
"title": "Změnit záznam",
|
||||
"submit": "Aktualizovat"
|
||||
},
|
||||
"remove": {
|
||||
"button": "Vymazat",
|
||||
"title": "Smazání",
|
||||
"submit": "Vymazat",
|
||||
"confirm": {
|
||||
"_": "Opravdu chcete smazat tyto %d řádky?",
|
||||
"1": "Opravdu chcete smazat tento 1 řádek?"
|
||||
}
|
||||
},
|
||||
"multi": {
|
||||
"title": "Mnohočetný výběr",
|
||||
"restore": "Vrátit změny",
|
||||
"noMulti": "Toto pole může být editováno individuálně, ale ne jako soušást skupiny.",
|
||||
"info": "Vybrané položky obsahují různé hodnoty pro tento vstup. Chcete-li upravit a nastavit všechny položky tohoto vstupu na stejnou hodnotu, klikněte nebo klepněte sem, jinak si zachovají své individuální hodnoty."
|
||||
},
|
||||
"error": {
|
||||
"system": "Došlo k systémové chybě (<a target=\"\\\" rel=\"nofollow\" href=\"\\\">Více informací<\/a>)."
|
||||
}
|
||||
},
|
||||
"infoThousands": " ",
|
||||
"decimal": ",",
|
||||
"thousands": " ",
|
||||
"stateRestore": {
|
||||
"creationModal": {
|
||||
"button": "Vytvořit",
|
||||
"columns": {
|
||||
"search": "Vyhledávání v buňce",
|
||||
"visible": "Viditelnost buňky"
|
||||
},
|
||||
"name": "Název:",
|
||||
"order": "Řazení",
|
||||
"paging": "Stránkování",
|
||||
"scroller": "Pozice skrolování",
|
||||
"select": "Výběr",
|
||||
"title": "Vytvořit nový Stav",
|
||||
"toggleLabel": "Zahrnout",
|
||||
"search": "Filtrování",
|
||||
"searchBuilder": "Rozšířené filtrování"
|
||||
},
|
||||
"duplicateError": "Stav s tímto názvem ji existuje.",
|
||||
"emptyError": "Název nemůže být prázný.",
|
||||
"emptyStates": "Žádné uložené stavy",
|
||||
"removeConfirm": "Opravdu chcete odstranbit %s?",
|
||||
"removeError": "Chyba při odstraňování stavu.",
|
||||
"removeJoiner": "a",
|
||||
"removeSubmit": "Odstranit",
|
||||
"removeTitle": "Odstranit Stav",
|
||||
"renameButton": "Vymazat",
|
||||
"renameLabel": "Nové jméno pro %s:",
|
||||
"renameTitle": "Přejmenování Stavu"
|
||||
},
|
||||
"searchPlaceholder": "Příklad zástupného prvku"
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
{
|
||||
"emptyTable": "Keine Daten in der Tabelle vorhanden",
|
||||
"info": "_START_ bis _END_ von _TOTAL_ Einträgen",
|
||||
"infoEmpty": "Keine Daten vorhanden",
|
||||
"infoFiltered": "(gefiltert von _MAX_ Einträgen)",
|
||||
"infoThousands": ".",
|
||||
"loadingRecords": "Wird geladen ..",
|
||||
"processing": "Bitte warten ..",
|
||||
"paginate": {
|
||||
"first": "Erste",
|
||||
"next": "Nächste",
|
||||
"last": "Letzte",
|
||||
"previous": "Vorherige"
|
||||
},
|
||||
"aria": {
|
||||
"sortAscending": ": aktivieren, um Spalte aufsteigend zu sortieren",
|
||||
"sortDescending": ": aktivieren, um Spalte absteigend zu sortieren"
|
||||
},
|
||||
"select": {
|
||||
"rows": {
|
||||
"_": "%d Zeilen ausgewählt",
|
||||
"1": "1 Zeile ausgewählt"
|
||||
},
|
||||
"cells": {
|
||||
"1": "1 Zelle ausgewählt",
|
||||
"_": "%d Zellen ausgewählt"
|
||||
},
|
||||
"columns": {
|
||||
"1": "1 Spalte ausgewählt",
|
||||
"_": "%d Spalten ausgewählt"
|
||||
}
|
||||
},
|
||||
"buttons": {
|
||||
"print": "Drucken",
|
||||
"copy": "Kopieren",
|
||||
"copyTitle": "In Zwischenablage kopieren",
|
||||
"copySuccess": {
|
||||
"_": "%d Zeilen kopiert",
|
||||
"1": "1 Zeile kopiert"
|
||||
},
|
||||
"collection": "Aktionen <span class=\"ui-button-icon-primary ui-icon ui-icon-triangle-1-s\"><\/span>",
|
||||
"colvis": "Spaltensichtbarkeit",
|
||||
"colvisRestore": "Sichtbarkeit wiederherstellen",
|
||||
"csv": "CSV",
|
||||
"excel": "Excel",
|
||||
"pageLength": {
|
||||
"-1": "Alle Zeilen anzeigen",
|
||||
"1": "Zeige 1 Zeile",
|
||||
"_": "Zeige %d Zeilen"
|
||||
},
|
||||
"pdf": "PDF",
|
||||
"createState": "Ansicht erstellen",
|
||||
"removeAllStates": "Alle Ansichten entfernen",
|
||||
"removeState": "Entfernen",
|
||||
"renameState": "Umbenennen",
|
||||
"savedStates": "Gespeicherte Ansicht",
|
||||
"stateRestore": "Ansicht %d",
|
||||
"updateState": "Aktualisieren",
|
||||
"copyKeys": "Taste <i>STRG<\\\/i> oder <i>⌘<\\\/i> + <i>C<\\\/i> drücken um die Tabelle<br \/>in den Zwischenspeicher zu kopieren.<br \/><br \/>Um den Vorgang abzubrechen, Nachricht anklicken oder Escape-Taste drücken.<\/i><\/i><\/i>"
|
||||
},
|
||||
"autoFill": {
|
||||
"cancel": "Abbrechen",
|
||||
"fill": "Alle Zellen mit <i>%d<i> füllen<\/i><\/i>",
|
||||
"fillHorizontal": "Alle horizontalen Zellen füllen",
|
||||
"fillVertical": "Alle vertikalen Zellen füllen",
|
||||
"info": "Automatische Vervollständigung"
|
||||
},
|
||||
"decimal": ",",
|
||||
"search": "Suche:",
|
||||
"searchBuilder": {
|
||||
"add": "Bedingung hinzufügen",
|
||||
"button": {
|
||||
"0": "Such-Baukasten",
|
||||
"_": "Such-Baukasten (%d)"
|
||||
},
|
||||
"condition": "Bedingung",
|
||||
"conditions": {
|
||||
"date": {
|
||||
"after": "Nach",
|
||||
"before": "Vor",
|
||||
"between": "Zwischen",
|
||||
"empty": "Leer",
|
||||
"not": "Nicht",
|
||||
"notBetween": "Nicht zwischen",
|
||||
"notEmpty": "Nicht leer",
|
||||
"equals": "Gleich"
|
||||
},
|
||||
"number": {
|
||||
"between": "Zwischen",
|
||||
"empty": "Leer",
|
||||
"equals": "Entspricht",
|
||||
"gt": "Größer als",
|
||||
"gte": "Größer als oder gleich",
|
||||
"lt": "Kleiner als",
|
||||
"lte": "Kleiner als oder gleich",
|
||||
"not": "Nicht",
|
||||
"notBetween": "Nicht zwischen",
|
||||
"notEmpty": "Nicht leer"
|
||||
},
|
||||
"string": {
|
||||
"contains": "Beinhaltet",
|
||||
"empty": "Leer",
|
||||
"endsWith": "Endet mit",
|
||||
"equals": "Entspricht",
|
||||
"not": "Nicht",
|
||||
"notEmpty": "Nicht leer",
|
||||
"startsWith": "Startet mit",
|
||||
"notContains": "enthält nicht",
|
||||
"notStartsWith": "startet nicht mit",
|
||||
"notEndsWith": "endet nicht mit"
|
||||
},
|
||||
"array": {
|
||||
"equals": "ist gleich",
|
||||
"empty": "ist leer",
|
||||
"contains": "enthält",
|
||||
"not": "ist ungleich",
|
||||
"notEmpty": "ist nicht leer",
|
||||
"without": "aber nicht"
|
||||
}
|
||||
},
|
||||
"data": "Daten",
|
||||
"deleteTitle": "Filterregel entfernen",
|
||||
"leftTitle": "Äußere Kriterien",
|
||||
"rightTitle": "Innere Kriterien",
|
||||
"title": {
|
||||
"0": "Such-Baukasten",
|
||||
"_": "Such-Baukasten (%d)"
|
||||
},
|
||||
"value": "Wert",
|
||||
"clearAll": "Alle entfernen",
|
||||
"logicAnd": "Und",
|
||||
"logicOr": "Oder"
|
||||
},
|
||||
"searchPanes": {
|
||||
"clearMessage": "Leeren",
|
||||
"collapse": {
|
||||
"0": "Suchmasken",
|
||||
"_": "Suchmasken (%d)"
|
||||
},
|
||||
"countFiltered": "{shown} ({total})",
|
||||
"emptyPanes": "Keine Suchmasken",
|
||||
"title": "Aktive Filter: %d",
|
||||
"showMessage": "zeige Alle",
|
||||
"collapseMessage": "Alle einklappen",
|
||||
"count": "{total}",
|
||||
"loadMessage": "Lade Suchmasken .."
|
||||
},
|
||||
"thousands": ".",
|
||||
"zeroRecords": "Keine passenden Einträge gefunden",
|
||||
"lengthMenu": "_MENU_ Zeilen anzeigen",
|
||||
"datetime": {
|
||||
"previous": "Vorher",
|
||||
"next": "Nachher",
|
||||
"hours": "Stunden",
|
||||
"minutes": "Minuten",
|
||||
"seconds": "Sekunden",
|
||||
"unknown": "Unbekannt",
|
||||
"weekdays": [
|
||||
"Sonntag",
|
||||
"Montag",
|
||||
"Dienstag",
|
||||
"Mittwoch",
|
||||
"Donnerstag",
|
||||
"Freitag",
|
||||
"Samstag"
|
||||
],
|
||||
"months": [
|
||||
"Januar",
|
||||
"Februar",
|
||||
"März",
|
||||
"April",
|
||||
"Mai",
|
||||
"Juni",
|
||||
"Juli",
|
||||
"August",
|
||||
"September",
|
||||
"Oktober",
|
||||
"November",
|
||||
"Dezember"
|
||||
]
|
||||
},
|
||||
"editor": {
|
||||
"close": "Schließen",
|
||||
"create": {
|
||||
"button": "Neu",
|
||||
"title": "Neuen Eintrag erstellen",
|
||||
"submit": "Erstellen"
|
||||
},
|
||||
"remove": {
|
||||
"confirm": {
|
||||
"_": "Sollen %d Zeilen gelöscht werden?",
|
||||
"1": "Soll diese Zeile gelöscht werden?"
|
||||
},
|
||||
"button": "Entfernen",
|
||||
"title": "Entfernen",
|
||||
"submit": "Entfernen"
|
||||
},
|
||||
"error": {
|
||||
"system": "Ein Systemfehler ist aufgetreten"
|
||||
},
|
||||
"multi": {
|
||||
"title": "Mehrere Werte",
|
||||
"restore": "Änderungen zurücksetzen",
|
||||
"noMulti": "Dieses Feld kann nur einzeln bearbeitet werden, nicht als Teil einer Mengen-Änderung.",
|
||||
"info": "Die ausgewählten Elemente enthalten mehrere Werte für dieses Feld. Um alle Elemente für dieses Feld zu bearbeiten und auf denselben Wert zu setzen, hier klicken oder tippen, andernfalls behalten diese ihre individuellen Werte bei."
|
||||
},
|
||||
"edit": {
|
||||
"button": "Bearbeiten",
|
||||
"title": "Eintrag bearbeiten",
|
||||
"submit": "Bearbeiten"
|
||||
}
|
||||
},
|
||||
"searchPlaceholder": "Suchen...",
|
||||
"stateRestore": {
|
||||
"creationModal": {
|
||||
"button": "Erstellen",
|
||||
"columns": {
|
||||
"search": "Spalten Suche",
|
||||
"visible": "Spalten Sichtbarkeit"
|
||||
},
|
||||
"name": "Name:",
|
||||
"order": "Sortieren",
|
||||
"paging": "Seiten",
|
||||
"scroller": "Scroll Position",
|
||||
"search": "Suche",
|
||||
"searchBuilder": "Such-Baukasten",
|
||||
"select": "Auswahl",
|
||||
"title": "Neue Ansicht erstellen",
|
||||
"toggleLabel": "Inkludiert:"
|
||||
},
|
||||
"duplicateError": "Eine Ansicht mit diesem Namen existiert bereits.",
|
||||
"emptyError": "Name darf nicht leer sein.",
|
||||
"emptyStates": "Keine gespeicherten Ansichten",
|
||||
"removeError": "Entfernen der Ansicht fehlgeschlagen.",
|
||||
"removeJoiner": " und ",
|
||||
"removeSubmit": "Entfernen",
|
||||
"removeTitle": "Ansicht entfernen",
|
||||
"renameButton": "Umbenennen",
|
||||
"renameLabel": "Neuer Name für %s:",
|
||||
"renameTitle": "Ansicht umbenennen",
|
||||
"removeConfirm": "Sicher dass %s entfernt werden soll?"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
{
|
||||
"processing": "Procesando...",
|
||||
"lengthMenu": "Mostrar _MENU_ registros",
|
||||
"zeroRecords": "No se encontraron resultados",
|
||||
"emptyTable": "Ningún dato disponible en esta tabla",
|
||||
"infoEmpty": "Mostrando registros del 0 al 0 de un total de 0 registros",
|
||||
"infoFiltered": "(filtrado de un total de _MAX_ registros)",
|
||||
"search": "Buscar:",
|
||||
"loadingRecords": "Cargando...",
|
||||
"paginate": {
|
||||
"first": "Primero",
|
||||
"last": "Último",
|
||||
"next": "Siguiente",
|
||||
"previous": "Anterior"
|
||||
},
|
||||
"aria": {
|
||||
"sortAscending": ": Activar para ordenar la columna de manera ascendente",
|
||||
"sortDescending": ": Activar para ordenar la columna de manera descendente"
|
||||
},
|
||||
"buttons": {
|
||||
"copy": "Copiar",
|
||||
"colvis": "Visibilidad",
|
||||
"collection": "Colección",
|
||||
"colvisRestore": "Restaurar visibilidad",
|
||||
"copyKeys": "Presione ctrl o u2318 + C para copiar los datos de la tabla al portapapeles del sistema. <br \/> <br \/> Para cancelar, haga clic en este mensaje o presione escape.",
|
||||
"copySuccess": {
|
||||
"1": "Copiada 1 fila al portapapeles",
|
||||
"_": "Copiadas %ds fila al portapapeles"
|
||||
},
|
||||
"copyTitle": "Copiar al portapapeles",
|
||||
"csv": "CSV",
|
||||
"excel": "Excel",
|
||||
"pageLength": {
|
||||
"-1": "Mostrar todas las filas",
|
||||
"_": "Mostrar %d filas"
|
||||
},
|
||||
"pdf": "PDF",
|
||||
"print": "Imprimir",
|
||||
"renameState": "Cambiar nombre",
|
||||
"updateState": "Actualizar",
|
||||
"createState": "Crear Estado",
|
||||
"removeAllStates": "Remover Estados",
|
||||
"removeState": "Remover",
|
||||
"savedStates": "Estados Guardados",
|
||||
"stateRestore": "Estado %d"
|
||||
},
|
||||
"autoFill": {
|
||||
"cancel": "Cancelar",
|
||||
"fill": "Rellene todas las celdas con <i>%d<\/i>",
|
||||
"fillHorizontal": "Rellenar celdas horizontalmente",
|
||||
"fillVertical": "Rellenar celdas verticalmente"
|
||||
},
|
||||
"decimal": ",",
|
||||
"searchBuilder": {
|
||||
"add": "Añadir condición",
|
||||
"button": {
|
||||
"0": "Constructor de búsqueda",
|
||||
"_": "Constructor de búsqueda (%d)"
|
||||
},
|
||||
"clearAll": "Borrar todo",
|
||||
"condition": "Condición",
|
||||
"conditions": {
|
||||
"date": {
|
||||
"before": "Antes",
|
||||
"between": "Entre",
|
||||
"empty": "Vacío",
|
||||
"equals": "Igual a",
|
||||
"notBetween": "No entre",
|
||||
"not": "Diferente de",
|
||||
"after": "Después",
|
||||
"notEmpty": "No Vacío"
|
||||
},
|
||||
"number": {
|
||||
"between": "Entre",
|
||||
"equals": "Igual a",
|
||||
"gt": "Mayor a",
|
||||
"gte": "Mayor o igual a",
|
||||
"lt": "Menor que",
|
||||
"lte": "Menor o igual que",
|
||||
"notBetween": "No entre",
|
||||
"notEmpty": "No vacío",
|
||||
"not": "Diferente de",
|
||||
"empty": "Vacío"
|
||||
},
|
||||
"string": {
|
||||
"contains": "Contiene",
|
||||
"empty": "Vacío",
|
||||
"endsWith": "Termina en",
|
||||
"equals": "Igual a",
|
||||
"startsWith": "Empieza con",
|
||||
"not": "Diferente de",
|
||||
"notContains": "No Contiene",
|
||||
"notStartsWith": "No empieza con",
|
||||
"notEndsWith": "No termina con",
|
||||
"notEmpty": "No Vacío"
|
||||
},
|
||||
"array": {
|
||||
"not": "Diferente de",
|
||||
"equals": "Igual",
|
||||
"empty": "Vacío",
|
||||
"contains": "Contiene",
|
||||
"notEmpty": "No Vacío",
|
||||
"without": "Sin"
|
||||
}
|
||||
},
|
||||
"data": "Data",
|
||||
"deleteTitle": "Eliminar regla de filtrado",
|
||||
"leftTitle": "Criterios anulados",
|
||||
"logicAnd": "Y",
|
||||
"logicOr": "O",
|
||||
"rightTitle": "Criterios de sangría",
|
||||
"title": {
|
||||
"0": "Constructor de búsqueda",
|
||||
"_": "Constructor de búsqueda (%d)"
|
||||
},
|
||||
"value": "Valor"
|
||||
},
|
||||
"searchPanes": {
|
||||
"clearMessage": "Borrar todo",
|
||||
"collapse": {
|
||||
"0": "Paneles de búsqueda",
|
||||
"_": "Paneles de búsqueda (%d)"
|
||||
},
|
||||
"count": "{total}",
|
||||
"countFiltered": "{shown} ({total})",
|
||||
"emptyPanes": "Sin paneles de búsqueda",
|
||||
"loadMessage": "Cargando paneles de búsqueda",
|
||||
"title": "Filtros Activos - %d",
|
||||
"showMessage": "Mostrar Todo",
|
||||
"collapseMessage": "Colapsar Todo"
|
||||
},
|
||||
"select": {
|
||||
"cells": {
|
||||
"1": "1 celda seleccionada",
|
||||
"_": "%d celdas seleccionadas"
|
||||
},
|
||||
"columns": {
|
||||
"1": "1 columna seleccionada",
|
||||
"_": "%d columnas seleccionadas"
|
||||
},
|
||||
"rows": {
|
||||
"1": "1 fila seleccionada",
|
||||
"_": "%d filas seleccionadas"
|
||||
}
|
||||
},
|
||||
"thousands": ".",
|
||||
"datetime": {
|
||||
"previous": "Anterior",
|
||||
"hours": "Horas",
|
||||
"minutes": "Minutos",
|
||||
"seconds": "Segundos",
|
||||
"unknown": "-",
|
||||
"amPm": [
|
||||
"AM",
|
||||
"PM"
|
||||
],
|
||||
"months": {
|
||||
"0": "Enero",
|
||||
"1": "Febrero",
|
||||
"10": "Noviembre",
|
||||
"11": "Diciembre",
|
||||
"2": "Marzo",
|
||||
"3": "Abril",
|
||||
"4": "Mayo",
|
||||
"5": "Junio",
|
||||
"6": "Julio",
|
||||
"7": "Agosto",
|
||||
"8": "Septiembre",
|
||||
"9": "Octubre"
|
||||
},
|
||||
"weekdays": {
|
||||
"0": "Dom",
|
||||
"1": "Lun",
|
||||
"2": "Mar",
|
||||
"4": "Jue",
|
||||
"5": "Vie",
|
||||
"3": "Mié",
|
||||
"6": "Sáb"
|
||||
},
|
||||
"next": "Próximo"
|
||||
},
|
||||
"editor": {
|
||||
"close": "Cerrar",
|
||||
"create": {
|
||||
"button": "Nuevo",
|
||||
"title": "Crear Nuevo Registro",
|
||||
"submit": "Crear"
|
||||
},
|
||||
"edit": {
|
||||
"button": "Editar",
|
||||
"title": "Editar Registro",
|
||||
"submit": "Actualizar"
|
||||
},
|
||||
"remove": {
|
||||
"button": "Eliminar",
|
||||
"title": "Eliminar Registro",
|
||||
"submit": "Eliminar",
|
||||
"confirm": {
|
||||
"_": "¿Está seguro de que desea eliminar %d filas?",
|
||||
"1": "¿Está seguro de que desea eliminar 1 fila?"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"system": "Ha ocurrido un error en el sistema (<a target=\"\\\" rel=\"\\ nofollow\" href=\"\\\">Más información<\\\/a>).<\/a>"
|
||||
},
|
||||
"multi": {
|
||||
"title": "Múltiples Valores",
|
||||
"restore": "Deshacer Cambios",
|
||||
"noMulti": "Este registro puede ser editado individualmente, pero no como parte de un grupo.",
|
||||
"info": "Los elementos seleccionados contienen diferentes valores para este registro. Para editar y establecer todos los elementos de este registro con el mismo valor, haga clic o pulse aquí, de lo contrario conservarán sus valores individuales."
|
||||
}
|
||||
},
|
||||
"info": "Mostrando _START_ a _END_ de _TOTAL_ registros",
|
||||
"stateRestore": {
|
||||
"creationModal": {
|
||||
"button": "Crear",
|
||||
"name": "Nombre:",
|
||||
"order": "Clasificación",
|
||||
"paging": "Paginación",
|
||||
"select": "Seleccionar",
|
||||
"columns": {
|
||||
"search": "Búsqueda de Columna",
|
||||
"visible": "Visibilidad de Columna"
|
||||
},
|
||||
"title": "Crear Nuevo Estado",
|
||||
"toggleLabel": "Incluir:",
|
||||
"scroller": "Posición de desplazamiento",
|
||||
"search": "Búsqueda",
|
||||
"searchBuilder": "Búsqueda avanzada"
|
||||
},
|
||||
"removeJoiner": "y",
|
||||
"removeSubmit": "Eliminar",
|
||||
"renameButton": "Cambiar Nombre",
|
||||
"duplicateError": "Ya existe un Estado con este nombre.",
|
||||
"emptyStates": "No hay Estados guardados",
|
||||
"removeTitle": "Remover Estado",
|
||||
"renameTitle": "Cambiar Nombre Estado",
|
||||
"emptyError": "El nombre no puede estar vacío.",
|
||||
"removeConfirm": "¿Seguro que quiere eliminar %s?",
|
||||
"removeError": "Error al eliminar el Estado",
|
||||
"renameLabel": "Nuevo nombre para %s:"
|
||||
},
|
||||
"infoThousands": "."
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user