mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
review updates
This commit is contained in:
@ -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
|
||||
#
|
||||
|
@ -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'
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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,
|
||||
})
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 %}
|
||||
|
@ -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>
|
||||
|
@ -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 %}
|
||||
|
@ -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">
|
||||
|
Reference in New Issue
Block a user