diff --git a/docs/models/dcim/interface.md b/docs/models/dcim/interface.md index e3237c2ee..87a1411b9 100644 --- a/docs/models/dcim/interface.md +++ b/docs/models/dcim/interface.md @@ -13,6 +13,9 @@ Physical interfaces may be arranged into a link aggregation group (LAG) and asso ### Power over Ethernet (PoE) +!!! note + This feature was added in NetBox v3.3. + Physical interfaces can be assigned a PoE mode to indicate PoE capability: power supplying equipment (PSE) or powered device (PD). Additionally, a PoE mode may be specified. This can be one of the listed IEEE 802.3 standards, or a passive setting (24 or 48 volts across two or four pairs). ### Wireless Interfaces diff --git a/docs/models/ipam/l2vpn.md b/docs/models/ipam/l2vpn.md index 9f9b4703c..214b8aab0 100644 --- a/docs/models/ipam/l2vpn.md +++ b/docs/models/ipam/l2vpn.md @@ -17,5 +17,5 @@ Each L2VPN instance must have one of the following type associated with it: * MPLS-EVPN * PBB-EVPN -!!!note - Choosing VPWS, EPL, EP-LAN, EP-TREE will result in only being able to add 2 terminations to a given L2VPN. +!!! note + Choosing VPWS, EPL, EP-LAN, EP-TREE will result in only being able to add two terminations to a given L2VPN. diff --git a/docs/models/ipam/l2vpntermination.md b/docs/models/ipam/l2vpntermination.md index cc1843639..e7c6a6810 100644 --- a/docs/models/ipam/l2vpntermination.md +++ b/docs/models/ipam/l2vpntermination.md @@ -1,15 +1,15 @@ # L2VPN Termination -A L2VPN Termination is the termination point of a L2VPN. Certain types of L2VPN's may only have 2 termination points (point-to-point) while others may have many terminations (multipoint). +A L2VPN Termination is the termination point of a L2VPN. Certain types of L2VPNs may only have 2 termination points (point-to-point) while others may have many terminations (multipoint). Each termination consists of a L2VPN it is a member of as well as the connected endpoint which can be an interface or a VLAN. -The following types of L2VPN's are considered point-to-point: +The following types of L2VPNs are considered point-to-point: * VPWS * EPL * EP-LAN * EP-TREE -!!!note - Choosing any of the above types of L2VPN's will result in only being able to add 2 terminations to a given L2VPN. +!!! note + Choosing any of the above types will result in only being able to add 2 terminations to a given L2VPN. diff --git a/netbox/circuits/filtersets.py b/netbox/circuits/filtersets.py index 8005c0afe..cee38fb18 100644 --- a/netbox/circuits/filtersets.py +++ b/netbox/circuits/filtersets.py @@ -4,7 +4,7 @@ from django.db.models import Q from dcim.filtersets import CabledObjectFilterSet from dcim.models import Region, Site, SiteGroup from ipam.models import ASN -from netbox.filtersets import ChangeLoggedModelFilterSet, NetBoxModelFilterSet, OrganizationalModelFilterSet +from netbox.filtersets import NetBoxModelFilterSet, OrganizationalModelFilterSet from tenancy.filtersets import ContactModelFilterSet, TenancyFilterSet from utilities.filters import TreeNodeMultipleChoiceFilter from .choices import * diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 1a66312da..1fe21ed4b 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -1031,8 +1031,8 @@ class InterfacePoEModeChoices(ChoiceSet): MODE_PSE = 'pse' CHOICES = ( - (MODE_PD, 'Powered device (PD)'), - (MODE_PSE, 'Power sourcing equipment (PSE)'), + (MODE_PD, 'PD'), + (MODE_PSE, 'PSE'), ) diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index b4ab226ae..6d51302d3 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -1073,13 +1073,15 @@ class InterfaceBulkEditForm( choices=add_blank_choice(InterfacePoEModeChoices), required=False, initial='', - widget=StaticSelect() + widget=StaticSelect(), + label='PoE mode' ) poe_type = forms.ChoiceField( choices=add_blank_choice(InterfacePoETypeChoices), required=False, initial='', - widget=StaticSelect() + widget=StaticSelect(), + label='PoE type' ) mark_connected = forms.NullBooleanField( required=False, diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 9645efdbf..8f62b0626 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -227,6 +227,10 @@ class PathEndpoint(models.Model): # Return the path as a list of three-tuples (A termination(s), cable(s), B termination(s)) return list(zip(*[iter(path)] * 3)) + @property + def path(self): + return self._path + @cached_property def connected_endpoints(self): """ diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 43b84974b..6075cb5a0 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -1,3 +1,4 @@ +import decimal from collections import OrderedDict import yaml @@ -279,6 +280,12 @@ class DeviceType(NetBoxModel): def clean(self): super().clean() + # U height must be divisible by 0.5 + if self.u_height % decimal.Decimal(0.5): + raise ValidationError({ + 'u_height': "U height must be in increments of 0.5 rack units." + }) + # If editing an existing DeviceType to have a larger u_height, first validate that *all* instances of it have # room to expand within their racks. This validation will impose a very high performance penalty when there are # many instances to check, but increasing the u_height of a DeviceType should be a very rare occurrence. @@ -811,7 +818,11 @@ class Device(NetBoxModel, ConfigContextModel): 'position': "Cannot select a rack position without assigning a rack.", }) - # Validate position/face combination + # Validate rack position and face + if self.position and self.position % decimal.Decimal(0.5): + raise ValidationError({ + 'position': "Position must be in increments of 0.5 rack units." + }) if self.position and not self.face: raise ValidationError({ 'face': "Must specify rack face when defining rack position.", diff --git a/netbox/dcim/svg/racks.py b/netbox/dcim/svg/racks.py index 920bd662f..f228c416b 100644 --- a/netbox/dcim/svg/racks.py +++ b/netbox/dcim/svg/racks.py @@ -260,13 +260,14 @@ class RackElevationSVG: ) for ru in range(0, self.rack.u_height): + unit = ru + 1 if self.rack.desc_units else self.rack.u_height - ru y_offset = RACK_ELEVATION_BORDER_WIDTH + ru * self.unit_height text_coords = ( x_offset + self.unit_width / 2, y_offset + self.unit_height / 2 ) - link = Hyperlink(href=url_string.format(ru), target='_blank') + link = Hyperlink(href=url_string.format(unit), target='_blank') link.add(Rect((x_offset, y_offset), (self.unit_width, self.unit_height), class_='slot')) link.add(Text('add device', insert=text_coords, class_='add-device')) diff --git a/netbox/dcim/tables/sites.py b/netbox/dcim/tables/sites.py index e452badea..5dc2aa611 100644 --- a/netbox/dcim/tables/sites.py +++ b/netbox/dcim/tables/sites.py @@ -125,6 +125,7 @@ class LocationTable(TenancyColumnsMixin, NetBoxTable): site = tables.Column( linkify=True ) + status = columns.ChoiceFieldColumn() rack_count = columns.LinkedCountColumn( viewname='dcim:rack_list', url_params={'location_id': 'pk'}, diff --git a/netbox/templates/dcim/frontport.html b/netbox/templates/dcim/frontport.html index 4cea7989b..e5f1df5ae 100644 --- a/netbox/templates/dcim/frontport.html +++ b/netbox/templates/dcim/frontport.html @@ -41,7 +41,11 @@ Color -   + {% if object.color %} +   + {% else %} + {{ ''|placeholder }} + {% endif %} diff --git a/netbox/templates/dcim/rearport.html b/netbox/templates/dcim/rearport.html index 3caae49c3..ae7b55316 100644 --- a/netbox/templates/dcim/rearport.html +++ b/netbox/templates/dcim/rearport.html @@ -41,7 +41,11 @@ Color -   + {% if object.color %} +   + {% else %} + {{ ''|placeholder }} + {% endif %} diff --git a/netbox/templates/virtualization/virtualmachine/base.html b/netbox/templates/virtualization/virtualmachine/base.html index 0c2f43de8..946467e31 100644 --- a/netbox/templates/virtualization/virtualmachine/base.html +++ b/netbox/templates/virtualization/virtualmachine/base.html @@ -5,7 +5,13 @@ {% block breadcrumbs %} {{ block.super }} - + {% endblock %} {% block extra_controls %} diff --git a/netbox/virtualization/forms/models.py b/netbox/virtualization/forms/models.py index 018b50c99..a60d15281 100644 --- a/netbox/virtualization/forms/models.py +++ b/netbox/virtualization/forms/models.py @@ -166,7 +166,8 @@ class ClusterRemoveDevicesForm(ConfirmationForm): class VirtualMachineForm(TenancyForm, NetBoxModelForm): site = DynamicModelChoiceField( - queryset=Site.objects.all() + queryset=Site.objects.all(), + required=False ) cluster_group = DynamicModelChoiceField( queryset=ClusterGroup.objects.all(), @@ -178,6 +179,7 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm): ) cluster = DynamicModelChoiceField( queryset=Cluster.objects.all(), + required=False, query_params={ 'site_id': '$site', 'group_id': '$cluster_group', @@ -188,7 +190,8 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm): required=False, query_params={ 'cluster_id': '$cluster' - } + }, + help_text="Optionally pin this VM to a specific host device within the cluster" ) role = DynamicModelChoiceField( queryset=DeviceRole.objects.all(), @@ -208,7 +211,7 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm): fieldsets = ( ('Virtual Machine', ('name', 'role', 'status', 'tags')), - ('Cluster', ('site', 'cluster_group', 'cluster', 'device')), + ('Site/Cluster', ('site', 'cluster_group', 'cluster', 'device')), ('Tenancy', ('tenant_group', 'tenant')), ('Management', ('platform', 'primary_ip4', 'primary_ip6')), ('Resources', ('vcpus', 'memory', 'disk')),