diff --git a/serveradmin/common/static/js/serveradmin.js b/serveradmin/common/static/js/serveradmin.js index 60dd8d3a5..9cf775103 100644 --- a/serveradmin/common/static/js/serveradmin.js +++ b/serveradmin/common/static/js/serveradmin.js @@ -49,7 +49,7 @@ $(document).ready(function() { form = document.getElementById(formId); } else { - this.closest('form'); + form = this.closest('form'); } if (form) { diff --git a/serveradmin/serverdb/query_committer.py b/serveradmin/serverdb/query_committer.py index 80ece7880..816e03e7c 100644 --- a/serveradmin/serverdb/query_committer.py +++ b/serveradmin/serverdb/query_committer.py @@ -178,7 +178,7 @@ def _validate(attribute_lookup, changed, changed_objects): newer = _validate_commit(changed, changed_objects) if newer: - raise CommitNewerData('Newer data available', newer) + raise CommitNewerData(f'Newer data available for attribute {newer}', newer) def _delete_attributes(attribute_lookup, changed, changed_servers, deleted): diff --git a/serveradmin/serverdb/templates/serverdb/changes.html b/serveradmin/serverdb/templates/serverdb/changes.html index 99fd07f1a..064f26e37 100644 --- a/serveradmin/serverdb/templates/serverdb/changes.html +++ b/serveradmin/serverdb/templates/serverdb/changes.html @@ -4,6 +4,39 @@ {% block title %}Changes{% endblock %} +{% block additional_styles %} + +{% endblock %} + {% block content %}
@@ -57,6 +90,13 @@

+
+ +
+ + Requires hostname, object ID, or user/app filter +
+
@@ -75,7 +115,12 @@

App Owner Created - Changed + + + + + Changed + Deleted @@ -85,8 +130,18 @@

{{ commit.id }} {{ commit.change_on|date:"r" }} {{ commit.change_on|timesince }} - {{ commit.app|default:"Servershell" }} - {{ commit.user }} + + {% if commit.app %} + {{ commit.app }} + {% else %} + Servershell + {% endif %} + + + {% if commit.user %} + {{ commit.user }} + {% endif %} +
    {% for change in commit.change_set.get_queryset %} @@ -100,7 +155,27 @@

      {% for change in commit.change_set.get_queryset %} {% if change.change_type == 'change' %} -
    • History for {{ change.hostname }}
    • +
    • + {% with attr_changes=change|get_attribute_changes %} + {% if attr_changes %} + + {% endif %} + {% endwith %} + History for {{ change.hostname }} + {% with attr_changes=change|get_attribute_changes %} + {% if attr_changes %} +
      +
        + {% for attr_change in attr_changes %} +
      • {{ attr_change }}
      • + {% endfor %} +
      +
      + {% endif %} + {% endwith %} +
    • {% endif %} {% endfor %}
    @@ -127,3 +202,27 @@

{% endblock content %} + +{% block additional_scripts %} + +{% endblock %} diff --git a/serveradmin/serverdb/templatetags/changes.py b/serveradmin/serverdb/templatetags/changes.py index 963b0b5f2..bfacee657 100644 --- a/serveradmin/serverdb/templatetags/changes.py +++ b/serveradmin/serverdb/templatetags/changes.py @@ -1,19 +1,62 @@ """Serveradmin -Copyright (c) 2020 InnoGames GmbH +Copyright (c) 2025 InnoGames GmbH """ +from typing import Any, Union + from django import template from django.core.exceptions import ObjectDoesNotExist +from django.utils.html import escape +from django.utils.safestring import mark_safe -from serveradmin.serverdb.models import Server +from serveradmin.serverdb.models import Change, Server register = template.Library() @register.filter -def hostname(object_id): +def hostname(object_id: int) -> Union[str, int]: try: return Server.objects.get(server_id=object_id).hostname except ObjectDoesNotExist: return object_id + + +@register.filter +def get_attribute_changes(change: Change) -> list: + """Extract attribute changes from a Change object's change_json. + + Returns a list of HTML-safe formatted strings. + Only works for change_type='change'. + """ + if change.change_type != 'change': + return [] + + changes_list = [] + + for attribute_id, attr_change in change.change_json.items(): + if attribute_id == 'object_id' or not isinstance(attr_change, dict): + continue + + prefix = f'{attribute_id}:' + action = attr_change.get('action') + old_val = escape(attr_change.get('old')) + new_val = escape(attr_change.get('new')) + + if action == 'update': + if attr_change.get('new') in (None, ''): + changes_list.append(mark_safe(f'{prefix} {old_val}')) + else: + changes_list.append(mark_safe(f'{prefix} {old_val} → {new_val}')) + elif action == 'new': + changes_list.append(mark_safe(f'{prefix} + {new_val}')) + elif action == 'delete': + changes_list.append(mark_safe(f'{prefix} {old_val}')) + elif action == 'multi': + for val in attr_change.get('remove', []): + changes_list.append(mark_safe(f'{prefix} {escape(str(val))}')) + for val in attr_change.get('add', []): + changes_list.append(mark_safe(f'{prefix} + {escape(str(val))}')) + + return changes_list diff --git a/serveradmin/serverdb/views.py b/serveradmin/serverdb/views.py index 1e46d462e..1fb30af1f 100644 --- a/serveradmin/serverdb/views.py +++ b/serveradmin/serverdb/views.py @@ -66,6 +66,14 @@ def changes(request): commits = commits.filter( Q(app__name=f_user_or_app) | Q(user__username=f_user_or_app)) + f_attribute = request.GET.get('attribute') + if f_attribute: + # Only allow attribute filter if another filter is set (no index) + if f_hostname or f_object_id or f_user_or_app: + commits = commits.filter(change__change_json__has_key=f_attribute) + else: + f_attribute = None + commits = commits.select_related('app', 'user') # This complex statement is just here to be able to prefetch the changes' @@ -108,6 +116,7 @@ def count(self): 'object_id': f_object_id, 'commit_id': f_commit, 'user_or_app': f_user_or_app, + 'attribute': f_attribute, }) diff --git a/serveradmin/servershell/templates/servershell/inspect.html b/serveradmin/servershell/templates/servershell/inspect.html index cbb37e781..3fb6bd8be 100644 --- a/serveradmin/servershell/templates/servershell/inspect.html +++ b/serveradmin/servershell/templates/servershell/inspect.html @@ -26,6 +26,7 @@

Edit Goto Servershell + History
@@ -72,6 +73,7 @@