From 4e0c99b8b000dec6d5b4f3ff28ed1e75891724f8 Mon Sep 17 00:00:00 2001 From: Arthur Date: Wed, 28 Feb 2024 15:34:28 -0800 Subject: [PATCH 1/7] 10587 temp commit --- netbox/extras/migrations/0109_script_model.py | 2 +- netbox/extras/views.py | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/netbox/extras/migrations/0109_script_model.py b/netbox/extras/migrations/0109_script_model.py index 89b343a8222..0fd928812ce 100644 --- a/netbox/extras/migrations/0109_script_model.py +++ b/netbox/extras/migrations/0109_script_model.py @@ -99,7 +99,7 @@ def update_scripts(apps, schema_editor): object_type=scriptmodule_ct, 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/views.py b/netbox/extras/views.py index 56840f7a9aa..0c7a6a48b54 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -1149,6 +1149,34 @@ class ScriptResultView(generic.ObjectView): def get_required_permission(self): return 'extras.view_script' + def job_to_result_array(self, job): + results = [] + if job.data: + if 'log' in job.data: + for log in job.data['log']: + result = { + 'method': None, + 'time': log.get('time', None), + 'level': log.get('level', None), + 'object': log.get('object', 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, + } + + return data + def get(self, request, **kwargs): job = get_object_or_404(Job.objects.all(), pk=kwargs.get('job_pk')) From c027c4fa24b8eb9f37049f6cdd6da6a10e1b9e68 Mon Sep 17 00:00:00 2001 From: Arthur Date: Wed, 28 Feb 2024 15:53:15 -0800 Subject: [PATCH 2/7] 10587 temp commit --- netbox/extras/migrations/0109_script_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/extras/migrations/0109_script_model.py b/netbox/extras/migrations/0109_script_model.py index 0fd928812ce..cb0a7744f8d 100644 --- a/netbox/extras/migrations/0109_script_model.py +++ b/netbox/extras/migrations/0109_script_model.py @@ -96,7 +96,7 @@ 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) From bbdf5c76cbfb8fc1cf1c8d693207204d79955b41 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 29 Feb 2024 14:06:44 -0800 Subject: [PATCH 3/7] 10587 fix migrations --- .../migrations/0108_convert_reports_to_scripts.py | 3 --- netbox/extras/migrations/0109_script_model.py | 12 ++++++++++-- .../0110_remove_eventrule_action_parameters.py | 3 +++ 3 files changed, 13 insertions(+), 5 deletions(-) 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 cb0a7744f8d..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): @@ -100,6 +102,12 @@ def update_scripts(apps, schema_editor): 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_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', + ), ] From 7d44da0a2afda54186d0cec37a9586593b1f7c17 Mon Sep 17 00:00:00 2001 From: Arthur Date: Fri, 1 Mar 2024 10:44:33 -0800 Subject: [PATCH 4/7] 10587 pagination --- netbox/extras/tables/tables.py | 68 +++++++- netbox/extras/views.py | 67 ++++--- .../templates/extras/htmx/script_result.html | 165 ++++++------------ 3 files changed, 169 insertions(+), 131 deletions(-) diff --git a/netbox/extras/tables/tables.py b/netbox/extras/tables/tables.py index 8482c5e24a9..048f5cdd3ff 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,67 @@ 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', + ) + 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 0c7a6a48b54..ead25e57604 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,47 +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 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) + data.append(result) + + table_logs = ScriptResultsTable(data, user=request.user) + table_logs.configure(request) 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, - } - - return data + 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 ed5dd9cbd29..438adc46056 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 %} - - - - - - - {% endfor %} -
{% trans "Line" %}{% trans "Time" %}{% trans "Level" %}{% trans "Message" %}
{{ forloop.counter }}{{ log.time|placeholder }}{% log_level log.status %}{{ log.message|markdown }}
- {% 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 %} - - {# Summary of test methods #}
-
{% 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' with table=table_logs %} +
- {# Detailed results for individual tests #} -
-
{% trans "Test Details" %}
- - - - - - - - - - + {# 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 "Time" %}{% trans "Level" %}{% trans "Object" %}{% trans "Message" %}
{% for test, data in tests.items %} - + + - {% for time, level, obj, url, message in data.log %} - - - - - - - {% endfor %} {% endfor %} - -
- {{ test }} - {{ test }} + {{ data.success }} + {{ data.info }} + {{ data.warning }} + {{ data.failure }} +
{{ time }} - - - {% if obj and url %} - {{ obj }} - {% elif obj %} - {{ obj }} - {% else %} - {{ ''|placeholder }} - {% endif %} - {{ message|markdown }}
-
+ +
+ {# 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 %} -{% elif job.started %} - {% include 'extras/inc/result_pending.html' %} -{% endif %} + From ab9734a9d89a2b3ff13ff57e266da746dd1125b2 Mon Sep 17 00:00:00 2001 From: Arthur Date: Mon, 4 Mar 2024 10:26:51 -0800 Subject: [PATCH 5/7] 10587 pagination --- netbox/extras/views.py | 1 + netbox/templates/extras/htmx/script_result.html | 2 ++ 2 files changed, 3 insertions(+) diff --git a/netbox/extras/views.py b/netbox/extras/views.py index ead25e57604..2a55135e580 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -1204,6 +1204,7 @@ def get(self, request, **kwargs): if job.completed: table_logs, table_tests = self.get_table(job, request, bulk_actions=False) + breakpoint() context = { 'script': job.object, 'job': job, diff --git a/netbox/templates/extras/htmx/script_result.html b/netbox/templates/extras/htmx/script_result.html index 438adc46056..965e2d614f7 100644 --- a/netbox/templates/extras/htmx/script_result.html +++ b/netbox/templates/extras/htmx/script_result.html @@ -19,12 +19,14 @@

{% if job.completed %} + {% if table.logs and not table_tests %}
{% trans "Log" %}
{% include 'htmx/table.html' with table=table_logs %}
+ {% endif %} {# Script output. Legacy reports will not have this. #} {% if 'output' in job.data %} From 44fa0cba1820ead192e954479c1bc79428272b75 Mon Sep 17 00:00:00 2001 From: Arthur Date: Mon, 4 Mar 2024 13:13:35 -0800 Subject: [PATCH 6/7] 10587 pagination --- netbox/extras/views.py | 18 ++--- .../templates/extras/htmx/script_result.html | 50 ++++++------- netbox/templates/extras/script_result.html | 74 +++++++++++++++---- 3 files changed, 89 insertions(+), 53 deletions(-) diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 2a55135e580..25bac5f1d4b 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -1154,7 +1154,7 @@ def get_required_permission(self): def get_table(self, job, request, bulk_actions=True): data = [] tests = None - table_logs = table_tests = None + table = None index = 0 if job.data: if 'log' in job.data: @@ -1171,8 +1171,8 @@ def get_table(self, job, request, bulk_actions=True): } data.append(result) - table_logs = ScriptResultsTable(data, user=request.user) - table_logs.configure(request) + table = ScriptResultsTable(data, user=request.user) + table.configure(request) else: tests = job.data @@ -1192,24 +1192,22 @@ def get_table(self, job, request, bulk_actions=True): } data.append(result) - table_tests = ReportResultsTable(data, user=request.user) - table_tests.configure(request) + table = ReportResultsTable(data, user=request.user) + table.configure(request) - return table_logs, table_tests + return table 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) + table = self.get_table(job, request, bulk_actions=False) - breakpoint() context = { 'script': job.object, 'job': job, - 'table_logs': table_logs, - 'table_tests': table_tests, + 'table': table, } if job.data and 'log' in job.data: diff --git a/netbox/templates/extras/htmx/script_result.html b/netbox/templates/extras/htmx/script_result.html index 965e2d614f7..e532e07e14f 100644 --- a/netbox/templates/extras/htmx/script_result.html +++ b/netbox/templates/extras/htmx/script_result.html @@ -18,29 +18,7 @@ {% badge job.get_status_display job.get_status_color %}

{% if job.completed %} - - {% if table.logs and not table_tests %} -
-
-
{% trans "Log" %}
- {% include 'htmx/table.html' with table=table_logs %} -
-
- {% 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 %} - - {% if table_tests %} + {% if tests %} {# Summary of test methods #}
{% trans "Test Summary" %}
@@ -58,15 +36,29 @@
{% trans "Test Summary" %}
{% endfor %}
+ {% endif %} - {# Detailed results for individual tests #} -
-
-
{% trans "Test Details" %}
- {% include 'htmx/table.html' with table=table_tests %} -
+ {% if table %} +
+
+
{% trans "Log" %}
+ {% include 'htmx/table.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 %} + {% 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 %} From 954c406b0e463a6ba1ea89d1f47d849c3dc0fa21 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 7 Mar 2024 08:01:47 -0800 Subject: [PATCH 7/7] 10587 review changes --- netbox/extras/tables/tables.py | 8 +------- netbox/extras/views.py | 9 +++++---- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/netbox/extras/tables/tables.py b/netbox/extras/tables/tables.py index 048f5cdd3ff..a9d80783c56 100644 --- a/netbox/extras/tables/tables.py +++ b/netbox/extras/tables/tables.py @@ -531,9 +531,6 @@ class Meta(BaseTable.Meta): fields = ( 'index', 'time', 'status', 'message', ) - default_columns = ( - 'index', 'time', 'status', 'message', - ) class ReportResultsTable(BaseTable): @@ -559,7 +556,7 @@ class ReportResultsTable(BaseTable): verbose_name=_('Object') ) url = tables.Column( - verbose_name=_('Url') + verbose_name=_('URL') ) message = tables.Column( verbose_name=_('Message') @@ -570,6 +567,3 @@ class Meta(BaseTable.Meta): 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 25bac5f1d4b..a0370544f4c 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -1165,15 +1165,16 @@ def get_table(self, job, request, bulk_actions=True): index += 1 result = { 'index': index, - 'time': log.get('time', None), - 'status': log.get('status', None), - 'message': log.get('message', None), + '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: @@ -1198,8 +1199,8 @@ def get_table(self, job, request, bulk_actions=True): return table def get(self, request, **kwargs): + table = None job = get_object_or_404(Job.objects.all(), pk=kwargs.get('job_pk')) - table_logs = table_tests = None if job.completed: table = self.get_table(job, request, bulk_actions=False)