1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

#7853 - Initial work on Speed/Duplex.

TODO: Documentation, Tests, Form order
This commit is contained in:
Daniel Sheppard
2022-01-08 12:25:30 -06:00
parent 447a5f01a9
commit 0f58faaddb
12 changed files with 93 additions and 12 deletions

View File

@ -721,6 +721,7 @@ class InterfaceSerializer(PrimaryModelSerializer, LinkTerminationSerializer, Con
bridge = NestedInterfaceSerializer(required=False, allow_null=True) bridge = NestedInterfaceSerializer(required=False, allow_null=True)
lag = NestedInterfaceSerializer(required=False, allow_null=True) lag = NestedInterfaceSerializer(required=False, allow_null=True)
mode = ChoiceField(choices=InterfaceModeChoices, allow_blank=True, required=False) 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_role = ChoiceField(choices=WirelessRoleChoices, required=False, allow_null=True)
rf_channel = ChoiceField(choices=WirelessChannelChoices, required=False) rf_channel = ChoiceField(choices=WirelessChannelChoices, required=False)
untagged_vlan = NestedVLANSerializer(required=False, allow_null=True) untagged_vlan = NestedVLANSerializer(required=False, allow_null=True)
@ -746,7 +747,7 @@ class InterfaceSerializer(PrimaryModelSerializer, LinkTerminationSerializer, Con
model = Interface model = Interface
fields = [ fields = [
'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'enabled', 'parent', 'bridge', 'lag', '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', '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', 'cable', 'wireless_link', 'link_peer', 'link_peer_type', 'wireless_lans', 'vrf', 'connected_endpoint',
'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created', 'connected_endpoint_type', 'connected_endpoint_reachable', 'tags', 'custom_fields', 'created',

View File

@ -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): class InterfaceModeChoices(ChoiceSet):
MODE_ACCESS = 'access' MODE_ACCESS = 'access'

View File

@ -1196,6 +1196,8 @@ class InterfaceFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableT
queryset=Interface.objects.all(), queryset=Interface.objects.all(),
label='LAG interface (ID)', label='LAG interface (ID)',
) )
speed = MultiValueNumberFilter()
duplex = django_filters.CharFilter()
mac_address = MultiValueMACAddressFilter() mac_address = MultiValueMACAddressFilter()
wwn = MultiValueWWNFilter() wwn = MultiValueWWNFilter()
tag = TagFilter() tag = TagFilter()

View File

@ -72,12 +72,12 @@ class PowerOutletBulkCreateForm(
class InterfaceBulkCreateForm( 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 DeviceBulkAddComponentForm
): ):
model = Interface model = Interface
field_order = ( 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',
) )

View File

