Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

10587 script pagination #15343

Merged
merged 7 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions netbox/extras/migrations/0108_convert_reports_to_scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,4 @@ class Migration(migrations.Migration):
migrations.DeleteModel(
name='Report',
),
migrations.DeleteModel(
name='ReportModule',
),
]
16 changes: 12 additions & 4 deletions netbox/extras/migrations/0109_script_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ class Migration(migrations.Migration):
model_name='eventrule',
name='action_parameters',
),
migrations.DeleteModel(
name='ReportModule',
),
]
62 changes: 61 additions & 1 deletion netbox/extras/tables/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__ = (
Expand All @@ -21,6 +21,8 @@
'JournalEntryTable',
'ObjectChangeTable',
'SavedFilterTable',
'ReportResultsTable',
'ScriptResultsTable',
'TaggedItemTable',
'TagTable',
'WebhookTable',
Expand Down Expand Up @@ -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',
)
57 changes: 56 additions & 1 deletion netbox/extras/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -26,6 +27,7 @@
from . import filtersets, forms, tables
from .models import *
from .scripts import run_script
from .tables import ReportResultsTable, ScriptResultsTable


#
Expand Down Expand Up @@ -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
arthanson marked this conversation as resolved.
Show resolved Hide resolved
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
arthanson marked this conversation as resolved.
Show resolved Hide resolved

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
arthanson marked this conversation as resolved.
Show resolved Hide resolved
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,
arthanson marked this conversation as resolved.
Show resolved Hide resolved
}

if job.data and 'log' in job.data:
# Script
context['tests'] = job.data.get('tests', {})
Expand Down
159 changes: 49 additions & 110 deletions netbox/templates/extras/htmx/script_result.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,124 +3,63 @@
{% load log_levels %}
{% load i18n %}

<p>
{% if job.started %}
{% trans "Started" %}: <strong>{{ job.started|annotated_date }}</strong>
{% elif job.scheduled %}
{% trans "Scheduled for" %}: <strong>{{ job.scheduled|annotated_date }}</strong> ({{ job.scheduled|naturaltime }})
{% else %}
{% trans "Created" %}: <strong>{{ job.created|annotated_date }}</strong>
{% endif %}
<div class="htmx-container">
<p>
{% if job.started %}
{% trans "Started" %}: <strong>{{ job.started|annotated_date }}</strong>
{% elif job.scheduled %}
{% trans "Scheduled for" %}: <strong>{{ job.scheduled|annotated_date }}</strong> ({{ job.scheduled|naturaltime }})
{% else %}
{% trans "Created" %}: <strong>{{ job.created|annotated_date }}</strong>
{% endif %}
{% if job.completed %}
{% trans "Duration" %}: <strong>{{ job.duration }}</strong>
{% endif %}
<span id="pending-result-label">{% badge job.get_status_display job.get_status_color %}</span>
</p>
{% if job.completed %}
{% trans "Duration" %}: <strong>{{ job.duration }}</strong>
{% endif %}
<span id="pending-result-label">{% badge job.get_status_display job.get_status_color %}</span>
</p>
{% if job.completed %}

{# Script log. Legacy reports will not have this. #}
{% if 'log' in job.data %}
<div class="card mb-3">
<h5 class="card-header">{% trans "Log" %}</h5>
{% if job.data.log %}
<table class="table table-hover panel-body">
<tr>
<th>{% trans "Line" %}</th>
<th>{% trans "Time" %}</th>
<th>{% trans "Level" %}</th>
<th>{% trans "Message" %}</th>
</tr>
{% for log in job.data.log %}
{% if tests %}
{# Summary of test methods #}
<div class="card">
<h5 class="card-header">{% trans "Test Summary" %}</h5>
<table class="table table-hover">
{% for test, data in tests.items %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ log.time|placeholder }}</td>
<td>{% log_level log.status %}</td>
<td>{{ log.message|markdown }}</td>
<td class="font-monospace"><a href="#{{ test }}">{{ test }}</a></td>
<td class="text-end report-stats">
<span class="badge text-bg-success">{{ data.success }}</span>
<span class="badge text-bg-info">{{ data.info }}</span>
<span class="badge text-bg-warning">{{ data.warning }}</span>
<span class="badge text-bg-danger">{{ data.failure }}</span>
</td>
</tr>
{% endfor %}
</table>
{% else %}
<div class="card-body text-muted">{% trans "None" %}</div>
{% endif %}
</div>
{% endif %}

{# Script output. Legacy reports will not have this. #}
{% if 'output' in job.data %}
<div class="card mb-3">
<h5 class="card-header">{% trans "Output" %}</h5>
{% if job.data.output %}
<pre class="card-body font-monospace">{{ job.data.output }}</pre>
{% else %}
<div class="card-body text-muted">{% trans "None" %}</div>
{% endif %}
</div>
{% endif %}

{# Test method logs (for legacy Reports) #}
{% if tests %}
</div>
{% endif %}

{# Summary of test methods #}
{% if table %}
<div class="card">
<h5 class="card-header">{% trans "Test Summary" %}</h5>
<table class="table table-hover">
{% for test, data in tests.items %}
<tr>
<td class="font-monospace"><a href="#{{ test }}">{{ test }}</a></td>
<td class="text-end report-stats">
<span class="badge text-bg-success">{{ data.success }}</span>
<span class="badge text-bg-info">{{ data.info }}</span>
<span class="badge text-bg-warning">{{ data.warning }}</span>
<span class="badge text-bg-danger">{{ data.failure }}</span>
</td>
</tr>
{% endfor %}
</table>
<div class="table-responsive" id="object_list">
<h5 class="card-header">{% trans "Log" %}</h5>
{% include 'htmx/table.html' %}
</div>
</div>
{% endif %}

{# Detailed results for individual tests #}
<div class="card">
<h5 class="card-header">{% trans "Test Details" %}</h5>
<table class="table table-hover report">
<thead>
<tr class="table-headings">
<th>{% trans "Time" %}</th>
<th>{% trans "Level" %}</th>
<th>{% trans "Object" %}</th>
<th>{% trans "Message" %}</th>
</tr>
</thead>
<tbody>
{% for test, data in tests.items %}
<tr>
<th colspan="4" style="font-family: monospace">
<a name="{{ test }}"></a>{{ test }}
</th>
</tr>
{% for time, level, obj, url, message in data.log %}
<tr class="{% if level == 'failure' %}danger{% elif level %}{{ level }}{% endif %}">
<td>{{ time }}</td>
<td>
<label class="badge text-bg-{% if level == 'failure' %}danger{% else %}{{ level }}{% endif %}">{{ level|title }}</label>
</td>
<td>
{% if obj and url %}
<a href="{{ url }}">{{ obj }}</a>
{% elif obj %}
{{ obj }}
{% else %}
{{ ''|placeholder }}
{% endif %}
</td>
<td class="rendered-markdown">{{ message|markdown }}</td>
</tr>
{% endfor %}
{% endfor %}
</tbody>
</table>
</div>
{# Script output. Legacy reports will not have this. #}
{% if 'output' in job.data %}
<div class="card mb-3">
<h5 class="card-header">{% trans "Output" %}</h5>
{% if job.data.output %}
<pre class="card-body font-monospace">{{ job.data.output }}</pre>
{% else %}
<div class="card-body text-muted">{% trans "None" %}</div>
{% endif %}
</div>
{% endif %}

{% elif job.started %}
{% include 'extras/inc/result_pending.html' %}
{% endif %}
{% elif job.started %}
{% include 'extras/inc/result_pending.html' %}
{% endif %}
</div>
Loading
Loading