jeremystretch 3 лет назад
Родитель
Сommit
43b27cc052

+ 3 - 0
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

+ 2 - 2
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.

+ 4 - 4
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.

+ 1 - 1
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 *

+ 2 - 2
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'),
     )
 
 

+ 4 - 2
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,

+ 4 - 0
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):
         """

+ 12 - 1
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.",

+ 2 - 1
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'))
 

+ 1 - 0
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'},

+ 5 - 1
netbox/templates/dcim/frontport.html

@@ -41,7 +41,11 @@
                         <tr>
                           <th scope="row">Color</th>
                           <td>
-                            <span class="badge color-label" style="background-color: #{{ object.color }}">&nbsp;</span>
+                            {% if object.color %}
+                              <span class="badge color-label" style="background-color: #{{ object.color }}">&nbsp;</span>
+                            {% else %}
+                              {{ ''|placeholder }}
+                            {% endif %}
                           </td>
                         </tr>
                         <tr>

+ 5 - 1
netbox/templates/dcim/rearport.html

@@ -41,7 +41,11 @@
                         <tr>
                           <th scope="row">Color</th>
                           <td>
-                            <span class="badge color-label" style="background-color: #{{ object.color }}">&nbsp;</span>
+                            {% if object.color %}
+                              <span class="badge color-label" style="background-color: #{{ object.color }}">&nbsp;</span>
+                            {% else %}
+                              {{ ''|placeholder }}
+                            {% endif %}
                           </td>
                         </tr>
                         <tr>

+ 7 - 1
netbox/templates/virtualization/virtualmachine/base.html

@@ -5,7 +5,13 @@
 
 {% block breadcrumbs %}
   {{ block.super }}
-  <li class="breadcrumb-item"><a href="{% url 'virtualization:virtualmachine_list' %}?cluster_id={{ object.cluster.pk }}">{{ object.cluster }}</a></li>
+  <li class="breadcrumb-item">
+    {% if object.cluster %}
+      <a href="{% url 'virtualization:virtualmachine_list' %}?cluster_id={{ object.cluster.pk }}">{{ object.cluster }}</a>
+    {% else %}
+      <a href="{% url 'virtualization:virtualmachine_list' %}?site_id={{ object.site.pk }}">{{ object.site }}</a>
+    {% endif %}
+  </li>
 {% endblock %}
 
 {% block extra_controls %}

+ 6 - 3
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')),