From 0f58faaddbfce5eef16e9ecc56bdf1528769e21c Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Sat, 8 Jan 2022 12:25:30 -0600 Subject: [PATCH] #7853 - Initial work on Speed/Duplex. TODO: Documentation, Tests, Form order --- netbox/dcim/api/serializers.py | 3 ++- netbox/dcim/choices.py | 13 +++++++++++ netbox/dcim/filtersets.py | 2 ++ netbox/dcim/forms/bulk_create.py | 4 ++-- netbox/dcim/forms/bulk_edit.py | 11 ++++++--- netbox/dcim/forms/bulk_import.py | 6 ++++- netbox/dcim/forms/filtersets.py | 14 +++++++++-- netbox/dcim/forms/models.py | 7 +++--- .../migrations/0150_interface_speed_duplex.py | 23 +++++++++++++++++++ netbox/dcim/models/device_components.py | 12 ++++++++++ netbox/templates/dcim/interface.html | 8 +++++++ netbox/templates/dcim/interface_edit.html | 2 ++ 12 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 netbox/dcim/migrations/0150_interface_speed_duplex.py diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 4d8638231..766373796 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -721,6 +721,7 @@ class InterfaceSerializer(PrimaryModelSerializer, LinkTerminationSerializer, Con bridge = NestedInterfaceSerializer(required=False, allow_null=True) lag = NestedInterfaceSerializer(required=False, allow_null=True) mode = ChoiceField(choices=InterfaceModeChoices, allow_blank=True, required=False) + duplex = ChoiceField(choices=InterfaceDuplexChoices, allow_blank=True, required=False) rf_role = ChoiceField(choices=WirelessRoleChoices, required=False, allow_null=True) rf_channel = ChoiceField(choices=WirelessChannelChoices, required=False) untagged_vlan = NestedVLANSerializer(required=False, allow_null=True) @@ -746,7 +747,7 @@ class InterfaceSerializer(PrimaryModelSerializer, LinkTerminationSerializer, Con model = Interface fields = [ 'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'enabled', 'parent', 'bridge', 'lag', - 'mtu', 'mac_address', 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel', + 'mtu', 'mac_address', 'speed', 'duplex', 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'mark_connected', 'cable', 'wireless_link', 'link_peer', 'link_peer_type', 'wireless_lans', 'vrf', 'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 368ee1336..fa158c750 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -943,6 +943,19 @@ class InterfaceTypeChoices(ChoiceSet): ) +class InterfaceDuplexChoices(ChoiceSet): + + DUPLEX_HALF = 'half' + DUPLEX_FULL = 'full' + DUPLEX_AUTO = 'auto' + + CHOICES = ( + (DUPLEX_HALF, 'Half'), + (DUPLEX_FULL, 'Full'), + (DUPLEX_AUTO, 'Auto'), + ) + + class InterfaceModeChoices(ChoiceSet): MODE_ACCESS = 'access' diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 104836120..8a83a8a6b 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -1196,6 +1196,8 @@ class InterfaceFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableT queryset=Interface.objects.all(), label='LAG interface (ID)', ) + speed = MultiValueNumberFilter() + duplex = django_filters.CharFilter() mac_address = MultiValueMACAddressFilter() wwn = MultiValueWWNFilter() tag = TagFilter() diff --git a/netbox/dcim/forms/bulk_create.py b/netbox/dcim/forms/bulk_create.py index 02c8feb4b..4d73fcc2a 100644 --- a/netbox/dcim/forms/bulk_create.py +++ b/netbox/dcim/forms/bulk_create.py @@ -72,12 +72,12 @@ class PowerOutletBulkCreateForm( class InterfaceBulkCreateForm( - form_from_model(Interface, ['type', 'enabled', 'mtu', 'mgmt_only', 'mark_connected']), + form_from_model(Interface, ['type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'mark_connected']), DeviceBulkAddComponentForm ): model = Interface field_order = ( - 'name_pattern', 'label_pattern', 'type', 'enabled', 'mtu', 'mgmt_only', 'mark_connected', 'description', 'tags', + 'name_pattern', 'label_pattern', 'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'mark_connected', 'description', 'tags', ) diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index 69fa6eb3a..3d73ada47 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -11,7 +11,7 @@ from ipam.models import ASN, VLAN, VRF from tenancy.models import Tenant from utilities.forms import ( add_blank_choice, BulkEditForm, BulkEditNullBooleanSelect, ColorField, CommentField, DynamicModelChoiceField, - DynamicModelMultipleChoiceField, form_from_model, SmallTextarea, StaticSelect, + DynamicModelMultipleChoiceField, form_from_model, SmallTextarea, StaticSelect, SelectSpeedWidget, ) __all__ = ( @@ -1028,7 +1028,7 @@ class PowerOutletBulkEditForm( class InterfaceBulkEditForm( form_from_model(Interface, [ - 'label', 'type', 'parent', 'bridge', 'lag', 'mac_address', 'wwn', 'mtu', 'mgmt_only', 'mark_connected', + 'label', 'type', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'mtu', 'mgmt_only', 'mark_connected', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', ]), AddRemoveTagsForm, @@ -1064,6 +1064,11 @@ class InterfaceBulkEditForm( }, label='LAG' ) + speed = forms.IntegerField( + required=False, + widget=SelectSpeedWidget(attrs={'readonly': None}), + label='Speed' + ) mgmt_only = forms.NullBooleanField( required=False, widget=BulkEditNullBooleanSelect, @@ -1089,7 +1094,7 @@ class InterfaceBulkEditForm( class Meta: nullable_fields = [ - 'label', 'parent', 'bridge', 'lag', 'mac_address', 'wwn', 'mtu', 'description', 'mode', 'rf_channel', + 'label', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'mtu', 'description', 'mode', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'vrf', ] diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py index fce98f7cb..ef8d79082 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -618,6 +618,10 @@ class InterfaceCSVForm(CustomFieldModelCSVForm): choices=InterfaceTypeChoices, help_text='Physical medium' ) + duplex = CSVChoiceField( + choices=InterfaceDuplexChoices, + help_text='Duplex' + ) mode = CSVChoiceField( choices=InterfaceModeChoices, required=False, @@ -638,7 +642,7 @@ class InterfaceCSVForm(CustomFieldModelCSVForm): class Meta: model = Interface fields = ( - 'device', 'name', 'label', 'parent', 'bridge', 'lag', 'type', 'enabled', 'mark_connected', 'mac_address', + 'device', 'name', 'label', 'parent', 'bridge', 'lag', 'type', 'speed', 'duplex', 'enabled', 'mark_connected', 'mac_address', 'wwn', 'mtu', 'mgmt_only', 'description', 'mode', 'vrf', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', ) diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index c231f56df..188a5f242 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -10,7 +10,7 @@ from ipam.models import ASN, VRF from tenancy.forms import TenancyFilterForm from utilities.forms import ( APISelectMultiple, add_blank_choice, ColorField, DynamicModelMultipleChoiceField, FilterForm, StaticSelect, - StaticSelectMultiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, + StaticSelectMultiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, SelectSpeedWidget, ) from wireless.choices import * @@ -920,7 +920,7 @@ class InterfaceFilterForm(DeviceComponentFilterForm): model = Interface field_groups = [ ['q', 'tag'], - ['name', 'label', 'kind', 'type', 'enabled', 'mgmt_only'], + ['name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only'], ['vrf_id', 'mac_address', 'wwn'], ['rf_role', 'rf_channel', 'rf_channel_width', 'tx_power'], ['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'], @@ -935,6 +935,16 @@ class InterfaceFilterForm(DeviceComponentFilterForm): required=False, widget=StaticSelectMultiple() ) + speed = forms.IntegerField( + required=False, + label='Select Speed', + widget=SelectSpeedWidget(attrs={'readonly': None}) + ) + duplex = forms.ChoiceField( + choices=InterfaceDuplexChoices, + required=False, + label='Select Duplex' + ) enabled = forms.NullBooleanField( required=False, widget=StaticSelect( diff --git a/netbox/dcim/forms/models.py b/netbox/dcim/forms/models.py index 801659574..07fa07e12 100644 --- a/netbox/dcim/forms/models.py +++ b/netbox/dcim/forms/models.py @@ -14,7 +14,7 @@ from tenancy.forms import TenancyForm from utilities.forms import ( APISelect, add_blank_choice, BootstrapMixin, ClearableFileInput, CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, NumericArrayField, SelectWithPK, SmallTextarea, - SlugField, StaticSelect, + SlugField, StaticSelect, SelectSpeedWidget, ) from virtualization.models import Cluster, ClusterGroup from wireless.models import WirelessLAN, WirelessLANGroup @@ -1274,12 +1274,12 @@ class InterfaceForm(InterfaceCommonForm, CustomFieldModelForm): class Meta: model = Interface fields = [ - 'device', 'name', 'label', 'type', 'enabled', 'parent', 'bridge', 'lag', 'mac_address', 'wwn', 'mtu', + 'device', 'name', 'label', 'type', 'speed', 'duplex', 'enabled', 'parent', 'bridge', 'lag', 'mac_address', 'wwn', 'mtu', 'mgmt_only', 'mark_connected', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'wireless_lans', 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags', ] fieldsets = ( - ('Interface', ('device', 'name', 'type', 'label', 'description', 'tags')), + ('Interface', ('device', 'name', 'type', 'speed', 'duplex', 'label', 'description', 'tags')), ('Addressing', ('vrf', 'mac_address', 'wwn')), ('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')), ('Related Interfaces', ('parent', 'bridge', 'lag')), @@ -1295,6 +1295,7 @@ class InterfaceForm(InterfaceCommonForm, CustomFieldModelForm): 'mode': StaticSelect(), 'rf_role': StaticSelect(), 'rf_channel': StaticSelect(), + 'speed': SelectSpeedWidget(attrs={'readonly': None}), } labels = { 'mode': '802.1Q Mode', diff --git a/netbox/dcim/migrations/0150_interface_speed_duplex.py b/netbox/dcim/migrations/0150_interface_speed_duplex.py new file mode 100644 index 000000000..f9517107a --- /dev/null +++ b/netbox/dcim/migrations/0150_interface_speed_duplex.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.10 on 2022-01-08 18:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0149_interface_vrf'), + ] + + operations = [ + migrations.AddField( + model_name='interface', + name='duplex', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AddField( + model_name='interface', + name='speed', + field=models.PositiveIntegerField(blank=True, null=True), + ), + ] diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 916161ced..d876e7755 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -551,6 +551,18 @@ class Interface(ModularComponentModel, BaseInterface, LinkTermination, PathEndpo verbose_name='Management only', help_text='This interface is used only for out-of-band management' ) + speed = models.PositiveIntegerField( + verbose_name='Speed', + blank=True, + null=True + ) + duplex = models.CharField( + verbose_name='Duplex', + max_length=50, + blank=True, + null=True, + choices=InterfaceDuplexChoices + ) wwn = WWNField( null=True, blank=True, diff --git a/netbox/templates/dcim/interface.html b/netbox/templates/dcim/interface.html index bc9611992..bf81a33f2 100644 --- a/netbox/templates/dcim/interface.html +++ b/netbox/templates/dcim/interface.html @@ -46,6 +46,14 @@ Type {{ object.get_type_display }} + + Speed + {{ object.speed|humanize_speed|placeholder }} + + + Duplex + {{ object.get_duplex_display }} + Enabled {% checkmark object.enabled %} diff --git a/netbox/templates/dcim/interface_edit.html b/netbox/templates/dcim/interface_edit.html index f41e5ced6..e45cdd685 100644 --- a/netbox/templates/dcim/interface_edit.html +++ b/netbox/templates/dcim/interface_edit.html @@ -16,6 +16,8 @@ {% endif %} {% render_field form.name %} {% render_field form.type %} + {% render_field form.speed %} + {% render_field form.duplex %} {% render_field form.label %} {% render_field form.description %} {% render_field form.tags %}