diff --git a/netbox/extras/migrations/0108_convert_reports_to_scripts.py b/netbox/extras/migrations/0108_convert_reports_to_scripts.py index 07235355013..b547c41c32a 100644 --- a/netbox/extras/migrations/0108_convert_reports_to_scripts.py +++ b/netbox/extras/migrations/0108_convert_reports_to_scripts.py @@ -25,7 +25,4 @@ class Migration(migrations.Migration): migrations.DeleteModel( name='Report', ), - migrations.DeleteModel( - name='ReportModule', - ), ] diff --git a/netbox/extras/migrations/0109_script_model.py b/netbox/extras/migrations/0109_script_model.py index 89b343a8222..7570077a771 100644 --- a/netbox/extras/migrations/0109_script_model.py +++ b/netbox/extras/migrations/0109_script_model.py @@ -82,10 +82,12 @@ def update_scripts(apps, schema_editor): ContentType = apps.get_model('contenttypes', 'ContentType') Script = apps.get_model('extras', 'Script') ScriptModule = apps.get_model('extras', 'ScriptModule') + ReportModule = apps.get_model('extras', 'ReportModule') Job = apps.get_model('core', 'Job') - script_ct = ContentType.objects.get_for_model(Script) - scriptmodule_ct = ContentType.objects.get_for_model(ScriptModule) + script_ct = ContentType.objects.get_for_model(Script, for_concrete_model=False) + scriptmodule_ct = ContentType.objects.get_for_model(ScriptModule, for_concrete_model=False) + reportmodule_ct = ContentType.objects.get_for_model(ReportModule, for_concrete_model=False) for module in ScriptModule.objects.all(): for script_name in get_module_scripts(module): @@ -96,10 +98,16 @@ def update_scripts(apps, schema_editor): # Update all Jobs associated with this ScriptModule & script name to point to the new Script object Job.objects.filter( - object_type=scriptmodule_ct, + object_type_id=scriptmodule_ct.id, + object_id=module.pk, + name=script_name + ).update(object_type_id=script_ct.id, object_id=script.pk) + # Update all Jobs associated with this ScriptModule & script name to point to the new Script object + Job.objects.filter( + object_type_id=reportmodule_ct.id, object_id=module.pk, name=script_name - ).update(object_type=script_ct, object_id=script.pk) + ).update(object_type_id=script_ct.id, object_id=script.pk) def update_event_rules(apps, schema_editor): diff --git a/netbox/extras/migrations/0110_remove_eventrule_action_parameters.py b/netbox/extras/migrations/0110_remove_eventrule_action_parameters.py index 9103524622f..b7373bdce23 100644 --- a/netbox/extras/migrations/0110_remove_eventrule_action_parameters.py +++ b/netbox/extras/migrations/0110_remove_eventrule_action_parameters.py @@ -12,4 +12,7 @@ class Migration(migrations.Migration): model_name='eventrule', name='action_parameters', ), + migrations.DeleteModel( + name='ReportModule', + ), ] diff --git a/netbox/extras/tables/tables.py b/netbox/extras/tables/tables.py index 8482c5e24a9..a9d80783c56 100644 --- a/netbox/extras/tables/tables.py +++ b/netbox/extras/tables/tables.py @@ -5,7 +5,7 @@ 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 @@ 'JournalEntryTable', 'ObjectChangeTable', 'SavedFilterTable', + 'ReportResultsTable', + 'ScriptResultsTable', 'TaggedItemTable', 'TagTable', 'WebhookTable', @@ -507,3 +509,61 @@ class Meta(NetBoxTable.Meta): 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', + ) + + +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', + ) diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 56840f7a9aa..a0370544f4c 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -17,6 +17,7 @@ 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 . import filtersets, forms, tables from .models import * from .scripts import run_script +from .tables import ReportResultsTable, ScriptResultsTable # @@ -1143,19 +1145,72 @@ def get(self, request, module, name, path=''): 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 get_table(self, job, request, bulk_actions=True): + data = [] + tests = None + table = 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 = { + 'index': index, + 'time': log.get('time'), + 'status': log.get('status'), + 'message': log.get('message'), + } + data.append(result) + + table = ScriptResultsTable(data, user=request.user) + table.configure(request) + else: + # for legacy reports + 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 = ReportResultsTable(data, user=request.user) + table.configure(request) + + return table + def get(self, request, **kwargs): + table = None job = get_object_or_404(Job.objects.all(), pk=kwargs.get('job_pk')) + if job.completed: + table = self.get_table(job, request, bulk_actions=False) + context = { 'script': job.object, 'job': job, + 'table': table, } + 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 ed5dd9cbd29..e532e07e14f 100644 --- a/netbox/templates/extras/htmx/script_result.html +++ b/netbox/templates/extras/htmx/script_result.html @@ -3,124 +3,63 @@ {% 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 %} + {% if 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 %} -
- {% 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 %} +
+ {% endif %} - {# Summary of test methods #} + {% if table %}
-
{% trans "Test Summary" %}
- - {% for test, data in tests.items %} - - - - - {% endfor %} -
{{ test }} - {{ data.success }} - {{ data.info }} - {{ data.warning }} - {{ data.failure }} -
+
+
{% trans "Log" %}
+ {% include 'htmx/table.html' %} +
+ {% endif %} - {# 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 }}
-
+ {# 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 %} + {% elif job.started %} + {% include 'extras/inc/result_pending.html' %} {% endif %} -{% elif job.started %} - {% include 'extras/inc/result_pending.html' %} -{% endif %} + diff --git a/netbox/templates/extras/script_result.html b/netbox/templates/extras/script_result.html index 8f6d817c7a8..030e73903bc 100644 --- a/netbox/templates/extras/script_result.html +++ b/netbox/templates/extras/script_result.html @@ -32,28 +32,74 @@ {% block tabs %} {% endblock %} {% block content %} -
-
-
- {% include 'extras/htmx/script_result.html' %} + {# Object list tab #} +
+ + {# Object table controls #} +
+
+ {% if request.user.is_authenticated %} +
+ +
+ {% endif %} +
+ +
+ {% csrf_token %} + {# "Select all" form #} + {% if table.paginator.num_pages > 1 %} +
+
+
+
+ + +
+
+
+
+ {% endif %} + +
+ {% csrf_token %} + + + {# Objects table #} +
+ {% include 'extras/htmx/script_result.html' %} +
+ {# /Objects table #} + +
+
-
-
-

{{ script.filename }}

-
{{ script.source }}
-
+ {# /Object list tab #} + + {# Filters tab #} + {% if filter_form %} +
+ {% include 'inc/filter_list.html' %} +
+ {% endif %} + {# /Filters tab #} + {% endblock content %} {% block modals %} - {% include 'inc/htmx_modal.html' %} + {% table_config_form table table_name="ObjectTable" %} {% endblock modals %}