Compare commits

..

No commits in common. "cda5ce739ff63d0958b4c4ff4ef27964451689a0" and "42ee06470c3941d276e49aab8daf59925eca7feb" have entirely different histories.

11 changed files with 14 additions and 300 deletions

View File

@ -49,7 +49,7 @@ class GroupsMenuItem(MenuItemHook):
MenuItemHook.__init__( MenuItemHook.__init__(
self, self,
text=_("Groups"), text=_("Groups"),
classes="fa-solid fa-users", classes="fa-solid fa-user",
url_name="groupmanagement:groups", url_name="groupmanagement:groups",
order=25, order=25,
navactive=[ navactive=[

View File

@ -68,7 +68,7 @@ msgstr "Englisch"
#: allianceauth/authentication/models.py:72 #: allianceauth/authentication/models.py:72
msgid "Czech" msgid "Czech"
msgstr "Tschechisch" msgstr ""
#: allianceauth/authentication/models.py:73 #: allianceauth/authentication/models.py:73
#: allianceauth/project_template/project_name/settings/base.py:101 #: allianceauth/project_template/project_name/settings/base.py:101
@ -108,7 +108,7 @@ msgstr "Russisch"
#: allianceauth/authentication/models.py:80 #: allianceauth/authentication/models.py:80
#: allianceauth/project_template/project_name/settings/base.py:107 #: allianceauth/project_template/project_name/settings/base.py:107
msgid "Dutch" msgid "Dutch"
msgstr "Niederländisch" msgstr ""
#: allianceauth/authentication/models.py:81 #: allianceauth/authentication/models.py:81
#: allianceauth/project_template/project_name/settings/base.py:108 #: allianceauth/project_template/project_name/settings/base.py:108
@ -123,7 +123,7 @@ msgstr "Ukrainisch"
#: allianceauth/authentication/models.py:83 #: allianceauth/authentication/models.py:83
#: allianceauth/project_template/project_name/settings/base.py:111 #: allianceauth/project_template/project_name/settings/base.py:111
msgid "Simplified Chinese" msgid "Simplified Chinese"
msgstr "Vereinfachtes Chinesisch" msgstr ""
#: allianceauth/authentication/models.py:99 #: allianceauth/authentication/models.py:99
#: allianceauth/menu/templates/menu/menu-user.html:42 #: allianceauth/menu/templates/menu/menu-user.html:42
@ -1664,7 +1664,7 @@ msgstr "Anstehende Flotten"
#: allianceauth/optimer/templates/optimer/management.html:44 #: allianceauth/optimer/templates/optimer/management.html:44
#: allianceauth/timerboard/templates/timerboard/view.html:62 #: allianceauth/timerboard/templates/timerboard/view.html:62
msgid "No upcoming timers." msgid "No upcoming timers."
msgstr "Keine anstehenden Timer." msgstr "Keine bevorstehenden Timer."
#: allianceauth/optimer/templates/optimer/management.html:52 #: allianceauth/optimer/templates/optimer/management.html:52
msgid "Past Fleet Operations" msgid "Past Fleet Operations"
@ -2804,12 +2804,12 @@ msgstr "Entankernd"
#: allianceauth/timerboard/models.py:58 #: allianceauth/timerboard/models.py:58
msgid "Abandoned" msgid "Abandoned"
msgstr "Aufgegeben" msgstr ""
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:7 #: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:7
#: allianceauth/timerboard/templates/timerboard/view.html:53 #: allianceauth/timerboard/templates/timerboard/view.html:53
msgid "Upcoming Timers" msgid "Upcoming Timers"
msgstr "Anstehende Timer" msgstr "Bevorstehende Timefr"
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:15 #: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:15
msgid "Timer" msgid "Timer"

View File

@ -1,21 +1 @@
"""
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 from .core import notify # noqa: F401

View File

@ -90,7 +90,7 @@ class MumbleUser(AbstractServiceModel):
blank=True, blank=True,
null=True, null=True,
editable=False, editable=False,
help_text="Client release. For official releases, this equals the version. For snapshots and git compiles, this will be something else." help_text="The Mumble Release the user last authenticated with"
) )
version = models.IntegerField( version = models.IntegerField(
verbose_name="Mumble Version", verbose_name="Mumble Version",
@ -159,5 +159,4 @@ class MumbleUser(AbstractServiceModel):
class Meta: class Meta:
permissions = ( permissions = (
("access_mumble", "Can access the Mumble service"), ("access_mumble", "Can access the Mumble service"),
("view_connection_history", "Can access the connection history of the Mumble service"),
) )

View File

@ -1,205 +0,0 @@
{% extends "allianceauth/base-bs5.html" %}
{% load i18n %}
{% block page_title %}
{% translate "Mumble" %}
{% endblock page_title %}
{% block header_nav_brand %}
<a class="navbar-brand">{% trans "Mumble History" %} - {{ mumble_url }}</a>
{% 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 %}
<script>
$(document).ready(function () {
const MUMBLESTATS_DATETIME_FORMAT = 'YYYY-MM-DD, HH:mm';
'use strict';
$("#table-mumble-connection-history").DataTable({
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 moment(data).utc().format(MUMBLESTATS_DATETIME_FORMAT);
},
className: 'text-end',
},
{
data: 'last_disconnect',
render: (data) => {
return moment(data).utc().format(MUMBLESTATS_DATETIME_FORMAT);
},
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({
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 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'
}
}
}
});
// 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 %}

View File

@ -48,9 +48,4 @@
</a> </a>
{% endif %} {% endif %}
{% 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 %} {% endblock %}

View File

@ -10,10 +10,6 @@ module_urls = [
path('deactivate/', views.DeleteMumbleView.as_view(), name='deactivate'), path('deactivate/', views.DeleteMumbleView.as_view(), name='deactivate'),
path('reset_password/', views.ResetPasswordMumbleView.as_view(), name='reset_password'), path('reset_password/', views.ResetPasswordMumbleView.as_view(), name='reset_password'),
path('set_password/', views.SetPasswordMumbleView.as_view(), name='set_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 = [ urlpatterns = [

View File

@ -3,11 +3,6 @@ import logging
from allianceauth.services.forms import ServicePasswordModelForm from allianceauth.services.forms import ServicePasswordModelForm
from allianceauth.services.abstract import BaseCreatePasswordServiceAccountView, BaseDeactivateServiceAccountView, \ from allianceauth.services.abstract import BaseCreatePasswordServiceAccountView, BaseDeactivateServiceAccountView, \
BaseResetPasswordServiceAccountView, BaseSetPasswordServiceAccountView 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 from .models import MumbleUser
@ -40,51 +35,3 @@ class ResetPasswordMumbleView(MumbleViewMixin, BaseResetPasswordServiceAccountVi
class SetPasswordMumbleView(MumbleViewMixin, BaseSetPasswordServiceAccountView): class SetPasswordMumbleView(MumbleViewMixin, BaseSetPasswordServiceAccountView):
form_class = MumblePasswordForm 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)),
})

View File

@ -10,6 +10,11 @@
<i class="fa-solid fa-gauge-high fa-fw"></i> {% translate "Dashboard" %} <i class="fa-solid fa-gauge-high fa-fw"></i> {% translate "Dashboard" %}
</a> </a>
</li> </li>
<li>
<a class="{% navactive request 'groupmanagement:groups' %}" href="{% url 'groupmanagement:groups' %}">
<i class="fa-solid fa-users fa-fw"></i> {% translate "Groups" %}
</a>
</li>
{% menu_items %} {% menu_items %}
</ul> </ul>

View File

@ -1,3 +0,0 @@
<!-- Start Chart.js js from cdnjs -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js" integrity="sha512-CQBWl4fJHWbryGE+Pc7UAxWMUMNMWzWxF4SQo9CgkJIN1kx6djDQZjh3Y8SZ1d+6I+1zze6Z7kHXO7q3UyZAWw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- End Chart.js js from cdnjs -->

View File

@ -45,7 +45,7 @@ class BootstrapDarkThemeHook(ThemeHook):
self, self,
"Bootstrap Dark", "Bootstrap Dark",
"Powerful, extensible, and feature-packed frontend toolkit.", "Powerful, extensible, and feature-packed frontend toolkit.",
html_tags={"data-theme": "bootstrap-dark", "data-bs-theme":"dark"}, html_tags={"data-theme": "bootstrap-dark"},
css=CSS_STATICS, css=CSS_STATICS,
js=JS_STATICS, js=JS_STATICS,
header_padding="3.5em" header_padding="3.5em"