Skip to content

Commit

Permalink
Netbox 4.1 support
Browse files Browse the repository at this point in the history
  • Loading branch information
kkthxbye-code committed Oct 30, 2024
1 parent c63dbad commit a19c4a2
Show file tree
Hide file tree
Showing 19 changed files with 101 additions and 76 deletions.
2 changes: 1 addition & 1 deletion netbox_script_manager/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
__version__ = "0.4.0"


from extras.plugins import PluginConfig
from netbox.plugins import PluginConfig


class NetboxScriptManagerConfig(PluginConfig):
Expand Down
4 changes: 2 additions & 2 deletions netbox_script_manager/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from netbox.api.serializers import NetBoxModelSerializer
from netbox.config import get_config
from rest_framework import serializers
from tenancy.api.nested_serializers import NestedTenantSerializer
from tenancy.api.serializers import TenantSerializer
from utilities.templatetags.builtins.filters import render_markdown

from netbox_script_manager.choices import ScriptExecutionStatusChoices
Expand Down Expand Up @@ -34,7 +34,7 @@ def to_representation(self, value):
class ScriptInstanceSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name="plugins-api:netbox_script_manager-api:scriptinstance-detail")
name = serializers.CharField(required=True)
tenant = NestedTenantSerializer(required=False, allow_null=True)
tenant = TenantSerializer(required=False, allow_null=True, nested=True)

class Meta:
model = ScriptInstance
Expand Down
2 changes: 1 addition & 1 deletion netbox_script_manager/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from rest_framework.response import Response
from rest_framework.routers import APIRootView
from utilities.permissions import get_permission_for_model
from utilities.utils import copy_safe_request
from utilities.request import copy_safe_request

from .. import util
from ..choices import ScriptExecutionStatusChoices
Expand Down
51 changes: 32 additions & 19 deletions netbox_script_manager/forms.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from django import forms
from django.contrib.auth.models import User
from users.models import User
from django.utils.translation import gettext as _
from extras.choices import DurationChoices
from extras.forms.mixins import SavedFiltersMixin
from netbox.forms.mixins import SavedFiltersMixin
from netbox.forms import NetBoxModelFilterSetForm, NetBoxModelForm
from tenancy.models import Tenant
from utilities.forms import BootstrapMixin, FilterForm
from utilities.forms import FilterForm
from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, TagFilterField
from utilities.forms.widgets import APISelectMultiple, DateTimePicker, NumberWithOptions
from utilities.utils import local_now
from utilities.datetime import local_now
from utilities.forms.rendering import FieldSet

from .choices import ScriptExecutionStatusChoices
from .models import ScriptExecution, ScriptInstance
Expand Down Expand Up @@ -57,20 +58,18 @@ class ScriptInstanceFilterForm(NetBoxModelFilterSetForm):

class ScriptExecutionFilterForm(SavedFiltersMixin, FilterForm):
fieldsets = (
(None, ("q", "filter_id")),
(
"Creation",
(
"created__before",
"created__after",
"scheduled__before",
"scheduled__after",
"started__before",
"started__after",
"completed__before",
"completed__after",
"user",
),
FieldSet("q", "filter_id", name="Query Filters"),
FieldSet(
"created__before",
"created__after",
"scheduled__before",
"scheduled__after",
"started__before",
"started__after",
"completed__before",
"completed__after",
"user",
name="Creation and Timing",
),
)
model = ScriptExecution
Expand All @@ -94,7 +93,7 @@ class ScriptExecutionFilterForm(SavedFiltersMixin, FilterForm):
)


class ScriptForm(BootstrapMixin, forms.Form):
class ScriptForm(forms.Form):
default_renderer = forms.renderers.DjangoTemplates()

