diff --git a/netbox_routing/api/_serializers/eigrp.py b/netbox_routing/api/_serializers/eigrp.py new file mode 100644 index 0000000..ef23a82 --- /dev/null +++ b/netbox_routing/api/_serializers/eigrp.py @@ -0,0 +1,71 @@ +from rest_framework import serializers + +from dcim.api.serializers_.device_components import InterfaceSerializer +from dcim.api.serializers_.devices import DeviceSerializer +from ipam.api.serializers_.ip import PrefixSerializer +from netbox.api.serializers import NetBoxModelSerializer +from netbox_routing.models import ( + EIGRPRouter, EIGRPAddressFamily, EIGRPNetwork, EIGRPInterface +) + + +__all__ = ( + 'EIGRPRouterSerializer', + 'EIGRPAddressFamilySerializer', + 'EIGRPNetworkSerializer', + 'EIGRPInterfaceSerializer', +) + + +class EIGRPRouterSerializer(NetBoxModelSerializer): + url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:eigrprouter-detail') + device = DeviceSerializer(nested=True) + + class Meta: + model = EIGRPRouter + fields = ('url', 'id', 'display', 'name', 'pid', 'rid', 'device', 'description', 'comments', ) + brief_fields = ('url', 'id', 'display', 'name', 'pid', 'rid', 'device', ) + + +class EIGRPAddressFamilySerializer(NetBoxModelSerializer): + url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:eigrpaddressfamily-detail') + router = EIGRPRouterSerializer(nested=True) + + + class Meta: + model = EIGRPAddressFamily + fields = ( + 'url', 'id', 'display', 'router', 'family', 'description', 'comments', + ) + brief_fields = ('url', 'id', 'display', 'router', 'family',) + + +class EIGRPNetworkSerializer(NetBoxModelSerializer): + url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:eigrpnetwork-detail') + router = EIGRPRouterSerializer(nested=True) + address_family = EIGRPAddressFamilySerializer(nested=True) + network = PrefixSerializer(nested=True) + + class Meta: + model = EIGRPNetwork + fields = ( + 'url', 'id', 'display', 'router', 'address_family', 'network', 'description', 'comments', + ) + brief_fields = ('url', 'id', 'display', 'router', 'address_family', 'network',) + + +class EIGRPInterfaceSerializer(NetBoxModelSerializer): + url = serializers.HyperlinkedIdentityField(view_name='plugins-api:netbox_routing-api:eigrpinterface-detail') + router = EIGRPRouterSerializer(nested=True) + address_family = EIGRPAddressFamilySerializer(nested=True) + interface = InterfaceSerializer(nested=True) + + class Meta: + model = EIGRPInterface + fields = ( + 'url', 'id', 'display', 'router', 'address_family', 'interface', 'passive', 'bfd', + 'authentication', 'passphrase', 'description', 'comments', + ) + brief_fields = ( + 'url', 'id', 'display', 'router', 'address_family', 'interface', + ) diff --git a/netbox_routing/api/serializers.py b/netbox_routing/api/serializers.py index 03a2a88..579285f 100644 --- a/netbox_routing/api/serializers.py +++ b/netbox_routing/api/serializers.py @@ -6,6 +6,7 @@ BGPRouterSerializer, BGPScopeSerializer, BGPAddressFamilySerializer, BGPSettingSerializer ) from netbox_routing.api._serializers.ospf import * +from netbox_routing.api._serializers.eigrp import * __all__ = ( 'StaticRouteSerializer', @@ -14,6 +15,11 @@ 'OSPFAreaSerializer', 'OSPFInterfaceSerializer', + 'EIGRPRouterSerializer', + 'EIGRPAddressFamilySerializer', + 'EIGRPNetworkSerializer', + 'EIGRPInterfaceSerializer', + 'PrefixListSerializer', 'PrefixListEntrySerializer', 'RouteMapSerializer', diff --git a/netbox_routing/api/urls.py b/netbox_routing/api/urls.py index ed6f77c..00d6761 100644 --- a/netbox_routing/api/urls.py +++ b/netbox_routing/api/urls.py @@ -1,7 +1,8 @@ from netbox.api.routers import NetBoxRouter from .views import StaticRouteViewSet, PrefixListViewSet, RouteMapViewSet, PrefixListEntryViewSet, \ RouteMapEntryViewSet, OSPFInstanceViewSet, OSPFAreaViewSet, OSPFInterfaceViewSet, BGPRouterViewSet, \ - BGPScopeViewSet, BGPAddressFamilyViewSet, BGPSettingViewSet + BGPScopeViewSet, BGPAddressFamilyViewSet, BGPSettingViewSet, EIGRPRouterViewSet, EIGRPAddressFamilyViewSet, \ + EIGRPNetworkViewSet, EIGRPInterfaceViewSet router = NetBoxRouter() router.register('staticroute', StaticRouteViewSet) @@ -12,6 +13,10 @@ router.register('ospf-instance', OSPFInstanceViewSet) router.register('ospf-area', OSPFAreaViewSet) router.register('ospf-interface', OSPFInterfaceViewSet) +router.register('eigrp-router', EIGRPRouterViewSet) +router.register('eigrp-address-family', EIGRPAddressFamilyViewSet) +router.register('eigrp-network', EIGRPNetworkViewSet) +router.register('eigrp-interface', EIGRPInterfaceViewSet) router.register('prefix-list', PrefixListViewSet) router.register('prefix-list-entry', PrefixListEntryViewSet) router.register('route-map', RouteMapViewSet) diff --git a/netbox_routing/api/views/__init__.py b/netbox_routing/api/views/__init__.py index e8b9bae..6d4c5f7 100644 --- a/netbox_routing/api/views/__init__.py +++ b/netbox_routing/api/views/__init__.py @@ -2,6 +2,10 @@ from .ospf import OSPFInstanceViewSet, OSPFAreaViewSet, OSPFInterfaceViewSet from .bgp import BGPRouterViewSet, BGPScopeViewSet, BGPAddressFamilyViewSet, BGPSettingViewSet from .objects import PrefixListViewSet, PrefixListEntryViewSet, RouteMapViewSet, RouteMapEntryViewSet +from .eigrp import ( + EIGRPRouterViewSet, EIGRPAddressFamilyViewSet, + EIGRPNetworkViewSet, EIGRPInterfaceViewSet +) __all__ = ( 'StaticRouteViewSet', @@ -11,6 +15,11 @@ 'BGPAddressFamilyViewSet', 'BGPSettingViewSet', + 'EIGRPRouterViewSet', + 'EIGRPAddressFamilyViewSet', + 'EIGRPNetworkViewSet', + 'EIGRPInterfaceViewSet', + 'OSPFInstanceViewSet', 'OSPFAreaViewSet', 'OSPFInterfaceViewSet', diff --git a/netbox_routing/api/views/eigrp.py b/netbox_routing/api/views/eigrp.py new file mode 100644 index 0000000..b19622f --- /dev/null +++ b/netbox_routing/api/views/eigrp.py @@ -0,0 +1,41 @@ +from netbox.api.viewsets import NetBoxModelViewSet +from netbox_routing import filtersets +from netbox_routing.api.serializers import ( + EIGRPRouterSerializer, EIGRPRouterSerializer, EIGRPAddressFamilySerializer, + EIGRPInterfaceSerializer, EIGRPNetworkSerializer +) +from netbox_routing.models import ( + EIGRPRouter, EIGRPAddressFamily, EIGRPNetwork, EIGRPInterface +) + + +__all__ = ( + 'EIGRPRouterViewSet', + 'EIGRPAddressFamilyViewSet', + 'EIGRPNetworkViewSet', + 'EIGRPInterfaceViewSet', +) + + +class EIGRPRouterViewSet(NetBoxModelViewSet): + queryset = EIGRPRouter.objects.all() + serializer_class = EIGRPRouterSerializer + filterset_class = filtersets.EIGRPRouterFilterSet + + +class EIGRPAddressFamilyViewSet(NetBoxModelViewSet): + queryset = EIGRPAddressFamily.objects.all() + serializer_class = EIGRPAddressFamilySerializer + filterset_class = filtersets.EIGRPAddressFamilyFilterSet + + +class EIGRPNetworkViewSet(NetBoxModelViewSet): + queryset = EIGRPNetwork.objects.all() + serializer_class = EIGRPNetworkSerializer + filterset_class = filtersets.EIGRPNetworkFilterSet + + +class EIGRPInterfaceViewSet(NetBoxModelViewSet): + queryset = EIGRPInterface.objects.all() + serializer_class = EIGRPInterfaceSerializer + filterset_class = filtersets.EIGRPInterfaceFilterSet diff --git a/netbox_routing/choices/eigrp.py b/netbox_routing/choices/eigrp.py new file mode 100644 index 0000000..795aec3 --- /dev/null +++ b/netbox_routing/choices/eigrp.py @@ -0,0 +1,11 @@ +from utilities.choices import ChoiceSet + + +class EIGRPRouterChoices(ChoiceSet): + CLASSIC = 'classic' + NAMED = 'named' + + CHOICES = [ + (CLASSIC, 'Classic Router'), + (NAMED, 'Named Router') + ] \ No newline at end of file diff --git a/netbox_routing/constants/eigrp.py b/netbox_routing/constants/eigrp.py new file mode 100644 index 0000000..cee05c5 --- /dev/null +++ b/netbox_routing/constants/eigrp.py @@ -0,0 +1,11 @@ +from django.db.models import Q + +EIGRP_ROUTER_MODELS = Q( + app_label='netbox_routing', + model__in=('eigrpnamedrouter', 'eigrpclassicrouter', ) +) + +EIGRP_ASSIGNABLE_MODELS = Q( + app_label='netbox_routing', + model__in=('eigrpnamedrouter', 'eigrpclassicrouter', 'eigrpaddressfamily', ) +) \ No newline at end of file diff --git a/netbox_routing/fields/ip.py b/netbox_routing/fields/ip.py index ac15df5..326f699 100644 --- a/netbox_routing/fields/ip.py +++ b/netbox_routing/fields/ip.py @@ -2,6 +2,7 @@ from django.db import models from netaddr import AddrFormatError, IPAddress +from ipam import lookups from ipam.formfields import IPAddressFormField @@ -14,7 +15,9 @@ def from_db_value(self, value, expression, connection): return self.to_python(value) def to_python(self, value): - if not value: + if value is None: + return None + elif not value: return value try: # Always return a netaddr.IPNetwork object. (netaddr.IPAddress does not provide a mask.) @@ -25,6 +28,8 @@ def to_python(self, value): raise ValidationError(e) def get_prep_value(self, value): + if value is None: + return None if isinstance(value, list): return [str(self.to_python(v)) for v in value] return str(self.to_python(value)) @@ -35,4 +40,16 @@ def form_class(self): def formfield(self, **kwargs): defaults = {'form_class': self.form_class()} defaults.update(kwargs) - return super().formfield(**defaults) \ No newline at end of file + return super().formfield(**defaults) + + +IPAddressField.register_lookup(lookups.IExact) +IPAddressField.register_lookup(lookups.EndsWith) +IPAddressField.register_lookup(lookups.IEndsWith) +IPAddressField.register_lookup(lookups.StartsWith) +IPAddressField.register_lookup(lookups.IStartsWith) +IPAddressField.register_lookup(lookups.Regex) +IPAddressField.register_lookup(lookups.IRegex) +IPAddressField.register_lookup(lookups.NetContained) +IPAddressField.register_lookup(lookups.NetContainedOrEqual) +IPAddressField.register_lookup(lookups.NetFamily) \ No newline at end of file diff --git a/netbox_routing/filtersets/__init__.py b/netbox_routing/filtersets/__init__.py index 8e81c05..819cdf6 100644 --- a/netbox_routing/filtersets/__init__.py +++ b/netbox_routing/filtersets/__init__.py @@ -2,6 +2,7 @@ from .objects import PrefixListFilterSet, PrefixListEntryFilterSet, RouteMapFilterSet, RouteMapEntryFilterSet from .ospf import * from .bgp import * +from .eigrp import * __all__ = ( @@ -14,6 +15,11 @@ 'OSPFAreaFilterSet', 'OSPFInterfaceFilterSet', + 'EIGRPRouterFilterSet', + 'EIGRPAddressFamilyFilterSet', + 'EIGRPNetworkFilterSet', + 'EIGRPInterfaceFilterSet', + 'PrefixListFilterSet', 'PrefixListEntryFilterSet', 'RouteMapFilterSet', diff --git a/netbox_routing/filtersets/eigrp.py b/netbox_routing/filtersets/eigrp.py new file mode 100644 index 0000000..4a2123e --- /dev/null +++ b/netbox_routing/filtersets/eigrp.py @@ -0,0 +1,234 @@ +import django_filters +import netaddr +from django import forms +from django.core.exceptions import ValidationError +from django.db.models import Q +from django.utils.translation import gettext as _ + +from dcim.models import Device, Interface +from ipam.models import Prefix +from utilities.filters import MultiValueCharFilter + +from netbox.filtersets import NetBoxModelFilterSet +from netbox_routing.models import EIGRPAddressFamily, EIGRPRouter, EIGRPNetwork, EIGRPInterface + +__all__ = ( + 'EIGRPRouterFilterSet', + 'EIGRPAddressFamilyFilterSet', + 'EIGRPNetworkFilterSet', + 'EIGRPInterfaceFilterSet' +) + + +class RouterMixin: + + def filter_rid(self, queryset, name, value): + try: + return queryset.filter(**{f'{name}__in': value}) + except ValidationError: + return queryset.none() + + +class EIGRPRouterFilterSet(RouterMixin, NetBoxModelFilterSet): + device_id = django_filters.ModelMultipleChoiceFilter( + field_name='device', + queryset=Device.objects.all(), + label='Device (ID)', + ) + device = django_filters.ModelMultipleChoiceFilter( + field_name='device__name', + queryset=Device.objects.all(), + to_field_name='name', + label='Device', + ) + rid = MultiValueCharFilter( + method='filter_rid', + label=_('Router ID'), + ) + + class Meta: + model = EIGRPRouter + fields = ('device_id', 'device', 'mode', 'rid') + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + qs_filter = Q() + qs_filter |= Q(name__icontains=value) + qs_filter |= Q(device__name__icontains=value) + qs_filter |= Q(router_id__icontains=value) + + return queryset.filter(qs_filter).distinct() + + +class EIGRPAddressFamilyFilterSet(RouterMixin, NetBoxModelFilterSet): + router_id = django_filters.ModelMultipleChoiceFilter( + field_name='router', + queryset=EIGRPRouter.objects.all(), + label='Router (ID)', + ) + router = django_filters.ModelMultipleChoiceFilter( + field_name='router__name', + queryset=EIGRPRouter.objects.all(), + to_field_name='name', + label='Router (Name)', + ) + device_id = django_filters.ModelMultipleChoiceFilter( + field_name='router__device', + queryset=Device.objects.all(), + label='Device (ID)', + ) + device = django_filters.ModelMultipleChoiceFilter( + field_name='router__device__name', + queryset=Device.objects.all(), + to_field_name='name', + label='Device', + ) + rid = MultiValueCharFilter( + method='filter_rid', + label=_('Router ID'), + ) + + class Meta: + model = EIGRPAddressFamily + fields = ('router_id', 'router', 'device_id', 'device', 'rid', 'family' ) + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + qs_filter = Q( + Q(rid__icontains=value) + ) + qs_filter |= Q(eigrprouternamed__name__icontains=value) + return queryset.filter(qs_filter).distinct() + + +class EIGRPNetworkFilterSet(NetBoxModelFilterSet): + router_id = django_filters.ModelMultipleChoiceFilter( + field_name='router', + queryset=EIGRPRouter.objects.all(), + label='Router (ID)', + ) + router = django_filters.ModelMultipleChoiceFilter( + field_name='router__name', + queryset=EIGRPRouter.objects.all(), + to_field_name='name', + label='Router (Name)', + ) + address_family_id = django_filters.ModelMultipleChoiceFilter( + field_name='address_family', + queryset=EIGRPAddressFamily.objects.all(), + label='Address Family (ID)', + ) + device_id = django_filters.ModelMultipleChoiceFilter( + field_name='router__device', + queryset=Device.objects.all(), + label='Device (ID)', + ) + device = django_filters.ModelMultipleChoiceFilter( + field_name='router__device__name', + queryset=Device.objects.all(), + to_field_name='name', + label='Device', + ) + network_id = django_filters.ModelMultipleChoiceFilter( + field_name='network', + queryset=Prefix.objects.all(), + label='Network (ID)', + ) + network = django_filters.CharFilter( + method='filter_prefix', + label=_('Network'), + ) + + class Meta: + model = EIGRPNetwork + fields = ( + 'router_id', 'router', 'address_family_id', 'address_family', 'device_id', 'device', 'network_id', + 'network', + ) + + 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 search(self, queryset, name, value): + if not value.strip(): + return queryset + qs_filter = Q( + Q(eigrprouternamed__name__icontains=value) | + Q(address_family__rid__icontains=value) + ) + qs_filter |= Q(network__contains=value.strip()) + try: + prefix = str(netaddr.IPNetwork(value.strip()).cidr) + qs_filter |= Q(network__net_contains_or_equals=prefix) + except (netaddr.AddrFormatError, ValueError): + pass + return queryset.filter(qs_filter).distinct() + + +class EIGRPInterfaceFilterSet(NetBoxModelFilterSet): + router_id = django_filters.ModelMultipleChoiceFilter( + field_name='router', + queryset=EIGRPRouter.objects.all(), + label='Router (ID)', + ) + router = django_filters.ModelMultipleChoiceFilter( + field_name='router__name', + queryset=EIGRPRouter.objects.all(), + to_field_name='name', + label='Router (Name)', + ) + address_family_id = django_filters.ModelMultipleChoiceFilter( + field_name='address_family', + queryset=EIGRPAddressFamily.objects.all(), + label='Address Family (ID)', + ) + device_id = django_filters.ModelMultipleChoiceFilter( + field_name='interface__device', + queryset=Device.objects.all(), + label='Device (ID)', + ) + device = django_filters.ModelMultipleChoiceFilter( + field_name='interface__device__name', + queryset=Device.objects.all(), + to_field_name='name', + label='Device', + ) + interface_id = django_filters.ModelMultipleChoiceFilter( + field_name='interface', + queryset=Interface.objects.all(), + label='Interface (ID)', + ) + interface = django_filters.ModelMultipleChoiceFilter( + field_name='interface__name', + queryset=Interface.objects.all(), + to_field_name='name', + label='Interface', + ) + + class Meta: + model = EIGRPInterface + fields = ( + 'router_id', 'router', 'address_family_id', 'address_family', 'device_id', 'device', 'interface_id', + 'interface', 'bfd', 'passive', 'authentication', 'passphrase' + ) + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + qs_filter = ( + Q(router__name__icontains=value) | + Q(router__device__name__icontains=value) | + Q(interface__name__icontains=value) | + Q(interface__label__icontains=value) | + Q(interface__device__name__icontains=value) + ) + return queryset.filter(qs_filter).distinct() + diff --git a/netbox_routing/forms/__init__.py b/netbox_routing/forms/__init__.py index 686bae5..8d8ced7 100644 --- a/netbox_routing/forms/__init__.py +++ b/netbox_routing/forms/__init__.py @@ -5,8 +5,10 @@ from .ospf import OSPFAreaForm, OSPFInstanceForm, OSPFInterfaceForm from .bgp import BGPRouterForm, BGPScopeForm, BGPAddressFamilyForm from .static import StaticRouteForm +from .eigrp import * __all__ = ( + # Static Routes 'StaticRouteForm', 'StaticRouteFilterForm', @@ -27,6 +29,28 @@ 'OSPFInterfaceBulkEditForm', 'OSPFInterfaceImportForm', + # EIGRP + 'EIGRPRouterForm', + 'EIGRPRouterBulkEditForm', + 'EIGRPRouterFilterForm', + 'EIGRPRouterImportForm', + + 'EIGRPAddressFamilyForm', + 'EIGRPAddressFamilyBulkEditForm', + 'EIGRPAddressFamilyFilterForm', + 'EIGRPAddressFamilyImportForm', + + 'EIGRPNetworkForm', + 'EIGRPNetworkBulkEditForm', + 'EIGRPNetworkFilterForm', + 'EIGRPNetworkImportForm', + + 'EIGRPInterfaceForm', + 'EIGRPInterfaceBulkEditForm', + 'EIGRPInterfaceFilterForm', + 'EIGRPInterfaceImportForm', + + # BGP 'BGPRouterForm', 'BGPScopeForm', 'BGPAddressFamilyForm', diff --git a/netbox_routing/forms/bulk_edit/__init__.py b/netbox_routing/forms/bulk_edit/__init__.py index 0cff3d5..fbd0dcc 100644 --- a/netbox_routing/forms/bulk_edit/__init__.py +++ b/netbox_routing/forms/bulk_edit/__init__.py @@ -1,6 +1,7 @@ from .static import * from .objects import * from .ospf import * +from .eigrp import * __all__ = ( @@ -12,6 +13,12 @@ 'OSPFInterfaceBulkEditForm', 'OSPFAreaBulkEditForm', + # EIGRP + 'EIGRPRouterBulkEditForm', + 'EIGRPAddressFamilyBulkEditForm', + 'EIGRPNetworkBulkEditForm', + 'EIGRPInterfaceBulkEditForm', + # Route Objects 'PrefixListEntryBulkEditForm', 'RouteMapEntryBulkEditForm' diff --git a/netbox_routing/forms/bulk_edit/eigrp.py b/netbox_routing/forms/bulk_edit/eigrp.py new file mode 100644 index 0000000..e59b2a7 --- /dev/null +++ b/netbox_routing/forms/bulk_edit/eigrp.py @@ -0,0 +1,132 @@ +from django import forms +from django.forms import CharField +from django.utils.translation import gettext as _ + +from dcim.models import Device +from ipam.models import VRF +from netbox.forms import NetBoxModelBulkEditForm +from netbox_routing.models import EIGRPRouter, EIGRPAddressFamily, EIGRPNetwork, EIGRPInterface +from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, add_blank_choice +from utilities.forms.fields import DynamicModelChoiceField, CommentField +from utilities.forms.rendering import FieldSet + +from netbox_routing import choices + +__all__ = ( + 'EIGRPRouterBulkEditForm', + 'EIGRPAddressFamilyBulkEditForm', + 'EIGRPNetworkBulkEditForm', + 'EIGRPInterfaceBulkEditForm', +) + + +class EIGRPRouterBulkEditForm(NetBoxModelBulkEditForm): + device = DynamicModelChoiceField( + queryset=Device.objects.all(), + label=_('Device'), + required=False, + selector=True + ) + + description = forms.CharField( + label=_('Description'), + max_length=200, + required=False + ) + comments = CommentField() + + model = EIGRPRouter + fieldsets = ( + FieldSet('device', 'mode', name='EIGRP'), + FieldSet('description', ), + ) + nullable_fields = () + + +class EIGRPAddressFamilyBulkEditForm(NetBoxModelBulkEditForm): + vrf = DynamicModelChoiceField( + queryset=VRF.objects.all(), + label=_('VRF'), + required=False, + selector=True + ) + family = CharField(max_length=4) + + description = forms.CharField( + label=_('Description'), + max_length=200, + required=False + ) + comments = CommentField() + + model = EIGRPAddressFamily + fieldsets = ( + FieldSet('description'), + ) + nullable_fields = () + + +class EIGRPNetworkBulkEditForm(NetBoxModelBulkEditForm): + router = DynamicModelChoiceField( + queryset=EIGRPRouter.objects.all(), + label=_('Router'), + required=False, + selector=True + ) + address_family = DynamicModelChoiceField( + queryset=EIGRPAddressFamily.objects.all(), + label=_('Router'), + required=False, + selector=True + ) + + description = forms.CharField( + label=_('Description'), + max_length=200, + required=False + ) + comments = CommentField() + + model = EIGRPNetwork + fieldsets = ( + FieldSet('description'), + ) + nullable_fields = () + + +class EIGRPInterfaceBulkEditForm(NetBoxModelBulkEditForm): + router = DynamicModelChoiceField( + queryset=EIGRPRouter.objects.all(), + label=_('EIGRP Router'), + required=False, + selector=True + ) + address_family = DynamicModelChoiceField( + queryset=EIGRPAddressFamily.objects.all(), + label=_('EIGRP Address Family'), + required=False, + selector=True + ) + passive = forms.ChoiceField(label=_('Passive'), choices=BOOLEAN_WITH_BLANK_CHOICES, required=False) + bfd = forms.ChoiceField(label=_('BFD'), choices=BOOLEAN_WITH_BLANK_CHOICES, required=False) + authentication = forms.ChoiceField( + label=_('Authentication'), + choices=add_blank_choice(choices.AuthenticationChoices), + required=False + ) + passphrase = forms.CharField(label=_('Passphrase'), required=False) + + description = forms.CharField( + label=_('Description'), + max_length=200, + required=False + ) + comments = CommentField() + + model = EIGRPInterface + fieldsets = ( + FieldSet('router', 'address_family', name='EIGRP'), + FieldSet('priority', 'bfd', 'authentication', 'passphrase', name='Attributes'), + FieldSet('description'), + ) + nullable_fields = () diff --git a/netbox_routing/forms/bulk_import/__init__.py b/netbox_routing/forms/bulk_import/__init__.py index fb69772..a9efb2f 100644 --- a/netbox_routing/forms/bulk_import/__init__.py +++ b/netbox_routing/forms/bulk_import/__init__.py @@ -1,4 +1,5 @@ from .ospf import * +from .eigrp import * __all__ = ( @@ -6,5 +7,11 @@ 'OSPFInstanceImportForm', 'OSPFAreaImportForm', 'OSPFInterfaceImportForm', + + # EIGRP + 'EIGRPRouterImportForm', + 'EIGRPAddressFamilyImportForm', + 'EIGRPNetworkImportForm', + 'EIGRPInterfaceImportForm', ) diff --git a/netbox_routing/forms/bulk_import/eigrp.py b/netbox_routing/forms/bulk_import/eigrp.py new file mode 100644 index 0000000..7e0d96b --- /dev/null +++ b/netbox_routing/forms/bulk_import/eigrp.py @@ -0,0 +1,82 @@ +from django.utils.translation import gettext as _ + +from dcim.models import Interface +from ipam.models import Prefix +from netbox.forms import NetBoxModelImportForm +from utilities.forms.fields import CSVModelChoiceField + +from netbox_routing.models import * + + +__all__ = ( + 'EIGRPRouterImportForm', + 'EIGRPAddressFamilyImportForm', + 'EIGRPNetworkImportForm', + 'EIGRPInterfaceImportForm', +) + + +class EIGRPRouterImportForm(NetBoxModelImportForm): + device = CSVModelChoiceField( + queryset=Interface.objects.all(), + required=False, + to_field_name='name', + help_text=_('Name of device') + ) + + class Meta: + model = EIGRPRouter + fields = ('device', 'rid', 'mode', 'name', 'pid', 'description', 'comments', 'tags',) + + +class EIGRPAddressFamilyImportForm(NetBoxModelImportForm): + + class Meta: + model = EIGRPAddressFamily + fields = ('router', 'family', 'rid', 'description', 'comments', 'tags',) + + +class EIGRPNetworkImportForm(NetBoxModelImportForm): + router = CSVModelChoiceField( + queryset=EIGRPRouter.objects.all(), + required=True, + help_text=_('PK of Router Instance') + ) + address_family = CSVModelChoiceField( + queryset=EIGRPAddressFamily.objects.all(), + required=False, + help_text=_('PK of Address Family') + ) + network = CSVModelChoiceField( + queryset=Prefix.objects.all(), + required=True, + to_field_name='prefix', + help_text=_('Prefix of Network') + ) + + class Meta: + model = EIGRPNetwork + fields = ('router', 'address_family', 'network', 'description', 'comments', 'tags',) + + +class EIGRPInterfaceImportForm(NetBoxModelImportForm): + router = CSVModelChoiceField( + queryset=EIGRPRouter.objects.all(), + required=True, + help_text=_('PK of Router Instance') + ) + address_family = CSVModelChoiceField( + queryset=EIGRPAddressFamily.objects.all(), + required=False, + help_text=_('PK of Address Family') + ) + interface = CSVModelChoiceField( + queryset=Interface.objects.all(), + required=False, + to_field_name='name', + help_text=_('Name of interface') + ) + + class Meta: + model = EIGRPInterface + fields = ('router', 'address_family', 'interface', 'description', 'comments', 'tags',) diff --git a/netbox_routing/forms/eigrp.py b/netbox_routing/forms/eigrp.py new file mode 100644 index 0000000..b051af3 --- /dev/null +++ b/netbox_routing/forms/eigrp.py @@ -0,0 +1,186 @@ +from django import forms +from django.core.exceptions import ValidationError +from django.forms import ChoiceField +from django.utils.translation import gettext as _ + +from dcim.models import Interface, Device +from ipam.choices import IPAddressFamilyChoices +from ipam.models import VRF, Prefix +from netbox.forms import NetBoxModelForm +from netbox_routing.choices.eigrp import EIGRPRouterChoices +from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, get_field_value +from utilities.forms.fields import DynamicModelChoiceField, CommentField +from utilities.forms.widgets import HTMXSelect + +from netbox_routing.models import * + + +__all__ = ( + 'EIGRPRouterForm', + 'EIGRPAddressFamilyForm', + 'EIGRPNetworkForm', + 'EIGRPInterfaceForm', +) + + +class EIGRPRouterForm(NetBoxModelForm): + device = DynamicModelChoiceField( + queryset=Device.objects.all(), + required=True, + selector=True, + label=_('Device'), + ) + comments = CommentField() + + class Meta: + model = EIGRPRouter + fields = ('device', 'mode', 'name', 'pid', 'rid', 'description', 'comments', ) + widgets = { + 'mode': HTMXSelect(), + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + mode = get_field_value(self, 'mode') + if mode == EIGRPRouterChoices.NAMED: + del self.fields['pid'] + elif mode == EIGRPRouterChoices.CLASSIC: + del self.fields['name'] + + +class EIGRPAddressFamilyForm(NetBoxModelForm): + router = DynamicModelChoiceField( + queryset=EIGRPRouter.objects.all(), + required=True, + selector=True, + label=_('Router'), + ) + vrf = DynamicModelChoiceField( + queryset=VRF.objects.all(), + required=False, + selector=True, + label=_('VRF'), + ) + family = ChoiceField( + required=True, + choices=IPAddressFamilyChoices, + label=_('Family'), + ) + comments = CommentField() + + + class Meta: + model = EIGRPAddressFamily + fields = ( + 'router', 'vrf', 'family', 'rid', 'description', 'comments', + ) + + +class EIGRPNetworkForm(NetBoxModelForm): + router = DynamicModelChoiceField( + queryset=EIGRPRouter.objects.all(), + required=True, + selector=True, + label=_('Router'), + ) + address_family = DynamicModelChoiceField( + queryset=EIGRPAddressFamily.objects.all(), + required=False, + selector=True, + label=_('Address Family'), + ) + network = DynamicModelChoiceField( + queryset=Prefix.objects.all(), + required=True, + selector=True, + label=_('Prefix'), + ) + comments = CommentField() + + + class Meta: + model = EIGRPNetwork + fields = ( + 'router', 'address_family', 'network', 'description', 'comments', + ) + + +class EIGRPInterfaceForm(NetBoxModelForm): + device = DynamicModelChoiceField( + queryset=Device.objects.all(), + required=False, + selector=True, + label=_('Device'), + ) + router = DynamicModelChoiceField( + queryset=EIGRPRouter.objects.all(), + required=True, + selector=True, + label=_('EIGRP Router'), + query_params={ + 'device_id': '$device', + } + ) + address_family = DynamicModelChoiceField( + queryset=EIGRPAddressFamily.objects.all(), + required=False, + selector=True, + label=_('Address Family'), + query_params={ + 'router_id': '$router', + } + ) + interface = DynamicModelChoiceField( + queryset=Interface.objects.all(), + required=True, + selector=True, + label=_('Interface'), + query_params={ + 'device_id': '$device', + } + ) + passive = forms.BooleanField( + required=False, + label='Passive Interface', + widget=forms.Select( + choices=BOOLEAN_WITH_BLANK_CHOICES + ) + ) + comments = CommentField() + + + class Meta: + model = EIGRPInterface + fields = ( + 'device', 'router', 'address_family', 'interface', 'passive', 'bfd', 'authentication', 'passphrase', + 'description', 'comments', + ) + + widgets = { + 'bfd': forms.Select(choices=BOOLEAN_WITH_BLANK_CHOICES), + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if self.instance.pk: + self.initial['device'] = self.instance.interface.device.pk + + def clean(self): + super().clean() + if self.cleaned_data.get('router') and self.cleaned_data.get('address_family'): + if self.cleaned_data.get('router') != self.cleaned_data.get('address_family').router: + raise ValidationError( + { + 'router': _('EIGRP Router and EIGRP Address Family Router must match'), + 'interface': _('EIGRP Router and EIGRP Address Family Router must match') + } + ) + if self.cleaned_data.get('router') and self.cleaned_data.get('interface'): + if self.cleaned_data.get('router').device != self.cleaned_data.get('interface').device: + raise ValidationError( + { + 'router': _('EIGRP Router Device and Interface Device must match'), + 'interface': _('EIGRP Interface Device and Interface Device must match') + } + ) diff --git a/netbox_routing/forms/filtersets/__init__.py b/netbox_routing/forms/filtersets/__init__.py index 627bc7d..019cd7f 100644 --- a/netbox_routing/forms/filtersets/__init__.py +++ b/netbox_routing/forms/filtersets/__init__.py @@ -3,6 +3,7 @@ from .ospf import OSPFAreaFilterForm, OSPFInstanceFilterForm, OSPFInterfaceFilterForm from .objects import PrefixListFilterForm, PrefixListEntryFilterForm, RouteMapFilterForm,\ RouteMapEntryFilterForm +from .eigrp import * __all__ = ( # Static @@ -14,6 +15,12 @@ 'BGPAddressFamilyFilterForm', 'BGPSettingFilterForm', + # EIGRP + 'EIGRPRouterFilterForm', + 'EIGRPAddressFamilyFilterForm', + 'EIGRPNetworkFilterForm', + 'EIGRPInterfaceFilterForm', + # OSPF 'OSPFAreaFilterForm', 'OSPFInstanceFilterForm', diff --git a/netbox_routing/forms/filtersets/eigrp.py b/netbox_routing/forms/filtersets/eigrp.py new file mode 100644 index 0000000..cf8e0b3 --- /dev/null +++ b/netbox_routing/forms/filtersets/eigrp.py @@ -0,0 +1,180 @@ +from django import forms +from django.utils.translation import gettext as _ + +from dcim.models import Interface, Device +from ipam.choices import IPAddressFamilyChoices +from ipam.forms.filtersets import PREFIX_MASK_LENGTH_CHOICES +from ipam.models import VRF, Prefix +from netbox.forms import NetBoxModelFilterSetForm +from netbox_routing.choices import AuthenticationChoices +from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, add_blank_choice +from utilities.forms.fields import TagFilterField, DynamicModelMultipleChoiceField, DynamicModelChoiceField +from utilities.forms.rendering import FieldSet + +from netbox_routing.models import EIGRPRouter, EIGRPAddressFamily, EIGRPNetwork, EIGRPInterface + + +__all__ = ( + 'EIGRPRouterFilterForm', + 'EIGRPAddressFamilyFilterForm', + 'EIGRPNetworkFilterForm', + 'EIGRPInterfaceFilterForm', +) + + +class EIGRPRouterFilterForm(NetBoxModelFilterSetForm): + model = EIGRPRouter + fieldsets = ( + FieldSet('q', 'filter_id', 'tag', 'device', 'mode'), + ) + + device_id = DynamicModelMultipleChoiceField( + queryset=Device.objects.all(), + required=False, + selector=True, + label=_('Device'), + ) + tag = TagFilterField(model) + + +class EIGRPAddressFamilyFilterForm(NetBoxModelFilterSetForm): + model = EIGRPAddressFamily + fieldsets = ( + FieldSet('q', 'filter_id', 'router_id', 'vrf_id', 'family', 'tag'), + ) + + router_id = DynamicModelMultipleChoiceField( + queryset=Device.objects.all(), + required=False, + selector=True, + label=_('EIGRP Router'), + ) + vrf_id = DynamicModelMultipleChoiceField( + queryset=VRF.objects.all(), + required=False, + selector=True, + label=_('VRF'), + ) + family = forms.ChoiceField( + required=False, + choices=add_blank_choice(IPAddressFamilyChoices), + label=_('Address family') + ) + tag = TagFilterField(model) + + +class EIGRPNetworkFilterForm(NetBoxModelFilterSetForm): + model = EIGRPNetwork + fieldsets = ( + FieldSet('q', 'filter_id', 'tag'), + FieldSet('router_id', 'address_family', name=_('EIGRP')), + FieldSet( + 'prefix_id', 'vrf_id', 'present_in_vrf_id', 'family', 'mask_length__lte', 'within_include', + 'mask_length', name=_('Prefix') + ), + ) + prefix_id = DynamicModelMultipleChoiceField( + queryset=Prefix.objects.all(), + required=False, + selector=True, + label=_('Prefix'), + ) + router_id = DynamicModelMultipleChoiceField( + queryset=Device.objects.all(), + required=False, + selector=True, + label=_('EIGRP Router'), + ) + address_family_id = DynamicModelMultipleChoiceField( + queryset=Device.objects.all(), + required=False, + selector=True, + label=_('EIGRP Address Family'), + ) + vrf_id = DynamicModelMultipleChoiceField( + queryset=VRF.objects.all(), + required=False, + label=_('Assigned VRF'), + null_option='Global' + ) + present_in_vrf_id = DynamicModelChoiceField( + queryset=VRF.objects.all(), + required=False, + label=_('Present in VRF') + ) + family = forms.ChoiceField( + required=False, + choices=add_blank_choice(IPAddressFamilyChoices), + label=_('Address family') + ) + mask_length__lte = forms.IntegerField( + widget=forms.HiddenInput() + ) + within_include = forms.CharField( + required=False, + widget=forms.TextInput( + attrs={ + 'placeholder': 'Prefix', + } + ), + label=_('Search within') + ) + mask_length = forms.MultipleChoiceField( + required=False, + choices=PREFIX_MASK_LENGTH_CHOICES, + label=_('Mask length') + ) + tag = TagFilterField(model) + + +class EIGRPInterfaceFilterForm(NetBoxModelFilterSetForm): + model = EIGRPInterface + fieldsets = ( + FieldSet('q', 'filter_id', 'tag'), + FieldSet('router_id', 'address_family_id', name=_('EIGRP')), + FieldSet('device_id', 'interface_id', name=_('Device')), + FieldSet('passive', 'bfd', 'authentication', name=_('Attributes')) + ) + router_id = DynamicModelMultipleChoiceField( + queryset=EIGRPRouter.objects.all(), + required=False, + selector=True, + label=_('EIGRP Router'), + ) + address_family_id = DynamicModelMultipleChoiceField( + queryset=EIGRPAddressFamily.objects.all(), + required=False, + selector=True, + label=_('EIGRP Address Family'), + ) + device_id = DynamicModelMultipleChoiceField( + queryset=Device.objects.all(), + required=False, + selector=True, + label=_('Device'), + ) + interface_id = DynamicModelMultipleChoiceField( + queryset=Interface.objects.all(), + required=False, + selector=True, + label=_('Interface'), + ) + bfd = forms.NullBooleanField( + required=False, + label='BFD Enabled', + widget=forms.Select( + choices=BOOLEAN_WITH_BLANK_CHOICES + ) + ) + passive = forms.BooleanField( + required=False, + label='Passive Interface', + widget=forms.Select( + choices=BOOLEAN_WITH_BLANK_CHOICES + ) + ) + authentication = forms.ChoiceField( + choices=add_blank_choice(AuthenticationChoices), + required=False + ) + tag = TagFilterField(model) diff --git a/netbox_routing/graphql/filters.py b/netbox_routing/graphql/filters.py index 62468f3..3f1f4ce 100644 --- a/netbox_routing/graphql/filters.py +++ b/netbox_routing/graphql/filters.py @@ -1,17 +1,17 @@ -from typing import Annotated - -import strawberry import strawberry_django +from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin from netbox_routing import filtersets, models -from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin - __all__ = ( 'StaticRouteFilter', 'OSPFInstanceFilter', 'OSPFAreaFilter', 'OSPFInterfaceFilter', + 'EIGRPRouterFilter', + 'EIGRPAddressFamilyFilter', + 'EIGRPNetworkFilter', + 'EIGRPInterfaceFilter', ) @@ -38,3 +38,27 @@ class OSPFAreaFilter(BaseFilterMixin): @autotype_decorator(filtersets.OSPFInterfaceFilterSet) class OSPFInterfaceFilter(BaseFilterMixin): pass + + +@strawberry_django.filter(models.EIGRPRouter, lookups=True) +@autotype_decorator(filtersets.EIGRPRouterFilterSet) +class EIGRPRouterFilter(BaseFilterMixin): + rid: str + + +@strawberry_django.filter(models.EIGRPAddressFamily, lookups=True) +@autotype_decorator(filtersets.EIGRPAddressFamilyFilterSet) +class EIGRPAddressFamilyFilter(BaseFilterMixin): + rid: str + + +@strawberry_django.filter(models.EIGRPNetwork, lookups=True) +@autotype_decorator(filtersets.EIGRPNetworkFilterSet) +class EIGRPNetworkFilter(BaseFilterMixin): + pass + + +@strawberry_django.filter(models.EIGRPInterface, lookups=True) +@autotype_decorator(filtersets.EIGRPInterfaceFilterSet) +class EIGRPInterfaceFilter(BaseFilterMixin): + pass diff --git a/netbox_routing/graphql/schema.py b/netbox_routing/graphql/schema.py index 07a73b2..57222b9 100644 --- a/netbox_routing/graphql/schema.py +++ b/netbox_routing/graphql/schema.py @@ -24,7 +24,23 @@ class OSPFQuery: ospf_interface_list: List[OSPFInterfaceType] = strawberry_django.field() +@strawberry.type(name="Query") +class EIGRPQuery: + eigrp_router: EIGRPRouterType = strawberry_django.field() + eigrp_router_list: List[EIGRPRouterType] = strawberry_django.field() + + eigrp_address_family: EIGRPAddressFamilyType = strawberry_django.field() + eigrp_address_family_list: List[EIGRPAddressFamilyType] = strawberry_django.field() + + eigrp_network: EIGRPNetworkType = strawberry_django.field() + eigrp_network_list: List[EIGRPNetworkType] = strawberry_django.field() + + eigrp_interface: EIGRPInterfaceType = strawberry_django.field() + eigrp_interface_list: List[EIGRPInterfaceType] = strawberry_django.field() + + schema = [ StaticRouteQuery, - OSPFQuery + OSPFQuery, + EIGRPQuery, ] diff --git a/netbox_routing/graphql/types.py b/netbox_routing/graphql/types.py index ca74368..e694ab2 100644 --- a/netbox_routing/graphql/types.py +++ b/netbox_routing/graphql/types.py @@ -1,4 +1,4 @@ -from typing import Annotated, List +from typing import Annotated, List, Union import strawberry import strawberry_django @@ -10,9 +10,15 @@ __all__ = ( 'StaticRouteType', + 'OSPFInstanceType', 'OSPFAreaType', 'OSPFInterfaceType', + + 'EIGRPRouterType', + 'EIGRPAddressFamilyType', + 'EIGRPNetworkType', + 'EIGRPInterfaceType', ) @@ -70,3 +76,56 @@ class OSPFInterfaceType(NetBoxObjectType): authentication: str | None passphrase: str | None + +@strawberry_django.type( + models.EIGRPRouter, + fields='__all__', + filters=EIGRPRouterFilter +) +class EIGRPRouterType(NetBoxObjectType): + + device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] + rid: str + type: str + name: str + pid: str + + +@strawberry_django.type( + models.EIGRPAddressFamily, + fields='__all__', + filters=EIGRPAddressFamilyFilter +) +class EIGRPAddressFamilyType(NetBoxObjectType): + + router: Annotated["EIGRPRouterType", strawberry.lazy('netbox_routing.graphql.types')] + rid: str + + +@strawberry_django.type( + models.EIGRPNetwork, + fields='__all__', + filters=EIGRPNetworkFilter +) +class EIGRPNetworkType(NetBoxObjectType): + + router: Annotated["EIGRPRouterType", strawberry.lazy('netbox_routing.graphql.types')] + address_family: Annotated["EIGRPAddressFamilyType", strawberry.lazy('netbox_routing.graphql.types')] | None + network: Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')] + + +@strawberry_django.type( + models.EIGRPInterface, + fields='__all__', + filters=EIGRPInterfaceFilter +) +class EIGRPInterfaceType(NetBoxObjectType): + + router: Annotated["EIGRPRouterType", strawberry.lazy('netbox_routing.graphql.types')] + address_family: Annotated["EIGRPAddressFamilyType", strawberry.lazy('netbox_routing.graphql.types')] | None + interface: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] + passive: str | None + bfd: bool | None + authentication: str | None + passphrase: str | None + diff --git a/netbox_routing/migrations/0010_eigrp.py b/netbox_routing/migrations/0010_eigrp.py new file mode 100644 index 0000000..8b2a214 --- /dev/null +++ b/netbox_routing/migrations/0010_eigrp.py @@ -0,0 +1,256 @@ +# Generated by Django 5.0.8 on 2024-09-27 13:00 + +import django.db.models.deletion +import netbox_routing.fields.ip +import taggit.managers +import utilities.json +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0190_nested_modules'), + ('extras', '0121_customfield_related_object_filter'), + ('ipam', '0070_vlangroup_vlan_id_ranges'), + ('netbox_routing', '0009_alter_staticroute_metric_alter_staticroute_permanent'), + ] + + operations = [ + migrations.CreateModel( + name='EIGRPAddressFamily', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False + ), + ), + ('created', models.DateTimeField(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=utilities.json.CustomFieldJSONEncoder, + ), + ), + ('description', models.CharField(blank=True, max_length=200)), + ('comments', models.TextField(blank=True)), + ('family', models.PositiveSmallIntegerField()), + ('rid', netbox_routing.fields.ip.IPAddressField(blank=True, null=True)), + ( + 'tags', + taggit.managers.TaggableManager( + through='extras.TaggedItem', to='extras.Tag' + ), + ), + ( + 'vrf', + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name='eigrp_address_families', + to='ipam.vrf', + ), + ), + ], + options={ + 'verbose_name': 'EIGRP Address Family', + }, + ), + migrations.CreateModel( + name='EIGRPRouter', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False + ), + ), + ('created', models.DateTimeField(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=utilities.json.CustomFieldJSONEncoder, + ), + ), + ('description', models.CharField(blank=True, max_length=200)), + ('comments', models.TextField(blank=True)), + ('mode', models.CharField(max_length=10)), + ('name', models.CharField(blank=True, max_length=100, null=True)), + ('pid', models.PositiveIntegerField(blank=True, null=True)), + ('rid', netbox_routing.fields.ip.IPAddressField(blank=True, null=True)), + ( + 'device', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name='eigrp', + to='dcim.device', + ), + ), + ( + 'tags', + taggit.managers.TaggableManager( + through='extras.TaggedItem', to='extras.Tag' + ), + ), + ], + options={ + 'verbose_name': 'EIGRP Router', + }, + ), + migrations.CreateModel( + name='EIGRPNetwork', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False + ), + ), + ('created', models.DateTimeField(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=utilities.json.CustomFieldJSONEncoder, + ), + ), + ('description', models.CharField(blank=True, max_length=200)), + ('comments', models.TextField(blank=True)), + ( + 'address_family', + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to='netbox_routing.eigrpaddressfamily', + ), + ), + ( + 'network', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name='eigrp', + to='ipam.prefix', + ), + ), + ( + 'tags', + taggit.managers.TaggableManager( + through='extras.TaggedItem', to='extras.Tag' + ), + ), + ( + 'router', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to='netbox_routing.eigrprouter', + ), + ), + ], + options={ + 'verbose_name': 'EIGRP Network', + }, + ), + migrations.CreateModel( + name='EIGRPInterface', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False + ), + ), + ('created', models.DateTimeField(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=utilities.json.CustomFieldJSONEncoder, + ), + ), + ('description', models.CharField(blank=True, max_length=200)), + ('comments', models.TextField(blank=True)), + ('passive', models.BooleanField()), + ('bfd', models.BooleanField()), + ( + 'authentication', + models.CharField(blank=True, max_length=50, null=True), + ), + ('passphrase', models.CharField(blank=True, max_length=200, null=True)), + ( + 'address_family', + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to='netbox_routing.eigrpaddressfamily', + ), + ), + ( + 'interface', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name='eigrp', + to='dcim.interface', + ), + ), + ( + 'tags', + taggit.managers.TaggableManager( + through='extras.TaggedItem', to='extras.Tag' + ), + ), + ( + 'router', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to='netbox_routing.eigrprouter', + ), + ), + ], + options={ + 'verbose_name': 'EIGRP Interface', + }, + ), + migrations.AddField( + model_name='eigrpaddressfamily', + name='router', + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to='netbox_routing.eigrprouter', + ), + ), + migrations.AddConstraint( + model_name='eigrpnetwork', + constraint=models.UniqueConstraint( + fields=('router', 'address_family', 'network'), + name='netbox_routing_eigrpnetwork_unique_network', + ), + ), + migrations.AddConstraint( + model_name='eigrpinterface', + constraint=models.UniqueConstraint( + fields=('router', 'address_family', 'interface'), + name='netbox_routing_eigrpinterface_unique_interface', + ), + ), + migrations.AddConstraint( + model_name='eigrpaddressfamily', + constraint=models.UniqueConstraint( + fields=('router', 'vrf', 'family'), + name='netbox_routing_eigrpaddressfamily_unique_af', + ), + ), + ] diff --git a/netbox_routing/models/__init__.py b/netbox_routing/models/__init__.py index 52e9c90..c7eb5f5 100644 --- a/netbox_routing/models/__init__.py +++ b/netbox_routing/models/__init__.py @@ -2,16 +2,26 @@ from .ospf import OSPFArea, OSPFInstance, OSPFInterface from .objects import PrefixList, PrefixListEntry, RouteMap, RouteMapEntry from .bgp import BGPRouter, BGPScope, BGPAddressFamily, BGPSetting +from .eigrp import * __all__ = ( 'StaticRoute', + 'OSPFArea', 'OSPFInstance', 'OSPFInterface', + + 'EIGRPRouter', + 'EIGRPAddressFamily', + 'EIGRPNetwork', + 'EIGRPInterface', + 'PrefixList', 'PrefixListEntry', 'RouteMap', 'RouteMapEntry', + + # Not fully implemented 'BGPRouter', 'BGPScope', 'BGPAddressFamily', diff --git a/netbox_routing/models/eigrp.py b/netbox_routing/models/eigrp.py new file mode 100644 index 0000000..7ddfa39 --- /dev/null +++ b/netbox_routing/models/eigrp.py @@ -0,0 +1,216 @@ +from django.db import models +from django.urls import reverse +from django.utils.translation import gettext as _ + +from ipam.choices import IPAddressFamilyChoices +from netbox.models import PrimaryModel +from netbox_routing import choices +from netbox_routing.choices.eigrp import EIGRPRouterChoices +from netbox_routing.fields.ip import IPAddressField + +__all__ = ( + 'EIGRPRouter', + 'EIGRPAddressFamily', + 'EIGRPNetwork', + 'EIGRPInterface', +) + + +class EIGRPRouter(PrimaryModel): + device = models.ForeignKey( + verbose_name=_('Device'), + to='dcim.Device', + related_name='eigrp', + on_delete=models.CASCADE, + blank=False, + null=False + ) + mode = models.CharField( + verbose_name=_('Mode'), + max_length=10, + choices=EIGRPRouterChoices + ) + name = models.CharField( + verbose_name=_('Name'), + max_length=100, + blank=True, + null=True + ) + pid = models.PositiveIntegerField( + verbose_name=_('Process ID'), + blank=True, + null=True + ) + rid = IPAddressField( + verbose_name=_('Router ID'), + blank=True, + null=True + ) + + class Meta: + verbose_name = 'EIGRP Router' + + def __str__(self): + if self.pid: + return f'Process {self.pid} ({self.rid})' + return f'{self.name} ({self.rid})' + + def get_absolute_url(self): + return reverse('plugins:netbox_routing:eigrprouter', args=[self.pk]) + + @property + def identifier(self): + if self.name: + return f'{self.name}' + elif self.pid: + return f'{self.pid}' + return f'{self}' + + +class EIGRPAddressFamily(PrimaryModel): + router = models.ForeignKey( + verbose_name=_('Router'), + to=EIGRPRouter, + on_delete=models.CASCADE, + blank=False, + null=False + ) + vrf = models.ForeignKey( + verbose_name=_('VRF'), + to='ipam.VRF', + related_name='eigrp_address_families', + on_delete=models.CASCADE, + blank=True, + null=True + ) + family = models.PositiveSmallIntegerField( + verbose_name=_('Address Family'), + choices=IPAddressFamilyChoices, + blank=False, + null=False + ) + rid = IPAddressField( + verbose_name=_('Router ID'), + blank=True, + null=True + ) + + class Meta: + verbose_name = 'EIGRP Address Family' + constraints = ( + models.UniqueConstraint( + fields=('router', 'vrf', 'family'), + name='%(app_label)s_%(class)s_unique_af' + ), + ) + + def __str__(self): + if self.vrf: + return f'{self.router.identifier} ({self.family} vrf {self.vrf.name})' + return f'{self.router.identifier} ({self.family})' + + def get_absolute_url(self): + return reverse('plugins:netbox_routing:eigrpaddressfamily', args=[self.pk]) + + +class EIGRPNetwork(PrimaryModel): + router = models.ForeignKey( + verbose_name=_('Router'), + to=EIGRPRouter, + on_delete=models.CASCADE, + blank=False, + null=False + ) + address_family = models.ForeignKey( + verbose_name=_('Address Family'), + to=EIGRPAddressFamily, + on_delete=models.CASCADE, + blank=True, + null=True + ) + network = models.ForeignKey( + verbose_name=_('Network'), + to='ipam.Prefix', + related_name='eigrp', + on_delete=models.CASCADE, + blank=False, + null=False + ) + + class Meta: + verbose_name = 'EIGRP Network' + constraints = ( + models.UniqueConstraint( + fields=('router', 'address_family', 'network',), + name='%(app_label)s_%(class)s_unique_network' + ), + ) + + def __str__(self): + if self.address_family: + return f'{self.network} ({self.router.identifier} {self.address_family})' + return f'{self.network} ({self.router.identifier})' + + def get_absolute_url(self): + return reverse('plugins:netbox_routing:eigrpnetwork', args=[self.pk]) + + +class EIGRPInterface(PrimaryModel): + router = models.ForeignKey( + verbose_name=_('Router'), + to=EIGRPRouter, + on_delete=models.CASCADE, + blank=False, + null=False + ) + address_family = models.ForeignKey( + verbose_name=_('Address Family'), + to=EIGRPAddressFamily, + on_delete=models.CASCADE, + blank=True, + null=True + ) + interface = models.ForeignKey( + verbose_name=_('Interface'), + to='dcim.Interface', + related_name='eigrp', + on_delete=models.CASCADE, + blank=False, + null=False + ) + passive = models.BooleanField( + verbose_name=_('Passive'), + ) + bfd = models.BooleanField( + verbose_name=_('BFD'), + ) + authentication = models.CharField( + verbose_name=_('Authentication'), + max_length=50, + choices=choices.AuthenticationChoices, + blank=True, + null=True + ) + passphrase = models.CharField( + verbose_name=_('Passphrase'), + max_length=200, + blank=True, + null=True + ) + + class Meta: + verbose_name = 'EIGRP Interface' + constraints = ( + models.UniqueConstraint( + fields=('router', 'address_family', 'interface',), + name='%(app_label)s_%(class)s_unique_interface' + ), + ) + + def __str__(self): + if self.address_family: + return f'{self.interface.name} ({self.router.identifier} {self.address_family})' + return f'{self.interface.name} ({self.router.identifier})' + + def get_absolute_url(self): + return reverse('plugins:netbox_routing:eigrpinterface', args=[self.pk]) diff --git a/netbox_routing/models/base.py b/netbox_routing/models/mixins.py similarity index 100% rename from netbox_routing/models/base.py rename to netbox_routing/models/mixins.py diff --git a/netbox_routing/navigation/__init__.py b/netbox_routing/navigation/__init__.py index 59ee73e..1bcd4d8 100644 --- a/netbox_routing/navigation/__init__.py +++ b/netbox_routing/navigation/__init__.py @@ -3,6 +3,7 @@ from .bgp import MENUITEMS as BGP_MENU from .objects import MENUITEMS as OBJECT_MENU from .ospf import MENUITEMS as OSPF_MENU +from .eigrp import eigrp from .static import MENUITEMS as STATIC_MENU @@ -17,6 +18,7 @@ ('Static', STATIC_MENU), # ('BGP', BGP_MENU), ('OSPF', OSPF_MENU), + ('EIGRP', eigrp), ), icon_class='mdi mdi-router' ) \ No newline at end of file diff --git a/netbox_routing/navigation/eigrp.py b/netbox_routing/navigation/eigrp.py new file mode 100644 index 0000000..d69be57 --- /dev/null +++ b/netbox_routing/navigation/eigrp.py @@ -0,0 +1,49 @@ +from netbox.choices import ButtonColorChoices +from netbox.plugins import PluginMenuItem, PluginMenuButton + + +__all__ = ( + 'eigrp', +) + + +routers = PluginMenuItem( + link='plugins:netbox_routing:eigrprouter_list', + link_text='Routers', + permissions=['netbox_routing.view_eigrprouter'], + buttons=( + PluginMenuButton('plugins:netbox_routing:eigrprouter_add', 'Add', 'mdi mdi-plus', ButtonColorChoices.GREEN), + PluginMenuButton( + 'plugins:netbox_routing:eigrprouter_import', + 'Import', + 'mdi mdi-upload', + ButtonColorChoices.CYAN + ), + ) +) +address_families = PluginMenuItem( + link='plugins:netbox_routing:eigrpaddressfamily_list', + link_text='Address Families', + permissions=['netbox_routing.view_eigrpaddressfamily'], + buttons=( + PluginMenuButton('plugins:netbox_routing:eigrpaddressfamily_add', 'Add', 'mdi mdi-plus', ButtonColorChoices.GREEN), + ) +) +networks = PluginMenuItem( + link='plugins:netbox_routing:eigrpnetwork_list', + link_text='Networks', + permissions=['netbox_routing.view_eigrpnetwork'], + buttons=( + PluginMenuButton('plugins:netbox_routing:eigrpnetwork_add', 'Add', 'mdi mdi-plus', ButtonColorChoices.GREEN), + ) +) +interfaces = PluginMenuItem( + link='plugins:netbox_routing:eigrpinterface_list', + link_text='Interfaces', + permissions=['netbox_routing.view_eigrpinterface'], + buttons=( + PluginMenuButton('plugins:netbox_routing:eigrpinterface_add', 'Add', 'mdi mdi-plus', ButtonColorChoices.GREEN), + ) +) + +eigrp = (routers, address_families, networks, interfaces) diff --git a/netbox_routing/tables/eigrp.py b/netbox_routing/tables/eigrp.py new file mode 100644 index 0000000..93e8e85 --- /dev/null +++ b/netbox_routing/tables/eigrp.py @@ -0,0 +1,71 @@ +import django_tables2 as tables +from django.utils.translation import gettext_lazy as _ + +from netbox.tables import NetBoxTable +from netbox_routing.models import EIGRPAddressFamily, EIGRPRouter, EIGRPInterface + + +__all__ = ( + 'EIGRPAddressFamilyTable', + 'EIGRPRouterTable', + 'EIGRPNetworkTable', + 'EIGRPInterfaceTable', +) + + +class EIGRPRouterTable(NetBoxTable): + class Meta(NetBoxTable.Meta): + model = EIGRPRouter + fields = ('pk', 'id', 'name', 'mode', 'pid', 'rid', 'device') + default_columns = ('pk', 'id', 'name', 'device') + + +class EIGRPAddressFamilyTable(NetBoxTable): + router = tables.Column( + verbose_name=_('Router'), + linkify=True + ) + class Meta(NetBoxTable.Meta): + model = EIGRPAddressFamily + fields = ('pk', 'id', 'family', 'router') + default_columns = ('pk', 'id', 'family', 'router') + + +class EIGRPNetworkTable(NetBoxTable): + router = tables.Column( + verbose_name=_('Router'), + linkify=True + ) + address_family = tables.Column( + verbose_name=_('Address Family'), + linkify=True + ) + network = tables.Column( + verbose_name=_('Network'), + linkify=True + ) + + class Meta(NetBoxTable.Meta): + model = EIGRPInterface + fields = ('pk', 'id', 'router', 'address_family', 'network') + default_columns = ('pk', 'id', 'router', 'address_family', 'network') + + +class EIGRPInterfaceTable(NetBoxTable): + router = tables.Column( + verbose_name=_('Router'), + linkify=True + ) + address_family = tables.Column( + verbose_name=_('Address Family'), + linkify=True + ) + interface = tables.Column( + verbose_name=_('Interface'), + linkify=True + ) + + class Meta(NetBoxTable.Meta): + model = EIGRPInterface + fields = ('pk', 'id', 'router', 'address_family', 'interface', 'passive', 'bfd', 'authentication', 'passphrase') + default_columns = ('pk', 'id', 'router', 'address_family', 'interface') diff --git a/netbox_routing/templates/netbox_routing/eigrpaddressfamily.html b/netbox_routing/templates/netbox_routing/eigrpaddressfamily.html new file mode 100644 index 0000000..77c5ebb --- /dev/null +++ b/netbox_routing/templates/netbox_routing/eigrpaddressfamily.html @@ -0,0 +1,55 @@ +{% extends 'generic/object.html' %} +{% load humanize %} +{% load helpers %} +{% load plugins %} + +{% block content %} +
+
+
+
EIGRP Details
+
+ + + + + + + + + + + + + + + + + +
Router + {{ object.router }} +
VRF + {{ object.vrf }} +
Family + {{ object.family }} +
Router ID + {% if object.rid %}{{ object.rid }}{% else %}{{ object.router.rid}}{% endif %} +
+
+
+ {% plugin_left_page object %} +
+
+ {% include 'inc/panels/related_objects.html' %} + {% include 'inc/panels/custom_fields.html' %} + {% include 'inc/panels/comments.html' %} + {% include 'inc/panels/tags.html' %} + {% plugin_right_page object %} +
+
+
+
+ {% plugin_full_width_page object %} +
+
+{% endblock %} \ No newline at end of file diff --git a/netbox_routing/templates/netbox_routing/eigrpinterface.html b/netbox_routing/templates/netbox_routing/eigrpinterface.html new file mode 100644 index 0000000..8215fb3 --- /dev/null +++ b/netbox_routing/templates/netbox_routing/eigrpinterface.html @@ -0,0 +1,80 @@ +{% extends 'generic/object.html' %} +{% load humanize %} +{% load helpers %} +{% load plugins %} + +{% block content %} +
+
+
+
+
+ + + + + + + + + + + + + +
Router + {{ object.router|linkify }} +
Address Family + {{ object.address_family|linkify }} +
Interface + {{ object.interface|linkify }} +
+
+
+
+
Attributes
+
+ + + + + + + + + + + + + + + + + +
Passive + {{ object.passive|placeholder }} +
BFD + {{ object.bfd|placeholder }} +
Authentication Type + {{ object.authentication|placeholder }} +
Passphrase (or keyring) + {{ object.passphrase|placeholder }} +
+
+
+ {% plugin_left_page object %} +
+
+ {% include 'inc/panels/related_objects.html' %} + {% include 'inc/panels/custom_fields.html' %} + {% include 'inc/panels/comments.html' %} + {% include 'inc/panels/tags.html' %} + {% plugin_right_page object %} +
+
+
+
+ {% plugin_full_width_page object %} +
+
+{% endblock %} \ No newline at end of file diff --git a/netbox_routing/templates/netbox_routing/eigrpnetwork.html b/netbox_routing/templates/netbox_routing/eigrpnetwork.html new file mode 100644 index 0000000..bfe58ba --- /dev/null +++ b/netbox_routing/templates/netbox_routing/eigrpnetwork.html @@ -0,0 +1,49 @@ +{% extends 'generic/object.html' %} +{% load humanize %} +{% load helpers %} +{% load plugins %} + +{% block content %} +
+
+
+
+
+ + + + + + + + + + + + + +
Router + {{ object.router|linkify }} +
Address Family + {{ object.address_family|linkify }} +
Network + {{ object.network|linkify }} +
+
+
+ {% plugin_left_page object %} +
+
+ {% include 'inc/panels/related_objects.html' %} + {% include 'inc/panels/custom_fields.html' %} + {% include 'inc/panels/comments.html' %} + {% include 'inc/panels/tags.html' %} + {% plugin_right_page object %} +
+
+
+
+ {% plugin_full_width_page object %} +
+
+{% endblock %} \ No newline at end of file diff --git a/netbox_routing/templates/netbox_routing/eigrprouter.html b/netbox_routing/templates/netbox_routing/eigrprouter.html new file mode 100644 index 0000000..ba285de --- /dev/null +++ b/netbox_routing/templates/netbox_routing/eigrprouter.html @@ -0,0 +1,55 @@ +{% extends 'generic/object.html' %} +{% load humanize %} +{% load helpers %} +{% load plugins %} + +{% block content %} +
+
+
+
EIGRP Details
+
+ + + + + + + + + + + + + + + + + +
Device + {{ object.device|linkify }} +
Router ID + {{ object.rid }} +
Name + {{ object.name }} +
Process ID + {{ object.process_id }} +
+
+
+ {% plugin_left_page object %} +
+
+ {% include 'inc/panels/related_objects.html' %} + {% include 'inc/panels/custom_fields.html' %} + {% include 'inc/panels/comments.html' %} + {% include 'inc/panels/tags.html' %} + {% plugin_right_page object %} +
+
+
+
+ {% plugin_full_width_page object %} +
+
+{% endblock %} \ No newline at end of file diff --git a/netbox_routing/urls.py b/netbox_routing/urls.py index dfe26de..d2a095a 100644 --- a/netbox_routing/urls.py +++ b/netbox_routing/urls.py @@ -1,11 +1,13 @@ -from django.urls import path +from django.urls import path, include from netbox.views.generic import ObjectChangeLogView +from utilities.urls import get_model_urls from . import views from .models import StaticRoute, PrefixList, PrefixListEntry, RouteMap, RouteMapEntry, OSPFInstance, OSPFArea, \ OSPFInterface, BGPRouter, BGPScope, BGPAddressFamily + urlpatterns = [ path('routes/static/', views.StaticRouteListView.as_view(), name='staticroute_list'), path('routes/static/add/', views.StaticRouteEditView.as_view(), name='staticroute_add'), @@ -50,6 +52,32 @@ path('ospf/interface//delete/', views.OSPFInterfaceDeleteView.as_view(), name='ospfinterface_delete'), path('ospf/interface//changelog/', ObjectChangeLogView.as_view(), name='ospfinterface_changelog', kwargs={'model': OSPFInterface}), + path('eigrp/router/', views.EIGRPRouterListView.as_view(), name='eigrprouter_list'), + path('eigrp/router/add/', views.EIGRPRouterEditView.as_view(), name='eigrprouter_add'), + path('eigrp/router/edit/', views.EIGRPRouterBulkEditView.as_view(), name='eigrprouter_bulk_edit'), + path('eigrp/router/delete/', views.EIGRPRouterBulkDeleteView.as_view(), name='eigrprouter_bulk_delete'), + path('eigrp/router/import/', views.EIGRPRouterImportView.as_view(), name='eigrprouter_import'), + path('eigrp/router//', include(get_model_urls('netbox_routing', 'eigrprouter'))), + + path('eigrp/address-family/', views.EIGRPAddressFamilyListView.as_view(), name='eigrpaddressfamily_list'), + path('eigrp/address-family/add/', views.EIGRPAddressFamilyEditView.as_view(), name='eigrpaddressfamily_add'), + path('eigrp/address-family/edit/', views.EIGRPAddressFamilyBulkEditView.as_view(), name='eigrpaddressfamily_bulk_edit'), + path('eigrp/address-family/delete/', views.EIGRPAddressFamilyBulkDeleteView.as_view(), name='eigrpaddressfamily_bulk_delete'), + path('eigrp/address-family//', include(get_model_urls('netbox_routing', 'eigrpaddressfamily'))), + + path('eigrp/network/', views.EIGRPNetworkListView.as_view(), name='eigrpnetwork_list'), + path('eigrp/network/add/', views.EIGRPNetworkEditView.as_view(), name='eigrpnetwork_add'), + path('eigrp/network/edit/', views.EIGRPNetworkBulkEditView.as_view(), name='eigrpnetwork_bulk_edit'), + path('eigrp/network/delete/', views.EIGRPNetworkBulkDeleteView.as_view(), name='eigrpnetwork_bulk_delete'), + path('eigrp/network//', include(get_model_urls('netbox_routing', 'eigrpnetwork'))), + + path('eigrp/interface/', views.EIGRPInterfaceListView.as_view(), name='eigrpinterface_list'), + path('eigrp/interface/add/', views.EIGRPInterfaceEditView.as_view(), name='eigrpinterface_add'), + path('eigrp/interface/import/', views.EIGRPInterfaceListView.as_view(), name='eigrpinterface_import'), + path('eigrp/interface/edit/', views.EIGRPInterfaceBulkEditView.as_view(), name='eigrpinterface_bulk_edit'), + path('eigrp/interface/delete/', views.EIGRPInterfaceBulkDeleteView.as_view(), name='eigrpinterface_bulk_delete'), + path('eigrp/interface//', include(get_model_urls('netbox_routing', 'eigrpinterface'))), + path('bgp/router/', views.BGPRouterListView.as_view(), name='bgprouter_list'), path('bgp/router/add/', views.BGPRouterEditView.as_view(), name='bgprouter_add'), path('bgp/router//', views.BGPRouterView.as_view(), name='bgprouter'), diff --git a/netbox_routing/views/__init__.py b/netbox_routing/views/__init__.py index 63e6deb..ba0e2a7 100644 --- a/netbox_routing/views/__init__.py +++ b/netbox_routing/views/__init__.py @@ -7,6 +7,7 @@ RouteMapEntryBulkDeleteView, PrefixListEntryBulkDeleteView, PrefixListEntryBulkEditView from .ospf import * +from .eigrp import * from .bgp import * from .core import * @@ -41,6 +42,38 @@ 'OSPFInterfaceEditView', 'OSPFInterfaceDeleteView', + # EIGRP + 'EIGRPRouterListView', + 'EIGRPRouterView', + 'EIGRPRouterInterfacesView', + 'EIGRPRouterEditView', + 'EIGRPRouterImportView', + 'EIGRPRouterBulkEditView', + 'EIGRPRouterDeleteView', + 'EIGRPRouterBulkDeleteView', + + 'EIGRPAddressFamilyListView', + 'EIGRPAddressFamilyView', + 'EIGRPAddressFamilyInterfacesView', + 'EIGRPAddressFamilyEditView', + 'EIGRPAddressFamilyBulkEditView', + 'EIGRPAddressFamilyDeleteView', + 'EIGRPAddressFamilyBulkDeleteView', + + 'EIGRPNetworkListView', + 'EIGRPNetworkView', + 'EIGRPNetworkEditView', + 'EIGRPNetworkBulkEditView', + 'EIGRPNetworkDeleteView', + 'EIGRPNetworkBulkDeleteView', + + 'EIGRPInterfaceListView', + 'EIGRPInterfaceView', + 'EIGRPInterfaceEditView', + 'EIGRPInterfaceBulkEditView', + 'EIGRPInterfaceDeleteView', + 'EIGRPInterfaceBulkDeleteView', + 'BGPRouterView', 'BGPRouterEditView', diff --git a/netbox_routing/views/eigrp.py b/netbox_routing/views/eigrp.py new file mode 100644 index 0000000..029d2f1 --- /dev/null +++ b/netbox_routing/views/eigrp.py @@ -0,0 +1,309 @@ +from netbox.views.generic import ObjectListView, ObjectEditView, ObjectView, ObjectDeleteView, ObjectChildrenView, \ + BulkImportView, BulkEditView, BulkDeleteView +from netbox_routing.filtersets.eigrp import * +from netbox_routing.forms import * +from netbox_routing.tables.eigrp import * +from utilities.views import register_model_view, ViewTab + +from netbox_routing.models import * + + +__all__ = ( + 'EIGRPRouterListView', + 'EIGRPRouterView', + 'EIGRPRouterAddressFamiliesView', + 'EIGRPRouterInterfacesView', + 'EIGRPRouterNetworksView', + 'EIGRPRouterEditView', + 'EIGRPRouterImportView', + 'EIGRPRouterBulkEditView', + 'EIGRPRouterDeleteView', + 'EIGRPRouterBulkDeleteView', + + 'EIGRPAddressFamilyListView', + 'EIGRPAddressFamilyView', + 'EIGRPAddressFamilyInterfacesView', + 'EIGRPAddressFamilyNetworksView', + 'EIGRPAddressFamilyEditView', + 'EIGRPAddressFamilyBulkEditView', + 'EIGRPAddressFamilyDeleteView', + 'EIGRPAddressFamilyBulkDeleteView', + + 'EIGRPNetworkListView', + 'EIGRPNetworkView', + 'EIGRPNetworkEditView', + 'EIGRPNetworkBulkEditView', + 'EIGRPNetworkDeleteView', + 'EIGRPNetworkBulkDeleteView', + + 'EIGRPInterfaceListView', + 'EIGRPInterfaceView', + 'EIGRPInterfaceEditView', + 'EIGRPInterfaceBulkEditView', + 'EIGRPInterfaceDeleteView', + 'EIGRPInterfaceBulkDeleteView', +) + + +# +# Instance +# +@register_model_view(EIGRPRouter, name='list') +class EIGRPRouterListView(ObjectListView): + queryset = EIGRPRouter.objects.all() + table = EIGRPRouterTable + filterset = EIGRPRouterFilterSet + filterset_form = EIGRPRouterFilterForm + + +@register_model_view(EIGRPRouter) +class EIGRPRouterView(ObjectView): + queryset = EIGRPRouter.objects.all() + template_name = 'netbox_routing/eigrprouter.html' + + +@register_model_view(EIGRPRouter, name='address_families') +class EIGRPRouterAddressFamiliesView(ObjectChildrenView): + queryset = EIGRPRouter.objects.all() + child_model = EIGRPAddressFamily + table = EIGRPAddressFamilyTable + filterset = EIGRPAddressFamilyFilterSet + tab = ViewTab( + label='Address Families', + badge=lambda obj: EIGRPAddressFamily.objects.filter(router=obj).count(), + hide_if_empty=False, + ) + + def get_children(self, request, parent): + return self.child_model.objects.filter(router=parent) + + +@register_model_view(EIGRPRouter, name='interfaces') +class EIGRPRouterInterfacesView(ObjectChildrenView): + queryset = EIGRPRouter.objects.all() + child_model = EIGRPInterface + table = EIGRPInterfaceTable + filterset = EIGRPInterfaceFilterSet + tab = ViewTab( + label='Interfaces', + badge=lambda obj: EIGRPInterface.objects.filter(router=obj).count(), + hide_if_empty=False, + ) + + def get_children(self, request, parent): + return self.child_model.objects.filter(router=parent) + + +@register_model_view(EIGRPRouter, name='networks') +class EIGRPRouterNetworksView(ObjectChildrenView): + queryset = EIGRPRouter.objects.all() + child_model = EIGRPNetwork + table = EIGRPNetworkTable + filterset = EIGRPNetworkFilterSet + tab = ViewTab( + label='Networks', + badge=lambda obj: EIGRPNetwork.objects.filter(router=obj).count(), + hide_if_empty=False, + ) + + def get_children(self, request, parent): + return self.child_model.objects.filter(router=parent) + + +@register_model_view(EIGRPRouter, name='edit') +class EIGRPRouterEditView(ObjectEditView): + queryset = EIGRPRouter.objects.all() + form = EIGRPRouterForm + + +@register_model_view(EIGRPRouter, name='bulk_edit') +class EIGRPRouterBulkEditView(BulkEditView): + queryset = EIGRPRouter.objects.all() + filterset = EIGRPRouterFilterSet + table = EIGRPRouterTable + form = EIGRPRouterBulkEditForm + + +@register_model_view(EIGRPRouter, name='delete') +class EIGRPRouterDeleteView(ObjectDeleteView): + queryset = EIGRPRouter.objects.all() + + +@register_model_view(EIGRPRouter, name='bulk_delete') +class EIGRPRouterBulkDeleteView(BulkDeleteView): + queryset = EIGRPRouter.objects.all() + filterset = EIGRPRouterFilterSet + table = EIGRPRouterTable + + +class EIGRPRouterImportView(BulkImportView): + queryset = EIGRPRouter.objects.all() + model_form = EIGRPRouterImportForm + +# +# Address Family +# +@register_model_view(EIGRPAddressFamily, name='list') +class EIGRPAddressFamilyListView(ObjectListView): + queryset = EIGRPAddressFamily.objects.all() + table = EIGRPAddressFamilyTable + filterset = EIGRPAddressFamilyFilterSet + filterset_form = EIGRPAddressFamilyFilterForm + + +@register_model_view(EIGRPAddressFamily) +class EIGRPAddressFamilyView(ObjectView): + queryset = EIGRPAddressFamily.objects.all() + template_name = 'netbox_routing/eigrpaddressfamily.html' + + +@register_model_view(EIGRPAddressFamily, name='interfaces') +class EIGRPAddressFamilyInterfacesView(ObjectChildrenView): + #template_name = 'netbox_routing/object_children.html' + queryset = EIGRPAddressFamily.objects.all() + child_model = EIGRPInterface + table = EIGRPInterfaceTable + filterset = EIGRPInterfaceFilterSet + tab = ViewTab( + label='Interfaces', + badge=lambda obj: EIGRPInterface.objects.filter(address_family=obj).count(), + ) + + +@register_model_view(EIGRPAddressFamily, name='networks') +class EIGRPAddressFamilyNetworksView(ObjectChildrenView): + #template_name = 'netbox_routing/object_children.html' + queryset = EIGRPAddressFamily.objects.all() + child_model = EIGRPNetwork + table = EIGRPNetworkTable + filterset = EIGRPNetworkFilterSet + tab = ViewTab( + label='Interfaces', + badge=lambda obj: EIGRPInterface.objects.filter(address_family=obj).count(), + ) + + def get_children(self, request, parent): + return self.child_model.objects.filter(address_famiily=parent) + + +@register_model_view(EIGRPAddressFamily, name='edit') +class EIGRPAddressFamilyEditView(ObjectEditView): + queryset = EIGRPAddressFamily.objects.all() + form = EIGRPAddressFamilyForm + + +@register_model_view(EIGRPAddressFamily, name='bulk_edit') +class EIGRPAddressFamilyBulkEditView(BulkEditView): + queryset = EIGRPAddressFamily.objects.all() + table = EIGRPAddressFamilyTable + filterset = EIGRPAddressFamilyFilterSet + form = EIGRPAddressFamilyBulkEditForm + + +@register_model_view(EIGRPAddressFamily, name='delete') +class EIGRPAddressFamilyDeleteView(ObjectDeleteView): + queryset = EIGRPAddressFamily.objects.all() + + +@register_model_view(EIGRPAddressFamily, name='delete') +class EIGRPAddressFamilyBulkDeleteView(BulkDeleteView): + queryset = EIGRPAddressFamily.objects.all() + table = EIGRPAddressFamilyTable + filterset = EIGRPAddressFamilyFilterSet + + +class EIGRPAddressFamilyImportView(BulkImportView): + queryset = EIGRPAddressFamily.objects.all() + model_form = EIGRPAddressFamilyImportForm + + +# +# Network +# +@register_model_view(EIGRPNetwork, name='list') +class EIGRPNetworkListView(ObjectListView): + queryset = EIGRPNetwork.objects.all() + table = EIGRPNetworkTable + filterset = EIGRPNetworkFilterSet + filterset_form = EIGRPNetworkFilterForm + + +@register_model_view(EIGRPNetwork) +class EIGRPNetworkView(ObjectView): + queryset = EIGRPNetwork.objects.all() + template_name = 'netbox_routing/eigrpnetwork.html' + + +@register_model_view(EIGRPNetwork, name='edit') +class EIGRPNetworkEditView(ObjectEditView): + queryset = EIGRPNetwork.objects.all() + form = EIGRPNetworkForm + + +@register_model_view(EIGRPNetwork, name='delete') +class EIGRPNetworkDeleteView(ObjectDeleteView): + queryset = EIGRPNetwork.objects.all() + + +class EIGRPNetworkImportView(BulkImportView): + queryset = EIGRPNetwork.objects.all() + model_form = EIGRPNetworkImportForm + + +class EIGRPNetworkBulkEditView(BulkEditView): + queryset = EIGRPNetwork.objects.all() + filterset = EIGRPNetworkFilterSet + table = EIGRPNetworkTable + form = EIGRPNetworkBulkEditForm + + +class EIGRPNetworkBulkDeleteView(BulkDeleteView): + queryset = EIGRPNetwork.objects.all() + filterset = EIGRPNetworkFilterSet + table = EIGRPNetworkTable + + +# +# Interface +# +@register_model_view(EIGRPInterface, name='list') +class EIGRPInterfaceListView(ObjectListView): + queryset = EIGRPInterface.objects.all() + table = EIGRPInterfaceTable + filterset = EIGRPInterfaceFilterSet + filterset_form = EIGRPInterfaceFilterForm + + +@register_model_view(EIGRPInterface) +class EIGRPInterfaceView(ObjectView): + queryset = EIGRPInterface.objects.all() + template_name = 'netbox_routing/eigrpinterface.html' + + +@register_model_view(EIGRPInterface, name='edit') +class EIGRPInterfaceEditView(ObjectEditView): + queryset = EIGRPInterface.objects.all() + form = EIGRPInterfaceForm + + +@register_model_view(EIGRPInterface, name='delete') +class EIGRPInterfaceDeleteView(ObjectDeleteView): + queryset = EIGRPInterface.objects.all() + + +class EIGRPInterfaceImportView(BulkImportView): + queryset = EIGRPInterface.objects.all() + model_form = EIGRPInterfaceImportForm + + +class EIGRPInterfaceBulkEditView(BulkEditView): + queryset = EIGRPInterface.objects.all() + filterset = EIGRPInterfaceFilterSet + table = EIGRPInterfaceTable + form = EIGRPInterfaceBulkEditForm + + +class EIGRPInterfaceBulkDeleteView(BulkDeleteView): + queryset = EIGRPInterface.objects.all() + filterset = EIGRPInterfaceFilterSet + table = EIGRPInterfaceTable diff --git a/pyproject.toml b/pyproject.toml index 34f0799..47950c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ description = "A NetBox Routing Plugin" readme = "README.md" requires-python = ">=3.10" keywords = ["netbox-plugin", ] -version = "0.2.3" +version = "0.2.4-alpha1" license = {file = "LICENSE"} classifiers = [ "Programming Language :: Python :: 3",