@ -11,7 +11,7 @@ from ipam.models import ASN, VLAN, VRF
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.forms import ( from utilities.forms import (
add_blank_choice, BulkEditForm, BulkEditNullBooleanSelect, ColorField, CommentField, DynamicModelChoiceField, add_blank_choice, BulkEditForm, BulkEditNullBooleanSelect, ColorField, CommentField, DynamicModelChoiceField,
DynamicModelMultipleChoiceField, form_from_model, SmallTextarea, StaticSelect, DynamicModelMultipleChoiceField, form_from_model, SmallTextarea, StaticSelect, SelectSpeedWidget,
) )
__all__ = ( __all__ = (
@ -1028,7 +1028,7 @@ class PowerOutletBulkEditForm(
class InterfaceBulkEditForm( class InterfaceBulkEditForm(
form_from_model(Interface, [ 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', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power',
]), ]),
AddRemoveTagsForm, AddRemoveTagsForm,
@ -1064,6 +1064,11 @@ class InterfaceBulkEditForm(
}, },
label='LAG' label='LAG'
) )
speed = forms.IntegerField(
required=False,
widget=SelectSpeedWidget(attrs={'readonly': None}),
label='Speed'
)
mgmt_only = forms.NullBooleanField( mgmt_only = forms.NullBooleanField(
required=False, required=False,
widget=BulkEditNullBooleanSelect, widget=BulkEditNullBooleanSelect,
@ -1089,7 +1094,7 @@ class InterfaceBulkEditForm(
class Meta: class Meta:
nullable_fields = [ 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', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'vrf',
] ]

View File

@ -618,6 +618,10 @@ class InterfaceCSVForm(CustomFieldModelCSVForm):
choices=InterfaceTypeChoices, choices=InterfaceTypeChoices,
help_text='Physical medium' help_text='Physical medium'
) )
duplex = CSVChoiceField(
choices=InterfaceDuplexChoices,
help_text='Duplex'
)
mode = CSVChoiceField( mode = CSVChoiceField(
choices=InterfaceModeChoices, choices=InterfaceModeChoices,
required=False, required=False,
@ -638,7 +642,7 @@ class InterfaceCSVForm(CustomFieldModelCSVForm):
class Meta: class Meta:
model = Interface model = Interface
fields = ( 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', 'wwn', 'mtu', 'mgmt_only', 'description', 'mode', 'vrf', 'rf_role', 'rf_channel', 'rf_channel_frequency',
'rf_channel_width', 'tx_power', 'rf_channel_width', 'tx_power',
) )

View File

@ -10,7 +10,7 @@ from ipam.models import ASN, VRF
from tenancy.forms import TenancyFilterForm from tenancy.forms import TenancyFilterForm
from utilities.forms import ( from utilities.forms import (
APISelectMultiple, add_blank_choice, ColorField, DynamicModelMultipleChoiceField, FilterForm, StaticSelect, 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 * from wireless.choices import *
@ -920,7 +920,7 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
model = Interface model = Interface
field_groups = [ field_groups = [
['q', 'tag'], ['q', 'tag'],
['name', 'label', 'kind', 'type', 'enabled', 'mgmt_only'], ['name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only'],
['vrf_id', 'mac_address', 'wwn'], ['vrf_id', 'mac_address', 'wwn'],
['rf_role', 'rf_channel', 'rf_channel_width', 'tx_power'], ['rf_role', 'rf_channel', 'rf_channel_width', 'tx_power'],
['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'], ['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
@ -935,6 +935,16 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
required=False, required=False,
widget=StaticSelectMultiple() 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( enabled = forms.NullBooleanField(
required=False, required=False,
widget=StaticSelect( widget=StaticSelect(

View File

@ -14,7 +14,7 @@ from tenancy.forms import TenancyForm
from utilities.forms import ( from utilities.forms import (
APISelect, add_blank_choice, BootstrapMixin, ClearableFileInput, CommentField, ContentTypeChoiceField, APISelect, add_blank_choice, BootstrapMixin, ClearableFileInput, CommentField, ContentTypeChoiceField,
DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, NumericArrayField, SelectWithPK, SmallTextarea, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, NumericArrayField, SelectWithPK, SmallTextarea,
SlugField, StaticSelect, SlugField, StaticSelect, SelectSpeedWidget,
) )
from virtualization.models import Cluster, ClusterGroup from virtualization.models import Cluster, ClusterGroup
from wireless.models import WirelessLAN, WirelessLANGroup from wireless.models import WirelessLAN, WirelessLANGroup
@ -1274,12 +1274,12 @@ class InterfaceForm(InterfaceCommonForm, CustomFieldModelForm):
class Meta: class Meta:
model = Interface model = Interface
fields = [ 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', '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', 'rf_channel_width', 'tx_power', 'wireless_lans', 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
] ]
fieldsets = ( fieldsets = (
('Interface', ('device', 'name', 'type', 'label', 'description', 'tags')), ('Interface', ('device', 'name', 'type', 'speed', 'duplex', 'label', 'description', 'tags')),
('Addressing', ('vrf', 'mac_address', 'wwn')), ('Addressing', ('vrf', 'mac_address', 'wwn')),
('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')), ('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
('Related Interfaces', ('parent', 'bridge', 'lag')), ('Related Interfaces', ('parent', 'bridge', 'lag')),
@ -1295,6 +1295,7 @@ class InterfaceForm(InterfaceCommonForm, CustomFieldModelForm):
'mode': StaticSelect(), 'mode': StaticSelect(),
'rf_role': StaticSelect(), 'rf_role': StaticSelect(),
'rf_channel': StaticSelect(), 'rf_channel': StaticSelect(),
'speed': SelectSpeedWidget(attrs={'readonly': None}),
} }
labels = { labels = {
'mode': '802.1Q Mode', 'mode': '802.1Q Mode',

View File

@ -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),
),
]

View File

@ -551,6 +551,18 @@ class Interface(ModularComponentModel, BaseInterface, LinkTermination, PathEndpo
verbose_name='Management only', verbose_name='Management only',
help_text='This interface is used only for out-of-band management' 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( wwn = WWNField(
null=True, null=True,
blank=True, blank=True,

View File

@ -46,6 +46,14 @@
<th scope="row">Type</th> <th scope="row">Type</th>
<td>{{ object.get_type_display }}</td> <td>{{ object.get_type_display }}</td>
</tr> </tr>
<tr>
<th scope="row">Speed</th>
<td>{{ object.speed|humanize_speed|placeholder }}</td>
</tr>
<tr>
<th scope="row">Duplex</th>
<td>{{ object.get_duplex_display }}</td>
</tr>
<tr> <tr>
<th scope="row">Enabled</th> <th scope="row">Enabled</th>
<td>{% checkmark object.enabled %}</td> <td>{% checkmark object.enabled %}</td>

View File

@ -16,6 +16,8 @@
{% endif %} {% endif %}
{% render_field form.name %} {% render_field form.name %}
{% render_field form.type %} {% render_field form.type %}
{% render_field form.speed %}
{% render_field form.duplex %}
{% render_field form.label %} {% render_field form.label %}
{% render_field form.description %} {% render_field form.description %}
{% render_field form.tags %} {% render_field form.tags %}