jeremystretch 3 лет назад
Родитель
Сommit
0bcc59a1e9

+ 1 - 1
docs/release-notes/version-3.4.md

@@ -131,7 +131,7 @@ This release introduces a new programmatic API that enables plugins and custom s
 * extras.ExportTemplate
     * Renamed `content_type` field to `content_types`
 * extras.JobResult
-    * Added the `scheduled` field
+    * Added `scheduled` and `started` datetime fields
 * ipam.Aggregate
     * Added a `comments` field
 * ipam.ASN

+ 2 - 2
netbox/extras/api/serializers.py

@@ -385,8 +385,8 @@ class JobResultSerializer(BaseModelSerializer):
     class Meta:
         model = JobResult
         fields = [
-            'id', 'url', 'display', 'status', 'created', 'scheduled', 'completed', 'name', 'obj_type', 'user', 'data',
-            'job_id',
+            'id', 'url', 'display', 'status', 'created', 'scheduled', 'started', 'completed', 'name', 'obj_type',
+            'user', 'data', 'job_id',
         ]
 
 

+ 19 - 12
netbox/extras/filtersets.py

@@ -503,15 +503,6 @@ class JobResultFilterSet(BaseFilterSet):
         field_name='created',
         lookup_expr='gte'
     )
-    completed = django_filters.DateTimeFilter()
-    completed__before = django_filters.DateTimeFilter(
-        field_name='completed',
-        lookup_expr='lte'
-    )
-    completed__after = django_filters.DateTimeFilter(
-        field_name='completed',
-        lookup_expr='gte'
-    )
     scheduled = django_filters.DateTimeFilter()
     scheduled__before = django_filters.DateTimeFilter(
         field_name='scheduled',
@@ -521,6 +512,24 @@ class JobResultFilterSet(BaseFilterSet):
         field_name='scheduled',
         lookup_expr='gte'
     )
+    started = django_filters.DateTimeFilter()
+    started__before = django_filters.DateTimeFilter(
+        field_name='started',
+        lookup_expr='lte'
+    )
+    started__after = django_filters.DateTimeFilter(
+        field_name='started',
+        lookup_expr='gte'
+    )
+    completed = django_filters.DateTimeFilter()
+    completed__before = django_filters.DateTimeFilter(
+        field_name='completed',
+        lookup_expr='lte'
+    )
+    completed__after = django_filters.DateTimeFilter(
+        field_name='completed',
+        lookup_expr='gte'
+    )
     status = django_filters.MultipleChoiceFilter(
         choices=JobResultStatusChoices,
         null_value=None
@@ -528,9 +537,7 @@ class JobResultFilterSet(BaseFilterSet):
 
     class Meta:
         model = JobResult
-        fields = [
-            'id', 'status', 'created', 'scheduled', 'completed', 'user', 'obj_type', 'name'
-        ]
+        fields = ('id', 'status', 'user', 'obj_type', 'name')
 
     def search(self, queryset, name, value):
         if not value.strip():

+ 14 - 7
netbox/extras/forms/filtersets.py

@@ -73,11 +73,10 @@ class JobResultFilterForm(SavedFiltersMixin, FilterForm):
         (None, ('q', 'filter_id')),
         ('Attributes', ('obj_type', 'status')),
         ('Creation', (
-            'created__before', 'created__after', 'completed__before', 'completed__after', 'scheduled__before',
-            'scheduled__after', 'user',
+            'created__before', 'created__after', 'scheduled__before', 'scheduled__after', 'started__before',
+            'started__after', 'completed__before', 'completed__after', 'user',
         )),
     )
-
     obj_type = ContentTypeChoiceField(
         label=_('Object Type'),
         queryset=ContentType.objects.all(),
@@ -96,19 +95,27 @@ class JobResultFilterForm(SavedFiltersMixin, FilterForm):
         required=False,
         widget=DateTimePicker()
     )