_commit = forms.BooleanField(
Expand Down Expand Up @@ -126,6 +125,20 @@ def __init__(self, *args, scheduling_enabled=True, **kwargs):
now = local_now().strftime("%Y-%m-%d %H:%M:%S")
self.fields["_schedule_at"].help_text += f" (current time: <strong>{now}</strong>)"

# Stupid workaround for the insanely hacky netbox core code mentioned in #16293
# We can't use the default form renderer because it messes up checkboxes,
# so we have to manually inject the form-control class into all input fields.
for field in self.fields.values():
widget_type = type(field.widget)
if issubclass(widget_type, forms.widgets.Input) or issubclass(widget_type, forms.widgets.Textarea):
if hasattr(field.widget, "input_type") and field.widget.input_type == "checkbox":
continue

if "class" in field.widget.attrs:
field.widget.attrs["class"] += " form-control"
else:
field.widget.attrs["class"] = "form-control"

# Remove scheduling fields if scheduling is disabled
if not scheduling_enabled:
self.fields.pop("_schedule_at")
Expand Down
11 changes: 4 additions & 7 deletions netbox_script_manager/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@

import django_rq
from django.conf import settings
from django.contrib.auth.models import User
from users.models import User
from django.contrib.postgres.fields import ArrayField
from django.core import serializers
from django.core.validators import MinValueValidator
from django.db import models
from django.urls import reverse
from django.utils import timezone
from netbox.models import PrimaryModel
from netbox.models.features import ChangeLoggingMixin, ExportTemplatesMixin, WebhooksMixin
from netbox.models.features import ChangeLoggingMixin, ExportTemplatesMixin, EventRulesMixin
from utilities.querysets import RestrictedQuerySet

from .choices import LogLevelChoices, ScriptExecutionStatusChoices
Expand Down Expand Up @@ -45,9 +45,6 @@ class Meta:
def __str__(self):
return str(self.pk)

def get_absolute_url(self):
return reverse("plugins:netbox_script_manager:script_log_line", args=[self.pk])

def get_level_color(self):
return LogLevelChoices.colors.get(self.level)

Expand All @@ -74,7 +71,7 @@ def get_absolute_url(self):
return reverse("plugins:netbox_script_manager:scriptartifact_download", args=[self.pk])


class ScriptExecution(ExportTemplatesMixin, WebhooksMixin, ChangeLoggingMixin, models.Model):
class ScriptExecution(ExportTemplatesMixin, EventRulesMixin, ChangeLoggingMixin, models.Model):
script_instance = models.ForeignKey(
to="ScriptInstance",
on_delete=models.CASCADE,
Expand Down Expand Up @@ -153,7 +150,7 @@ def delete(self, *args, **kwargs):
if task:
task.cancel()

def serialize_object(obj, resolve_tags=True, extra=None):
def serialize_object(obj, resolve_tags=True, extra=None, exclude=None):
"""
While the netbox serialize_object claims to support excluding fields, it doesn't in reality.
"""
Expand Down
4 changes: 1 addition & 3 deletions netbox_script_manager/navigation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from extras.plugins import PluginMenuButton, PluginMenuItem
from utilities.choices import ButtonColorChoices
from netbox.plugins import PluginMenuButton, PluginMenuItem

menu_items = (
PluginMenuItem(
Expand All @@ -11,7 +10,6 @@
link="plugins:netbox_script_manager:scriptinstance_load",
title="Load Scripts",
icon_class="mdi mdi-refresh",
color=ButtonColorChoices.CYAN,
permissions=["netbox_script_manager.add_scriptinstance"],
),
),
Expand Down
13 changes: 7 additions & 6 deletions netbox_script_manager/project-static/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
// Mapping of log level to bootstrap color
let levelColors = {
debug: "bg-gray",
info: "bg-cyan",
success: "bg-green",
warning: "bg-yellow",
failure: "bg-red",
debug: "text-bg-gray",
info: "text.bg-cyan",
success: "text-bg-green",
warning: "text-bg-yellow",
failure: "text-bg-red",
};
// Column definitions
Expand All @@ -44,7 +44,7 @@
renderValue: (v) => {
let bgColor = levelColors[v.level.toLowerCase()];
return `<span class="badge ${bgColor}">${capitalize(
v.level
v.level,
)}</span>`;
},
parseHTML: true,
Expand Down Expand Up @@ -148,4 +148,5 @@
{rows}
bind:filterSelections={selection}
classNameTable={["table table-hover"]}
classNameInput={["ts-control"]}
/>
12 changes: 6 additions & 6 deletions netbox_script_manager/scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
from django.db import transaction
from django.forms.fields import BooleanField
from django.utils.functional import classproperty
from extras.context_managers import change_logging
from netbox.context_managers import event_tracking
from extras.scripts import ScriptVariable
from extras.signals import clear_webhooks
from core.signals import clear_events
from utilities.exceptions import AbortScript, AbortTransaction

from .choices import LogLevelChoices, ScriptExecutionStatusChoices
Expand Down Expand Up @@ -263,7 +263,7 @@ def _run_script():
raise AbortTransaction()
except AbortTransaction:
script.log_info("Database changes have been reverted automatically.")
clear_webhooks.send(request)
clear_events.send(request)

script_execution.data["output"] = str(output)
script_execution.terminate()
Expand All @@ -280,14 +280,14 @@ def _run_script():
script_execution.data["output"] = str(output)

script_execution.terminate(status=ScriptExecutionStatusChoices.STATUS_ERRORED)
clear_webhooks.send(request)
clear_events.send(request)

logger.info(f"Script completed in {script_execution.duration}")

# Execute the script. If commit is True, wrap it with the change_logging context manager to ensure we process
# change logging, webhooks, etc.
if commit:
with change_logging(request):
with event_tracking(request):
_run_script()
else:
_run_script()
Expand All @@ -307,7 +307,7 @@ def _run_script():
"output": None,
}

with change_logging(request):
with event_tracking(request):
next_execution = ScriptExecution(
script_instance=script_execution.script_instance,
task_id=uuid.uuid4(),
Expand Down
4 changes: 2 additions & 2 deletions netbox_script_manager/static/netbox_script_manager/main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion netbox_script_manager/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class ScriptInstanceTable(NetBoxTable):
last_execution = tables.TemplateColumn(
template_code="""
{% if value %}
{{ value.created }}
{{ value.created|isodatetime }}
{% else %}
<span class="text-muted">Never</span>
{% endif %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ <h5 class="card-header">Result</h5>
</tr>
<tr>
<th scope="row">Created</th>
<td>{{ object.created|annotated_date }}</td>
<td>{{ object.created|isodatetime }}</td>
</tr>
<tr>
<th scope="row">Started</th>
<td>{{ object.started|annotated_date }}</td>
<td>{{ object.started|isodatetime }}</td>
</tr>
<tr>
<th scope="row">Completed</th>
<td>{{ object.completed|annotated_date }}</td>
<td>{{ object.completed|isodatetime }}</td>
</tr>
<tr>
<th scope="row">Duration</th>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
{% load perms %}
{% load scriptmanager %}

{% block controls %}
{% block control-buttons %}
<div class="controls">
<div class="control-group">
{% if request.user|can_delete:object %}
{% delete_button object %}
{% endif %}
<a href="{{ object.script_instance.get_absolute_url }}{{ object.data.input|urlencode_dict }}" type="submit" class="btn btn-sm btn-primary">
<a href="{{ object.script_instance.get_absolute_url }}{{ object.data.input|urlencode_dict }}" type="submit" class="btn btn-primary">
<i class="mdi mdi-refresh"></i> Rerun
</a>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<div class="noprint bulk-buttons">
<div class="bulk-button-group">
{% if 'bulk_delete' in actions %}
<button type="submit" name="_delete" formaction="{% url 'plugins:netbox_script_manager:scriptexecution_bulk_delete' %}?return_url={% url 'plugins:netbox_script_manager:scriptinstance_execution' pk=object.pk %}" class="btn btn-danger btn-sm">
<button type="submit" name="_delete" formaction="{% url 'plugins:netbox_script_manager:scriptexecution_bulk_delete' %}?return_url={% url 'plugins:netbox_script_manager:scriptinstance_execution' pk=object.pk %}" class="btn btn-danger">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
</button>
{% endif %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

{% block extra_controls %}
{% if perms.netbox_script_manager.sync_scriptinstance %}
<a href="{% url 'plugins:netbox_script_manager:scriptinstance_sync' %}{% querystring request %}" class="btn btn-sm btn-primary">
<a href="{% url 'plugins:netbox_script_manager:scriptinstance_sync' %}{% querystring request %}" class="btn btn-primary">
<i class="mdi mdi-refresh"></i> Git Sync
</a>
{% endif %}
{% if perms.netbox_script_manager.add_scriptinstance %}
<a href="{% url 'plugins:netbox_script_manager:scriptinstance_load' %}{% querystring request %}" class="btn btn-sm btn-primary">
<a href="{% url 'plugins:netbox_script_manager:scriptinstance_load' %}{% querystring request %}" class="btn btn-primary">
<i class="mdi mdi-refresh"></i> Load Scripts
</a>
{% endif %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
{% load static %}
{% load scriptmanager %}

{% block controls %}
{% block control-buttons %}
<div class="controls">
<div class="control-group">
{% if request.user|can_delete:object %}
{% delete_button object %}
{% endif %}
<a href="{{ object.script_instance.get_absolute_url }}{{ object.data.input|urlencode_dict }}" type="submit" class="btn btn-sm btn-primary">
<a href="{{ object.script_instance.get_absolute_url }}{{ object.data.input|urlencode_dict }}" type="submit" class="btn btn-primary">
<i class="mdi mdi-refresh"></i> Rerun
</a>
</div>
Expand All @@ -20,7 +20,7 @@

{% block subtitle %}
<div class="object-subtitle">
<span>Created {{ object.created|annotated_date }}</span>
<span>Created {{ object.created|isodatetime }}</span>
</div>
{% endblock %}

Expand Down Expand Up @@ -81,15 +81,15 @@ <h5 class="card-header">Result</h5>
</tr>
<tr>
<th scope="row">Created</th>
<td>{{ object.created|annotated_date }}</td>
<td>{{ object.created|isodatetime }}</td>
</tr>
<tr>
<th scope="row">Started</th>
<td>{{ object.started|annotated_date }}</td>
<td>{{ object.started|isodatetime }}</td>
</tr>
<tr>
<th scope="row">Completed</th>
<td>{{ object.completed|annotated_date }}</td>
<td>{{ object.completed|isodatetime }}</td>
</tr>
<tr>
<th scope="row">Duration</th>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
{% load buttons %}
{% load scriptmanager %}

{% block controls %}
{% block control-buttons %}
<div class="controls">
<div class="control-group">
{% if request.user|can_delete:object %}
{% delete_button object %}
{% endif %}
<a href="{{ object.script_instance.get_absolute_url }}{{ object.data.input|urlencode_dict }}" type="submit" class="btn btn-sm btn-primary">
<a href="{{ object.script_instance.get_absolute_url }}{{ object.data.input|urlencode_dict }}" type="submit" class="btn btn-primary">
<i class="mdi mdi-refresh"></i> Rerun
</a>
</div>
Expand Down
2 changes: 1 addition & 1 deletion netbox_script_manager/templatetags/scriptmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import traceback

from django import template
from utilities.utils import dict_to_querydict
from utilities.querydict import dict_to_querydict

register = template.Library()

Expand Down
Loading

0 comments on commit a19c4a2

Please sign in to comment.