diff --git a/netbox/extras/tables/tables.py b/netbox/extras/tables/tables.py index 8482c5e24..048f5cdd3 100644 --- a/netbox/extras/tables/tables.py +++ b/netbox/extras/tables/tables.py @@ -5,7 +5,7 @@ from django.conf import settings from django.utils.translation import gettext_lazy as _ from extras.models import * -from netbox.tables import NetBoxTable, columns +from netbox.tables import BaseTable, NetBoxTable, columns from .template_code import * __all__ = ( @@ -21,6 +21,8 @@ __all__ = ( 'JournalEntryTable', 'ObjectChangeTable', 'SavedFilterTable', + 'ReportResultsTable', + 'ScriptResultsTable', 'TaggedItemTable', 'TagTable', 'WebhookTable', @@ -507,3 +509,67 @@ class JournalEntryTable(NetBoxTable): default_columns = ( 'pk', 'created', 'created_by', 'assigned_object_type', 'assigned_object', 'kind', 'comments' ) + + +class ScriptResultsTable(BaseTable): + index = tables.Column( + verbose_name=_('Line') + ) + time = tables.Column( + verbose_name=_('Time') + ) + status = tables.TemplateColumn( + template_code="""{% load log_levels %}{% log_level record.status %}""", + verbose_name=_('Level') + ) + message = tables.Column( + verbose_name=_('Message') + ) + + class Meta(BaseTable.Meta): + empty_text = _('No results found') + fields = ( + 'index', 'time', 'status', 'message', + ) + default_columns = ( + 'index', 'time', 'status', 'message', + ) + + +class ReportResultsTable(BaseTable): + index = tables.Column( + verbose_name=_('Line') + ) + method = tables.Column( + verbose_name=_('Method') + ) + time = tables.Column( + verbose_name=_('Time') + ) + status = tables.Column( + empty_values=(), + verbose_name=_('Level') + ) + status = tables.TemplateColumn( + template_code="""{% load log_levels %}{% log_level record.status %}""", + verbose_name=_('Level') + ) + + object = tables.Column( + verbose_name=_('Object') + ) + url = tables.Column( + verbose_name=_('Url') + ) + message = tables.Column( + verbose_name=_('Message') + ) + + class Meta(BaseTable.Meta): + empty_text = _('No results found') + fields = ( + 'index', 'method', 'time', 'status', 'object', 'url', 'message', + ) + default_columns = ( + 'index', 'method', 'time', 'status', 'object', 'url', 'message', + ) diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 0c7a6a48b..ead25e576 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -17,6 +17,7 @@ from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm from extras.dashboard.utils import get_widget_class from netbox.constants import DEFAULT_ACTION_PERMISSIONS from netbox.views import generic +from netbox.views.generic.mixins import TableMixin from utilities.forms import ConfirmationForm, get_field_value from utilities.paginator import EnhancedPaginator, get_paginate_count from utilities.rqworker import get_workers_for_queue @@ -26,6 +27,7 @@ from utilities.views import ContentTypePermissionRequiredMixin, register_model_v from . import filtersets, forms, tables from .models import * from .scripts import run_script +from .tables import ReportResultsTable, ScriptResultsTable # @@ -1143,47 +1145,72 @@ class LegacyScriptRedirectView(ContentTypePermissionRequiredMixin, View): return redirect(f'{url}{path}') -class ScriptResultView(generic.ObjectView): +class ScriptResultView(TableMixin, generic.ObjectView): queryset = Job.objects.all() def get_required_permission(self): return 'extras.view_script' - def job_to_result_array(self, job): - results = [] + def get_table(self, job, request, bulk_actions=True): + data = [] + tests = None + table_logs = table_tests = None + index = 0 if job.data: if 'log' in job.data: + if 'tests' in job.data: + tests = job.data['tests'] + for log in job.data['log']: + index += 1 result = { - 'method': None, + 'index': index, 'time': log.get('time', None), - 'level': log.get('level', None), - 'object': log.get('object', None), + 'status': log.get('status', None), 'message': log.get('message', None), } - results.append(result) - else: - for test in job.data: - if 'log' in test: - for time, level, obj, url, message in test['log']: - result = { - 'method': test, - 'time': time, - 'level': level, - 'object': obj, - 'url': url, - 'message': message, - } + data.append(result) - return data + table_logs = ScriptResultsTable(data, user=request.user) + table_logs.configure(request) + else: + tests = job.data + + if tests: + for method, test_data in tests.items(): + if 'log' in test_data: + for time, status, obj, url, message in test_data['log']: + index += 1 + result = { + 'index': index, + 'method': method, + 'time': time, + 'status': status, + 'object': obj, + 'url': url, + 'message': message, + } + data.append(result) + + table_tests = ReportResultsTable(data, user=request.user) + table_tests.configure(request) + + return table_logs, table_tests def get(self, request, **kwargs): job = get_object_or_404(Job.objects.all(), pk=kwargs.get('job_pk')) + table_logs = table_tests = None + + if job.completed: + table_logs, table_tests = self.get_table(job, request, bulk_actions=False) context = { 'script': job.object, 'job': job, + 'table_logs': table_logs, + 'table_tests': table_tests, } + if job.data and 'log' in job.data: # Script context['tests'] = job.data.get('tests', {}) diff --git a/netbox/templates/extras/htmx/script_result.html b/netbox/templates/extras/htmx/script_result.html index ed5dd9cbd..438adc460 100644 --- a/netbox/templates/extras/htmx/script_result.html +++ b/netbox/templates/extras/htmx/script_result.html @@ -3,124 +3,69 @@ {% load log_levels %} {% load i18n %} -

- {% if job.started %} - {% trans "Started" %}: {{ job.started|annotated_date }} - {% elif job.scheduled %} - {% trans "Scheduled for" %}: {{ job.scheduled|annotated_date }} ({{ job.scheduled|naturaltime }}) - {% else %} - {% trans "Created" %}: {{ job.created|annotated_date }} - {% endif %} +

+

+ {% if job.started %} + {% trans "Started" %}: {{ job.started|annotated_date }} + {% elif job.scheduled %} + {% trans "Scheduled for" %}: {{ job.scheduled|annotated_date }} ({{ job.scheduled|naturaltime }}) + {% else %} + {% trans "Created" %}: {{ job.created|annotated_date }} + {% endif %} + {% if job.completed %} + {% trans "Duration" %}: {{ job.duration }} + {% endif %} + {% badge job.get_status_display job.get_status_color %} +

{% if job.completed %} - {% trans "Duration" %}: {{ job.duration }} - {% endif %} - {% badge job.get_status_display job.get_status_color %} -

-{% if job.completed %} - {# Script log. Legacy reports will not have this. #} - {% if 'log' in job.data %} -
-
{% trans "Log" %}
- {% if job.data.log %} - - - - - - - - {% for log in job.data.log %} +
+
+
{% trans "Log" %}
+ {% include 'htmx/table.html' with table=table_logs %} +
+
+ + {# Script output. Legacy reports will not have this. #} + {% if 'output' in job.data %} +
+
{% trans "Output" %}
+ {% if job.data.output %} +
{{ job.data.output }}
+ {% else %} +
{% trans "None" %}
+ {% endif %} +
+ {% endif %} + + {% if table_tests %} + {# Summary of test methods #} +
+
{% trans "Test Summary" %}
+
{% trans "Line" %}{% trans "Time" %}{% trans "Level" %}{% trans "Message" %}
+ {% for test, data in tests.items %} - - - - + + {% endfor %}
{{ forloop.counter }}{{ log.time|placeholder }}{% log_level log.status %}{{ log.message|markdown }}{{ test }} + {{ data.success }} + {{ data.info }} + {{ data.warning }} + {{ data.failure }} +
- {% else %} -
{% trans "None" %}
- {% endif %} -
+
+ + {# Detailed results for individual tests #} +
+
+
{% trans "Test Details" %}
+ {% include 'htmx/table.html' with table=table_tests %} +
+
+ {% endif %} + {% elif job.started %} + {% include 'extras/inc/result_pending.html' %} {% endif %} - - {# Script output. Legacy reports will not have this. #} - {% if 'output' in job.data %} -
-
{% trans "Output" %}
- {% if job.data.output %} -
{{ job.data.output }}
- {% else %} -
{% trans "None" %}
- {% endif %} -
- {% endif %} - - {# Test method logs (for legacy Reports) #} - {% if tests %} - - {# Summary of test methods #} -
-
{% trans "Test Summary" %}
- - {% for test, data in tests.items %} - - - - - {% endfor %} -
{{ test }} - {{ data.success }} - {{ data.info }} - {{ data.warning }} - {{ data.failure }} -
-
- - {# Detailed results for individual tests #} -
-
{% trans "Test Details" %}
- - - - - - - - - - - {% for test, data in tests.items %} - - - - {% for time, level, obj, url, message in data.log %} - - - - - - - {% endfor %} - {% endfor %} - -
{% trans "Time" %}{% trans "Level" %}{% trans "Object" %}{% trans "Message" %}
- {{ test }} -
{{ time }} - - - {% if obj and url %} - {{ obj }} - {% elif obj %} - {{ obj }} - {% else %} - {{ ''|placeholder }} - {% endif %} - {{ message|markdown }}
-
- - {% endif %} -{% elif job.started %} - {% include 'extras/inc/result_pending.html' %} -{% endif %} +