mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-27 01:36:11 -06:00
Reference database object by GFK when running scripts & reports via UI
This commit is contained in:
parent
15590f1f48
commit
ccb09b0f7b
@ -118,11 +118,10 @@ class DataSource(PrimaryModel):
|
|||||||
DataSource.objects.filter(pk=self.pk).update(status=self.status)
|
DataSource.objects.filter(pk=self.pk).update(status=self.status)
|
||||||
|
|
||||||
# Enqueue a sync job
|
# Enqueue a sync job
|
||||||
job_result = Job.enqueue_job(
|
job_result = Job.enqueue(
|
||||||
import_string('core.jobs.sync_datasource'),
|
import_string('core.jobs.sync_datasource'),
|
||||||
name=self.name,
|
instance=self,
|
||||||
obj_type=ContentType.objects.get_for_model(DataSource),
|
user=request.user
|
||||||
user=request.user,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return job_result
|
return job_result
|
||||||
|
@ -194,6 +194,41 @@ class Job(models.Model):
|
|||||||
|
|
||||||
return job
|
return job
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def enqueue(cls, func, instance, name=None, user=None, schedule_at=None, interval=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Create a Job instance and enqueue a job using the given callable
|
||||||
|
|
||||||
|
Args:
|
||||||
|
func: The callable object to be enqueued for execution
|
||||||
|
instance: The NetBox object to which this job pertains
|
||||||
|
name: Name for the job (optional)
|
||||||
|
user: The user responsible for running the job
|
||||||
|
schedule_at: Schedule the job to be executed at the passed date and time
|
||||||
|
interval: Recurrence interval (in minutes)
|
||||||
|
"""
|
||||||
|
object_type = ContentType.objects.get_for_model(instance, for_concrete_model=False)
|
||||||
|
rq_queue_name = get_queue_for_model(object_type.model)
|
||||||
|
queue = django_rq.get_queue(rq_queue_name)
|
||||||
|
status = JobStatusChoices.STATUS_SCHEDULED if schedule_at else JobStatusChoices.STATUS_PENDING
|
||||||
|
job = Job.objects.create(
|
||||||
|
object_type=object_type,
|
||||||
|
object_id=instance.pk,
|
||||||
|
name=name,
|
||||||
|
status=status,
|
||||||
|
scheduled=schedule_at,
|
||||||
|
interval=interval,
|
||||||
|
user=user,
|
||||||
|
job_id=uuid.uuid4()
|
||||||
|
)
|
||||||
|
|
||||||
|
if schedule_at:
|
||||||
|
queue.enqueue_at(schedule_at, func, job_id=str(job.job_id), job_result=job, **kwargs)
|
||||||
|
else:
|
||||||
|
queue.enqueue(func, job_id=str(job.job_id), job_result=job, **kwargs)
|
||||||
|
|
||||||
|
return job
|
||||||
|
|
||||||
def trigger_webhooks(self, event):
|
def trigger_webhooks(self, event):
|
||||||
from extras.models import Webhook
|
from extras.models import Webhook
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
from django_rq.queues import get_connection
|
from django_rq.queues import get_connection
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import inspect
|
import inspect
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
|
||||||
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
@ -35,6 +36,12 @@ class ReportModule(PythonModuleMixin, ManagedFile):
|
|||||||
"""
|
"""
|
||||||
Proxy model for report module files.
|
Proxy model for report module files.
|
||||||
"""
|
"""
|
||||||
|
jobs = GenericRelation(
|
||||||
|
to='core.Job',
|
||||||
|
content_type_field='object_type',
|
||||||
|
object_id_field='object_id'
|
||||||
|
)
|
||||||
|
|
||||||
objects = ReportModuleManager()
|
objects = ReportModuleManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -28,24 +28,24 @@ def run_report(job_result, *args, **kwargs):
|
|||||||
Helper function to call the run method on a report. This is needed to get around the inability to pickle an instance
|
Helper function to call the run method on a report. This is needed to get around the inability to pickle an instance
|
||||||
method for queueing into the background processor.
|
method for queueing into the background processor.
|
||||||
"""
|
"""
|
||||||
module_name, report_name = job_result.name.split('.', 1)
|
job_result.start()
|
||||||
report = get_report(module_name, report_name)()
|
|
||||||
|
module = ReportModule.objects.get(pk=job_result.object_id)
|
||||||
|
report = module.reports.get(job_result.name)()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
job_result.start()
|
|
||||||
report.run(job_result)
|
report.run(job_result)
|
||||||
except Exception:
|
except Exception:
|
||||||
job_result.terminate(status=JobStatusChoices.STATUS_ERRORED)
|
job_result.terminate(status=JobStatusChoices.STATUS_ERRORED)
|
||||||
logging.error(f"Error during execution of report {job_result.name}")
|
logging.error(f"Error during execution of report {job_result.name}")
|
||||||
finally:
|
finally:
|
||||||
# Schedule the next job if an interval has been set
|
# Schedule the next job if an interval has been set
|
||||||
start_time = job_result.scheduled or job_result.started
|
if job_result.interval:
|
||||||
if start_time and job_result.interval:
|
new_scheduled_time = job_result.scheduled + timedelta(minutes=job_result.interval)
|
||||||
new_scheduled_time = start_time + timedelta(minutes=job_result.interval)
|
Job.enqueue(
|
||||||
Job.enqueue_job(
|
|
||||||
run_report,
|
run_report,
|
||||||
|
instance=job_result.object,
|
||||||
name=job_result.name,
|
name=job_result.name,
|
||||||
obj_type=job_result.obj_type,
|
|
||||||
user=job_result.user,
|
user=job_result.user,
|
||||||
job_timeout=report.job_timeout,
|
job_timeout=report.job_timeout,
|
||||||
schedule_at=new_scheduled_time,
|
schedule_at=new_scheduled_time,
|
||||||
|
@ -444,10 +444,10 @@ def run_script(data, request, commit=True, *args, **kwargs):
|
|||||||
job_result = kwargs.pop('job_result')
|
job_result = kwargs.pop('job_result')
|
||||||
job_result.start()
|
job_result.start()
|
||||||
|
|
||||||
module_name, script_name = job_result.name.split('.', 1)
|
module = ScriptModule.objects.get(pk=job_result.object_id)
|
||||||
script = get_script(module_name, script_name)()
|
script = module.scripts.get(job_result.name)()
|
||||||
|
|
||||||
logger = logging.getLogger(f"netbox.scripts.{module_name}.{script_name}")
|
logger = logging.getLogger(f"netbox.scripts.{script.full_name}")
|
||||||
logger.info(f"Running script (commit={commit})")
|
logger.info(f"Running script (commit={commit})")
|
||||||
|
|
||||||
# Add files to form data
|
# Add files to form data
|
||||||
@ -500,10 +500,10 @@ def run_script(data, request, commit=True, *args, **kwargs):
|
|||||||
# Schedule the next job if an interval has been set
|
# Schedule the next job if an interval has been set
|
||||||
if job_result.interval:
|
if job_result.interval:
|
||||||
new_scheduled_time = job_result.scheduled + timedelta(minutes=job_result.interval)
|
new_scheduled_time = job_result.scheduled + timedelta(minutes=job_result.interval)
|
||||||
Job.enqueue_job(
|
Job.enqueue(
|
||||||
run_script,
|
run_script,
|
||||||
|
instance=job_result.object,
|
||||||
name=job_result.name,
|
name=job_result.name,
|
||||||
obj_type=job_result.obj_type,
|
|
||||||
user=job_result.user,
|
user=job_result.user,
|
||||||
schedule_at=new_scheduled_time,
|
schedule_at=new_scheduled_time,
|
||||||
interval=job_result.interval,
|
interval=job_result.interval,
|
||||||
|
@ -95,14 +95,14 @@ urlpatterns = [
|
|||||||
# Reports
|
# Reports
|
||||||
path('reports/', views.ReportListView.as_view(), name='report_list'),
|
path('reports/', views.ReportListView.as_view(), name='report_list'),
|
||||||
path('reports/add/', views.ReportModuleCreateView.as_view(), name='reportmodule_add'),
|
path('reports/add/', views.ReportModuleCreateView.as_view(), name='reportmodule_add'),
|
||||||
path('reports/results/<int:job_result_pk>/', views.ReportResultView.as_view(), name='report_result'),
|
path('reports/results/<int:job_pk>/', views.ReportResultView.as_view(), name='report_result'),
|
||||||
path('reports/<int:pk>/', include(get_model_urls('extras', 'reportmodule'))),
|
path('reports/<int:pk>/', include(get_model_urls('extras', 'reportmodule'))),
|
||||||
path('reports/<path:module>.<str:name>/', views.ReportView.as_view(), name='report'),
|
path('reports/<path:module>.<str:name>/', views.ReportView.as_view(), name='report'),
|
||||||
|
|
||||||
# Scripts
|
# Scripts
|
||||||
path('scripts/', views.ScriptListView.as_view(), name='script_list'),
|
path('scripts/', views.ScriptListView.as_view(), name='script_list'),
|
||||||
path('scripts/add/', views.ScriptModuleCreateView.as_view(), name='scriptmodule_add'),
|
path('scripts/add/', views.ScriptModuleCreateView.as_view(), name='scriptmodule_add'),
|
||||||
path('scripts/results/<int:job_result_pk>/', views.ScriptResultView.as_view(), name='script_result'),
|
path('scripts/results/<int:job_pk>/', views.ScriptResultView.as_view(), name='script_result'),
|
||||||
path('scripts/<int:pk>/', include(get_model_urls('extras', 'scriptmodule'))),
|
path('scripts/<int:pk>/', include(get_model_urls('extras', 'scriptmodule'))),
|
||||||
path('scripts/<path:module>.<str:name>/', views.ScriptView.as_view(), name='script'),
|
path('scripts/<path:module>.<str:name>/', views.ScriptView.as_view(), name='script'),
|
||||||
|
|
||||||
|
@ -845,10 +845,11 @@ class ReportView(ContentTypePermissionRequiredMixin, View):
|
|||||||
module = get_object_or_404(ReportModule.objects.restrict(request.user), file_path=f'{module}.py')
|
module = get_object_or_404(ReportModule.objects.restrict(request.user), file_path=f'{module}.py')
|
||||||
report = module.reports[name]()
|
report = module.reports[name]()
|
||||||
|
|
||||||
report_content_type = ContentType.objects.get(app_label='extras', model='report')
|
object_type = ContentType.objects.get(app_label='extras', model='reportmodule')
|
||||||
report.result = Job.objects.filter(
|
report.result = Job.objects.filter(
|
||||||
object_type=report_content_type,
|
object_type=object_type,
|
||||||
name=report.full_name,
|
object_id=module.pk,
|
||||||
|
name=report.name,
|
||||||
status__in=JobStatusChoices.TERMINAL_STATE_CHOICES
|
status__in=JobStatusChoices.TERMINAL_STATE_CHOICES
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
@ -876,17 +877,17 @@ class ReportView(ContentTypePermissionRequiredMixin, View):
|
|||||||
})
|
})
|
||||||
|
|
||||||
# Run the Report. A new Job is created.
|
# Run the Report. A new Job is created.
|
||||||
job_result = Job.enqueue_job(
|
job = Job.enqueue(
|
||||||
run_report,
|
run_report,
|
||||||
name=report.full_name,
|
instance=module,
|
||||||
obj_type=ContentType.objects.get_for_model(Report),
|
name=report.class_name,
|
||||||
user=request.user,
|
user=request.user,
|
||||||
schedule_at=form.cleaned_data.get('schedule_at'),
|
schedule_at=form.cleaned_data.get('schedule_at'),
|
||||||
interval=form.cleaned_data.get('interval'),
|
interval=form.cleaned_data.get('interval'),
|
||||||
job_timeout=report.job_timeout
|
job_timeout=report.job_timeout
|
||||||
)
|
)
|
||||||
|
|
||||||
return redirect('extras:report_result', job_result_pk=job_result.pk)
|
return redirect('extras:report_result', job_pk=job.pk)
|
||||||
|
|
||||||
return render(request, 'extras/report.html', {
|
return render(request, 'extras/report.html', {
|
||||||
'module': module,
|
'module': module,
|
||||||
@ -902,28 +903,26 @@ class ReportResultView(ContentTypePermissionRequiredMixin, View):
|
|||||||
def get_required_permission(self):
|
def get_required_permission(self):
|
||||||
return 'extras.view_report'
|
return 'extras.view_report'
|
||||||
|
|
||||||
def get(self, request, job_result_pk):
|
def get(self, request, job_pk):
|
||||||
report_content_type = ContentType.objects.get(app_label='extras', model='report')
|
object_type = ContentType.objects.get_by_natural_key(app_label='extras', model='reportmodule')
|
||||||
result = get_object_or_404(Job.objects.all(), pk=job_result_pk, object_type=report_content_type)
|
job = get_object_or_404(Job.objects.all(), pk=job_pk, object_type=object_type)
|
||||||
|
|
||||||
# Retrieve the Report and attach the Job to it
|
module = job.object
|
||||||
module, report_name = result.name.split('.', maxsplit=1)
|
report = module.reports[job.name]
|
||||||
report = get_report(module, report_name)
|
|
||||||
report.result = result
|
|
||||||
|
|
||||||
# If this is an HTMX request, return only the result HTML
|
# If this is an HTMX request, return only the result HTML
|
||||||
if is_htmx(request):
|
if is_htmx(request):
|
||||||
response = render(request, 'extras/htmx/report_result.html', {
|
response = render(request, 'extras/htmx/report_result.html', {
|
||||||
'report': report,
|
'report': report,
|
||||||
'result': result,
|
'job': job,
|
||||||
})
|
})
|
||||||
if result.completed or not result.started:
|
if job.completed or not job.started:
|
||||||
response.status_code = 286
|
response.status_code = 286
|
||||||
return response
|
return response
|
||||||
|
|
||||||
return render(request, 'extras/report_result.html', {
|
return render(request, 'extras/report_result.html', {
|
||||||
'report': report,
|
'report': report,
|
||||||
'result': result,
|
'job': job,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@ -982,9 +981,11 @@ class ScriptView(ContentTypePermissionRequiredMixin, View):
|
|||||||
form = script.as_form(initial=normalize_querydict(request.GET))
|
form = script.as_form(initial=normalize_querydict(request.GET))
|
||||||
|
|
||||||
# Look for a pending Job (use the latest one by creation timestamp)
|
# Look for a pending Job (use the latest one by creation timestamp)
|
||||||
|
object_type = ContentType.objects.get(app_label='extras', model='scriptmodule')
|
||||||
script.result = Job.objects.filter(
|
script.result = Job.objects.filter(
|
||||||
object_type=ContentType.objects.get_for_model(Script),
|
object_type=object_type,
|
||||||
name=script.full_name,
|
object_id=module.pk,
|
||||||
|
name=script.name,
|
||||||
).exclude(
|
).exclude(
|
||||||
status__in=JobStatusChoices.TERMINAL_STATE_CHOICES
|
status__in=JobStatusChoices.TERMINAL_STATE_CHOICES
|
||||||
).first()
|
).first()
|
||||||
@ -1008,10 +1009,10 @@ class ScriptView(ContentTypePermissionRequiredMixin, View):
|
|||||||
messages.error(request, "Unable to run script: RQ worker process not running.")
|
messages.error(request, "Unable to run script: RQ worker process not running.")
|
||||||
|
|
||||||
elif form.is_valid():
|
elif form.is_valid():
|
||||||
job_result = Job.enqueue_job(
|
job = Job.enqueue(
|
||||||
run_script,
|
run_script,
|
||||||
name=script.full_name,
|
instance=module,
|
||||||
obj_type=ContentType.objects.get_for_model(Script),
|
name=script.class_name,
|
||||||
user=request.user,
|
user=request.user,
|
||||||
schedule_at=form.cleaned_data.pop('_schedule_at'),
|
schedule_at=form.cleaned_data.pop('_schedule_at'),
|
||||||
interval=form.cleaned_data.pop('_interval'),
|
interval=form.cleaned_data.pop('_interval'),
|
||||||
@ -1021,7 +1022,7 @@ class ScriptView(ContentTypePermissionRequiredMixin, View):
|
|||||||
commit=form.cleaned_data.pop('_commit')
|
commit=form.cleaned_data.pop('_commit')
|
||||||
)
|
)
|
||||||
|
|
||||||
return redirect('extras:script_result', job_result_pk=job_result.pk)
|
return redirect('extras:script_result', job_pk=job.pk)
|
||||||
|
|
||||||
return render(request, 'extras/script.html', {
|
return render(request, 'extras/script.html', {
|
||||||
'module': module,
|
'module': module,
|
||||||
@ -1035,28 +1036,26 @@ class ScriptResultView(ContentTypePermissionRequiredMixin, View):
|
|||||||
def get_required_permission(self):
|
def get_required_permission(self):
|
||||||
return 'extras.view_script'
|
return 'extras.view_script'
|
||||||
|
|
||||||
def get(self, request, job_result_pk):
|
def get(self, request, job_pk):
|
||||||
script_content_type = ContentType.objects.get(app_label='extras', model='script')
|
object_type = ContentType.objects.get_by_natural_key(app_label='extras', model='scriptmodule')
|
||||||
result = get_object_or_404(Job.objects.all(), pk=job_result_pk, object_type=script_content_type)
|
job = get_object_or_404(Job.objects.all(), pk=job_pk, object_type=object_type)
|
||||||
|
|
||||||
module_name, script_name = result.name.split('.', 1)
|
module = job.object
|
||||||
module = get_object_or_404(ScriptModule.objects.restrict(request.user), file_path=f'{module_name}.py')
|
script = module.scripts[job.name]()
|
||||||
script = module.scripts[script_name]()
|
|
||||||
|
|
||||||
# If this is an HTMX request, return only the result HTML
|
# If this is an HTMX request, return only the result HTML
|
||||||
if is_htmx(request):
|
if is_htmx(request):
|
||||||
response = render(request, 'extras/htmx/script_result.html', {
|
response = render(request, 'extras/htmx/script_result.html', {
|
||||||
'script': script,
|
'script': script,
|
||||||
'result': result,
|
'job': job,
|
||||||
})
|
})
|
||||||
if result.completed or not result.started:
|
if job.completed or not job.started:
|
||||||
response.status_code = 286
|
response.status_code = 286
|
||||||
return response
|
return response
|
||||||
|
|
||||||
return render(request, 'extras/script_result.html', {
|
return render(request, 'extras/script_result.html', {
|
||||||
'script': script,
|
'script': script,
|
||||||
'result': result,
|
'job': job,
|
||||||
'class_name': script.__class__.__name__
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,24 +2,24 @@
|
|||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{% if result.started %}
|
{% if job.started %}
|
||||||
Started: <strong>{{ result.started|annotated_date }}</strong>
|
Started: <strong>{{ job.started|annotated_date }}</strong>
|
||||||
{% elif result.scheduled %}
|
{% elif job.scheduled %}
|
||||||
Scheduled for: <strong>{{ result.scheduled|annotated_date }}</strong> ({{ result.scheduled|naturaltime }})
|
Scheduled for: <strong>{{ job.scheduled|annotated_date }}</strong> ({{ job.scheduled|naturaltime }})
|
||||||
{% else %}
|
{% else %}
|
||||||
Created: <strong>{{ result.created|annotated_date }}</strong>
|
Created: <strong>{{ job.created|annotated_date }}</strong>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if result.completed %}
|
{% if job.completed %}
|
||||||
Duration: <strong>{{ result.duration }}</strong>
|
Duration: <strong>{{ job.duration }}</strong>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span id="pending-result-label">{% badge result.get_status_display result.get_status_color %}</span>
|
<span id="pending-result-label">{% badge job.get_status_display job.get_status_color %}</span>
|
||||||
</p>
|
</p>
|
||||||
{% if result.completed %}
|
{% if job.completed %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">Report Methods</h5>
|
<h5 class="card-header">Report Methods</h5>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
{% for method, data in result.data.items %}
|
{% for method, data in job.data.items %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="font-monospace"><a href="#{{ method }}">{{ method }}</a></td>
|
<td class="font-monospace"><a href="#{{ method }}">{{ method }}</a></td>
|
||||||
<td class="text-end report-stats">
|
<td class="text-end report-stats">
|
||||||
@ -46,7 +46,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for method, data in result.data.items %}
|
{% for method, data in job.data.items %}
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="4" style="font-family: monospace">
|
<th colspan="4" style="font-family: monospace">
|
||||||
<a name="{{ method }}"></a>{{ method }}
|
<a name="{{ method }}"></a>{{ method }}
|
||||||
@ -75,6 +75,6 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% elif result.started %}
|
{% elif job.started %}
|
||||||
{% include 'extras/inc/result_pending.html' %}
|
{% include 'extras/inc/result_pending.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -3,19 +3,19 @@
|
|||||||
{% load log_levels %}
|
{% load log_levels %}
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{% if result.started %}
|
{% if job.started %}
|
||||||
Started: <strong>{{ result.started|annotated_date }}</strong>
|
Started: <strong>{{ job.started|annotated_date }}</strong>
|
||||||
{% elif result.scheduled %}
|
{% elif job.scheduled %}
|
||||||
Scheduled for: <strong>{{ result.scheduled|annotated_date }}</strong> ({{ result.scheduled|naturaltime }})
|
Scheduled for: <strong>{{ job.scheduled|annotated_date }}</strong> ({{ job.scheduled|naturaltime }})
|
||||||
{% else %}
|
{% else %}
|
||||||
Created: <strong>{{ result.created|annotated_date }}</strong>
|
Created: <strong>{{ job.created|annotated_date }}</strong>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if result.completed %}
|
{% if job.completed %}
|
||||||
Duration: <strong>{{ result.duration }}</strong>
|
Duration: <strong>{{ job.duration }}</strong>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span id="pending-result-label">{% badge result.get_status_display result.get_status_color %}</span>
|
<span id="pending-result-label">{% badge job.get_status_display job.get_status_color %}</span>
|
||||||
</p>
|
</p>
|
||||||
{% if result.completed %}
|
{% if job.completed %}
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<h5 class="card-header">Script Log</h5>
|
<h5 class="card-header">Script Log</h5>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@ -25,7 +25,7 @@
|
|||||||
<th>Level</th>
|
<th>Level</th>
|
||||||
<th>Message</th>
|
<th>Message</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for log in result.data.log %}
|
{% for log in job.data.log %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ forloop.counter }}</td>
|
<td>{{ forloop.counter }}</td>
|
||||||
<td>{% log_level log.status %}</td>
|
<td>{% log_level log.status %}</td>
|
||||||
@ -47,11 +47,11 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<h4>Output</h4>
|
<h4>Output</h4>
|
||||||
{% if result.data.output %}
|
{% if job.data.output %}
|
||||||
<pre class="block">{{ result.data.output }}</pre>
|
<pre class="block">{{ job.data.output }}</pre>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="text-muted">None</p>
|
<p class="text-muted">None</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% elif result.started %}
|
{% elif job.started %}
|
||||||
{% include 'extras/inc/result_pending.html' %}
|
{% include 'extras/inc/result_pending.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-12">
|
<div class="col col-md-12">
|
||||||
{% if report.result %}
|
{% if report.result %}
|
||||||
Last run: <a href="{% url 'extras:report_result' job_result_pk=report.result.pk %}">
|
Last run: <a href="{% url 'extras:report_result' job_pk=report.result.pk %}">
|
||||||
<strong>{{ report.result.created|annotated_date }}</strong>
|
<strong>{{ report.result.created|annotated_date }}</strong>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
<td>{{ report.description|markdown|placeholder }}</td>
|
<td>{{ report.description|markdown|placeholder }}</td>
|
||||||
{% if last_result %}
|
{% if last_result %}
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'extras:report_result' job_result_pk=last_result.pk %}">{{ last_result.created|annotated_date }}</a>
|
<a href="{% url 'extras:report_result' job_pk=last_result.pk %}">{{ last_result.created|annotated_date }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% badge last_result.get_status_display last_result.get_status_color %}
|
{% badge last_result.get_status_display last_result.get_status_color %}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
{% block content-wrapper %}
|
{% block content-wrapper %}
|
||||||
<div class="row p-3">
|
<div class="row p-3">
|
||||||
<div class="col col-md-12"{% if not result.completed %} hx-get="{% url 'extras:report_result' job_result_pk=result.pk %}" hx-trigger="every 5s"{% endif %}>
|
<div class="col col-md-12"{% if not job.completed %} hx-get="{% url 'extras:report_result' job_pk=job.pk %}" hx-trigger="every 5s"{% endif %}>
|
||||||
{% include 'extras/htmx/report_result.html' %}
|
{% include 'extras/htmx/report_result.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -13,8 +13,8 @@
|
|||||||
{% block controls %}
|
{% block controls %}
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
{% if request.user|can_delete:result %}
|
{% if request.user|can_delete:job %}
|
||||||
{% delete_button result %}
|
{% delete_button job %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
</td>
|
</td>
|
||||||
{% if last_result %}
|
{% if last_result %}
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'extras:script_result' job_result_pk=last_result.pk %}">{{ last_result.created|annotated_date }}</a>
|
<a href="{% url 'extras:script_result' job_pk=last_result.pk %}">{{ last_result.created|annotated_date }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
{% badge last_result.get_status_display last_result.get_status_color %}
|
{% badge last_result.get_status_display last_result.get_status_color %}
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}">Scripts</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}">Scripts</a></li>
|
||||||
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}#module.{{ script.module }}">{{ script.module|bettertitle }}</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}#module.{{ script.module }}">{{ script.module|bettertitle }}</a></li>
|
||||||
<li class="breadcrumb-item"><a href="{% url 'extras:script' module=script.module name=class_name %}">{{ script }}</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'extras:script' module=script.module name=script.class_name %}">{{ script }}</a></li>
|
||||||
<li class="breadcrumb-item">{{ result.created|annotated_date }}</li>
|
<li class="breadcrumb-item">{{ job.created|annotated_date }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
@ -28,8 +28,8 @@
|
|||||||
{% block controls %}
|
{% block controls %}
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
{% if request.user|can_delete:result %}
|
{% if request.user|can_delete:job %}
|
||||||
{% delete_button result %}
|
{% delete_button job %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -47,7 +47,7 @@
|
|||||||
<div class="tab-content mb-3">
|
<div class="tab-content mb-3">
|
||||||
<div role="tabpanel" class="tab-pane active" id="log">
|
<div role="tabpanel" class="tab-pane active" id="log">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-12"{% if not result.completed %} hx-get="{% url 'extras:script_result' job_result_pk=result.pk %}" hx-trigger="every 5s"{% endif %}>
|
<div class="col col-md-12"{% if not job.completed %} hx-get="{% url 'extras:script_result' job_pk=job.pk %}" hx-trigger="every 5s"{% endif %}>
|
||||||
{% include 'extras/htmx/script_result.html' %}
|
{% include 'extras/htmx/script_result.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user