From 77da6928b23224e8b53af74e4ca2a13d27528561 Mon Sep 17 00:00:00 2001 From: Joel Falknau Date: Thu, 12 Sep 2024 15:53:53 +1000 Subject: [PATCH 1/4] add Chart.js 4.4.1 to Bundles --- allianceauth/templates/bundles/chart-js.html | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 allianceauth/templates/bundles/chart-js.html diff --git a/allianceauth/templates/bundles/chart-js.html b/allianceauth/templates/bundles/chart-js.html new file mode 100644 index 00000000..9747b5ed --- /dev/null +++ b/allianceauth/templates/bundles/chart-js.html @@ -0,0 +1,3 @@ + + + From 3f54d49d8b1cb4c1e7222ec2eaf0bb4be7ebd9a0 Mon Sep 17 00:00:00 2001 From: Joel Falknau Date: Thu, 12 Sep 2024 15:54:06 +1000 Subject: [PATCH 2/4] update help text from mumble definition --- allianceauth/services/modules/mumble/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/allianceauth/services/modules/mumble/models.py b/allianceauth/services/modules/mumble/models.py index 72ab63fc..8c7d0ead 100644 --- a/allianceauth/services/modules/mumble/models.py +++ b/allianceauth/services/modules/mumble/models.py @@ -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"), ) From 1dea92ed76cf31ff9d56da4547b2c6504a7ac8b2 Mon Sep 17 00:00:00 2001 From: Joel Falknau Date: Thu, 12 Sep 2024 15:54:14 +1000 Subject: [PATCH 3/4] add Connection History --- .../mumble/mumble_connection_history.html | 185 ++++++++++++++++++ .../services/mumble/mumble_service_ctrl.html | 5 + allianceauth/services/modules/mumble/urls.py | 4 + allianceauth/services/modules/mumble/views.py | 53 +++++ 4 files changed, 247 insertions(+) create mode 100644 allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html diff --git a/allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html b/allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html new file mode 100644 index 00000000..99ad0be0 --- /dev/null +++ b/allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html @@ -0,0 +1,185 @@ +{% extends "allianceauth/base-bs5.html" %} +{% load i18n %} +{% load humanize %} + +{% 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 %} +
+
+ {% translate "Server Connection History" %} +
+
+
+
+ + + + + + + + + + + +
{% translate "User" %}{% translate "Display_Name" %}{% translate "Release" %}{% translate "Version" %}{% translate "Last Connection" %}{% translate "Last Disconnection" %}
+
+
+
+
+
+
+
+ {% translate "Server Connection Breakdown" %} +
+
+
+
+ +
+
+
+
+
+
+
+ {% translate "Server Connection Breakdown" %} +
+
+
+
+ + + + + + + +
{% translate "Version" %}{% translate "Number" %}
+
+
+
+
+
+{% 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 %} + +{% endblock extra_javascript %} + +{% block extra_css %} + {% include "bundles/datatables-css-bs5.html" %} +{% endblock extra_css %} +{% block extra_script %} +$(document).ready(function () { + $("#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" }, + { data: "last_connect" }, + ], + 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" }], + order: [[1, "desc"]], + processing: true, + stateSave: true, + stateDuration: 0, + }); + + // Initialize empty Pie chart + var ctx = document.getElementById("pieChart").getContext("2d"); + var 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: function (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: function (xhr, status, error) { + console.error("Error fetching pie chart data:", status, error); + }, + }); +}); + +{% endblock extra_script %} diff --git a/allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html b/allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html index e140ee7c..cf122157 100644 --- a/allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html +++ b/allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html @@ -48,4 +48,9 @@ {% endif %} {% endif %} + {% if request.user.is_superuser %} + + History + +{% endif %} {% endblock %} diff --git a/allianceauth/services/modules/mumble/urls.py b/allianceauth/services/modules/mumble/urls.py index ef7ff699..d3276e0b 100644 --- a/allianceauth/services/modules/mumble/urls.py +++ b/allianceauth/services/modules/mumble/urls.py @@ -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 = [ diff --git a/allianceauth/services/modules/mumble/views.py b/allianceauth/services/modules/mumble/views.py index ba8904fd..7dd07c82 100644 --- a/allianceauth/services/modules/mumble/views.py +++ b/allianceauth/services/modules/mumble/views.py @@ -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)), + }) From 1fb091acb29b443f226e7c93985005fb382ece81 Mon Sep 17 00:00:00 2001 From: Peter Pfeufer Date: Mon, 23 Sep 2024 15:09:35 +0200 Subject: [PATCH 4/4] [CHANGE] Some improvements - Fixed Bootstraps cards, rows and cols - Replaced style argument with Bootstrap class - Removed unused Django templatetag - JS modernized and moved to its own script tag instead of concatenating it with other more or less "global" scripts - Fixed Bootstrap classes --- .../mumble/mumble_connection_history.html | 290 ++++++++++-------- 1 file changed, 155 insertions(+), 135 deletions(-) diff --git a/allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html b/allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html index 99ad0be0..536e3d39 100644 --- a/allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html +++ b/allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html @@ -1,6 +1,6 @@ {% extends "allianceauth/base-bs5.html" %} + {% load i18n %} -{% load humanize %} {% block page_title %} {% translate "Mumble" %} @@ -9,6 +9,7 @@ {% block header_nav_brand %} {% trans "Mumble History" %} - {{ mumble_url }} {% endblock header_nav_brand %} + {% block header_nav_collapse_left %} {% endblock header_nav_collapse_left %} @@ -16,55 +17,55 @@ {% endblock header_nav_collapse_right %} {% block content %} -
+
{% translate "Server Connection History" %}
+
-
-
- - - - - - - - - - - -
{% translate "User" %}{% translate "Display_Name" %}{% translate "Release" %}{% translate "Version" %}{% translate "Last Connection" %}{% translate "Last Disconnection" %}
-
+
+ + + + + + + + + + + +
{% translate "User" %}{% translate "Displayed Name" %}{% translate "Release" %}{% translate "Version" %}{% translate "Last Connect" %}{% translate "Last Disconnect" %}
+
-
-
- {% translate "Server Connection Breakdown" %} -
-
-
-
- -
-
+
+
+
+ {% translate "Server Connection Breakdown" %} +
+ +
+
-
-
- {% translate "Server Connection Breakdown" %} -
-
-
-
- + +
+
+
+ {% translate "Server Connection Breakdown" %} +
+ +
+
+
- - + +
{% translate "Version" %}{% translate "Number" %}{% translate "Version" %}{% translate "Number" %}
@@ -74,112 +75,131 @@
{% 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 %} + {% endblock extra_javascript %} {% block extra_css %} {% include "bundles/datatables-css-bs5.html" %} {% endblock extra_css %} -{% block extra_script %} -$(document).ready(function () { - $("#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" }, - { data: "last_connect" }, - ], - 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" }], - order: [[1, "desc"]], - processing: true, - stateSave: true, - stateDuration: 0, - }); - - // Initialize empty Pie chart - var ctx = document.getElementById("pieChart").getContext("2d"); - var 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: function (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: function (xhr, status, error) { - console.error("Error fetching pie chart data:", status, error); - }, - }); -}); - -{% endblock extra_script %}