diff --git a/netbox_routing/__init__.py b/netbox_routing/__init__.py index aa2d8ea..b181896 100644 --- a/netbox_routing/__init__.py +++ b/netbox_routing/__init__.py @@ -1,6 +1,5 @@ from extras.plugins import PluginConfig - try: from importlib.metadata import metadata except ModuleNotFoundError: @@ -16,11 +15,11 @@ class NetboxRouting(PluginConfig): version = plugin.get('Version') author = plugin.get('Author') author_email = plugin.get('Author-email') - base_url = 'netbox-plugin-extensions' - min_version = '3.2' + base_url = 'routing' + min_version = '3.2.0b1' required_settings = [] caching_config = {} default_settings = {} -config = NetboxPluginExtensions \ No newline at end of file +config = NetboxRouting diff --git a/netbox_routing/api/__init__.py b/netbox_routing/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/netbox_routing/api/nested_serializers/__init__.py b/netbox_routing/api/nested_serializers/__init__.py new file mode 100644 index 0000000..3acd685 --- /dev/null +++ b/netbox_routing/api/nested_serializers/__init__.py @@ -0,0 +1,12 @@ +from .static import NestedStaticRouteSerializer +from .objects import NestedPrefixListSerializer, NestedPrefixListEntrySerializer, NestedRouteMapSerializer,\ + NestedRouteMapEntrySerializer + +__all__ = ( + 'NestedStaticRouteSerializer', + + 'NestedPrefixListSerializer', + 'NestedPrefixListEntrySerializer', + 'NestedRouteMapSerializer', + 'NestedRouteMapEntrySerializer', +) diff --git a/netbox_routing/api/nested_serializers/objects.py b/netbox_routing/api/nested_serializers/objects.py new file mode 100644 index 0000000..280dda8 --- /dev/null +++ b/netbox_routing/api/nested_serializers/objects.py @@ -0,0 +1,43 @@ +from rest_framework import serializers + +from netbox.api import WritableNestedSerializer +from netbox_routing.models import PrefixList, PrefixListEntry, RouteMap, RouteMapEntry + + +__all__ = ( + 'NestedStaticRouteSerializer' +) + + +class NestedPrefixListSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:prefixlist-detail') + + class Meta: + model = PrefixList + fields = ('url', 'id', 'name') + + +class NestedPrefixListEntrySerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:prefixlist-detail') + prefix_list = NestedPrefixListSerializer + + class Meta: + model = PrefixListEntry + fields = ('url', 'id', 'prefix_list') + + +class NestedRouteMapSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:prefixlist-detail') + + class Meta: + model = RouteMap + fields = ('url', 'id', 'name') + + +class NestedRouteMapEntrySerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:prefixlist-detail') + route_map = NestedRouteMapSerializer + + class Meta: + model = RouteMapEntry + fields = ('url', 'id', 'route_map') diff --git a/netbox_routing/api/nested_serializers/static.py b/netbox_routing/api/nested_serializers/static.py new file mode 100644 index 0000000..d1bb8e7 --- /dev/null +++ b/netbox_routing/api/nested_serializers/static.py @@ -0,0 +1,17 @@ +from rest_framework import serializers + +from netbox.api import WritableNestedSerializer +from netbox_routing.models import StaticRoute + + +__all__ = ( + 'NestedStaticRouteSerializer' +) + + +class NestedStaticRouteSerializer(WritableNestedSerializer): + url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:staticroute-detail') + + class Meta: + model = StaticRoute + fields = ('url', 'id', 'prefix', 'next_hop', 'name', 'metric', 'permanent') \ No newline at end of file diff --git a/netbox_routing/api/serializers/__init__.py b/netbox_routing/api/serializers/__init__.py new file mode 100644 index 0000000..d367121 --- /dev/null +++ b/netbox_routing/api/serializers/__init__.py @@ -0,0 +1,11 @@ +from .static import StaticRouteSerializer +from .objects import PrefixListSerializer, PrefixListEntrySerializer, RouteMapSerializer, RouteMapEntrySerializer + +__all__ = ( + 'StaticRouteSerializer', + + 'PrefixListSerializer', + 'PrefixListEntrySerializer', + 'RouteMapSerializer', + 'RouteMapEntrySerializer', +) diff --git a/netbox_routing/api/serializers/objects.py b/netbox_routing/api/serializers/objects.py new file mode 100644 index 0000000..83bb94e --- /dev/null +++ b/netbox_routing/api/serializers/objects.py @@ -0,0 +1,45 @@ +from rest_framework import serializers + +from netbox_routing.api.nested_serializers import NestedPrefixListSerializer, NestedRouteMapSerializer +from netbox.api.serializers import NetBoxModelSerializer +from netbox_routing.models import PrefixList, PrefixListEntry, RouteMap, RouteMapEntry + + +__all__ = ( + 'StaticRouteSerializer' +) + + +class PrefixListSerializer(NetBoxModelSerializer): + url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:prefixlist-detail') + + class Meta: + model = PrefixList + fields = ('url', 'id', 'name') + + +class PrefixListEntrySerializer(NetBoxModelSerializer): + url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:prefixlistentry-detail') + prefix_list = NestedPrefixListSerializer() + + class Meta: + model = PrefixListEntry + fields = ('url', 'id', 'prefix_list', 'sequence', 'type', 'prefix', 'le', 'ge') + + +class RouteMapSerializer(NetBoxModelSerializer): + url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:prefixlist-detail') + + class Meta: + model = RouteMap + fields = ('url', 'id', 'name') + + +class RouteMapEntrySerializer(NetBoxModelSerializer): + url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:prefixlistentry-detail') + route_map = NestedRouteMapSerializer() + + + class Meta: + model = RouteMapEntry + fields = ('url', 'id', 'route_map', 'sequence', 'type') diff --git a/netbox_routing/api/serializers/static.py b/netbox_routing/api/serializers/static.py new file mode 100644 index 0000000..ef00731 --- /dev/null +++ b/netbox_routing/api/serializers/static.py @@ -0,0 +1,21 @@ +from rest_framework import serializers + +from dcim.api.nested_serializers import NestedDeviceSerializer +from ipam.api.nested_serializers import NestedVRFSerializer +from netbox.api.serializers import NetBoxModelSerializer +from netbox_routing.models import StaticRoute + + +__all__ = ( + 'StaticRouteSerializer' +) + + +class StaticRouteSerializer(NetBoxModelSerializer): + url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:staticroute-detail') + devices = NestedDeviceSerializer(many=True) + vrf = NestedVRFSerializer() + + class Meta: + model = StaticRoute + fields = ('url', 'id', 'devices', 'vrf', 'prefix', 'next_hop', 'name', 'metric', 'permanent') diff --git a/netbox_routing/api/urls.py b/netbox_routing/api/urls.py new file mode 100644 index 0000000..57afef6 --- /dev/null +++ b/netbox_routing/api/urls.py @@ -0,0 +1,10 @@ +from netbox.api.routers import NetBoxRouter +from .views import StaticRouteViewSet, PrefixListViewSet, RouteMapViewSet, PrefixListEntryViewSet, RouteMapEntryViewSet + +router = NetBoxRouter() +router.register('staticroute', StaticRouteViewSet) +router.register('prefix-list', PrefixListViewSet) +router.register('prefix-list-entry', PrefixListEntryViewSet) +router.register('route-map', RouteMapViewSet) +router.register('route-map-entry', RouteMapEntryViewSet) +urlpatterns = router.urls diff --git a/netbox_routing/api/views/__init__.py b/netbox_routing/api/views/__init__.py new file mode 100644 index 0000000..91cf2b6 --- /dev/null +++ b/netbox_routing/api/views/__init__.py @@ -0,0 +1,10 @@ +from .static import StaticRouteViewSet +from .objects import PrefixListViewSet, PrefixListEntryViewSet, RouteMapViewSet, RouteMapEntryViewSet + +__all__ = ( + 'StaticRouteViewSet', + 'PrefixListViewSet', + 'PrefixListEntryViewSet', + 'RouteMapViewSet', + 'RouteMapEntryViewSet', +) diff --git a/netbox_routing/api/views/objects.py b/netbox_routing/api/views/objects.py new file mode 100644 index 0000000..020a649 --- /dev/null +++ b/netbox_routing/api/views/objects.py @@ -0,0 +1,24 @@ +from netbox.api.viewsets import ModelViewSet +from netbox_routing.api.serializers import PrefixListSerializer, PrefixListEntrySerializer, RouteMapSerializer, \ + RouteMapEntrySerializer +from netbox_routing.models import PrefixList, PrefixListEntry, RouteMap, RouteMapEntry + + +class PrefixListViewSet(ModelViewSet): + queryset = PrefixList.objects.all() + serializer_class = PrefixListSerializer + + +class PrefixListEntryViewSet(ModelViewSet): + queryset = PrefixListEntry.objects.all() + serializer_class = PrefixListEntrySerializer + + +class RouteMapViewSet(ModelViewSet): + queryset = RouteMap.objects.all() + serializer_class = RouteMapSerializer + + +class RouteMapEntryViewSet(ModelViewSet): + queryset = RouteMapEntry.objects.all() + serializer_class = RouteMapEntrySerializer diff --git a/netbox_routing/api/views/static.py b/netbox_routing/api/views/static.py new file mode 100644 index 0000000..f06f3a7 --- /dev/null +++ b/netbox_routing/api/views/static.py @@ -0,0 +1,8 @@ +from netbox.api.viewsets import ModelViewSet +from netbox_routing.api.serializers import StaticRouteSerializer +from netbox_routing.models import StaticRoute + + +class StaticRouteViewSet(ModelViewSet): + queryset = StaticRoute.objects.all() + serializer_class = StaticRouteSerializer diff --git a/netbox_routing/choices/__init__.py b/netbox_routing/choices/__init__.py new file mode 100644 index 0000000..73b25b4 --- /dev/null +++ b/netbox_routing/choices/__init__.py @@ -0,0 +1 @@ +from .bgp import * diff --git a/netbox_routing/choices/bgp.py b/netbox_routing/choices/bgp.py new file mode 100644 index 0000000..5e6b828 --- /dev/null +++ b/netbox_routing/choices/bgp.py @@ -0,0 +1,65 @@ +from utilities.choices import ChoiceSet + + +class BGPAdditionalPathSelectChoices(ChoiceSet): + ALL = 'all' + BACKUP = 'backup' + BEST_EXTERNAL = 'best-external' + GROUP_BEST = 'group-best' + + CHOICES = [ + (ALL, 'All'), + (BACKUP, 'Backup'), + (BEST_EXTERNAL, 'Best External'), + (GROUP_BEST, 'Group Best') + ] + + +class BGPBestPathASPath(ChoiceSet): + IGNORE = 'ignore' + MULTIPATH = 'multipath-relax' + + CHOICES = [ + (IGNORE, 'Ignore'), + (MULTIPATH, 'Multipath Relax Comparison') + ] + + +class BGPAddressFamilies(ChoiceSet): + IPV4_UNICAST = 'ipv4-unicast' + IPV6_UNICAST = 'ipv6-unicast' + VPNV4_UNICAST = 'vpnv4-unicast' + VPNV6_UNICAST = 'vpnv6-unicast' + IPV4_MULTICAST = 'ipv4-multicast' + IPV6_MULTICAST = 'ipv6-multicast' + VPNV4_MULTICAST = 'vpnv4-multicast' + VPNV6_MULTICAST = 'vpnv6-multicast' + IPV4_FLOWSPEC = 'ipv4-flowspec' + IPV6_FLOWSPEC = 'ipv6-flowspec' + VPNV4_FLOWSPEC = 'vpnv4-flowspec' + VPNV6_FLOWSPEC = 'vpnv6-flowspec' + NSAP = 'nsap' + L2VPNVPLS = 'l2vpn-vpls' + L2VPSEVPN = 'l2vpn-evpn' + LINKSTATE = 'link-state' + RTFILTER_UNICAST = 'rtfilter-unicast' + + CHOICES = [ + (IPV4_UNICAST, 'IPv4 Unicast'), + (IPV6_UNICAST, 'IPv6 Unicast'), + (VPNV4_UNICAST, 'VPNv4 Unicast'), + (VPNV6_UNICAST, 'VPNv6 Unicast'), + (IPV4_MULTICAST, 'IPv4 Multicast'), + (IPV6_MULTICAST, 'IPv6 Multicast'), + (VPNV4_UNICAST, 'VPNv4 Multicast'), + (VPNV6_MULTICAST, 'VPNv6 Multicast'), + (IPV4_FLOWSPEC, 'IPv4 Flowspec'), + (IPV6_FLOWSPEC, 'IPv6 Flowspec'), + (VPNV4_FLOWSPEC, 'VPNv4 Flowspec'), + (VPNV6_FLOWSPEC, 'VPNv6 Flowspec'), + (NSAP, 'NSAP'), + (L2VPNVPLS, 'L2VPN VPLS'), + (L2VPSEVPN, 'L2VPN EVPN'), + (LINKSTATE, 'LINK-STATE'), + (RTFILTER_UNICAST, 'RTFILTER') + ] diff --git a/netbox_routing/choices/objects.py b/netbox_routing/choices/objects.py new file mode 100644 index 0000000..baa2ce1 --- /dev/null +++ b/netbox_routing/choices/objects.py @@ -0,0 +1,11 @@ +from utilities.choices import ChoiceSet + + +class PermitDenyChoices(ChoiceSet): + PERMIT = 'permit' + DENY = 'deny' + + CHOICES = [ + (PERMIT, 'Permit'), + (DENY, 'Deny') + ] diff --git a/netbox_routing/constants/__init__.py b/netbox_routing/constants/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/netbox_routing/constants/bgp.py b/netbox_routing/constants/bgp.py new file mode 100644 index 0000000..b95719f --- /dev/null +++ b/netbox_routing/constants/bgp.py @@ -0,0 +1,26 @@ +from django.db.models import Q + +BGPSETTING_ASSIGNMENT_MODELS = Q( + Q(app_label='netbox_routing', model='bgprouter') | + Q(app_label='netbox_routing', model='bgpscope') +) + +BGPAF_ASSIGNMENT_MODELS = Q( + Q(app_label='netbox_routing', model='bgprouter') | + Q(app_label='netbox_routing', model='bgpscope') | + Q(app_label='netbox_routing', model='bgpneighbor') | + Q(app_label='ipam', model='VRF') +) + +BGPPEER_ASSIGNMENT_MODELS = Q( + Q(app_label='netbox_routing', model='bgprouter') | + Q(app_label='netbox_routing', model='bgpscope') | + Q(app_label='netbox_routing', model='bgpaddressfamily') +) + +BGPPEERAF_ASSIGNMENT_MODELS = Q( + Q(app_label='netbox_routing', model='bgppeer') | + Q(app_label='netbox_routing', model='bgppeergroup') | + Q(app_label='netbox_routing', model='bgptemplatepeer') | + Q(app_label='netbox_routing', model='bgptemplatepeerpolicy') +) \ No newline at end of file diff --git a/netbox_routing/fields/__init__.py b/netbox_routing/fields/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/netbox_routing/fields/ip.py b/netbox_routing/fields/ip.py new file mode 100644 index 0000000..bd3bd05 --- /dev/null +++ b/netbox_routing/fields/ip.py @@ -0,0 +1,40 @@ +from django.core.exceptions import ValidationError +from django.db import models +from netaddr import AddrFormatError, IPAddress + +from ipam.formfields import IPAddressFormField + + +class IPAddressField(models.Field): + + def python_type(self): + return IPAddress + + def from_db_value(self, value, expression, connection): + return self.to_python(value) + + def to_python(self, value): + if not value: + return value + try: + # Always return a netaddr.IPNetwork object. (netaddr.IPAddress does not provide a mask.) + return IPAddress(value) + except AddrFormatError: + raise ValidationError("Invalid IP address format: {}".format(value)) + except (TypeError, ValueError) as e: + raise ValidationError(e) + + def get_prep_value(self, value): + if not value: + return None + if isinstance(value, list): + return [str(self.to_python(v)) for v in value] + return str(self.to_python(value)) + + def form_class(self): + return IPAddressFormField + + def formfield(self, **kwargs): + defaults = {'form_class': self.form_class()} + defaults.update(kwargs) + return super().formfield(**defaults) \ No newline at end of file diff --git a/netbox_routing/filtersets/__init__.py b/netbox_routing/filtersets/__init__.py new file mode 100644 index 0000000..685e376 --- /dev/null +++ b/netbox_routing/filtersets/__init__.py @@ -0,0 +1,11 @@ +from .static import StaticRouteFilterSet +from .objects import PrefixListFilterSet, PrefixListEntryFilterSet, RouteMapFilterSet, RouteMapEntryFilterSet + +__all__ = ( + 'StaticRouteFilterSet', + + 'PrefixListFilterSet', + 'PrefixListEntryFilterSet', + 'RouteMapFilterSet', + 'RouteMapEntryFilterSet' +) diff --git a/netbox_routing/filtersets/objects.py b/netbox_routing/filtersets/objects.py new file mode 100644 index 0000000..46683ee --- /dev/null +++ b/netbox_routing/filtersets/objects.py @@ -0,0 +1,46 @@ +import django_filters +import netaddr + +from netbox.filtersets import NetBoxModelFilterSet +from netbox_routing.models import PrefixList, PrefixListEntry, RouteMapEntry, RouteMap + + +class PrefixListFilterSet(NetBoxModelFilterSet): + class Meta: + model = PrefixList + fields = () + + +class PrefixListEntryFilterSet(NetBoxModelFilterSet): + + prefix = django_filters.CharFilter( + method='filter_prefix', + label='Prefix', + ) + + class Meta: + model = PrefixListEntry + fields = ('prefix_list', 'prefix', 'sequence', 'type', 'le', 'ge') + + def filter_prefix(self, queryset, name, value): + if not value.strip(): + return queryset + try: + query = str(netaddr.IPNetwork(value).cidr) + return queryset.filter(prefix=query) + except (netaddr.AddrFormatError, ValueError): + return queryset.none() + + +class RouteMapFilterSet(NetBoxModelFilterSet): + + class Meta: + model = RouteMap + fields = () + + +class RouteMapEntryFilterSet(NetBoxModelFilterSet): + + class Meta: + model = RouteMapEntry + fields = ('route_map', 'sequence', 'type') diff --git a/netbox_routing/filtersets/static.py b/netbox_routing/filtersets/static.py new file mode 100644 index 0000000..01e5238 --- /dev/null +++ b/netbox_routing/filtersets/static.py @@ -0,0 +1,40 @@ +import django_filters +import netaddr + +from netbox.filtersets import NetBoxModelFilterSet +from netbox_routing.models import StaticRoute + + +class StaticRouteFilterSet(NetBoxModelFilterSet): + + prefix = django_filters.CharFilter( + method='filter_prefix', + label='Prefix', + ) + + next_hop = django_filters.CharFilter( + method='filter_address', + label='Prefix', + ) + + class Meta: + model = StaticRoute + fields = ('vrf', 'prefix', 'devices', 'metric', 'next_hop') + + def filter_prefix(self, queryset, name, value): + if not value.strip(): + return queryset + try: + query = str(netaddr.IPNetwork(value).cidr) + return queryset.filter(prefix=query) + except (netaddr.AddrFormatError, ValueError): + return queryset.none() + + def filter_address(self, queryset, name, value): + if not value.strip(): + return queryset + try: + query = str(netaddr.IPAddress(value)) + return queryset.filter(prefix=query) + except (netaddr.AddrFormatError, ValueError): + return queryset.none() diff --git a/netbox_routing/forms/__init__.py b/netbox_routing/forms/__init__.py new file mode 100644 index 0000000..cedec14 --- /dev/null +++ b/netbox_routing/forms/__init__.py @@ -0,0 +1,19 @@ +from .filtersets import * +from .objects import PrefixListForm, PrefixListEntryForm, RouteMapForm, RouteMapEntryForm +from .static import StaticRouteForm + +__all__ = ( + # Static Routes + 'StaticRouteForm', + 'StaticRouteFilterSetForm', + + # Objects + 'PrefixListForm', + 'PrefixListEntryForm', + 'RouteMapForm', + 'RouteMapEntryForm', + 'PrefixListFilterSetForm', + 'PrefixListEntryFilterSetForm', + 'RouteMapFilterSetForm', + 'RouteMapEntryFilterSetForm' +) diff --git a/netbox_routing/forms/fields.py b/netbox_routing/forms/fields.py new file mode 100644 index 0000000..e69de29 diff --git a/netbox_routing/forms/filtersets/__init__.py b/netbox_routing/forms/filtersets/__init__.py new file mode 100644 index 0000000..bdacec6 --- /dev/null +++ b/netbox_routing/forms/filtersets/__init__.py @@ -0,0 +1,11 @@ +from .static import StaticRouteFilterSetForm +from .objects import PrefixListFilterSetForm, PrefixListEntryFilterSetForm, RouteMapFilterSetForm,\ + RouteMapEntryFilterSetForm + +__all__ = ( + 'StaticRouteFilterSetForm', + 'PrefixListFilterSetForm', + 'PrefixListEntryFilterSetForm', + 'RouteMapFilterSetForm', + 'RouteMapEntryFilterSetForm' +) diff --git a/netbox_routing/forms/filtersets/objects.py b/netbox_routing/forms/filtersets/objects.py new file mode 100644 index 0000000..b2eee13 --- /dev/null +++ b/netbox_routing/forms/filtersets/objects.py @@ -0,0 +1,18 @@ +from netbox.forms import NetBoxModelFilterSetForm +from netbox_routing.models import PrefixList, PrefixListEntry, RouteMap, RouteMapEntry + + +class PrefixListFilterSetForm(NetBoxModelFilterSetForm): + model = PrefixList + + +class PrefixListEntryFilterSetForm(NetBoxModelFilterSetForm): + model = PrefixListEntry + + +class RouteMapFilterSetForm(NetBoxModelFilterSetForm): + model = RouteMap + + +class RouteMapEntryFilterSetForm(NetBoxModelFilterSetForm): + model = RouteMapEntry \ No newline at end of file diff --git a/netbox_routing/forms/filtersets/static.py b/netbox_routing/forms/filtersets/static.py new file mode 100644 index 0000000..cc3d34b --- /dev/null +++ b/netbox_routing/forms/filtersets/static.py @@ -0,0 +1,6 @@ +from netbox.forms import NetBoxModelFilterSetForm +from netbox_routing.models import StaticRoute + + +class StaticRouteFilterSetForm(NetBoxModelFilterSetForm): + model = StaticRoute \ No newline at end of file diff --git a/netbox_routing/forms/objects.py b/netbox_routing/forms/objects.py new file mode 100644 index 0000000..be0aaed --- /dev/null +++ b/netbox_routing/forms/objects.py @@ -0,0 +1,30 @@ +from netbox.forms import NetBoxModelForm +from netbox_routing.models import PrefixList, PrefixListEntry, RouteMap, RouteMapEntry + + +class PrefixListForm(NetBoxModelForm): + + class Meta: + model = PrefixList + fields = ('name',) + + +class PrefixListEntryForm(NetBoxModelForm): + + class Meta: + model = PrefixListEntry + fields = ('prefix_list', 'sequence', 'type', 'prefix', 'le', 'ge') + + +class RouteMapForm(NetBoxModelForm): + + class Meta: + model = RouteMap + fields = ('name',) + + +class RouteMapEntryForm(NetBoxModelForm): + + class Meta: + model = RouteMapEntry + fields = ('route_map', 'sequence', 'type') diff --git a/netbox_routing/forms/static.py b/netbox_routing/forms/static.py new file mode 100644 index 0000000..9ab5269 --- /dev/null +++ b/netbox_routing/forms/static.py @@ -0,0 +1,31 @@ +from dcim.models import Device +from ipam.models import VRF +from netbox.forms import NetBoxModelForm +from netbox_routing.models import StaticRoute +from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField + + +class StaticRouteForm(NetBoxModelForm): + devices = DynamicModelMultipleChoiceField( + queryset=Device.objects.all() + ) + vrf = DynamicModelChoiceField( + queryset=VRF.objects.all(), + required=False, + label='VRF' + ) + + class Meta: + model = StaticRoute + fields = ('devices', 'vrf', 'prefix', 'next_hop', 'name', 'metric', 'permanent') + + def __init__(self, data=None, instance=None, *args, **kwargs): + super().__init__(data=data, instance=instance, *args, **kwargs) + + if self.instance and self.instance.pk is not None: + self.fields['devices'].initial = self.instance.devices.all().values_list('id', flat=True) + + def save(self, *args, **kwargs): + instance = super().save(*args, **kwargs) + instance.devices.set(self.cleaned_data['devices']) + return instance diff --git a/netbox_routing/helpers/__init__.py b/netbox_routing/helpers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/netbox_routing/models/__init__.py b/netbox_routing/models/__init__.py index e69de29..2df28c2 100644 --- a/netbox_routing/models/__init__.py +++ b/netbox_routing/models/__init__.py @@ -0,0 +1,10 @@ +from .static import StaticRoute +from .objects import PrefixList, PrefixListEntry, RouteMap, RouteMapEntry + +__all__ = ( + 'StaticRoute', + 'PrefixList', + 'PrefixListEntry', + 'RouteMap', + 'RouteMapEntry' +) diff --git a/netbox_routing/models/bgp.py b/netbox_routing/models/bgp.py new file mode 100644 index 0000000..d944efd --- /dev/null +++ b/netbox_routing/models/bgp.py @@ -0,0 +1,307 @@ +from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.models import ContentType +from django.db import models + +from netbox.models import NetBoxModel, NestedGroupModel +from netbox_routing import choices +from netbox_routing.constants.bgp import BGPSETTING_ASSIGNMENT_MODELS, BGPAF_ASSIGNMENT_MODELS, \ + BGPPEER_ASSIGNMENT_MODELS, BGPPEERAF_ASSIGNMENT_MODELS +from netbox_routing.fields.ip import IPAddressField + + +class BGPSettings(NetBoxModel): + assigned_object_type = models.ForeignKey( + to=ContentType, + limit_choices_to=BGPSETTING_ASSIGNMENT_MODELS, + on_delete=models.PROTECT, + related_name='+', + blank=True, + null=True + ) + assigned_object_id = models.PositiveBigIntegerField( + blank=True, + null=True + ) + assigned_object = GenericForeignKey( + ct_field='assigned_object_type', + fk_field='assigned_object_id' + ) + router_id = IPAddressField() + auto_summary = models.BooleanField() + bgp_additional_paths_install = models.BooleanField() + bgp_additional_paths_receive = models.BooleanField() + bgp_additional_paths_send = models.BooleanField() + bgp_asnotation_dot = models.BooleanField() + bgp_graceful_restart = models.BooleanField() + + default_information_originate = models.BooleanField(verbose_name='Default Information Originate') + default_metric = models.PositiveBigIntegerField(verbose_name='Default Metric') + distance_ebgp = models.PositiveSmallIntegerField(verbose_name='eBGP Distance') + distance_ibgp = models.PositiveSmallIntegerField(verbose_name='iBGP Distance') + distance_embgp = models.PositiveSmallIntegerField(verbose_name='eBGP Distance (MultiProtocol)') + distance_imbgp = models.PositiveSmallIntegerField(verbose_name='iBGP Distance (MultiProtocol)') + paths_maximum = models.PositiveSmallIntegerField(verbose_name='Maximum Paths') + paths_maximum_secondary = models.PositiveSmallIntegerField(verbose_name='Maximum Secondary Paths') + timers_keepalive = models.PositiveSmallIntegerField(verbose_name='Keepalive Timer') + timers_hold = models.PositiveSmallIntegerField(verbose_name='Hold Timer') + + +class BGPRouter(NetBoxModel): + asn = models.ForeignKey( + to='ipam.ASN', + on_delete=models.PROTECT, + related_name='router', + verbose_name='ASN' + ) + + +class BGPScope(NetBoxModel): + router = models.ForeignKey( + to=BGPRouter, + on_delete=models.PROTECT, + related_name='scopes', + blank=False, + null=False + ) + vrf = models.ForeignKey( + to='ipam.VRF', + on_delete=models.PROTECT, + related_name='scopes', + blank=False, + null=False + ) + + +class BGPAddressFamily(NetBoxModel): + assigned_object_type = models.ForeignKey( + to=ContentType, + limit_choices_to=BGPAF_ASSIGNMENT_MODELS, + on_delete=models.PROTECT, + related_name='+', + blank=True, + null=True + ) + assigned_object_id = models.PositiveBigIntegerField( + blank=True, + null=True + ) + assigned_object = GenericForeignKey( + ct_field='assigned_object_type', + fk_field='assigned_object_id' + ) + + +class BGPTemplateSession(NetBoxModel): + name = models.CharField( + verbose_name='Name', + max_length=255 + ) + router = models.ForeignKey( + to=BGPRouter, + on_delete=models.PROTECT, + related_name='session_templates', + blank=False, + null=False + ) + enabled = models.BooleanField( + blank=True, + null=True + ) + prefixlist_out = models.ForeignKey( + to='netbox_routing.PrefixList', + on_delete=models.PROTECT, + related_name='template_afs_out', + blank=True, + null=True + ) + prefixlist_in = models.ForeignKey( + to='netbox_routing.PrefixList', + on_delete=models.PROTECT, + related_name='template_afs_in', + blank=True, + null=True + ) + routemap_out = models.ForeignKey( + to='netbox_routing.RouteMap', + on_delete=models.PROTECT, + related_name='template_afs_out', + blank=True, + null=True + ) + routemap_in = models.ForeignKey( + to='netbox_routing.RouteMap', + on_delete=models.PROTECT, + related_name='template_afs_in', + blank=True, + null=True + ) + + +class BGPTemplatePolicy(NetBoxModel): + name = models.CharField( + verbose_name='Name', + max_length=255 + ) + router = models.ForeignKey( + to=BGPRouter, + on_delete=models.PROTECT, + related_name='session_templates', + blank=False, + null=False + ) + enabled = models.BooleanField( + blank=True, + null=True + ) + + +class BGPTemplatePeer(NetBoxModel): + name = models.CharField( + verbose_name='Name', + max_length=255 + ) + remote_as = models.ForeignKey( + to='ipam.ASN', + on_delete=models.PROTECT, + related_name='+', + blank=True, + null=True + ) + enabled = models.BooleanField( + blank=True, + null=True + ) + + +class BGPPeerGroup(NetBoxModel): + name = models.CharField( + verbose_name='Name', + max_length=255 + ) + remote_as = models.ForeignKey( + to='ipam.ASN', + on_delete=models.PROTECT, + related_name='+', + blank=True, + null=True + ) + enabled = models.BooleanField( + blank=True, + null=True + ) + + +class BGPPeer(NetBoxModel): + assigned_object_type = models.ForeignKey( + to=ContentType, + limit_choices_to=BGPPEER_ASSIGNMENT_MODELS, + on_delete=models.PROTECT, + related_name='+', + blank=True, + null=True + ) + assigned_object_id = models.PositiveBigIntegerField( + blank=True, + null=True + ) + assigned_object = GenericForeignKey( + ct_field='assigned_object_type', + fk_field='assigned_object_id' + ) + peer = IPAddressField() + peer_group = models.ForeignKey( + to=BGPPeerGroup, + on_delete=models.PROTECT, + related_name='peers', + blank=True, + null=True + ) + peer_template = models.ForeignKey( + to=BGPTemplatePeer, + on_delete=models.PROTECT, + related_name='peers', + blank=True, + null=True + ) + peer_policy = models.ForeignKey( + to=BGPTemplatePolicy, + on_delete=models.PROTECT, + related_name='peers', + blank=True, + null=True + ) + remote_as = models.ForeignKey( + to='ipam.ASN', + on_delete=models.PROTECT, + related_name='+', + blank=True, + null=True + ) + enabled = models.BooleanField( + blank=True, + null=True + ) + + +class BGPPeerAddressFamily(NetBoxModel): + assigned_object_type = models.ForeignKey( + to=ContentType, + limit_choices_to=BGPPEERAF_ASSIGNMENT_MODELS, + on_delete=models.PROTECT, + related_name='+', + blank=True, + null=True + ) + assigned_object_id = models.PositiveBigIntegerField( + blank=True, + null=True + ) + assigned_object = GenericForeignKey( + ct_field='assigned_object_type', + fk_field='assigned_object_id' + ) + + peer_session = models.ForeignKey( + to=BGPTemplateSession, + on_delete=models.PROTECT, + related_name='peer_afs', + blank=True, + null=True + ) + + address_family = models.CharField( + choices=choices.BGPAddressFamilies + ) + enabled = models.BooleanField( + blank=True, + null=True + ) + + prefixlist_out = models.ForeignKey( + to='netbox_routing.PrefixList', + on_delete=models.PROTECT, + related_name='peer_afs_out', + blank=True, + null=True + ) + prefixlist_in = models.ForeignKey( + to='netbox_routing.PrefixList', + on_delete=models.PROTECT, + related_name='peer_afs_in', + blank=True, + null=True + ) + routemap_out = models.ForeignKey( + to='netbox_routing.RouteMap', + on_delete=models.PROTECT, + related_name='peer_afs_out', + blank=True, + null=True + ) + routemap_in = models.ForeignKey( + to='netbox_routing.RouteMap', + on_delete=models.PROTECT, + related_name='peer_afs_in', + blank=True, + null=True + ) diff --git a/netbox_routing/models/objects.py b/netbox_routing/models/objects.py new file mode 100644 index 0000000..ecea428 --- /dev/null +++ b/netbox_routing/models/objects.py @@ -0,0 +1,100 @@ +from django.urls import reverse + +from django.db import models +from django.db.models import F, Q, CheckConstraint +from django.core.exceptions import ValidationError + +from ipam.fields import IPNetworkField +from netbox.models import NetBoxModel +from netbox_routing.choices.objects import PermitDenyChoices + + +__all__ = ( + 'RouteMap', + 'RouteMapEntry', + 'PrefixList', + 'PrefixListEntry' +) + + +class RouteMap(NetBoxModel): + name = models.CharField( + max_length=255 + ) + + def get_absolute_url(self): + return reverse('plugins:netbox_routing:routemap', args=[self.pk]) + +class RouteMapEntry(NetBoxModel): + route_map = models.ForeignKey( + to="netbox_routing.RouteMap", + on_delete=models.PROTECT, + related_name='entries', + verbose_name='Route Map' + ) + type = models.CharField(max_length=6, choices=PermitDenyChoices) + sequence = models.PositiveSmallIntegerField() + + def get_absolute_url(self): + return reverse('plugins:netbox_routing:routemapentry', args=[self.pk]) + + +class PrefixList(NetBoxModel): + name = models.CharField( + max_length=255 + ) + + def get_absolute_url(self): + return reverse('plugins:netbox_routing:prefixlist', args=[self.pk]) + + +class PrefixListEntry(NetBoxModel): + prefix_list = models.ForeignKey( + to="netbox_routing.PrefixList", + on_delete=models.PROTECT, + related_name='entries', + verbose_name='Prefix List' + ) + sequence = models.PositiveSmallIntegerField() + type = models.CharField(max_length=6, choices=PermitDenyChoices) + prefix = IPNetworkField(help_text='IPv4 or IPv6 network with mask') + ge = models.PositiveSmallIntegerField() + le = models.PositiveSmallIntegerField() + + def get_absolute_url(self): + return reverse('plugins:netbox_routing:prefixlistentry', args=[self.pk]) + + def clean(self): + super().clean() + + if self.prefix.version == 6: + if self.le is not None and self.le > 128: + raise ValidationError({ + 'le': 'LE value cannot be longer then 128' + }) + if self.ge is not None and self.ge > 128: + raise ValidationError({ + 'ge': 'GE value cannot be longer then 128' + }) + elif self.prefix.version == 4: + if self.le is not None and self.le > 32: + raise ValidationError({ + 'le': 'LE value cannot be longer then 32' + }) + if self.ge is not None and self.ge > 32: + raise ValidationError({ + 'ge': 'GE value cannot be longer then 32' + }) + + if self.ge and self.le and self.ge < self.le: + raise ValidationError({ + 'ge': 'GE cannot be more then LE', + 'le': 'LE cannot be less then GE' + }) + + if self.ge is not None and self.prefix.prefix.prefixlen >= self.ge: + raise ValidationError('Prefix\'s length cannot be longer then greater or equals value') + + if self.le is not None and self.prefix.prefix.prefixlen >= self.le: + raise ValidationError('Prefix\'s length cannot be longer then greater or equals value') + diff --git a/netbox_routing/models/static.py b/netbox_routing/models/static.py new file mode 100644 index 0000000..31daf87 --- /dev/null +++ b/netbox_routing/models/static.py @@ -0,0 +1,53 @@ +from django.db import models +from django.db.models import CheckConstraint, Q +from django.urls import reverse + +from ipam.fields import IPNetworkField +from netbox.models import NetBoxModel +from netbox_routing.fields.ip import IPAddressField + + +__all__ = ( + 'StaticRoute' +) + + +class StaticRoute(NetBoxModel): + devices = models.ManyToManyField( + to='dcim.Device', + related_name='static_routes' + ) + vrf = models.ForeignKey( + to='ipam.VRF', + on_delete=models.PROTECT, + related_name='staticroutes', + blank=True, + null=True, + verbose_name='VRF' + ) + prefix = IPNetworkField(help_text='IPv4 or IPv6 network with mask') + next_hop = IPAddressField() + name = models.CharField( + max_length=50, + verbose_name='Name', + blank=True, + null=True, + help_text='Optional name for this static route' + ) + metric = models.PositiveSmallIntegerField( + verbose_name='Metric' + ) + permanent = models.BooleanField() + + class Meta: + constraints = [ + CheckConstraint(check=Q(Q(metric__lte=255) & Q(metric__gte=0)), name='metric_gte_lte') + ] + + def __str__(self): + if self.vrf is None: + return f'{self.prefix} NH {self.next_hop}' + return f'{self.prefix} VRF {self.vrf} NH {self.next_hop}' + + def get_absolute_url(self): + return reverse('plugins:netbox_routing:staticroute', args=[self.pk]) diff --git a/netbox_routing/navigation.py b/netbox_routing/navigation.py new file mode 100644 index 0000000..3274ec1 --- /dev/null +++ b/netbox_routing/navigation.py @@ -0,0 +1,29 @@ +from extras.plugins import PluginMenuButton, PluginMenuItem +from utilities.choices import ButtonColorChoices + +menu_items = ( + PluginMenuItem( + link='plugins:netbox_routing:staticroute_list', + link_text='Static Route', + buttons=( + PluginMenuButton('plugins:netbox_routing:staticroute_add', 'Add', 'mdi mdi-plus', ButtonColorChoices.GREEN), + PluginMenuButton('plugins:netbox_routing:staticroute_import', 'Import', 'mdi mdi-upload', ButtonColorChoices.CYAN), + ) + ), + PluginMenuItem( + link='plugins:netbox_routing:prefixlist_list', + link_text='Prefix Lists', + buttons=( + PluginMenuButton('plugins:netbox_routing:prefixlist_add', 'Add', 'mdi mdi-plus', ButtonColorChoices.GREEN), + PluginMenuButton('plugins:netbox_routing:prefixlist_import', 'Import', 'mdi mdi-upload', ButtonColorChoices.CYAN), + ) + ), + PluginMenuItem( + link='plugins:netbox_routing:routemap_list', + link_text='Route Maps', + buttons=( + PluginMenuButton('plugins:netbox_routing:routemap_add', 'Add', 'mdi mdi-plus', ButtonColorChoices.GREEN), + PluginMenuButton('plugins:netbox_routing:routemap_import', 'Import', 'mdi mdi-upload', ButtonColorChoices.CYAN), + ) + ), +) \ No newline at end of file diff --git a/netbox_routing/tables/__init__.py b/netbox_routing/tables/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/netbox_routing/tables/objects.py b/netbox_routing/tables/objects.py new file mode 100644 index 0000000..2a2f019 --- /dev/null +++ b/netbox_routing/tables/objects.py @@ -0,0 +1,30 @@ +from netbox.tables import NetBoxTable +from netbox_routing.models import PrefixList, PrefixListEntry, RouteMap, RouteMapEntry + + +class PrefixListTable(NetBoxTable): + class Meta(NetBoxTable.Meta): + model = PrefixList + fields = ('pk', 'id', 'name') + default_columns = ('pk', 'id', 'name') + + +class PrefixListEntryTable(NetBoxTable): + class Meta(NetBoxTable.Meta): + model = PrefixListEntry + fields = ('pk', 'id', 'prefix_list', 'sequence', 'type', 'prefix', 'le', 'ge') + default_columns = ('pk', 'id', 'prefix_list', 'sequence', 'type', 'prefix', 'le', 'ge') + + +class RouteMapTable(NetBoxTable): + class Meta(NetBoxTable.Meta): + model = RouteMap + fields = ('pk', 'id', 'name') + default_columns = ('pk', 'id', 'name') + + +class RouteMapEntryTable(NetBoxTable): + class Meta(NetBoxTable.Meta): + model = RouteMapEntry + fields = ('pk', 'id', 'route_map', 'sequence', 'type') + default_columns = ('pk', 'id', 'route_map', 'sequence', 'type') diff --git a/netbox_routing/tables/static.py b/netbox_routing/tables/static.py new file mode 100644 index 0000000..fb7a439 --- /dev/null +++ b/netbox_routing/tables/static.py @@ -0,0 +1,9 @@ +from netbox.tables import NetBoxTable +from netbox_routing.models import StaticRoute + + +class StaticRouteTable(NetBoxTable): + class Meta(NetBoxTable.Meta): + model = StaticRoute + fields = ('pk', 'id', 'vrf', 'prefix', 'next_hop', 'name') + default_columns = ('pk', 'id', 'vrf', 'prefix', 'next_hop', 'name') diff --git a/netbox_routing/templates/netbox_routing/staticroute.html b/netbox_routing/templates/netbox_routing/staticroute.html new file mode 100644 index 0000000..45ed6ba --- /dev/null +++ b/netbox_routing/templates/netbox_routing/staticroute.html @@ -0,0 +1,70 @@ +{% extends 'generic/object.html' %} +{% load humanize %} +{% load helpers %} +{% load plugins %} + +{% block content %} +
+
+
+
Static Route
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
VRF + {% if object.vrf %} + {{ object.vrf }} + {% else %} + Global + {% endif %} +
Prefix + {{ object.prefix }} +
Next Hop + {{ object.next_hop }} +
Name + {{ object.name|placeholder }} +
Metric + {{ object.metric }} +
Permanent + {{ object.permanent }} +
+
+
+ {% plugin_left_page object %} +
+
+ {% include 'inc/panels/custom_fields.html' %} + {% include 'inc/panels/tags.html' %} + {% plugin_right_page object %} +
+
+
+
+ {% include 'inc/panel_table.html' with table=devices heading='Devices' %} + {% plugin_full_width_page object %} +
+
+{% endblock %} \ No newline at end of file diff --git a/netbox_routing/urls.py b/netbox_routing/urls.py new file mode 100644 index 0000000..af0bbb8 --- /dev/null +++ b/netbox_routing/urls.py @@ -0,0 +1,48 @@ +from django.urls import path + +from netbox.views.generic import ObjectChangeLogView + +from . import views +from .models import StaticRoute, PrefixList, PrefixListEntry, RouteMap, RouteMapEntry + +urlpatterns = [ + path('routes/static/', views.StaticRouteListView.as_view(), name='staticroute_list'), + path('routes/static/add/', views.StaticRouteEditView.as_view(), name='staticroute_add'), + path('routes/static/import/', views.StaticRouteListView.as_view(), name='staticroute_import'), + path('routes/static//', views.StaticRouteView.as_view(), name='staticroute'), + path('routes/static//edit/', views.StaticRouteEditView.as_view(), name='staticroute_edit'), + path('routes/static//delete/', views.StaticRouteDeleteView.as_view(), name='staticroute_delete'), + path('routes/static//changelog/', ObjectChangeLogView.as_view(), name='staticroute_changelog', kwargs={'model': StaticRoute}), + + path('prefix-list/', views.PrefixListListView.as_view(), name='prefixlist_list'), + path('prefix-list/add/', views.PrefixListEditView.as_view(), name='prefixlist_add'), + path('prefix-list/import/', views.PrefixListListView.as_view(), name='prefixlist_import'), + path('prefix-list//', views.PrefixListView.as_view(), name='prefixlist'), + path('prefix-list//edit/', views.PrefixListView.as_view(), name='prefixlist_edit'), + path('prefix-list//delete/', views.PrefixListView.as_view(), name='prefixlist_delete'), + path('prefix-list//changelog/', views.PrefixListView.as_view(), name='prefixlist_changelog', kwargs={'model': PrefixList}), + + path('prefix-list-entry/', views.PrefixListEntryListView.as_view(), name='prefixlistentry_list'), + path('prefix-list-entry/add/', views.PrefixListEntryEditView.as_view(), name='prefixlistentry_add'), + path('prefix-list-entry/import/', views.PrefixListEntryListView.as_view(), name='prefixlistentry_import'), + path('prefix-list-entry//', views.PrefixListEntryView.as_view(), name='prefixlistentry'), + path('prefix-list-entry//edit/', views.PrefixListEntryEditView.as_view(), name='prefixlistentry_edit'), + path('prefix-list-entry//delete/', views.PrefixListEntryDeleteView.as_view(), name='prefixlistentry_delete'), + path('prefix-list-entry//changelog/', ObjectChangeLogView.as_view(), name='prefixlistentry_changelog', kwargs={'model': PrefixListEntry}), + + path('route-map/', views.RouteMapListView.as_view(), name='routemap_list'), + path('route-map/add/', views.RouteMapEditView.as_view(), name='routemap_add'), + path('route-map/import/', views.RouteMapListView.as_view(), name='routemap_import'), + path('route-map//', views.RouteMapView.as_view(), name='routemap'), + path('route-map//edit/', views.RouteMapView.as_view(), name='routemap_edit'), + path('route-map//delete/', views.RouteMapView.as_view(), name='routemap_delete'), + path('route-map//changelog/', views.RouteMapView.as_view(), name='routemap_changelog', kwargs={'model': RouteMap}), + + path('route-map-entry/', views.RouteMapEntryListView.as_view(), name='routemapentry_list'), + path('route-map-entry/add/', views.RouteMapEntryEditView.as_view(), name='routemapentry_add'), + path('route-map-entry/import/', views.RouteMapEntryListView.as_view(), name='routemapentry_import'), + path('route-map-entry//', views.RouteMapEntryView.as_view(), name='routemapentry'), + path('route-map-entry//edit/', views.RouteMapEntryEditView.as_view(), name='routemapentry_edit'), + path('route-map-entry//delete/', views.RouteMapEntryDeleteView.as_view(), name='routemapentry_delete'), + path('route-map-entry//changelog/', ObjectChangeLogView.as_view(), name='routemapentry_changelog', kwargs={'model': RouteMapEntry}), +] diff --git a/netbox_routing/views/__init__.py b/netbox_routing/views/__init__.py new file mode 100644 index 0000000..98b372f --- /dev/null +++ b/netbox_routing/views/__init__.py @@ -0,0 +1,32 @@ +from .static import StaticRouteListView, StaticRouteEditView, StaticRouteView, StaticRouteDeleteView + +from .objects import PrefixListView, PrefixListEditView, PrefixListListView, PrefixListDeleteView, RouteMapListView, \ + RouteMapView, RouteMapEditView, RouteMapDeleteView, PrefixListEntryListView, PrefixListEntryEditView, \ + PrefixListEntryDeleteView, PrefixListEntryView, RouteMapEntryListView, RouteMapEntryView, RouteMapEntryEditView, \ + RouteMapEntryDeleteView + +__all__ = ( + 'StaticRouteListView', + 'StaticRouteView', + 'StaticRouteEditView', + 'StaticRouteDeleteView', + + 'PrefixListListView', + 'PrefixListView', + 'PrefixListEditView', + 'PrefixListDeleteView', + 'PrefixListEntryListView', + 'PrefixListEntryView', + 'PrefixListEntryEditView', + 'PrefixListEntryDeleteView', + + 'RouteMapListView', + 'RouteMapView', + 'RouteMapEditView', + 'RouteMapDeleteView', + 'RouteMapEntryListView', + 'RouteMapEntryView', + 'RouteMapEntryEditView', + 'RouteMapEntryDeleteView' + +) diff --git a/netbox_routing/views/objects.py b/netbox_routing/views/objects.py new file mode 100644 index 0000000..47d97a1 --- /dev/null +++ b/netbox_routing/views/objects.py @@ -0,0 +1,104 @@ + +from netbox.views.generic import ObjectListView, ObjectView, ObjectEditView, ObjectDeleteView +from netbox_routing.filtersets import PrefixListFilterSet, PrefixListEntryFilterSet, RouteMapEntryFilterSet, \ + RouteMapFilterSet +from netbox_routing.forms import PrefixListFilterSetForm, PrefixListForm, PrefixListEntryFilterSetForm, \ + PrefixListEntryForm, RouteMapEntryForm, RouteMapEntryFilterSetForm, RouteMapForm, RouteMapFilterSetForm +from netbox_routing.models import PrefixList, PrefixListEntry, RouteMapEntry, RouteMap +from netbox_routing.tables.objects import PrefixListTable, PrefixListEntryTable, RouteMapEntryTable, RouteMapTable + + +# +# Prefix List +# +class PrefixListListView(ObjectListView): + queryset = PrefixList.objects.all() + table = PrefixListTable + filterset = PrefixListFilterSet + filterset_form = PrefixListFilterSetForm + + +class PrefixListView(ObjectView): + queryset = PrefixList.objects.all() + template_name = 'netbox_routing/prefixlist.html' + + +class PrefixListEditView(ObjectEditView): + queryset = PrefixList.objects.all() + form = PrefixListForm + + +class PrefixListDeleteView(ObjectDeleteView): + pass + + +# +# Prefix List Entry +# +class PrefixListEntryListView(ObjectListView): + queryset = PrefixListEntry.objects.all() + table = PrefixListEntryTable + filterset = PrefixListEntryFilterSet + filterset_form = PrefixListEntryFilterSetForm + + +class PrefixListEntryView(ObjectView): + queryset = PrefixListEntry.objects.all() + template_name = 'netbox_routing/prefixlist.html' + + +class PrefixListEntryEditView(ObjectEditView): + queryset = PrefixListEntry.objects.all() + form = PrefixListEntryForm + + +class PrefixListEntryDeleteView(ObjectDeleteView): + pass + + +# +# Route Map +# +class RouteMapListView(ObjectListView): + queryset = RouteMap.objects.all() + table = RouteMapTable + filterset = RouteMapFilterSet + filterset_form = RouteMapFilterSetForm + + +class RouteMapView(ObjectView): + queryset = RouteMap.objects.all() + template_name = 'netbox_routing/routemap.html' + + +class RouteMapEditView(ObjectEditView): + queryset = RouteMap.objects.all() + form = RouteMapForm + + +class RouteMapDeleteView(ObjectDeleteView): + pass + + +# +# Prefix List Entry +# +class RouteMapEntryListView(ObjectListView): + queryset = RouteMapEntry.objects.all() + table = RouteMapEntryTable + filterset = RouteMapEntryFilterSet + filterset_form = RouteMapEntryFilterSetForm + + +class RouteMapEntryView(ObjectView): + queryset = RouteMapEntry.objects.all() + template_name = 'netbox_routing/prefixlist.html' + + +class RouteMapEntryEditView(ObjectEditView): + queryset = RouteMapEntry.objects.all() + form = RouteMapEntryForm + + +class RouteMapEntryDeleteView(ObjectDeleteView): + pass diff --git a/netbox_routing/views/static.py b/netbox_routing/views/static.py new file mode 100644 index 0000000..46df223 --- /dev/null +++ b/netbox_routing/views/static.py @@ -0,0 +1,52 @@ +from dcim.models import Device +from dcim.tables import DeviceTable +from netbox.views.generic import ObjectListView, ObjectEditView, ObjectView, ObjectDeleteView +from netbox_routing.filtersets.static import StaticRouteFilterSet +from netbox_routing.forms import StaticRouteForm +from netbox_routing.forms.filtersets.static import StaticRouteFilterSetForm +from netbox_routing.models import StaticRoute +from netbox_routing.tables.static import StaticRouteTable + + +__all__ = ( + 'StaticRouteListView', + 'StaticRouteView', + 'StaticRouteEditView', + 'StaticRouteDeleteView', +) + + +class StaticRouteListView(ObjectListView): + queryset = StaticRoute.objects.all() + table = StaticRouteTable + filterset = StaticRouteFilterSet + filterset_form = StaticRouteFilterSetForm + + +class StaticRouteView(ObjectView): + queryset = StaticRoute.objects.all() + template_name = 'netbox_routing/staticroute.html' + + def get_extra_context(self, request, instance): + devices = instance.devices.all() + + devices_table = DeviceTable( + list(devices), + exclude=('tenant', 'device_role', 'serial', 'asset_tag', 'face', 'primary_ip', 'airflow', 'primary_ip4', + 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'contacts', 'tags', + 'created', 'last_updated',), + orderable=False + ) + + return { + 'devices': devices_table, + } + + +class StaticRouteEditView(ObjectEditView): + queryset = StaticRoute.objects.all() + form = StaticRouteForm + + +class StaticRouteDeleteView(ObjectDeleteView): + pass diff --git a/setup.py b/setup.py index 303f660..f64b29a 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='netbox-routing', - version='1.1.0', + version='0.0.1', description='NetBox Routing', long_description='Plugin for documentation of routing configuration and objects', url='https://github.com/dansheps/netbox-routing/',