John Anderson 5 лет назад
Родитель
Сommit
41f92ef8e6

+ 37 - 0
netbox/extras/choices.py

@@ -120,6 +120,43 @@ class TemplateLanguageChoices(ChoiceSet):
     }
 
 
+#
+# Log Levels for Reports and Scripts
+#
+
+class LogLevelChoices(ChoiceSet):
+
+    LOG_DEFAULT = 'default'
+    LOG_SUCCESS = 'sucess'
+    LOG_INFO = 'info'
+    LOG_WARNING = 'warning'
+    LOG_FAILURE = 'failure'
+
+    CHOICES = (
+        (LOG_DEFAULT, 'Default'),
+        (LOG_SUCCESS, 'Success'),
+        (LOG_INFO, 'Info'),
+        (LOG_WARNING, 'Warning'),
+        (LOG_FAILURE, 'Failure'),
+    )
+
+    CLASS_MAP = (
+        (LOG_DEFAULT, 'default'),
+        (LOG_SUCCESS, 'success'),
+        (LOG_INFO, 'info'),
+        (LOG_WARNING, 'warning'),
+        (LOG_FAILURE, 'danger'),
+    )
+
+    LEGACY_MAP = (
+        (LOG_DEFAULT, 0),
+        (LOG_SUCCESS, 10),
+        (LOG_INFO, 20),
+        (LOG_WARNING, 30),
+        (LOG_FAILURE, 40),
+    )
+
+
 #
 # Job results
 #

+ 0 - 14
netbox/extras/constants.py

@@ -1,17 +1,3 @@
-# Report logging levels
-LOG_DEFAULT = 0
-LOG_SUCCESS = 10
-LOG_INFO = 20
-LOG_WARNING = 30
-LOG_FAILURE = 40
-LOG_LEVEL_CODES = {
-    LOG_DEFAULT: 'default',
-    LOG_SUCCESS: 'success',
-    LOG_INFO: 'info',
-    LOG_WARNING: 'warning',
-    LOG_FAILURE: 'failure',
-}
-
 # Webhook content types
 HTTP_CONTENT_TYPE_JSON = 'application/json'
 

+ 11 - 11
netbox/extras/reports.py

@@ -9,8 +9,7 @@ from django.db.models import Q
 from django.utils import timezone
 from django_rq import job
 
-from .choices import JobResultStatusChoices
-from .constants import *
+from .choices import JobResultStatusChoices, LogLevelChoices
 from .models import JobResult
 
 
@@ -77,7 +76,8 @@ def run_report(job_result, *args, **kwargs):
 
     try:
         report.run(job_result)
-    except Exception:
+    except Exception as e:
+        print(e)
         job_result.set_status(JobResultStatusChoices.STATUS_ERRORED)
         logging.error(f"Error during execution of report {job_result.name}")
 
@@ -153,15 +153,15 @@ class Report(object):
     def full_name(self):
         return '.'.join([self.__module__, self.__class__.__name__])
 
-    def _log(self, obj, message, level=LOG_DEFAULT):
+    def _log(self, obj, message, level=LogLevelChoices.LOG_DEFAULT):
         """
         Log a message from a test method. Do not call this method directly; use one of the log_* wrappers below.
         """
