added files
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
**/netbox-docker
|
||||||
|
**/*.pyc
|
||||||
|
**/__pycache__
|
||||||
|
**/.idea
|
||||||
|
**/.vscode
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[[source]]
|
||||||
|
url = "https://pypi.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
name = "pypi"
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
python_version = "3.9"
|
||||||
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,11 @@
|
|||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: netbox-slm
|
||||||
|
Version: 0.1
|
||||||
|
Summary: Software Lifecycle Management Netbox Plugin
|
||||||
|
Home-page: https://github.com/ICTU/netbox-slm
|
||||||
|
Author: Hedde van der Heide
|
||||||
|
License: Apache 2.0
|
||||||
|
Platform: UNKNOWN
|
||||||
|
|
||||||
|
UNKNOWN
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
README
|
||||||
|
setup.py
|
||||||
|
netbox_slm/__init__.py
|
||||||
|
netbox_slm/admin.py
|
||||||
|
netbox_slm/filtersets.py
|
||||||
|
netbox_slm/forms.py
|
||||||
|
netbox_slm/middleware.py
|
||||||
|
netbox_slm/models.py
|
||||||
|
netbox_slm/navigation.py
|
||||||
|
netbox_slm/signals.py
|
||||||
|
netbox_slm/tables.py
|
||||||
|
netbox_slm/template_content.py
|
||||||
|
netbox_slm/urls.py
|
||||||
|
netbox_slm/views.py
|
||||||
|
netbox_slm.egg-info/PKG-INFO
|
||||||
|
netbox_slm.egg-info/SOURCES.txt
|
||||||
|
netbox_slm.egg-info/dependency_links.txt
|
||||||
|
netbox_slm.egg-info/not-zip-safe
|
||||||
|
netbox_slm.egg-info/top_level.txt
|
||||||
|
netbox_slm/migrations/0001_initial.py
|
||||||
|
netbox_slm/migrations/__init__.py
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
netbox_slm
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
from extras.plugins import PluginConfig
|
||||||
|
|
||||||
|
|
||||||
|
class SLMConfig(PluginConfig):
|
||||||
|
name = 'netbox_slm'
|
||||||
|
verbose_name = 'Software Lifecycle Management'
|
||||||
|
description = 'Software Lifecycle Management'
|
||||||
|
version = '0.1'
|
||||||
|
author = 'Hedde van der Heide'
|
||||||
|
author_email = 'hedde.vanderheide@ictu.nl'
|
||||||
|
base_url = 'slm'
|
||||||
|
required_settings = []
|
||||||
|
default_settings = {
|
||||||
|
'version_info': False
|
||||||
|
}
|
||||||
|
|
||||||
|
config = SLMConfig
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductVersionInline(admin.TabularInline):
|
||||||
|
model = SoftwareProductVersion
|
||||||
|
fields = ('name',)
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(SoftwareProduct)
|
||||||
|
class SoftwareProductAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name',)
|
||||||
|
inlines = (SoftwareProductVersionInline,)
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from netbox.api.serializers import PrimaryModelSerializer
|
||||||
|
# from netbox_slm.api.nested_serializers import (
|
||||||
|
# NestedRecordSerializer,
|
||||||
|
# NestedNameServerSerializer,
|
||||||
|
# )
|
||||||
|
from netbox_slm.models import SoftwareProduct
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductSerializer(PrimaryModelSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(
|
||||||
|
view_name="plugins-api:netbox_slm-api:softwareproduct-detail"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = SoftwareProduct
|
||||||
|
fields = (
|
||||||
|
"id",
|
||||||
|
"url",
|
||||||
|
"name",
|
||||||
|
"tags",
|
||||||
|
"custom_field_data",
|
||||||
|
"created",
|
||||||
|
"last_updated",
|
||||||
|
)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from netbox.api import OrderedDefaultRouter
|
||||||
|
from netbox_slm.api.views import (
|
||||||
|
NetboxSLMRootView,
|
||||||
|
SoftwareProductViewSet
|
||||||
|
)
|
||||||
|
|
||||||
|
router = OrderedDefaultRouter()
|
||||||
|
router.APIRootView = NetboxSLMRootView
|
||||||
|
|
||||||
|
router.register("softwareproducts", SoftwareProductViewSet)
|
||||||
|
urlpatterns = router.urls
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.routers import APIRootView
|
||||||
|
|
||||||
|
from extras.api.views import CustomFieldModelViewSet
|
||||||
|
from netbox_slm.api.serializers import SoftwareProductSerializer
|
||||||
|
from netbox_slm.filters import SoftwareProductFilter
|
||||||
|
from netbox_slm.models import SoftwareProduct
|
||||||
|
|
||||||
|
|
||||||
|
class NetboxSLMRootView(APIRootView):
|
||||||
|
"""
|
||||||
|
NetboxSLM API root view
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_view_name(self):
|
||||||
|
return "NetboxSLM"
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductViewSet(CustomFieldModelViewSet):
|
||||||
|
queryset = SoftwareProduct.objects.all()
|
||||||
|
serializer_class = SoftwareProductSerializer
|
||||||
|
filterset_class = SoftwareProductFilter
|
||||||
|
|
||||||
|
# @action(detail=True, methods=["get"])
|
||||||
|
# def records(self, request, pk=None):
|
||||||
|
# records = Record.objects.filter(zone=pk)
|
||||||
|
# serializer = RecordSerializer(records, many=True, context={"request": request})
|
||||||
|
# return Response(serializer.data)
|
||||||
|
|
||||||
|
# for reference: https://github.com/auroraresearchlab/netbox-dns/blob/main/netbox_dns/api/views.py
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
from django.forms import BoundField
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from utilities.forms import DynamicModelMultipleChoiceField
|
||||||
|
|
||||||
|
|
||||||
|
class CustomDynamicModelMultipleChoiceField(DynamicModelMultipleChoiceField):
|
||||||
|
def get_bound_field(self, form, field_name):
|
||||||
|
bound_field = BoundField(form, self, field_name)
|
||||||
|
|
||||||
|
# Set initial value based on prescribed child fields (if not already set)
|
||||||
|
if not self.initial and self.initial_params:
|
||||||
|
filter_kwargs = {}
|
||||||
|
for kwarg, child_field in self.initial_params.items():
|
||||||
|
value = form.initial.get(child_field.lstrip("$"))
|
||||||
|
if value:
|
||||||
|
filter_kwargs[kwarg] = value
|
||||||
|
if filter_kwargs:
|
||||||
|
self.initial = self.queryset.filter(**filter_kwargs).first()
|
||||||
|
|
||||||
|
# Modify the QuerySet of the field before we return it. Limit choices to any data already bound: Options
|
||||||
|
# will be populated on-demand via the APISelect widget.
|
||||||
|
data = bound_field.value()
|
||||||
|
if data:
|
||||||
|
field_name = getattr(self, "to_field_name") or "pk"
|
||||||
|
filter = self.filter(field_name=field_name)
|
||||||
|
try:
|
||||||
|
self.queryset = filter.filter(self.queryset, data)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
# Catch any error caused by invalid initial data passed from the user
|
||||||
|
self.queryset = self.queryset.none()
|
||||||
|
else:
|
||||||
|
self.queryset = self.queryset.none()
|
||||||
|
|
||||||
|
# Set the data URL on the APISelect widget (if not already set)
|
||||||
|
widget = bound_field.field.widget
|
||||||
|
if not widget.attrs.get("data-url"):
|
||||||
|
app_label = self.queryset.model._meta.app_label
|
||||||
|
model_name = self.queryset.model._meta.model_name
|
||||||
|
|
||||||
|
#
|
||||||
|
# Custom url for work with plugin api
|
||||||
|
#
|
||||||
|
data_url = reverse(
|
||||||
|
"plugins-api:{}-api:{}-list".format(app_label, model_name)
|
||||||
|
)
|
||||||
|
widget.attrs["data-url"] = data_url
|
||||||
|
|
||||||
|
return bound_field
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import django_filters
|
||||||
|
from django.db.models import Q
|
||||||
|
from extras.filters import TagFilter
|
||||||
|
from netbox.filtersets import PrimaryModelFilterSet
|
||||||
|
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductFilter(PrimaryModelFilterSet):
|
||||||
|
"""Filter capabilities for SoftwareProduct instances."""
|
||||||
|
|
||||||
|
q = django_filters.CharFilter(
|
||||||
|
method="search",
|
||||||
|
label="Search",
|
||||||
|
)
|
||||||
|
name = django_filters.CharFilter(
|
||||||
|
lookup_expr="icontains",
|
||||||
|
)
|
||||||
|
# tag = TagFilter()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = SoftwareProduct
|
||||||
|
fields = ("name",) # "tag")
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
"""Perform the filtered search."""
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
qs_filter = Q(name__icontains=value) # | Q(status__icontains=value)
|
||||||
|
return queryset.filter(qs_filter)
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductVersionFilter(SoftwareProductFilter):
|
||||||
|
class Meta:
|
||||||
|
model = SoftwareProductVersion
|
||||||
|
fields = ("name",) # "tag")
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
from django import forms
|
||||||
|
from extras.forms import (
|
||||||
|
CustomFieldModelForm,
|
||||||
|
CustomFieldModelCSVForm,
|
||||||
|
AddRemoveTagsForm,
|
||||||
|
CustomFieldModelBulkEditForm,
|
||||||
|
CustomFieldModelFilterForm,
|
||||||
|
)
|
||||||
|
from dcim.models import Manufacturer
|
||||||
|
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion
|
||||||
|
from utilities.forms import (
|
||||||
|
BootstrapMixin, DynamicModelChoiceField,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
from netbox_slm.fields import CustomDynamicModelMultipleChoiceField
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductForm(BootstrapMixin, CustomFieldModelForm):
|
||||||
|
"""Form for creating a new SoftwareProduct object."""
|
||||||
|
|
||||||
|
manufacturer = DynamicModelChoiceField(
|
||||||
|
queryset=Manufacturer.objects.all(),
|
||||||
|
required=False,
|
||||||
|
# initial_params={
|
||||||
|
# 'device_types': 'device_type'
|
||||||
|
# }
|
||||||
|
)
|
||||||
|
|
||||||
|
# tags = DynamicModelMultipleChoiceField(
|
||||||
|
# queryset=Tag.objects.all(),
|
||||||
|
# required=False,
|
||||||
|
# )
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = SoftwareProduct
|
||||||
|
fields = ("name", "manufacturer",) # "tags")
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
|
||||||
|
"""Form for filtering SoftwareProduct instances."""
|
||||||
|
|
||||||
|
model = SoftwareProduct
|
||||||
|
|
||||||
|
q = forms.CharField(required=False, label="Search")
|
||||||
|
|
||||||
|
name = forms.CharField(
|
||||||
|
required=False,
|
||||||
|
label="Name",
|
||||||
|
)
|
||||||
|
|
||||||
|
# tag = TagFilterField(SoftwareProduct)
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductCSVForm(CustomFieldModelCSVForm):
|
||||||
|
class Meta:
|
||||||
|
model = SoftwareProduct
|
||||||
|
fields = ("name",)
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||||
|
pk = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=SoftwareProduct.objects.all(),
|
||||||
|
widget=forms.MultipleHiddenInput(),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
nullable_fields = []
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductVersionForm(BootstrapMixin, CustomFieldModelForm):
|
||||||
|
"""Form for creating a new SoftwareProductVersion object."""
|
||||||
|
|
||||||
|
software_product = CustomDynamicModelMultipleChoiceField(
|
||||||
|
queryset=SoftwareProduct.objects.all(),
|
||||||
|
required=False,
|
||||||
|
# initial_params={
|
||||||
|
# 'device_types': 'device_type'
|
||||||
|
# }
|
||||||
|
)
|
||||||
|
|
||||||
|
# tags = DynamicModelMultipleChoiceField(
|
||||||
|
# queryset=Tag.objects.all(),
|
||||||
|
# required=False,
|
||||||
|
# )
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = SoftwareProductVersion
|
||||||
|
fields = ("name", "software_product",) # "tags")
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductVersionFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
|
||||||
|
"""Form for filtering SoftwareProductVersion instances."""
|
||||||
|
|
||||||
|
model = SoftwareProductVersion
|
||||||
|
|
||||||
|
q = forms.CharField(required=False, label="Search")
|
||||||
|
|
||||||
|
name = forms.CharField(
|
||||||
|
required=False,
|
||||||
|
label="Name",
|
||||||
|
)
|
||||||
|
|
||||||
|
# tag = TagFilterField(SoftwareProduct)
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductVersionCSVForm(CustomFieldModelCSVForm):
|
||||||
|
class Meta:
|
||||||
|
model = SoftwareProductVersion
|
||||||
|
fields = ("name",)
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductVersionBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||||
|
pk = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=SoftwareProduct.objects.all(),
|
||||||
|
widget=forms.MultipleHiddenInput(),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
nullable_fields = []
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Generated by Django 3.2.8 on 2021-11-04 15:38
|
||||||
|
|
||||||
|
import django.core.serializers.json
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import taggit.managers
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0133_port_colors'),
|
||||||
|
('extras', '0062_clear_secrets_changelog'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SoftwareProduct',
|
||||||
|
fields=[
|
||||||
|
('created', models.DateField(auto_now_add=True, null=True)),
|
||||||
|
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||||
|
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)),
|
||||||
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=128)),
|
||||||
|
('description', models.CharField(max_length=255)),
|
||||||
|
('manufacturer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='software_products', to='dcim.manufacturer')),
|
||||||
|
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SoftwareProductVersion',
|
||||||
|
fields=[
|
||||||
|
('created', models.DateField(auto_now_add=True, null=True)),
|
||||||
|
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||||
|
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder)),
|
||||||
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=64)),
|
||||||
|
('software_product', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='softwareproduct_versions', to='netbox_slm.softwareproduct')),
|
||||||
|
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.urls import reverse
|
||||||
|
from extras.utils import extras_features
|
||||||
|
from netbox.models import PrimaryModel
|
||||||
|
from utilities.querysets import RestrictedQuerySet
|
||||||
|
|
||||||
|
|
||||||
|
# @extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||||
|
class SoftwareProduct(PrimaryModel):
|
||||||
|
"""
|
||||||
|
A SoftwareProduct represents ...
|
||||||
|
"""
|
||||||
|
name = models.CharField(max_length=128)
|
||||||
|
description = models.CharField(max_length=255)
|
||||||
|
|
||||||
|
manufacturer = models.ForeignKey(
|
||||||
|
to='dcim.Manufacturer',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='software_products'
|
||||||
|
)
|
||||||
|
|
||||||
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse("plugins:netbox_slm:softwareproduct", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
|
|
||||||
|
# @extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
||||||
|
class SoftwareProductVersion(PrimaryModel):
|
||||||
|
software_product = models.ForeignKey(
|
||||||
|
to='netbox_slm.SoftwareProduct',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='softwareproduct_versions'
|
||||||
|
)
|
||||||
|
name = models.CharField(max_length=64)
|
||||||
|
|
||||||
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse("plugins:netbox_slm:softwareproductversion", kwargs={"pk": self.pk})
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
from extras.plugins import PluginMenuButton, PluginMenuItem
|
||||||
|
from utilities.choices import ButtonColorChoices
|
||||||
|
|
||||||
|
menu_items = (
|
||||||
|
PluginMenuItem(
|
||||||
|
link='plugins:netbox_slm:softwareproduct_list',
|
||||||
|
link_text='Software Products',
|
||||||
|
buttons=(
|
||||||
|
PluginMenuButton(
|
||||||
|
"plugins:netbox_slm:softwareproduct_add",
|
||||||
|
"Add",
|
||||||
|
"mdi mdi-plus-thick",
|
||||||
|
ButtonColorChoices.GREEN,
|
||||||
|
permissions=["netbox_slm.add_softwareproduct"],
|
||||||
|
),
|
||||||
|
PluginMenuButton(
|
||||||
|
"plugins:netbox_slm:softwareproduct_import",
|
||||||
|
"Import",
|
||||||
|
"mdi mdi-upload",
|
||||||
|
ButtonColorChoices.CYAN,
|
||||||
|
permissions=["netbox_slm.add_softwareproduct"],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
PluginMenuItem(
|
||||||
|
link='plugins:netbox_slm:softwareproductversion_list',
|
||||||
|
link_text='Versions',
|
||||||
|
buttons=(
|
||||||
|
PluginMenuButton(
|
||||||
|
"plugins:netbox_slm:softwareproductversion_add",
|
||||||
|
"Add",
|
||||||
|
"mdi mdi-plus-thick",
|
||||||
|
ButtonColorChoices.GREEN,
|
||||||
|
permissions=["netbox_slm.add_softwareproductversion"],
|
||||||
|
),
|
||||||
|
PluginMenuButton(
|
||||||
|
"plugins:netbox_slm:softwareproductversion_import",
|
||||||
|
"Import",
|
||||||
|
"mdi mdi-upload",
|
||||||
|
ButtonColorChoices.CYAN,
|
||||||
|
permissions=["netbox_slm.add_softwareproductversion"],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import django_tables2 as tables
|
||||||
|
from django_tables2.utils import Accessor
|
||||||
|
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion
|
||||||
|
from utilities.tables import BaseTable, ChoiceFieldColumn, ToggleColumn
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductTable(BaseTable):
|
||||||
|
"""Table for displaying SoftwareProduct objects."""
|
||||||
|
|
||||||
|
pk = ToggleColumn()
|
||||||
|
name = tables.LinkColumn()
|
||||||
|
manufacturer = tables.Column(
|
||||||
|
accessor=Accessor('manufacturer'),
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# tags = TagColumn(
|
||||||
|
# url_name="plugins:netbox_dns:zone_list",
|
||||||
|
# )
|
||||||
|
|
||||||
|
class Meta(BaseTable.Meta):
|
||||||
|
model = SoftwareProduct
|
||||||
|
fields = (
|
||||||
|
"pk",
|
||||||
|
"name",
|
||||||
|
"manufacturer",
|
||||||
|
# "tags",
|
||||||
|
)
|
||||||
|
default_columns = (
|
||||||
|
"pk",
|
||||||
|
"name",
|
||||||
|
"manufacturer",
|
||||||
|
# "tags",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductVersionTable(BaseTable):
|
||||||
|
"""Table for displaying SoftwareProductVersion objects."""
|
||||||
|
|
||||||
|
pk = ToggleColumn()
|
||||||
|
name = tables.LinkColumn()
|
||||||
|
software_product = tables.Column(
|
||||||
|
accessor=Accessor('software_product'),
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
manufacturer = tables.Column(
|
||||||
|
accessor=Accessor('software_product__manufacturer'),
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# tags = TagColumn(
|
||||||
|
# url_name="plugins:netbox_dns:zone_list",
|
||||||
|
# )
|
||||||
|
|
||||||
|
class Meta(BaseTable.Meta):
|
||||||
|
model = SoftwareProductVersion
|
||||||
|
fields = (
|
||||||
|
"pk",
|
||||||
|
"name",
|
||||||
|
"software_product",
|
||||||
|
"manufacturer",
|
||||||
|
# "tags",
|
||||||
|
)
|
||||||
|
default_columns = (
|
||||||
|
"pk",
|
||||||
|
"name",
|
||||||
|
"software_product",
|
||||||
|
"manufacturer",
|
||||||
|
# "tags",
|
||||||
|
)
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
{% extends 'base/layout.html' %}
|
||||||
|
{% load buttons %}
|
||||||
|
{% load custom_links %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load perms %}
|
||||||
|
{% load plugins %}
|
||||||
|
{% load view_helpers %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
{# Breadcrumbs #}
|
||||||
|
<nav class="breadcrumb-container px-3" aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
<li class="breadcrumb-item"><a href="{% url object|plugin_viewname:'list' %}">{{ object|meta:'verbose_name_plural'|bettertitle }}</a></li>
|
||||||
|
{% endblock %}
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block title %}{{ object }}{% endblock %}
|
||||||
|
|
||||||
|
{% block subtitle %}
|
||||||
|
<div class="object-subtitle">
|
||||||
|
<span>Created {{ object.created|annotated_date }}</span>
|
||||||
|
<span class="separator">·</span>
|
||||||
|
<span>Updated <span title="{{ object.last_updated }}">{{ object.last_updated|timesince }}</span> ago</span>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block controls %}
|
||||||
|
{# Clone/Edit/Delete Buttons #}
|
||||||
|
<div class="controls">
|
||||||
|
<div class="control-group">
|
||||||
|
{% plugin_buttons object %}
|
||||||
|
|
||||||
|
{# Extra buttons #}
|
||||||
|
{% block extra_controls %}{% endblock %}
|
||||||
|
|
||||||
|
{% if object.clone_fields and request.user|can_add:object %}
|
||||||
|
{% plugin_clone_button object %}
|
||||||
|
{% endif %}
|
||||||
|
{% if request.user|can_change:object %}
|
||||||
|
{% plugin_edit_button object %}
|
||||||
|
{% endif %}
|
||||||
|
{% if request.user|can_delete:object %}
|
||||||
|
{% plugin_delete_button object %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
{% custom_links object %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock controls %}
|
||||||
|
|
||||||
|
{% block tabs %}
|
||||||
|
<ul class="nav nav-tabs px-3">
|
||||||
|
{% block tab_items %}
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link{% if not active_tab %} active{% endif %}" href="{{ object.get_absolute_url }}">{{ object|meta:"verbose_name"|bettertitle }}</a>
|
||||||
|
</li>
|
||||||
|
{% endblock tab_items %}
|
||||||
|
{% if perms.extras.view_journalentry %}
|
||||||
|
{% with journal_viewname=object|plugin_viewname:'journal' %}
|
||||||
|
{% url journal_viewname pk=object.pk as journal_url %}
|
||||||
|
{% if journal_url %}
|
||||||
|
<li role="presentation" class="nav-item">
|
||||||
|
<a href="{{ journal_url }}" class="nav-link{% if active_tab == 'journal'%} active{% endif %}">
|
||||||
|
Journal {% badge object.journal_entries.count %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.extras.view_objectchange %}
|
||||||
|
{% with changelog_viewname=object|plugin_viewname:'changelog' %}
|
||||||
|
{% url changelog_viewname pk=object.pk as changelog_url %}
|
||||||
|
{% if changelog_url %}
|
||||||
|
<li role="presentation" class="nav-item">
|
||||||
|
<a href="{{ changelog_url }}" class="nav-link{% if active_tab == 'changelog'%} active{% endif %}">Change Log</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
{% endblock tabs %}
|
||||||
|
|
||||||
|
{% block content-wrapper %}
|
||||||
|
<div class="tab-content">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
{% extends 'base/layout.html' %}
|
||||||
|
{% load buttons %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
{% load static %}
|
||||||
|
{% load view_helpers %}
|
||||||
|
|
||||||
|
{% block controls %}
|
||||||
|
<div class="controls">
|
||||||
|
<div class="control-group">
|
||||||
|
{% block extra_controls %}{% endblock %}
|
||||||
|
{% if permissions.add and 'add' in action_buttons %}
|
||||||
|
{% add_button content_type.model_class|plugin_viewname:"add" %}
|
||||||
|
{% endif %}
|
||||||
|
{% if permissions.add and 'import' in action_buttons %}
|
||||||
|
{% import_button content_type.model_class|plugin_viewname:"import" %}
|
||||||
|
{% endif %}
|
||||||
|
{% if 'export' in action_buttons %}
|
||||||
|
{% export_button content_type %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock controls %}
|
||||||
|
|
||||||
|
{% block tabs %}
|
||||||
|
<ul class="nav nav-tabs px-3">
|
||||||
|
{% block tab_items %}
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link active" id="object-list-tab" data-bs-toggle="tab" data-bs-target="#object-list" type="button" role="tab" aria-controls="edit-form" aria-selected="true">
|
||||||
|
{% block title %}{{ content_type.model_class|meta:"verbose_name_plural"|bettertitle }}{% endblock %}
|
||||||
|
{% badge table.page.paginator.count %}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{% if filter_form %}
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="filters-form-tab" data-bs-toggle="tab" data-bs-target="#filters-form" type="button" role="tab" aria-controls="object-list" aria-selected="false">
|
||||||
|
Filters
|
||||||
|
{% if filter_form %}{% badge filter_form.changed_data|length bg_class="primary" %}{% endif %}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock tab_items %}
|
||||||
|
</ul>
|
||||||
|
{% endblock tabs %}
|
||||||
|
|
||||||
|
{% block content-wrapper %}
|
||||||
|
<div class="tab-content">
|
||||||
|
|
||||||
|
{# Object list #}
|
||||||
|
<div class="tab-pane show active" id="object-list" role="tabpanel" aria-labelledby="object-list-tab">
|
||||||
|
|
||||||
|
{# Applied filters #}
|
||||||
|
{% if filter_form %}
|
||||||
|
{% applied_filters filter_form request.GET %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# "Select all" form #}
|
||||||
|
{% if table.paginator.num_pages > 1 %}
|
||||||
|
{% with bulk_edit_url=content_type.model_class|validated_viewname:"bulk_edit" bulk_delete_url=content_type.model_class|validated_viewname:"bulk_delete" %}
|
||||||
|
<div id="select-all-box" class="d-none card noprint">
|
||||||
|
<form method="post" class="form col-md-12">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="float-end">
|
||||||
|
{% if bulk_edit_url and permissions.change %}
|
||||||
|
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm" disabled>
|
||||||
|
<span class="mdi mdi-pencil" aria-hidden="true"></span> Edit All
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if bulk_delete_url and permissions.delete %}
|
||||||
|
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm" disabled>
|
||||||
|
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete All
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="checkbox" id="select-all" name="_all" class="form-check-input" />
|
||||||
|
<label for="select-all" class="form-check-label">
|
||||||
|
Select <strong>all {{ table.rows|length }} {{ table.data.verbose_name_plural }}</strong> matching query
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Object table controls #}
|
||||||
|
{% include 'inc/table_controls.html' with table_modal="ObjectTable_config" %}
|
||||||
|
|
||||||
|
<form method="post" class="form form-horizontal">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="return_url" value="{% if return_url %}{{ return_url }}{% else %}{{ request.path }}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}{% endif %}" />
|
||||||
|
|
||||||
|
{# Object table #}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
{% render_table table 'inc/table.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Form buttons #}
|
||||||
|
{% if permissions.change or permissions.delete %}
|
||||||
|
{% with bulk_edit_url=content_type.model_class|plugin_validated_viewname:"bulk_edit" bulk_delete_url=content_type.model_class|plugin_validated_viewname:"bulk_delete" %}
|
||||||
|
<div class="noprint bulk-buttons">
|
||||||
|
<div class="bulk-button-group">
|
||||||
|
{% block bulk_buttons %}{% endblock %}
|
||||||
|
{% if bulk_edit_url and permissions.change %}
|
||||||
|
<button type="submit" name="_edit" formaction="{% url bulk_edit_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-warning btn-sm">
|
||||||
|
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit Selected
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if bulk_delete_url and permissions.delete %}
|
||||||
|
<button type="submit" name="_delete" formaction="{% url bulk_delete_url %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-sm">
|
||||||
|
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete Selected
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{# Paginator #}
|
||||||
|
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Filter form #}
|
||||||
|
{% if filter_form %}
|
||||||
|
<div class="tab-pane show" id="filters-form" role="tabpanel" aria-labelledby="filters-form-tab">
|
||||||
|
{% include 'inc/filter_list.html' %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Table config form #}
|
||||||
|
{% table_config_form table table_name="ObjectTable" %}
|
||||||
|
{% endblock content-wrapper %}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
{% extends 'netbox_slm/object.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load plugins %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
{% load view_helpers %}
|
||||||
|
{% load perms %}
|
||||||
|
|
||||||
|
{% block extra_controls %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
Software Product
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-hover attr-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Name</th>
|
||||||
|
<td>{{ object.name }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Versions</th>
|
||||||
|
<td>
|
||||||
|
{% for version in object.softwareproduct_versions.all %}
|
||||||
|
<a href="{% url 'plugins:netbox_slm:softwareproductversion' pk=version.pk %}">
|
||||||
|
<span class="badge" style="color: #ffffff; background-color: #9e9e9e">{{ version }}</span>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include 'inc/custom_fields_panel.html' %}
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-6">
|
||||||
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='plugins:netbox_slm:softwareproduct_list' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-12">
|
||||||
|
<div class="card">
|
||||||
|
<h5 class="card-header">Versions</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-hover attr-table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">NAME</th>
|
||||||
|
<th scope="col">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for version in versions %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ version.name|truncatechars:32 }}</td>
|
||||||
|
<td class="noprint text-end text-nowrap">
|
||||||
|
{% if request.user|can_change:object %}
|
||||||
|
{% plugin_edit_button version %}
|
||||||
|
{% endif %}
|
||||||
|
{% if request.user|can_delete:object %}
|
||||||
|
{% plugin_delete_button version %}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
{% extends 'netbox_slm/object.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load plugins %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
{% load view_helpers %}
|
||||||
|
{% load perms %}
|
||||||
|
|
||||||
|
{% block extra_controls %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
Software Product
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-hover attr-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Name</th>
|
||||||
|
<td>{{ object.name }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Installations</th>
|
||||||
|
<td>
|
||||||
|
<!-- {% for version in object.softwareproduct_versions.all %}-->
|
||||||
|
<!-- <a href="{% url 'plugins:netbox_slm:softwareproductversion' pk=version.pk %}">-->
|
||||||
|
<!-- <span class="badge" style="color: #ffffff; background-color: #9e9e9e">{{ version }}</span>-->
|
||||||
|
<!-- </a>-->
|
||||||
|
<!-- {% endfor %}-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include 'inc/custom_fields_panel.html' %}
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-6">
|
||||||
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='plugins:netbox_slm:softwareproduct_list' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--<div class="row">-->
|
||||||
|
<!-- <div class="col col-md-12">-->
|
||||||
|
<!-- <div class="card">-->
|
||||||
|
<!-- <h5 class="card-header">Records</h5>-->
|
||||||
|
<!-- <div class="card-body">-->
|
||||||
|
<!-- <table class="table table-hover attr-table table-striped">-->
|
||||||
|
<!-- <thead>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <th scope="col">TYPE</th>-->
|
||||||
|
<!-- <th scope="col">NAME</th>-->
|
||||||
|
<!-- <th scope="col">VALUE</th>-->
|
||||||
|
<!-- <th scope="col">TTL</th>-->
|
||||||
|
<!-- <th scope="col">Actions</th>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- </thead>-->
|
||||||
|
<!-- <tbody>-->
|
||||||
|
<!-- {% for record in records %}-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>{{ record.type }}</td>-->
|
||||||
|
<!-- <td>{{ record.name|truncatechars:32 }}</td>-->
|
||||||
|
<!-- <td>{{ record.value|truncatechars:64 }}</td>-->
|
||||||
|
<!-- <td>{{ record.ttl }}</td>-->
|
||||||
|
<!-- <td class="noprint text-end text-nowrap">-->
|
||||||
|
<!-- {% if request.user|can_change:object %}-->
|
||||||
|
<!-- {% plugin_edit_button record %}-->
|
||||||
|
<!-- {% endif %}-->
|
||||||
|
<!-- {% if request.user|can_delete:object %}-->
|
||||||
|
<!-- {% plugin_delete_button record %}-->
|
||||||
|
<!-- {% endif %}-->
|
||||||
|
<!-- </td>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- {% endfor %}-->
|
||||||
|
<!-- </tbody>-->
|
||||||
|
<!-- </table>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!--</div>-->
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
{% extends 'netbox_slm/object.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load plugins %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
{% load view_helpers %}
|
||||||
|
{% load perms %}
|
||||||
|
|
||||||
|
{% block extra_controls %}
|
||||||
|
{% comment %}
|
||||||
|
<a href="{% url 'plugins:netbox_slm:record_add' %}?zone={{ object.id }}" class="btn btn-sm btn-primary" role="button">
|
||||||
|
<i class="mdi mdi-plus-thick"></i> Add Record
|
||||||
|
</a>
|
||||||
|
{% endcomment %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
Zone
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-hover attr-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Name</th>
|
||||||
|
<td>{{ object.name }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Versions</th>
|
||||||
|
<td>
|
||||||
|
<ul>
|
||||||
|
<!-- {% for nameserver in object.nameservers.all %}-->
|
||||||
|
<!-- <li>-->
|
||||||
|
<!-- <a href="{% url 'plugins:netbox_slm:nameserver' pk=nameserver.pk %}">-->
|
||||||
|
<!-- {{ nameserver }}-->
|
||||||
|
<!-- </a>-->
|
||||||
|
<!-- </li>-->
|
||||||
|
<!-- {% endfor %}-->
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include 'inc/custom_fields_panel.html' %}
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-6">
|
||||||
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='plugins:netbox_slm:softwareproduct_list' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--<div class="row">-->
|
||||||
|
<!-- <div class="col col-md-12">-->
|
||||||
|
<!-- <div class="card">-->
|
||||||
|
<!-- <h5 class="card-header">Records</h5>-->
|
||||||
|
<!-- <div class="card-body">-->
|
||||||
|
<!-- <table class="table table-hover attr-table table-striped">-->
|
||||||
|
<!-- <thead>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <th scope="col">TYPE</th>-->
|
||||||
|
<!-- <th scope="col">NAME</th>-->
|
||||||
|
<!-- <th scope="col">VALUE</th>-->
|
||||||
|
<!-- <th scope="col">TTL</th>-->
|
||||||
|
<!-- <th scope="col">Actions</th>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- </thead>-->
|
||||||
|
<!-- <tbody>-->
|
||||||
|
<!-- {% for record in records %}-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>{{ record.type }}</td>-->
|
||||||
|
<!-- <td>{{ record.name|truncatechars:32 }}</td>-->
|
||||||
|
<!-- <td>{{ record.value|truncatechars:64 }}</td>-->
|
||||||
|
<!-- <td>{{ record.ttl }}</td>-->
|
||||||
|
<!-- <td class="noprint text-end text-nowrap">-->
|
||||||
|
<!-- {% if request.user|can_change:object %}-->
|
||||||
|
<!-- {% plugin_edit_button record %}-->
|
||||||
|
<!-- {% endif %}-->
|
||||||
|
<!-- {% if request.user|can_delete:object %}-->
|
||||||
|
<!-- {% plugin_delete_button record %}-->
|
||||||
|
<!-- {% endif %}-->
|
||||||
|
<!-- </td>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- {% endfor %}-->
|
||||||
|
<!-- </tbody>-->
|
||||||
|
<!-- </table>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!--</div>-->
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
from django import template
|
||||||
|
from django.urls import NoReverseMatch, reverse
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
from utilities.templatetags.buttons import _get_viewname
|
||||||
|
from utilities.utils import prepare_cloned_fields
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter()
|
||||||
|
def plugin_validated_viewname(model, action):
|
||||||
|
"""Return the view name for :
|
||||||
|
* the given model and action if valid
|
||||||
|
* or the given model and action if valid inside a plugin
|
||||||
|
* or None if invalid.
|
||||||
|
"""
|
||||||
|
core_viewname = f"{model._meta.app_label}:{model._meta.model_name}_{action}"
|
||||||
|
plugin_viewname = (
|
||||||
|
f"plugins:{model._meta.app_label}:{model._meta.model_name}_{action}"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
# Validate and return the view name. We don't return the actual URL yet because many of the templates
|
||||||
|
# are written to pass a name to {% url %}.
|
||||||
|
reverse(core_viewname)
|
||||||
|
return core_viewname
|
||||||
|
except NoReverseMatch:
|
||||||
|
try:
|
||||||
|
reverse(plugin_viewname)
|
||||||
|
return plugin_viewname
|
||||||
|
except NoReverseMatch:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter()
|
||||||
|
def plugin_viewname(model, action):
|
||||||
|
"""
|
||||||
|
Return the view name for the given model and action. Does not perform any validation.
|
||||||
|
"""
|
||||||
|
return f"plugins:{model._meta.app_label}:{model._meta.model_name}_{action}"
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Instance buttons
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
@register.inclusion_tag("buttons/clone.html")
|
||||||
|
def plugin_clone_button(instance):
|
||||||
|
viewname = _get_viewname(instance, "add")
|
||||||
|
url = reverse(f"plugins:{viewname}")
|
||||||
|
|
||||||
|
# Populate cloned field values
|
||||||
|
param_string = prepare_cloned_fields(instance)
|
||||||
|
if param_string:
|
||||||
|
url = f"{url}?{param_string}"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"url": url,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register.inclusion_tag("buttons/edit.html")
|
||||||
|
def plugin_edit_button(instance, **kwargs):
|
||||||
|
viewname = _get_viewname(instance, "edit")
|
||||||
|
url = reverse(f"plugins:{viewname}", kwargs={"pk": instance.pk, **kwargs})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"url": url,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register.inclusion_tag("buttons/delete.html")
|
||||||
|
def plugin_delete_button(instance, **kwargs):
|
||||||
|
viewname = _get_viewname(instance, "delete")
|
||||||
|
url = reverse(f"plugins:{viewname}", kwargs={"pk": instance.pk, **kwargs})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"url": url,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# List buttons
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
@register.inclusion_tag("buttons/add.html")
|
||||||
|
def plugin_add_button(url):
|
||||||
|
url = reverse(f"plugins:{url}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"add_url": url,
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from extras.views import ObjectChangeLogView
|
||||||
|
from netbox_slm import views
|
||||||
|
from netbox_slm.models import SoftwareProduct, SoftwareProductVersion
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
# Software Products
|
||||||
|
path("software-products/", views.SoftwareProductListView.as_view(), name='softwareproduct_list'),
|
||||||
|
path("software-products/add/", views.SoftwareProductEditView.as_view(), name='softwareproduct_add'),
|
||||||
|
path("software-products/import/", views.SoftwareProductBulkImportView.as_view(), name="softwareproduct_import"),
|
||||||
|
path("software-products/edit/", views.SoftwareProductBulkEditView.as_view(), name="softwareproduct_bulk_edit"),
|
||||||
|
path("software-products/delete/", views.SoftwareProductBulkDeleteView.as_view(), name="softwareproduct_bulk_delete"),
|
||||||
|
path("software-products/<int:pk>/", views.SoftwareProductView.as_view(), name="softwareproduct"),
|
||||||
|
path("software-products/<int:pk>/delete/", views.SoftwareProductDeleteView.as_view(), name="softwareproduct_delete"),
|
||||||
|
path("software-products/<int:pk>/edit/", views.SoftwareProductEditView.as_view(), name="softwareproduct_edit"),
|
||||||
|
path(
|
||||||
|
"software-products/<int:pk>/changelog/",
|
||||||
|
ObjectChangeLogView.as_view(),
|
||||||
|
name="softwareproduct_changelog",
|
||||||
|
kwargs={"model": SoftwareProduct},
|
||||||
|
),
|
||||||
|
|
||||||
|
# Software Product Versions
|
||||||
|
path("versions/", views.SoftwareProductVersionListView.as_view(), name='softwareproductversion_list'),
|
||||||
|
path("versions/add/", views.SoftwareProductVersionEditView.as_view(), name='softwareproductversion_add'),
|
||||||
|
path("versions/import/", views.SoftwareProductVersionBulkImportView.as_view(), name="softwareproductversion_import"),
|
||||||
|
path("versions/edit/", views.SoftwareProductVersionBulkEditView.as_view(), name="softwareproductversion_bulk_edit"),
|
||||||
|
path("versions/delete/", views.SoftwareProductVersionBulkDeleteView.as_view(), name="softwareproductversion_bulk_delete"),
|
||||||
|
path("versions/<int:pk>/", views.SoftwareProductVersionView.as_view(), name="softwareproductversion"),
|
||||||
|
path("versions/<int:pk>/delete/", views.SoftwareProductVersionDeleteView.as_view(), name="softwareproductversion_delete"),
|
||||||
|
path("versions/<int:pk>/edit/", views.SoftwareProductVersionEditView.as_view(), name="softwareproductversion_edit"),
|
||||||
|
path(
|
||||||
|
"versions/<int:pk>/changelog/",
|
||||||
|
ObjectChangeLogView.as_view(),
|
||||||
|
name="softwareproductversion_changelog",
|
||||||
|
kwargs={"model": SoftwareProductVersion},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
from netbox.views import generic
|
||||||
|
from netbox_slm.filters import SoftwareProductFilter, SoftwareProductVersionFilter
|
||||||
|
from netbox_slm.forms import (
|
||||||
|
SoftwareProductForm, SoftwareProductFilterForm, SoftwareProductCSVForm, SoftwareProductBulkEditForm,
|
||||||
|
SoftwareProductVersionForm, SoftwareProductVersionFilterForm, SoftwareProductVersionCSVForm,
|
||||||
|
SoftwareProductVersionBulkEditForm
|
||||||
|
)
|
||||||
|
from netbox_slm.models import (
|
||||||
|
SoftwareProduct, SoftwareProductVersion
|
||||||
|
)
|
||||||
|
from netbox_slm.tables import SoftwareProductTable, SoftwareProductVersionTable
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductListView(generic.ObjectListView):
|
||||||
|
"""View for listing all existing SoftwareProducts."""
|
||||||
|
|
||||||
|
queryset = SoftwareProduct.objects.all()
|
||||||
|
filterset = SoftwareProductFilter
|
||||||
|
filterset_form = SoftwareProductFilterForm
|
||||||
|
table = SoftwareProductTable
|
||||||
|
template_name = "netbox_slm/object_list.html"
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductView(generic.ObjectView):
|
||||||
|
"""Display SoftwareProduct details"""
|
||||||
|
|
||||||
|
queryset = SoftwareProduct.objects.all()
|
||||||
|
|
||||||
|
def get_extra_context(self, request, instance):
|
||||||
|
versions = instance.softwareproduct_versions.all()
|
||||||
|
return {"versions": versions}
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductEditView(generic.ObjectEditView):
|
||||||
|
"""View for editing and creating a SoftwareProduct instance."""
|
||||||
|
|
||||||
|
queryset = SoftwareProduct.objects.all()
|
||||||
|
model_form = SoftwareProductForm
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductDeleteView(generic.ObjectDeleteView):
|
||||||
|
"""View for deleting a SoftwareProduct instance"""
|
||||||
|
|
||||||
|
queryset = SoftwareProduct.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductBulkImportView(generic.BulkImportView):
|
||||||
|
queryset = SoftwareProduct.objects.all()
|
||||||
|
model_form = SoftwareProductCSVForm
|
||||||
|
table = SoftwareProductTable
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductBulkEditView(generic.BulkEditView):
|
||||||
|
queryset = SoftwareProduct.objects.all()
|
||||||
|
filterset = SoftwareProductFilter
|
||||||
|
table = SoftwareProductTable
|
||||||
|
form = SoftwareProductBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductBulkDeleteView(generic.BulkDeleteView):
|
||||||
|
queryset = SoftwareProduct.objects.all()
|
||||||
|
table = SoftwareProductTable
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductVersionListView(generic.ObjectListView):
|
||||||
|
"""View for listing all existing SoftwareProductVersions."""
|
||||||
|
|
||||||
|
queryset = SoftwareProductVersion.objects.all()
|
||||||
|
filterset = SoftwareProductVersionFilter
|
||||||
|
filterset_form = SoftwareProductVersionFilterForm
|
||||||
|
table = SoftwareProductVersionTable
|
||||||
|
template_name = "netbox_slm/object_list.html"
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductVersionView(generic.ObjectView):
|
||||||
|
"""Display SoftwareProductVersion details"""
|
||||||
|
|
||||||
|
queryset = SoftwareProductVersion.objects.all()
|
||||||
|
|
||||||
|
# def get_extra_context(self, request, instance):
|
||||||
|
# records = instance.record_set.all()
|
||||||
|
# return {"records": records}
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductVersionEditView(generic.ObjectEditView):
|
||||||
|
"""View for editing and creating a SoftwareProductVersion instance."""
|
||||||
|
|
||||||
|
queryset = SoftwareProductVersion.objects.all()
|
||||||
|
model_form = SoftwareProductVersionForm
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductVersionDeleteView(generic.ObjectDeleteView):
|
||||||
|
"""View for deleting a SoftwareProductVersion instance"""
|
||||||
|
|
||||||
|
queryset = SoftwareProductVersion.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductVersionBulkImportView(generic.BulkImportView):
|
||||||
|
queryset = SoftwareProductVersion.objects.all()
|
||||||
|
model_form = SoftwareProductVersionCSVForm
|
||||||
|
table = SoftwareProductVersionTable
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductVersionBulkEditView(generic.BulkEditView):
|
||||||
|
queryset = SoftwareProductVersion.objects.all()
|
||||||
|
filterset = SoftwareProductVersionFilter
|
||||||
|
table = SoftwareProductVersionTable
|
||||||
|
form = SoftwareProductVersionBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
class SoftwareProductVersionBulkDeleteView(generic.BulkDeleteView):
|
||||||
|
queryset = SoftwareProductVersion.objects.all()
|
||||||
|
table = SoftwareProductVersionTable
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
from setuptools import find_packages, setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='netbox-slm',
|
||||||
|
version='0.1',
|
||||||
|
description='Software Lifecycle Management Netbox Plugin',
|
||||||
|
url='https://github.com/ICTU/netbox-slm',
|
||||||
|
author='Hedde van der Heide',
|
||||||
|
license='Apache 2.0',
|
||||||
|
install_requires=[],
|
||||||
|
packages=find_packages(),
|
||||||
|
include_package_data=True,
|
||||||
|
zip_safe=False,
|
||||||
|
)
|
||||||
Executable
+11
@@ -0,0 +1,11 @@
|
|||||||
|
git clone -b release https://github.com/netbox-community/netbox-docker.git
|
||||||
|
cd netbox-docker
|
||||||
|
tee docker-compose.override.yml <<EOF
|
||||||
|
version: '3.4'
|
||||||
|
services:
|
||||||
|
netbox:
|
||||||
|
ports:
|
||||||
|
- 8000:8080
|
||||||
|
EOF
|
||||||
|
docker-compose pull
|
||||||
|
docker-compose up
|
||||||
Reference in New Issue
Block a user