-    completed__after = forms.DateTimeField(
+    scheduled__after = forms.DateTimeField(
         required=False,
         widget=DateTimePicker()
     )
-    completed__before = forms.DateTimeField(
+    scheduled__before = forms.DateTimeField(
         required=False,
         widget=DateTimePicker()
     )
-    scheduled__after = forms.DateTimeField(
+    started__after = forms.DateTimeField(
         required=False,
         widget=DateTimePicker()
     )
-    scheduled__before = forms.DateTimeField(
+    started__before = forms.DateTimeField(
+        required=False,
+        widget=DateTimePicker()
+    )
+    completed__after = forms.DateTimeField(
+        required=False,
+        widget=DateTimePicker()
+    )
+    completed__before = forms.DateTimeField(
         required=False,
         widget=DateTimePicker()
     )

+ 5 - 0
netbox/extras/migrations/0079_jobresult_scheduled.py → netbox/extras/migrations/0079_scheduled_jobs.py

@@ -13,6 +13,11 @@ class Migration(migrations.Migration):
             name='scheduled',
             field=models.DateTimeField(blank=True, null=True),
         ),
+        migrations.AddField(
+            model_name='jobresult',
+            name='started',
+            field=models.DateTimeField(blank=True, null=True),
+        ),
         migrations.AlterModelOptions(
             name='jobresult',
             options={'ordering': ['-created']},

+ 1 - 1
netbox/extras/migrations/0080_customlink_content_types.py

@@ -12,7 +12,7 @@ class Migration(migrations.Migration):
 
     dependencies = [
         ('contenttypes', '0002_remove_content_type_name'),
-        ('extras', '0079_jobresult_scheduled'),
+        ('extras', '0079_scheduled_jobs'),
     ]
 
     operations = [

+ 14 - 1
netbox/extras/models/models.py

@@ -585,6 +585,10 @@ class JobResult(models.Model):
         null=True,
         blank=True
     )
+    started = models.DateTimeField(
+        null=True,
+        blank=True
+    )
     completed = models.DateTimeField(
         null=True,
         blank=True
@@ -639,9 +643,18 @@ class JobResult(models.Model):
 
         return f"{int(minutes)} minutes, {seconds:.2f} seconds"
 
+    def start(self):
+        """
+        Record the job's start time and update its status to "running."
+        """
+        if self.started is None:
+            self.started = timezone.now()
+            self.status = JobResultStatusChoices.STATUS_RUNNING
+            JobResult.objects.filter(pk=self.pk).update(started=self.started, status=self.status)
+
     def set_status(self, status):
         """
-        Helper method to change the status of the job result. If the target status is terminal, the  completion
+        Helper method to change the status of the job result. If the target status is terminal, the completion
         time is also set.
         """
         self.status = status

+ 1 - 0
netbox/extras/reports.py

@@ -83,6 +83,7 @@ def run_report(job_result, *args, **kwargs):
     report = get_report(module_name, report_name)
 
     try:
+        job_result.start()
         report.run(job_result)
     except Exception as e:
         job_result.set_status(JobResultStatusChoices.STATUS_ERRORED)

+ 3 - 5
netbox/extras/scripts.py

@@ -433,16 +433,14 @@ def is_variable(obj):
 def run_script(data, request, commit=True, *args, **kwargs):
     """
     A wrapper for calling Script.run(). This performs error handling and provides a hook for committing changes. It
-    exists outside of the Script class to ensure it cannot be overridden by a script author.
+    exists outside the Script class to ensure it cannot be overridden by a script author.
     """
     job_result = kwargs.pop('job_result')
-    module, script_name = job_result.name.split('.', 1)
+    job_result.start()
 
+    module, script_name = job_result.name.split('.', 1)
     script = get_script(module, script_name)()
 
-    job_result.status = JobResultStatusChoices.STATUS_RUNNING
-    job_result.save()
-
     logger = logging.getLogger(f"netbox.scripts.{module}.{script_name}")
     logger.info(f"Running script (commit={commit})")
 

+ 4 - 2
netbox/extras/tables/tables.py

@@ -49,9 +49,11 @@ class JobResultTable(NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = JobResult
         fields = (
-            'pk', 'id', 'name', 'obj_type', 'status', 'created', 'scheduled', 'completed', 'user', 'job_id',
+            'pk', 'id', 'name', 'obj_type', 'status', 'created', 'scheduled', 'started', 'completed', 'user', 'job_id',
+        )
+        default_columns = (
+            'pk', 'id', 'name', 'obj_type', 'status', 'created', 'scheduled', 'started', 'completed', 'user',
         )
-        default_columns = ('pk', 'id', 'name', 'obj_type', 'status', 'created', 'scheduled', 'completed', 'user',)
 
 
 class CustomLinkTable(NetBoxTable):

+ 2 - 2
netbox/extras/views.py

@@ -726,7 +726,7 @@ class ReportResultView(ContentTypePermissionRequiredMixin, View):
                 'report': report,
                 'result': result,
             })
-            if result.completed:
+            if result.completed or not result.started:
                 response.status_code = 286
             return response
 
@@ -860,7 +860,7 @@ class ScriptResultView(ContentTypePermissionRequiredMixin, GetScriptMixin, View)
                 'script': script,
                 'result': result,
             })
-            if result.completed:
+            if result.completed or not result.started:
                 response.status_code = 286
             return response
 

+ 6 - 3
netbox/templates/extras/htmx/report_result.html

@@ -1,9 +1,12 @@
 {% load helpers %}
 
 <p>
-  Initiated: <strong>{{ result.created|annotated_date }}</strong>
-  {% if result.scheduled %}
+  {% if result.started %}
+    Started: <strong>{{ result.started|annotated_date }}</strong>
+  {% elif result.scheduled %}
     Scheduled for: <strong>{{ result.scheduled|annotated_date }}</strong>
+  {% else %}
+    Created: <strong>{{ result.created|annotated_date }}</strong>
   {% endif %}
   {% if result.completed %}
     Duration: <strong>{{ result.duration }}</strong>
@@ -71,6 +74,6 @@
       </table>
     </div>
   </div>
-{% else %}
+{% elif result.started %}
   {% include 'extras/inc/result_pending.html' %}
 {% endif %}

+ 6 - 3
netbox/templates/extras/htmx/script_result.html

@@ -2,9 +2,12 @@
 {% load log_levels %}
 
 <p>
-  Initiated: <strong>{{ result.created|annotated_date }}</strong>
-  {% if result.scheduled %}
+  {% if result.started %}
+    Started: <strong>{{ result.started|annotated_date }}</strong>
+  {% elif result.scheduled %}
     Scheduled for: <strong>{{ result.scheduled|annotated_date }}</strong>
+  {% else %}
+    Created: <strong>{{ result.created|annotated_date }}</strong>
   {% endif %}
   {% if result.completed %}
     Duration: <strong>{{ result.duration }}</strong>
@@ -48,6 +51,6 @@
   {% else %}
     <p class="text-muted">None</p>
   {% endif %}
-{% else %}
+{% elif result.started %}
   {% include 'extras/inc/result_pending.html' %}
 {% endif %}

+ 1 - 1
netbox/templates/extras/report_result.html

@@ -4,7 +4,7 @@
 
 {% block content-wrapper %}
   <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 3s"{% endif %}>
+    <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 %}>
       {% include 'extras/htmx/report_result.html' %}
     </div>
   </div>

+ 1 - 1
netbox/templates/extras/script_result.html

@@ -47,7 +47,7 @@
   <div class="tab-content mb-3">
     <div role="tabpanel" class="tab-pane active" id="log">
       <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 3s"{% endif %}>
+        <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 %}>
           {% include 'extras/htmx/script_result.html' %}
         </div>
       </div>