mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-17 04:58:16 -06:00
10587 pagination
This commit is contained in:
parent
bbdf5c76cb
commit
7d44da0a2a
@ -5,7 +5,7 @@ from django.conf import settings
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from extras.models import *
|
from extras.models import *
|
||||||
from netbox.tables import NetBoxTable, columns
|
from netbox.tables import BaseTable, NetBoxTable, columns
|
||||||
from .template_code import *
|
from .template_code import *
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -21,6 +21,8 @@ __all__ = (
|
|||||||
'JournalEntryTable',
|
'JournalEntryTable',
|
||||||
'ObjectChangeTable',
|
'ObjectChangeTable',
|
||||||
'SavedFilterTable',
|
'SavedFilterTable',
|
||||||
|
'ReportResultsTable',
|
||||||
|
'ScriptResultsTable',
|
||||||
'TaggedItemTable',
|
'TaggedItemTable',
|
||||||
'TagTable',
|
'TagTable',
|
||||||
'WebhookTable',
|
'WebhookTable',
|
||||||
@ -507,3 +509,67 @@ class JournalEntryTable(NetBoxTable):
|
|||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'created', 'created_by', 'assigned_object_type', 'assigned_object', 'kind', 'comments'
|
'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',
|
||||||
|
)
|
||||||
|
@ -17,6 +17,7 @@ from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm
|
|||||||
from extras.dashboard.utils import get_widget_class
|
from extras.dashboard.utils import get_widget_class
|
||||||
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
|
from netbox.views.generic.mixins import TableMixin
|
||||||
from utilities.forms import ConfirmationForm, get_field_value
|
from utilities.forms import ConfirmationForm, get_field_value
|
||||||
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||||
from utilities.rqworker import get_workers_for_queue
|
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 . import filtersets, forms, tables
|
||||||
from .models import *
|
from .models import *
|
||||||
from .scripts import run_script
|
from .scripts import run_script
|
||||||
|
from .tables import ReportResultsTable, ScriptResultsTable
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -1143,47 +1145,72 @@ class LegacyScriptRedirectView(ContentTypePermissionRequiredMixin, View):
|
|||||||
return redirect(f'{url}{path}')
|
return redirect(f'{url}{path}')
|
||||||
|
|
||||||
|
|
||||||
class ScriptResultView(generic.ObjectView):
|
class ScriptResultView(TableMixin, generic.ObjectView):
|
||||||
queryset = Job.objects.all()
|
queryset = Job.objects.all()
|
||||||
|
|
||||||
def get_required_permission(self):
|
def get_required_permission(self):
|
||||||
return 'extras.view_script'
|
return 'extras.view_script'
|
||||||
|
|
||||||
def job_to_result_array(self, job):
|
def get_table(self, job, request, bulk_actions=True):
|
||||||
results = []
|
data = []
|
||||||
|
tests = None
|
||||||
|
table_logs = table_tests = None
|
||||||
|
index = 0
|
||||||
if job.data:
|
if job.data:
|
||||||
if 'log' in job.data:
|
if 'log' in job.data:
|
||||||
|
if 'tests' in job.data:
|
||||||
|
tests = job.data['tests']
|
||||||
|
|
||||||
for log in job.data['log']:
|
for log in job.data['log']:
|
||||||
|
index += 1
|
||||||
result = {
|
result = {
|
||||||
'method': None,
|
'index': index,
|
||||||
'time': log.get('time', None),
|
'time': log.get('time', None),
|
||||||
'level': log.get('level', None),
|
'status': log.get('status', None),
|
||||||
'object': log.get('object', None),
|
|
||||||
'message': log.get('message', None),
|
'message': log.get('message', None),
|
||||||
}
|
}
|
||||||
results.append(result)
|
data.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
|
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):
|
def get(self, request, **kwargs):
|
||||||
job = get_object_or_404(Job.objects.all(), pk=kwargs.get('job_pk'))
|
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 = {
|
context = {
|
||||||
'script': job.object,
|
'script': job.object,
|
||||||
'job': job,
|
'job': job,
|
||||||
|
'table_logs': table_logs,
|
||||||
|
'table_tests': table_tests,
|
||||||
}
|
}
|
||||||
|
|
||||||
if job.data and 'log' in job.data:
|
if job.data and 'log' in job.data:
|
||||||
# Script
|
# Script
|
||||||
context['tests'] = job.data.get('tests', {})
|
context['tests'] = job.data.get('tests', {})
|
||||||
|
@ -3,124 +3,69 @@
|
|||||||
{% load log_levels %}
|
{% load log_levels %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
<p>
|
<div class="htmx-container">
|
||||||
{% if job.started %}
|
<p>
|
||||||
{% trans "Started" %}: <strong>{{ job.started|annotated_date }}</strong>
|
{% if job.started %}
|
||||||
{% elif job.scheduled %}
|
{% trans "Started" %}: <strong>{{ job.started|annotated_date }}</strong>
|
||||||
{% trans "Scheduled for" %}: <strong>{{ job.scheduled|annotated_date }}</strong> ({{ job.scheduled|naturaltime }})
|
{% elif job.scheduled %}
|
||||||
{% else %}
|
{% trans "Scheduled for" %}: <strong>{{ job.scheduled|annotated_date }}</strong> ({{ job.scheduled|naturaltime }})
|
||||||
{% trans "Created" %}: <strong>{{ job.created|annotated_date }}</strong>
|
{% else %}
|
||||||
{% endif %}
|
{% 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 %}
|
{% 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. #}
|
<div class="card">
|
||||||
{% if 'log' in job.data %}
|
<div class="table-responsive" id="object_list">
|
||||||
<div class="card mb-3">
|
<h5 class="card-header">{% trans "Log" %}</h5>
|
||||||
<h5 class="card-header">{% trans "Log" %}</h5>
|
{% include 'htmx/table.html' with table=table_logs %}
|
||||||
{% if job.data.log %}
|
</div>
|
||||||
<table class="table table-hover panel-body">
|
</div>
|
||||||
<tr>
|
|
||||||
<th>{% trans "Line" %}</th>
|
{# Script output. Legacy reports will not have this. #}
|
||||||
<th>{% trans "Time" %}</th>
|
{% if 'output' in job.data %}
|
||||||
<th>{% trans "Level" %}</th>
|
<div class="card mb-3">
|
||||||
<th>{% trans "Message" %}</th>
|
<h5 class="card-header">{% trans "Output" %}</h5>
|
||||||
</tr>
|
{% if job.data.output %}
|
||||||
{% for log in job.data.log %}
|
<pre class="card-body font-monospace">{{ job.data.output }}</pre>
|
||||||
|
{% else %}
|
||||||
|
<div class="card-body text-muted">{% trans "None" %}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if table_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>
|
<tr>
|
||||||
<td>{{ forloop.counter }}</td>
|
<td class="font-monospace"><a href="#{{ test }}">{{ test }}</a></td>
|
||||||
<td>{{ log.time|placeholder }}</td>
|
<td class="text-end report-stats">
|
||||||
<td>{% log_level log.status %}</td>
|
<span class="badge text-bg-success">{{ data.success }}</span>
|
||||||
<td>{{ log.message|markdown }}</td>
|
<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>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% else %}
|
</div>
|
||||||
<div class="card-body text-muted">{% trans "None" %}</div>
|
|
||||||
{% endif %}
|
{# Detailed results for individual tests #}
|
||||||
</div>
|
<div class="card">
|
||||||
|
<div class="table-responsive" id="object_list">
|
||||||
|
<h5 class="card-header">{% trans "Test Details" %}</h5>
|
||||||
|
{% include 'htmx/table.html' with table=table_tests %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% elif job.started %}
|
||||||
|
{% include 'extras/inc/result_pending.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</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 %}
|
|
||||||
|
|
||||||
{# Test method logs (for legacy Reports) #}
|
|
||||||
{% 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 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>
|
|
||||||
|
|
||||||
{# 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>
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
{% elif job.started %}
|
|
||||||
{% include 'extras/inc/result_pending.html' %}
|
|
||||||
{% endif %}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user