|
|
@@ -10,6 +10,7 @@ from django.views.generic import View
|
|
|
from core.choices import JobStatusChoices, ManagedFileRootPathChoices
|
|
|
from core.forms import ManagedFileForm
|
|
|
from core.models import Job
|
|
|
+from core.tables import JobTable
|
|
|
from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm
|
|
|
from extras.dashboard.utils import get_widget_class
|
|
|
from netbox.views import generic
|
|
|
@@ -22,7 +23,7 @@ from utilities.views import ContentTypePermissionRequiredMixin, register_model_v
|
|
|
from . import filtersets, forms, tables
|
|
|
from .forms.reports import ReportForm
|
|
|
from .models import *
|
|
|
-from .reports import get_report, run_report
|
|
|
+from .reports import run_report
|
|
|
from .scripts import run_script
|
|
|
|
|
|
|
|
|
@@ -819,7 +820,7 @@ class ReportListView(ContentTypePermissionRequiredMixin, View):
|
|
|
report_modules = ReportModule.objects.restrict(request.user)
|
|
|
|
|
|
report_content_type = ContentType.objects.get(app_label='extras', model='report')
|
|
|
- job_results = {
|
|
|
+ jobs = {
|
|
|
r.name: r
|
|
|
for r in Job.objects.filter(
|
|
|
object_type=report_content_type,
|
|
|
@@ -830,7 +831,7 @@ class ReportListView(ContentTypePermissionRequiredMixin, View):
|
|
|
return render(request, 'extras/report_list.html', {
|
|
|
'model': ReportModule,
|
|
|
'report_modules': report_modules,
|
|
|
- 'job_results': job_results,
|
|
|
+ 'jobs': jobs,
|
|
|
})
|
|
|
|
|
|
|
|
|
@@ -845,10 +846,11 @@ class ReportView(ContentTypePermissionRequiredMixin, View):
|
|
|
module = get_object_or_404(ReportModule.objects.restrict(request.user), file_path=f'{module}.py')
|
|
|
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(
|
|
|
- object_type=report_content_type,
|
|
|
- name=report.full_name,
|
|
|
+ object_type=object_type,
|
|
|
+ object_id=module.pk,
|
|
|
+ name=report.name,
|
|
|
status__in=JobStatusChoices.TERMINAL_STATE_CHOICES
|
|
|
).first()
|
|
|
|
|
|
@@ -876,17 +878,17 @@ class ReportView(ContentTypePermissionRequiredMixin, View):
|
|
|
})
|
|
|
|
|
|
# Run the Report. A new Job is created.
|
|
|
- job_result = Job.enqueue_job(
|
|
|
+ job = Job.enqueue(
|
|
|
run_report,
|
|
|
- name=report.full_name,
|
|
|
- obj_type=ContentType.objects.get_for_model(Report),
|
|
|
+ instance=module,
|
|
|
+ name=report.class_name,
|
|
|
user=request.user,
|
|
|
schedule_at=form.cleaned_data.get('schedule_at'),
|
|
|
interval=form.cleaned_data.get('interval'),
|
|
|
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', {
|
|
|
'module': module,
|
|
|
@@ -895,6 +897,38 @@ class ReportView(ContentTypePermissionRequiredMixin, View):
|
|
|
})
|
|
|
|
|
|
|
|
|
+class ReportJobsView(ContentTypePermissionRequiredMixin, View):
|
|
|
+
|
|
|
+ def get_required_permission(self):
|
|
|
+ return 'extras.view_report'
|
|
|
+
|
|
|
+ def get(self, request, module, name):
|
|
|
+ module = get_object_or_404(ReportModule.objects.restrict(request.user), file_path=f'{module}.py')
|
|
|
+ report = module.reports[name]()
|
|
|
+
|
|
|
+ object_type = ContentType.objects.get(app_label='extras', model='reportmodule')
|
|
|
+ jobs = Job.objects.filter(
|
|
|
+ object_type=object_type,
|
|
|
+ object_id=module.pk,
|
|
|
+ name=report.name,
|
|
|
+ status__in=JobStatusChoices.TERMINAL_STATE_CHOICES
|
|
|
+ )
|
|
|
+
|
|
|
+ jobs_table = JobTable(
|
|
|
+ data=jobs,
|
|
|
+ orderable=False,
|
|
|
+ user=request.user
|
|
|
+ )
|
|
|
+ jobs_table.configure(request)
|
|
|
+
|
|
|
+ return render(request, 'extras/report/jobs.html', {
|
|
|
+ 'module': module,
|
|
|
+ 'report': report,
|
|
|
+ 'table': jobs_table,
|
|
|
+ 'tab': 'jobs',
|
|
|
+ })
|
|
|
+
|
|
|
+
|
|
|
class ReportResultView(ContentTypePermissionRequiredMixin, View):
|
|
|
"""
|
|
|
Display a Job pertaining to the execution of a Report.
|
|
|
@@ -902,28 +936,26 @@ class ReportResultView(ContentTypePermissionRequiredMixin, View):
|
|
|
def get_required_permission(self):
|
|
|
return 'extras.view_report'
|
|
|
|
|
|
- def get(self, request, job_result_pk):
|
|
|
- report_content_type = ContentType.objects.get(app_label='extras', model='report')
|
|
|
- result = get_object_or_404(Job.objects.all(), pk=job_result_pk, object_type=report_content_type)
|
|
|
+ def get(self, request, job_pk):
|
|
|
+ object_type = ContentType.objects.get_by_natural_key(app_label='extras', model='reportmodule')
|
|
|
+ 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, report_name = result.name.split('.', maxsplit=1)
|
|
|
- report = get_report(module, report_name)
|
|
|
- report.result = result
|
|
|
+ module = job.object
|
|
|
+ report = module.reports[job.name]
|
|
|
|
|
|
# If this is an HTMX request, return only the result HTML
|
|
|
if is_htmx(request):
|
|
|
response = render(request, 'extras/htmx/report_result.html', {
|
|
|
'report': report,
|
|
|
- 'result': result,
|
|
|
+ 'job': job,
|
|
|
})
|
|
|
- if result.completed or not result.started:
|
|
|
+ if job.completed or not job.started:
|
|
|
response.status_code = 286
|
|
|
return response
|
|
|
|
|
|
return render(request, 'extras/report_result.html', {
|
|
|
'report': report,
|
|
|
- 'result': result,
|
|
|
+ 'job': job,
|
|
|
})
|
|
|
|
|
|
|
|
|
@@ -956,7 +988,7 @@ class ScriptListView(ContentTypePermissionRequiredMixin, View):
|
|
|
script_modules = ScriptModule.objects.restrict(request.user)
|
|
|
|
|
|
script_content_type = ContentType.objects.get(app_label='extras', model='script')
|
|
|
- job_results = {
|
|
|
+ jobs = {
|
|
|
r.name: r
|
|
|
for r in Job.objects.filter(
|
|
|
object_type=script_content_type,
|
|
|
@@ -967,7 +999,7 @@ class ScriptListView(ContentTypePermissionRequiredMixin, View):
|
|
|
return render(request, 'extras/script_list.html', {
|
|
|
'model': ScriptModule,
|
|
|
'script_modules': script_modules,
|
|
|
- 'job_results': job_results,
|
|
|
+ 'jobs': jobs,
|
|
|
})
|
|
|
|
|
|
|
|
|
@@ -982,9 +1014,11 @@ class ScriptView(ContentTypePermissionRequiredMixin, View):
|
|
|
form = script.as_form(initial=normalize_querydict(request.GET))
|
|
|
|
|
|
# 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(
|
|
|
- object_type=ContentType.objects.get_for_model(Script),
|
|
|
- name=script.full_name,
|
|
|
+ object_type=object_type,
|
|
|
+ object_id=module.pk,
|
|
|
+ name=script.name,
|
|
|
).exclude(
|
|
|
status__in=JobStatusChoices.TERMINAL_STATE_CHOICES
|
|
|
).first()
|
|
|
@@ -1008,10 +1042,10 @@ class ScriptView(ContentTypePermissionRequiredMixin, View):
|
|
|
messages.error(request, "Unable to run script: RQ worker process not running.")
|
|
|
|
|
|
elif form.is_valid():
|
|
|
- job_result = Job.enqueue_job(
|
|
|
+ job = Job.enqueue(
|
|
|
run_script,
|
|
|
- name=script.full_name,
|
|
|
- obj_type=ContentType.objects.get_for_model(Script),
|
|
|
+ instance=module,
|
|
|
+ name=script.class_name,
|
|
|
user=request.user,
|
|
|
schedule_at=form.cleaned_data.pop('_schedule_at'),
|
|
|
interval=form.cleaned_data.pop('_interval'),
|
|
|
@@ -1021,7 +1055,7 @@ class ScriptView(ContentTypePermissionRequiredMixin, View):
|
|
|
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', {
|
|
|
'module': module,
|
|
|
@@ -1030,33 +1064,79 @@ class ScriptView(ContentTypePermissionRequiredMixin, View):
|
|
|
})
|
|
|
|
|
|
|
|
|
+class ScriptSourceView(ContentTypePermissionRequiredMixin, View):
|
|
|
+
|
|
|
+ def get_required_permission(self):
|
|
|
+ return 'extras.view_script'
|
|
|
+
|
|
|
+ def get(self, request, module, name):
|
|
|
+ module = get_object_or_404(ScriptModule.objects.restrict(request.user), file_path=f'{module}.py')
|
|
|
+ script = module.scripts[name]()
|
|
|
+
|
|
|
+ return render(request, 'extras/script/source.html', {
|
|
|
+ 'module': module,
|
|
|
+ 'script': script,
|
|
|
+ 'tab': 'source',
|
|
|
+ })
|
|
|
+
|
|
|
+
|
|
|
+class ScriptJobsView(ContentTypePermissionRequiredMixin, View):
|
|
|
+
|
|
|
+ def get_required_permission(self):
|
|
|
+ return 'extras.view_script'
|
|
|
+
|
|
|
+ def get(self, request, module, name):
|
|
|
+ module = get_object_or_404(ScriptModule.objects.restrict(request.user), file_path=f'{module}.py')
|
|
|
+ script = module.scripts[name]()
|
|
|
+
|
|
|
+ object_type = ContentType.objects.get(app_label='extras', model='scriptmodule')
|
|
|
+ jobs = Job.objects.filter(
|
|
|
+ object_type=object_type,
|
|
|
+ object_id=module.pk,
|
|
|
+ name=script.class_name,
|
|
|
+ status__in=JobStatusChoices.TERMINAL_STATE_CHOICES
|
|
|
+ )
|
|
|
+
|
|
|
+ jobs_table = JobTable(
|
|
|
+ data=jobs,
|
|
|
+ orderable=False,
|
|
|
+ user=request.user
|
|
|
+ )
|
|
|
+ jobs_table.configure(request)
|
|
|
+
|
|
|
+ return render(request, 'extras/script/jobs.html', {
|
|
|
+ 'module': module,
|
|
|
+ 'script': script,
|
|
|
+ 'table': jobs_table,
|
|
|
+ 'tab': 'jobs',
|
|
|
+ })
|
|
|
+
|
|
|
+
|
|
|
class ScriptResultView(ContentTypePermissionRequiredMixin, View):
|
|
|
|
|
|
def get_required_permission(self):
|
|
|
return 'extras.view_script'
|
|
|
|
|
|
- def get(self, request, job_result_pk):
|
|
|
- script_content_type = ContentType.objects.get(app_label='extras', model='script')
|
|
|
- result = get_object_or_404(Job.objects.all(), pk=job_result_pk, object_type=script_content_type)
|
|
|
+ def get(self, request, job_pk):
|
|
|
+ object_type = ContentType.objects.get_by_natural_key(app_label='extras', model='scriptmodule')
|
|
|
+ job = get_object_or_404(Job.objects.all(), pk=job_pk, object_type=object_type)
|
|
|
|
|
|
- module_name, script_name = result.name.split('.', 1)
|
|
|
- module = get_object_or_404(ScriptModule.objects.restrict(request.user), file_path=f'{module_name}.py')
|
|
|
- script = module.scripts[script_name]()
|
|
|
+ module = job.object
|
|
|
+ script = module.scripts[job.name]()
|
|
|
|
|
|
# If this is an HTMX request, return only the result HTML
|
|
|
if is_htmx(request):
|
|
|
response = render(request, 'extras/htmx/script_result.html', {
|
|
|
'script': script,
|
|
|
- 'result': result,
|
|
|
+ 'job': job,
|
|
|
})
|
|
|
- if result.completed or not result.started:
|
|
|
+ if job.completed or not job.started:
|
|
|
response.status_code = 286
|
|
|
return response
|
|
|
|
|
|
return render(request, 'extras/script_result.html', {
|
|
|
'script': script,
|
|
|
- 'result': result,
|
|
|
- 'class_name': script.__class__.__name__
|
|
|
+ 'job': job,
|
|
|
})
|
|
|
|
|
|
|