-        if level not in LOG_LEVEL_CODES:
+        if level not in LogLevelChoices.as_dict():
             raise Exception("Unknown logging level: {}".format(level))
         self._results[self.active_test]['log'].append((
             timezone.now().isoformat(),
-            LOG_LEVEL_CODES.get(level),
+            level,
             str(obj) if obj else None,
             obj.get_absolute_url() if getattr(obj, 'get_absolute_url', None) else None,
             message,
@@ -171,7 +171,7 @@ class Report(object):
         """
         Log a message which is not associated with a particular object.
         """
-        self._log(None, message, level=LOG_DEFAULT)
+        self._log(None, message, level=LogLevelChoices.LOG_DEFAULT)
         self.logger.info(message)
 
     def log_success(self, obj, message=None):
@@ -179,7 +179,7 @@ class Report(object):
         Record a successful test against an object. Logging a message is optional.
         """
         if message:
-            self._log(obj, message, level=LOG_SUCCESS)
+            self._log(obj, message, level=LogLevelChoices.LOG_SUCCESS)
         self._results[self.active_test]['success'] += 1
         self.logger.info(f"Success | {obj}: {message}")
 
@@ -187,7 +187,7 @@ class Report(object):
         """
         Log an informational message.
         """
-        self._log(obj, message, level=LOG_INFO)
+        self._log(obj, message, level=LogLevelChoices.LOG_INFO)
         self._results[self.active_test]['info'] += 1
         self.logger.info(f"Info | {obj}: {message}")
 
@@ -195,7 +195,7 @@ class Report(object):
         """
         Log a warning.
         """
-        self._log(obj, message, level=LOG_WARNING)
+        self._log(obj, message, level=LogLevelChoices.LOG_WARNING)
         self._results[self.active_test]['warning'] += 1
         self.logger.info(f"Warning | {obj}: {message}")
 
@@ -203,7 +203,7 @@ class Report(object):
         """
         Log a failure. Calling this method will automatically mark the report as failed.
         """
-        self._log(obj, message, level=LOG_FAILURE)
+        self._log(obj, message, level=LogLevelChoices.LOG_FAILURE)
         self._results[self.active_test]['failure'] += 1
         self.logger.info(f"Failure | {obj}: {message}")
         self.failed = True

+ 13 - 13
netbox/extras/scripts.py

@@ -19,11 +19,10 @@ from mptt.forms import TreeNodeChoiceField, TreeNodeMultipleChoiceField
 from mptt.models import MPTTModel
 
 from extras.api.serializers import ScriptOutputSerializer
-from extras.choices import JobResultStatusChoices
+from extras.choices import JobResultStatusChoices, LogLevelChoices
 from extras.models import JobResult
 from ipam.formfields import IPAddressFormField, IPNetworkFormField
 from ipam.validators import MaxPrefixLengthValidator, MinPrefixLengthValidator, prefix_validator
-from .constants import LOG_DEFAULT, LOG_FAILURE, LOG_INFO, LOG_SUCCESS, LOG_WARNING
 from utilities.exceptions import AbortTransaction
 from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField
 from .forms import ScriptForm
@@ -324,23 +323,23 @@ class BaseScript:
 
     def log_debug(self, message):
         self.logger.log(logging.DEBUG, message)
-        self.log.append((LOG_DEFAULT, message))
+        self.log.append((LogLevelChoices.LOG_DEFAULT, message))
 
     def log_success(self, message):
         self.logger.log(logging.INFO, message)  # No syslog equivalent for SUCCESS
-        self.log.append((LOG_SUCCESS, message))
+        self.log.append((LogLevelChoices.LOG_SUCCESS, message))
 
     def log_info(self, message):
         self.logger.log(logging.INFO, message)
-        self.log.append((LOG_INFO, message))
+        self.log.append((LogLevelChoices.LOG_INFO, message))
 
     def log_warning(self, message):
         self.logger.log(logging.WARNING, message)
-        self.log.append((LOG_WARNING, message))
+        self.log.append((LogLevelChoices.LOG_WARNING, message))
 
     def log_failure(self, message):
         self.logger.log(logging.ERROR, message)
-        self.log.append((LOG_FAILURE, message))
+        self.log.append((LogLevelChoices.LOG_FAILURE, message))
 
     # Convenience functions
 
@@ -428,11 +427,15 @@ def run_script(data, request, commit=True, *args, **kwargs):
     try:
         with transaction.atomic():
             script.output = script.run(**kwargs)
-            job_result.status = JobResultStatusChoices.STATUS_COMPLETED
+            job_result.data = ScriptOutputSerializer(script).data
+            job_result.set_status(JobResultStatusChoices.STATUS_COMPLETED)
+
             if not commit:
                 raise AbortTransaction()
+
     except AbortTransaction:
         pass
+
     except Exception as e:
         stacktrace = traceback.format_exc()
         script.log_failure(
@@ -440,7 +443,8 @@ def run_script(data, request, commit=True, *args, **kwargs):
         )
         logger.error(f"Exception raised during script execution: {e}")
         commit = False
-        job_result.status = JobResultStatusChoices.STATUS_FAILED
+        job_result.set_status(JobResultStatusChoices.STATUS_ERRORED)
+
     finally:
         if not commit:
             # Delete all pending changelog entries
@@ -449,10 +453,6 @@ def run_script(data, request, commit=True, *args, **kwargs):
                 "Database changes have been reverted automatically."
             )
 
-    job_result.data = ScriptOutputSerializer(script).data
-    job_result.completed = timezone.now()
-    job_result.save()
-
     logger.info(f"Script completed in {job_result.duration}")
 
     # Delete any previous terminal state results

+ 4 - 25
netbox/extras/templatetags/log_levels.py

@@ -1,6 +1,6 @@
 from django import template
 
-from extras.constants import LOG_DEFAULT, LOG_FAILURE, LOG_INFO, LOG_SUCCESS, LOG_WARNING
+from extras.choices import LogLevelChoices
 
 
 register = template.Library()
@@ -11,28 +11,7 @@ def log_level(level):
     """
     Display a label indicating a syslog severity (e.g. info, warning, etc.).
     """
-    # TODO: we should convert this to a choices class
-    levels = {
-        'default': {
-            'name': 'Default',
-            'class': 'default'
-        },
-        'success': {
-            'name': 'Success',
-            'class': 'success',
-        },
-        'info': {
-            'name': 'Info',
-            'class': 'info'
-        },
-        'warning': {
-            'name': 'Warning',
-            'class': 'warning'
-        },
-        'failure': {
-            'name': 'Failure',
-            'class': 'danger'
-        }
+    return {
+        'name': LogLevelChoices.as_dict()[level],
+        'class': dict(LogLevelChoices.CLASS_MAP)[level]
     }
-
-    return levels[level]

+ 15 - 1
netbox/extras/views.py

@@ -453,8 +453,22 @@ class ScriptListView(ContentTypePermissionRequiredMixin, View):
 
     def get(self, request):
 
+        scripts = get_scripts(use_names=True)
+        script_content_type = ContentType.objects.get(app_label='extras', model='script')
+        results = {
+            r.name: r
+            for r in JobResult.objects.filter(
+                obj_type=script_content_type,
+                status__in=JobResultStatusChoices.TERMINAL_STATE_CHOICES
+            ).defer('data')
+        }
+
+        for _scripts in scripts.values():
+            for script in _scripts.values():
+                script.result = results.get(script.full_name)
+
         return render(request, 'extras/script_list.html', {
-            'scripts': get_scripts(use_names=True),
+            'scripts': scripts,
         })
 
 

+ 2 - 4
netbox/project-static/js/job_result.js

@@ -3,10 +3,8 @@ var timeout = 1000;
 
 function updatePendingStatusLabel(status){
     var labelClass;
-    if (status.value === 'failed'){
+    if (status.value === 'failed' || status.value === 'errored'){
         labelClass = 'danger';
-    } else if (status.value === 'pending'){
-        labelClass = 'default';
     } else if (status.value === 'running'){
         labelClass = 'warning';
     } else if (status.value === 'completed'){
@@ -33,7 +31,7 @@ $(document).ready(function(){
                 context: this,
                 success: function(data) {
                     updatePendingStatusLabel(data.status);
-                    if (data.status.value === 'completed' || data.status.value === 'failed'){
+                    if (data.status.value === 'completed' || data.status.value === 'failed' || data.status.value === 'errored'){
                         jobTerminatedAction()
                     } else {
                         setTimeout(checkPendingResult, timeout);

+ 3 - 1
netbox/templates/extras/inc/job_label.html

@@ -1,11 +1,13 @@
 {% if result.status == 'failed' %}
     <label class="label label-danger">Failed</label>
+{% elif result.status == 'errored' %}
+    <label class="label label-danger">Errored</label>
 {% elif result.status == 'pending' %}
     <label class="label label-default">Pending</label>
 {% elif result.status == 'running' %}
     <label class="label label-warning">Running</label>
 {% elif result.status == 'completed' %}
-    <label class="label label-success">Passed</label>
+    <label class="label label-success">Completed</label>
 {% else %}
     <label class="label label-default">N/A</label>
 {% endif %}

+ 4 - 2
netbox/templates/extras/report_result.html

@@ -38,7 +38,7 @@
                 {% endif %}
                 <span id="pending-result-label">{% include 'extras/inc/job_label.html' with result=result %}</span>
             </p>
-            {% if result.completed %}
+            {% if result.completed and result.status != 'errored' %}
                 <div class="panel panel-default">
                     <div class="panel-heading">
                         <strong>Report Methods</strong>
@@ -97,8 +97,10 @@
                         </tbody>
                     </table>
                 </div>
+            {% elif result.status == 'errored' %}
+                <div class="well">Error during report execution</div>
             {% else %}
-                <div class="well">Pending results</div>
+            <div class="well">Pending results</div>
             {% endif %}
         </div>
     </div>

+ 34 - 3
netbox/templates/extras/script_list.html

@@ -4,15 +4,17 @@
 {% block content %}
     <h1>{% block title %}Scripts{% endblock %}</h1>
     <div class="row">
-        <div class="col-md-12">
+        <div class="col-md-9">
             {% if scripts %}
                 {% for module, module_scripts in scripts.items %}
                     <h3><a name="module.{{ module }}"></a>{{ module|bettertitle }}</h3>
                     <table class="table table-hover table-headings reports">
                         <thead>
                             <tr>
-                                <th class="col-md-3">Name</th>
-                                <th class="col-md-9">Description</th>
+                                <th>Name</th>
+                                <th>Status</th>
+                                <th>Description</th>
+                                <th class="text-right">Last Run</th>
                             </tr>
                         </thead>
                         <tbody>
@@ -21,7 +23,15 @@
                                     <td>
                                         <a href="{% url 'extras:script' module=script.module name=class_name %}" name="script.{{ class_name }}"><strong>{{ script }}</strong></a>
                                     </td>
+                                    <td>
+                                        {% include 'extras/inc/job_label.html' with result=script.result %}
+                                    </td>
                                     <td>{{ script.Meta.description }}</td>
+                                    {% if script.result %}
+                                        <td class="text-right">{{ script.result.created }}</td>
+                                    {% else %}
+                                        <td class="text-right text-muted">Never</td>
+                                    {% endif %}
                                 </tr>
                             {% endfor %}
                         </tbody>
@@ -34,5 +44,26 @@
                 </div>
             {% endif %}
         </div>
+        <div class="col-md-3">
+            {% if scripts %}
+                <div class="panel panel-default">
+                    {% for module, module_scripts in scripts.items %}
+                        <div class="panel-heading">
+                            <strong>{{ module|bettertitle }}</strong>
+                        </div>
+                        <ul class="list-group">
+                            {% for class_name, script in module_scripts.items %}
+                                <a href="#script.{{ class_name }}" class="list-group-item">
+                                    <i class="fa fa-list-alt"></i> {{ script.name }}
+                                    <div class="pull-right">
+                                        {% include 'extras/inc/job_label.html' with result=script.result %}
+                                    </div>
+                                </a>
+                            {% endfor %}
+                        </ul>
+                    {% endfor %}
+                </div>
+            {% endif %}
+        </div>
     </div>
 {% endblock %}

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

@@ -41,7 +41,7 @@
             <span id="pending-result-label">{% include 'extras/inc/job_label.html' with result=result %}</span>
         </p>
         <div role="tabpanel" class="tab-pane active" id="log">
-            {% if result.completed %}
+            {% if result.completed and result.status != 'errored' %}
                 <div class="row">
                     <div class="col-md-12">
                         <div class="panel panel-default">
@@ -76,6 +76,12 @@
                         </div>
                     </div>
                 </div>
+            {% elif result.stats == 'errored' %}
+                <div class="row">
+                    <div class="col-md-12">
+                        <div class="well">Error during script execution</div>
+                    </div>
+                </div>
             {% else %}
                 <div class="row">
                     <div class="col-md-12">