From 8e1535f7ecba14aa9259b0ad62f0eabeccc12d82 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 12 Oct 2021 10:46:41 -0400 Subject: [PATCH] Add RF channel fields to Interface --- netbox/dcim/api/serializers.py | 10 +- netbox/dcim/choices.py | 131 ++++++++++++++++++++++ netbox/dcim/constants.py | 1 + netbox/dcim/filtersets.py | 5 +- netbox/dcim/forms/bulk_edit.py | 6 +- netbox/dcim/forms/bulk_import.py | 2 +- netbox/dcim/forms/filtersets.py | 11 ++ netbox/dcim/forms/models.py | 5 +- netbox/dcim/forms/object_create.py | 15 ++- netbox/dcim/migrations/0136_wireless.py | 21 ++++ netbox/dcim/models/device_components.py | 18 +++ netbox/dcim/tables/devices.py | 4 +- netbox/templates/dcim/interface.html | 10 ++ netbox/templates/dcim/interface_edit.html | 10 ++ 14 files changed, 236 insertions(+), 13 deletions(-) create mode 100644 netbox/dcim/migrations/0136_wireless.py diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index d6e44c281..edd73b87e 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -632,6 +632,8 @@ class InterfaceSerializer(PrimaryModelSerializer, CableTerminationSerializer, Co parent = NestedInterfaceSerializer(required=False, allow_null=True) lag = NestedInterfaceSerializer(required=False, allow_null=True) mode = ChoiceField(choices=InterfaceModeChoices, allow_blank=True, required=False) + rf_channel = ChoiceField(choices=WirelessChannelChoices) + rf_channel_width = ChoiceField(choices=WirelessChannelWidthChoices) untagged_vlan = NestedVLANSerializer(required=False, allow_null=True) tagged_vlans = SerializedPKRelatedField( queryset=VLAN.objects.all(), @@ -646,10 +648,10 @@ class InterfaceSerializer(PrimaryModelSerializer, CableTerminationSerializer, Co model = Interface fields = [ 'id', 'url', 'display', 'device', 'name', 'label', 'type', 'enabled', 'parent', 'lag', 'mtu', 'mac_address', - 'wwn', 'mgmt_only', 'description', 'mode', 'untagged_vlan', 'tagged_vlans', 'mark_connected', 'cable', - 'cable_peer', 'cable_peer_type', 'connected_endpoint', 'connected_endpoint_type', - 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'last_updated', 'count_ipaddresses', - '_occupied', + 'wwn', 'mgmt_only', 'description', 'mode', 'rf_channel', 'rf_channel_width', 'untagged_vlan', + 'tagged_vlans', 'mark_connected', 'cable', 'cable_peer', 'cable_peer_type', 'connected_endpoint', + 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', + 'last_updated', 'count_ipaddresses', '_occupied', ] def validate(self, data): diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index acea294f8..9a78a74f9 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -1135,6 +1135,137 @@ class CableLengthUnitChoices(ChoiceSet): ) +# +# Wireless +# + +class WirelessChannelChoices(ChoiceSet): + CHANNEL_AUTO = 'auto' + + # 2.4 GHz + CHANNEL_24G_1 = '2.4g-1' + CHANNEL_24G_2 = '2.4g-2' + CHANNEL_24G_3 = '2.4g-3' + CHANNEL_24G_4 = '2.4g-4' + CHANNEL_24G_5 = '2.4g-5' + CHANNEL_24G_6 = '2.4g-6' + CHANNEL_24G_7 = '2.4g-7' + CHANNEL_24G_8 = '2.4g-8' + CHANNEL_24G_9 = '2.4g-9' + CHANNEL_24G_10 = '2.4g-10' + CHANNEL_24G_11 = '2.4g-11' + CHANNEL_24G_12 = '2.4g-12' + CHANNEL_24G_13 = '2.4g-13' + + # 5 GHz + CHANNEL_5G_32 = '5g-32' + CHANNEL_5G_34 = '5g-34' + CHANNEL_5G_36 = '5g-36' + CHANNEL_5G_38 = '5g-38' + CHANNEL_5G_40 = '5g-40' + CHANNEL_5G_42 = '5g-42' + CHANNEL_5G_44 = '5g-44' + CHANNEL_5G_46 = '5g-46' + CHANNEL_5G_48 = '5g-48' + CHANNEL_5G_50 = '5g-50' + CHANNEL_5G_52 = '5g-52' + CHANNEL_5G_54 = '5g-54' + CHANNEL_5G_56 = '5g-56' + CHANNEL_5G_58 = '5g-58' + CHANNEL_5G_60 = '5g-60' + CHANNEL_5G_62 = '5g-62' + CHANNEL_5G_64 = '5g-64' + CHANNEL_5G_100 = '5g-100' + CHANNEL_5G_102 = '5g-102' + CHANNEL_5G_104 = '5g-104' + CHANNEL_5G_106 = '5g-106' + CHANNEL_5G_108 = '5g-108' + CHANNEL_5G_110 = '5g-110' + CHANNEL_5G_112 = '5g-112' + CHANNEL_5G_114 = '5g-114' + CHANNEL_5G_116 = '5g-116' + CHANNEL_5G_118 = '5g-118' + CHANNEL_5G_120 = '5g-120' + CHANNEL_5G_122 = '5g-122' + CHANNEL_5G_124 = '5g-124' + CHANNEL_5G_126 = '5g-126' + CHANNEL_5G_128 = '5g-128' + + CHOICES = ( + (CHANNEL_AUTO, 'Auto'), + ( + '2.4 GHz (802.11b/g/n/ax)', + ( + (CHANNEL_24G_1, '1 (2412 MHz)'), + (CHANNEL_24G_2, '2 (2417 MHz)'), + (CHANNEL_24G_3, '3 (2422 MHz)'), + (CHANNEL_24G_4, '4 (2427 MHz)'), + (CHANNEL_24G_5, '5 (2432 MHz)'), + (CHANNEL_24G_6, '6 (2437 MHz)'), + (CHANNEL_24G_7, '7 (2442 MHz)'), + (CHANNEL_24G_8, '8 (2447 MHz)'), + (CHANNEL_24G_9, '9 (2452 MHz)'), + (CHANNEL_24G_10, '10 (2457 MHz)'), + (CHANNEL_24G_11, '11 (2462 MHz)'), + (CHANNEL_24G_12, '12 (2467 MHz)'), + (CHANNEL_24G_13, '13 (2472 MHz)'), + ) + ), + ( + '5 GHz (802.11a/n/ac/ax)', + ( + (CHANNEL_5G_32, '32 (5160 MHz)'), + (CHANNEL_5G_34, '34 (5170 MHz)'), + (CHANNEL_5G_36, '36 (5180 MHz)'), + (CHANNEL_5G_38, '38 (5190 MHz)'), + (CHANNEL_5G_40, '40 (5200 MHz)'), + (CHANNEL_5G_42, '42 (5210 MHz)'), + (CHANNEL_5G_44, '44 (5220 MHz)'), + (CHANNEL_5G_46, '46 (5230 MHz)'), + (CHANNEL_5G_48, '48 (5240 MHz)'), + (CHANNEL_5G_50, '50 (5250 MHz)'), + (CHANNEL_5G_52, '52 (5260 MHz)'), + (CHANNEL_5G_54, '54 (5270 MHz)'), + (CHANNEL_5G_56, '56 (5280 MHz)'), + (CHANNEL_5G_58, '58 (5290 MHz)'), + (CHANNEL_5G_60, '60 (5300 MHz)'), + (CHANNEL_5G_62, '62 (5310 MHz)'), + (CHANNEL_5G_64, '64 (5320 MHz)'), + (CHANNEL_5G_100, '100 (5500 MHz)'), + (CHANNEL_5G_102, '102 (5510 MHz)'), + (CHANNEL_5G_104, '104 (5520 MHz)'), + (CHANNEL_5G_106, '106 (5530 MHz)'), + (CHANNEL_5G_108, '108 (5540 MHz)'), + (CHANNEL_5G_110, '110 (5550 MHz)'), + (CHANNEL_5G_112, '112 (5560 MHz)'), + (CHANNEL_5G_114, '114 (5570 MHz)'), + (CHANNEL_5G_116, '116 (5580 MHz)'), + (CHANNEL_5G_118, '118 (5590 MHz)'), + (CHANNEL_5G_120, '120 (5600 MHz)'), + (CHANNEL_5G_122, '122 (5610 MHz)'), + (CHANNEL_5G_124, '124 (5620 MHz)'), + (CHANNEL_5G_126, '126 (5630 MHz)'), + (CHANNEL_5G_128, '128 (5640 MHz)'), + ) + ), + ) + + +class WirelessChannelWidthChoices(ChoiceSet): + + CHANNEL_WIDTH_20 = 20 + CHANNEL_WIDTH_40 = 40 + CHANNEL_WIDTH_80 = 80 + CHANNEL_WIDTH_160 = 160 + + CHOICES = ( + (CHANNEL_WIDTH_20, '20 MHz'), + (CHANNEL_WIDTH_40, '40 MHz'), + (CHANNEL_WIDTH_80, '80 MHz'), + (CHANNEL_WIDTH_160, '160 MHz'), + ) + + # # PowerFeeds # diff --git a/netbox/dcim/constants.py b/netbox/dcim/constants.py index 2a4d368f4..0d64b357b 100644 --- a/netbox/dcim/constants.py +++ b/netbox/dcim/constants.py @@ -42,6 +42,7 @@ WIRELESS_IFACE_TYPES = [ InterfaceTypeChoices.TYPE_80211N, InterfaceTypeChoices.TYPE_80211AC, InterfaceTypeChoices.TYPE_80211AD, + InterfaceTypeChoices.TYPE_80211AX, ] NONCONNECTABLE_IFACE_TYPES = VIRTUAL_IFACE_TYPES + WIRELESS_IFACE_TYPES diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 7f029097e..0c756957a 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -990,7 +990,10 @@ class InterfaceFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableT class Meta: model = Interface - fields = ['id', 'name', 'label', 'type', 'enabled', 'mtu', 'mgmt_only', 'mode', 'description'] + fields = [ + 'id', 'name', 'label', 'type', 'enabled', 'mtu', 'mgmt_only', 'mode', 'rf_channel', 'rf_channel_width', + 'description', + ] def filter_device(self, queryset, name, value): try: diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index fd87d7304..67a482a26 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -926,7 +926,7 @@ class PowerOutletBulkEditForm( class InterfaceBulkEditForm( form_from_model(Interface, [ 'label', 'type', 'parent', 'lag', 'mac_address', 'wwn', 'mtu', 'mgmt_only', 'mark_connected', 'description', - 'mode', + 'mode', 'rf_channel', 'rf_channel_width', ]), BootstrapMixin, AddRemoveTagsForm, @@ -977,8 +977,8 @@ class InterfaceBulkEditForm( class Meta: nullable_fields = [ - 'label', 'parent', 'lag', 'mac_address', 'wwn', 'mtu', 'description', 'mode', 'untagged_vlan', - 'tagged_vlans', + 'label', 'parent', 'lag', 'mac_address', 'wwn', 'mtu', 'description', 'mode', 'rf_channel', + 'rf_channel_width', 'untagged_vlan', 'tagged_vlans', ] def __init__(self, *args, **kwargs): diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py index ff9ab6fff..a2685c8e0 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -584,7 +584,7 @@ class InterfaceCSVForm(CustomFieldModelCSVForm): model = Interface fields = ( 'device', 'name', 'label', 'parent', 'lag', 'type', 'enabled', 'mark_connected', 'mac_address', 'wwn', - 'mtu', 'mgmt_only', 'description', 'mode', + 'mtu', 'mgmt_only', 'description', 'mode', 'rf_channel', 'rf_channel_width', ) def __init__(self, *args, **kwargs): diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index 4f4e10e96..605139c1b 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -963,6 +963,7 @@ class InterfaceFilterForm(DeviceComponentFilterForm): field_groups = [ ['q', 'tag'], ['name', 'label', 'type', 'enabled', 'mgmt_only', 'mac_address', 'wwn'], + ['rf_channel', 'rf_channel_width'], ['region_id', 'site_group_id', 'site_id', 'location_id', 'device_id'], ] type = forms.MultipleChoiceField( @@ -990,6 +991,16 @@ class InterfaceFilterForm(DeviceComponentFilterForm): required=False, label='WWN' ) + rf_channel = forms.MultipleChoiceField( + choices=WirelessChannelChoices, + required=False, + widget=StaticSelectMultiple() + ) + rf_channel_width = forms.MultipleChoiceField( + choices=WirelessChannelWidthChoices, + required=False, + widget=StaticSelectMultiple() + ) tag = TagFilterField(model) diff --git a/netbox/dcim/forms/models.py b/netbox/dcim/forms/models.py index a8c2991a4..435fab309 100644 --- a/netbox/dcim/forms/models.py +++ b/netbox/dcim/forms/models.py @@ -1098,12 +1098,15 @@ class InterfaceForm(BootstrapMixin, InterfaceCommonForm, CustomFieldModelForm): model = Interface fields = [ 'device', 'name', 'label', 'type', 'enabled', 'parent', 'lag', 'mac_address', 'wwn', 'mtu', 'mgmt_only', - 'mark_connected', 'description', 'mode', 'untagged_vlan', 'tagged_vlans', 'tags', + 'mark_connected', 'description', 'mode', 'rf_channel', 'rf_channel_width', 'untagged_vlan', 'tagged_vlans', + 'tags', ] widgets = { 'device': forms.HiddenInput(), 'type': StaticSelect(), 'mode': StaticSelect(), + 'rf_channel': StaticSelect(), + 'rf_channel_width': StaticSelect(), } labels = { 'mode': '802.1Q Mode', diff --git a/netbox/dcim/forms/object_create.py b/netbox/dcim/forms/object_create.py index 7577ad355..db28412e6 100644 --- a/netbox/dcim/forms/object_create.py +++ b/netbox/dcim/forms/object_create.py @@ -465,7 +465,19 @@ class InterfaceCreateForm(ComponentCreateForm, InterfaceCommonForm): mode = forms.ChoiceField( choices=add_blank_choice(InterfaceModeChoices), required=False, + widget=StaticSelect() + ) + rf_channel = forms.ChoiceField( + choices=add_blank_choice(WirelessChannelChoices), + required=False, widget=StaticSelect(), + label='Wireless channel' + ) + rf_channel_width = forms.ChoiceField( + choices=add_blank_choice(WirelessChannelWidthChoices), + required=False, + widget=StaticSelect(), + label='Channel width' ) untagged_vlan = DynamicModelChoiceField( queryset=VLAN.objects.all(), @@ -477,7 +489,8 @@ class InterfaceCreateForm(ComponentCreateForm, InterfaceCommonForm): ) field_order = ( 'device', 'name_pattern', 'label_pattern', 'type', 'enabled', 'parent', 'lag', 'mtu', 'mac_address', - 'description', 'mgmt_only', 'mark_connected', 'mode', 'untagged_vlan', 'tagged_vlans', 'tags' + 'description', 'mgmt_only', 'mark_connected', 'rf_channel', 'rf_channel_width', 'mode' 'untagged_vlan', + 'tagged_vlans', 'tags' ) def __init__(self, *args, **kwargs): diff --git a/netbox/dcim/migrations/0136_wireless.py b/netbox/dcim/migrations/0136_wireless.py new file mode 100644 index 000000000..429a72694 --- /dev/null +++ b/netbox/dcim/migrations/0136_wireless.py @@ -0,0 +1,21 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0135_location_tenant'), + ] + + operations = [ + migrations.AddField( + model_name='interface', + name='rf_channel', + field=models.CharField(blank=True, max_length=50), + ), + migrations.AddField( + model_name='interface', + name='rf_channel_width', + field=models.PositiveSmallIntegerField(blank=True, null=True), + ), + ] diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 386776b41..4e0d65f86 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -517,6 +517,18 @@ class Interface(ComponentModel, BaseInterface, CableTermination, PathEndpoint): verbose_name='WWN', help_text='64-bit World Wide Name' ) + rf_channel = models.CharField( + max_length=50, + choices=WirelessChannelChoices, + blank=True, + verbose_name='Wireless channel' + ) + rf_channel_width = models.PositiveSmallIntegerField( + choices=WirelessChannelWidthChoices, + blank=True, + null=True, + verbose_name='Channel width' + ) untagged_vlan = models.ForeignKey( to='ipam.VLAN', on_delete=models.SET_NULL, @@ -603,6 +615,12 @@ class Interface(ComponentModel, BaseInterface, CableTermination, PathEndpoint): if self.pk and self.lag_id == self.pk: raise ValidationError({'lag': "A LAG interface cannot be its own parent."}) + # RF channel attributes may be set only for wireless interfaces + if self.rf_channel and self.type not in WIRELESS_IFACE_TYPES: + raise ValidationError({'rf_channel': "Channel may be set only on wireless interfaces."}) + if self.rf_channel_width and self.type not in WIRELESS_IFACE_TYPES: + raise ValidationError({'rf_channel_width': "Channel width may be set only on wireless interfaces."}) + # Validate untagged VLAN if self.untagged_vlan and self.untagged_vlan.site not in [self.device.site, None]: raise ValidationError({ diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index c2b4b907b..1eae62a05 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -493,8 +493,8 @@ class InterfaceTable(DeviceComponentTable, BaseInterfaceTable, PathEndpointTable model = Interface fields = ( 'pk', 'name', 'device', 'label', 'enabled', 'type', 'mgmt_only', 'mtu', 'mode', 'mac_address', 'wwn', - 'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', 'ip_addresses', - 'untagged_vlan', 'tagged_vlans', + 'rf_channel', 'rf_channel_width', 'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', + 'connection', 'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans', ) default_columns = ('pk', 'name', 'device', 'label', 'enabled', 'type', 'description') diff --git a/netbox/templates/dcim/interface.html b/netbox/templates/dcim/interface.html index f9a9b0425..3283aac4f 100644 --- a/netbox/templates/dcim/interface.html +++ b/netbox/templates/dcim/interface.html @@ -39,6 +39,16 @@ Type {{ object.get_type_display }} + {% if object.is_wireless %} + + Channel + {{ object.get_rf_channel_display|placeholder }} + + + Channel Width + {{ object.get_rf_channel_width_display|placeholder }} + + {% endif %} Enabled diff --git a/netbox/templates/dcim/interface_edit.html b/netbox/templates/dcim/interface_edit.html index 041eab73a..e91c74d31 100644 --- a/netbox/templates/dcim/interface_edit.html +++ b/netbox/templates/dcim/interface_edit.html @@ -29,6 +29,16 @@ {% render_field form.mark_connected %} + {% if form.instance.is_wireless %} +
+
+
Wireless
+
+ {% render_field form.rf_channel %} + {% render_field form.rf_channel_width %} +
+ {% endif %} +
802.1Q Switching