Răsfoiți Sursa

Merge branch 'develop' into feature

jeremystretch 3 ani în urmă
părinte
comite
a74dba865c

+ 4 - 1
.github/workflows/stale.yml

@@ -27,7 +27,10 @@ jobs:
             This issue has been automatically marked as stale because it has not had
             recent activity. It will be closed if no further activity occurs. NetBox
             is governed by a small group of core maintainers which means not all opened
-            issues may receive direct feedback. Please see our [contributing guide](https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md).
+            issues may receive direct feedback. **Do not** attempt to circumvent this
+            process by "bumping" the issue; doing so will result in its immediate closure
+            and you may be barred from participating in any future discussions. Please see
+            our [contributing guide](https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md).
           stale-pr-label: 'pending closure'
           stale-pr-message: >
             This PR has been automatically marked as stale because it has not had

+ 3 - 3
CONTRIBUTING.md

@@ -160,9 +160,9 @@ to aid in issue management.
 
 It is natural that some new issues get more attention than others. The stale
 bot helps bring renewed attention to potentially valuable issues that may have
-been overlooked. **Do not** comment on an issue that has been marked stale in
-an effort to circumvent the bot: Doing so will not remove the stale label.
-(Stale labels can be removed only by maintainers.)
+been overlooked. **Do not** comment on a stale issue merely to "bump" it in an
+effort to circumvent the bot: This will result in the immediate closure of the
+issue, and you may be barred from participating in future discussions.
 
 ## Maintainer Guidance
 

+ 12 - 0
docs/release-notes/version-3.2.md

@@ -2,6 +2,18 @@
 
 ## v3.2.5 (FUTURE)
 
+### Enhancements
+
+* [#8882](https://github.com/netbox-community/netbox/issues/8882) - Support filtering IP addresses by multiple parent prefixes
+* [#8893](https://github.com/netbox-community/netbox/issues/8893) - Include count of IP ranges under tenant view
+
+### Bug Fixes
+
+* [#9480](https://github.com/netbox-community/netbox/issues/9480) - Fix sorting services & service templates by port numbers
+* [#9484](https://github.com/netbox-community/netbox/issues/9484) - Include services listening on "all IPs" under IP address view
+* [#9486](https://github.com/netbox-community/netbox/issues/9486) - Fix redirect URL when adding device components from the module view
+* [#9495](https://github.com/netbox-community/netbox/issues/9495) - Correct link to contacts in contact groups table column
+
 ---
 
 ## v3.2.4 (2022-05-31)

+ 9 - 7
netbox/ipam/filtersets.py

@@ -464,7 +464,7 @@ class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
         field_name='address',
         lookup_expr='family'
     )
-    parent = django_filters.CharFilter(
+    parent = MultiValueCharFilter(
         method='search_by_parent',
         label='Parent prefix',
     )
@@ -571,14 +571,16 @@ class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
         return queryset.filter(qs_filter)
 
     def search_by_parent(self, queryset, name, value):
-        value = value.strip()
         if not value:
             return queryset
-        try:
-            query = str(netaddr.IPNetwork(value.strip()).cidr)
-            return queryset.filter(address__net_host_contained=query)
-        except (AddrFormatError, ValueError):
-            return queryset.none()
+        q = Q()
+        for prefix in value:
+            try:
+                query = str(netaddr.IPNetwork(prefix.strip()).cidr)
+                q |= Q(address__net_host_contained=query)
+            except (AddrFormatError, ValueError):
+                return queryset.none()
+        return queryset.filter(q)
 
     def filter_address(self, queryset, name, value):
         try:

+ 4 - 2
netbox/ipam/tables/services.py

@@ -14,7 +14,8 @@ class ServiceTemplateTable(NetBoxTable):
         linkify=True
     )
     ports = tables.Column(
-        accessor=tables.A('port_list')
+        accessor=tables.A('port_list'),
+        order_by=tables.A('ports'),
     )
     tags = columns.TagColumn(
         url_name='ipam:servicetemplate_list'
@@ -35,7 +36,8 @@ class ServiceTable(NetBoxTable):
         order_by=('device', 'virtual_machine')
     )
     ports = tables.Column(
-        accessor=tables.A('port_list')
+        accessor=tables.A('port_list'),
+        order_by=tables.A('ports'),
     )
     tags = columns.TagColumn(
         url_name='ipam:service_list'

+ 2 - 4
netbox/ipam/tests/test_filtersets.py

@@ -823,10 +823,8 @@ class IPAddressTestCase(TestCase, ChangeLoggedFilterSetTests):
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
     def test_parent(self):
-        params = {'parent': '10.0.0.0/24'}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
-        params = {'parent': '2001:db8::/64'}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
+        params = {'parent': ['10.0.0.0/30', '2001:db8::/126']}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8)
 
     def test_filter_address(self):
         # Check IPv4 and IPv6, with and without a mask

+ 15 - 3
netbox/ipam/views.py

@@ -7,12 +7,12 @@ from django.urls import reverse
 from circuits.models import Provider, Circuit
 from circuits.tables import ProviderTable
 from dcim.filtersets import InterfaceFilterSet
-from dcim.models import Interface, Site
+from dcim.models import Interface, Site, Device
 from dcim.tables import SiteTable
 from netbox.views import generic
 from utilities.utils import count_related
 from virtualization.filtersets import VMInterfaceFilterSet
-from virtualization.models import VMInterface
+from virtualization.models import VMInterface, VirtualMachine
 from . import filtersets, forms, tables
 from .constants import *
 from .models import *
@@ -676,7 +676,19 @@ class IPAddressView(generic.ObjectView):
         related_ips_table = tables.IPAddressTable(related_ips, orderable=False)
         related_ips_table.configure(request)
 
-        services = Service.objects.restrict(request.user, 'view').filter(ipaddresses=instance)
+        # Find services belonging to the IP
+        service_filter = Q(ipaddresses=instance)
+
+        # Find services listening on all IPs on the assigned device/vm
+        if instance.assigned_object and instance.assigned_object.parent_object:
+            parent_object = instance.assigned_object.parent_object
+
+            if isinstance(parent_object, VirtualMachine):
+                service_filter |= (Q(virtual_machine=parent_object) & Q(ipaddresses=None))
+            elif isinstance(parent_object, Device):
+                service_filter |= (Q(device=parent_object) & Q(ipaddresses=None))
+
+        services = Service.objects.restrict(request.user, 'view').filter(service_filter)
 
         return {
             'parent_prefixes_table': parent_prefixes_table,

+ 7 - 7
netbox/templates/dcim/module.html

@@ -18,25 +18,25 @@
       </button>
       <ul class="dropdown-menu" aria-labeled-by="add-components">
         {% if perms.dcim.add_consoleport %}
-          <li><a class="dropdown-item" href="{% url 'dcim:consoleport_add' %}?device={{ object.device.pk }}&module={{ object.pk }}&return_url={% url 'dcim:device_consoleports' pk=object.pk %}">Console Ports</a></li>
+          <li><a class="dropdown-item" href="{% url 'dcim:consoleport_add' %}?device={{ object.device.pk }}&module={{ object.pk }}&return_url={% url 'dcim:device_consoleports' pk=object.device.pk %}">Console Ports</a></li>
         {% endif %}
         {% if perms.dcim.add_consoleserverport %}
-          <li><a class="dropdown-item" href="{% url 'dcim:consoleserverport_add' %}?device={{ object.device.pk }}&module={{ object.pk }}&return_url={% url 'dcim:device_consoleserverports' pk=object.pk %}">Console Server Ports</a></li>
+          <li><a class="dropdown-item" href="{% url 'dcim:consoleserverport_add' %}?device={{ object.device.pk }}&module={{ object.pk }}&return_url={% url 'dcim:device_consoleserverports' pk=object.device.pk %}">Console Server Ports</a></li>
         {% endif %}
         {% if perms.dcim.add_powerport %}
-          <li><a class="dropdown-item" href="{% url 'dcim:powerport_add' %}?device={{ object.device.pk }}&module={{ object.pk }}&return_url={% url 'dcim:device_powerports' pk=object.pk %}">Power Ports</a></li>
+          <li><a class="dropdown-item" href="{% url 'dcim:powerport_add' %}?device={{ object.device.pk }}&module={{ object.pk }}&return_url={% url 'dcim:device_powerports' pk=object.device.pk %}">Power Ports</a></li>
         {% endif %}
         {% if perms.dcim.add_poweroutlet %}
-          <li><a class="dropdown-item" href="{% url 'dcim:poweroutlet_add' %}?device={{ object.device.pk }}&module={{ object.pk }}&return_url={% url 'dcim:device_poweroutlets' pk=object.pk %}">Power Outlets</a></li>
+          <li><a class="dropdown-item" href="{% url 'dcim:poweroutlet_add' %}?device={{ object.device.pk }}&module={{ object.pk }}&return_url={% url 'dcim:device_poweroutlets' pk=object.device.pk %}">Power Outlets</a></li>
         {% endif %}
         {% if perms.dcim.add_interface %}
-          <li><a class="dropdown-item" href="{% url 'dcim:interface_add' %}?device={{ object.device.pk }}&module={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Interfaces</a></li>
+          <li><a class="dropdown-item" href="{% url 'dcim:interface_add' %}?device={{ object.device.pk }}&module={{ object.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.device.pk %}">Interfaces</a></li>
         {% endif %}
         {% if perms.dcim.add_frontport %}
-          <li><a class="dropdown-item" href="{% url 'dcim:frontport_add' %}?device={{ object.device.pk }}&module={{ object.pk }}&return_url={% url 'dcim:device_frontports' pk=object.pk %}">Front Ports</a></li>
+          <li><a class="dropdown-item" href="{% url 'dcim:frontport_add' %}?device={{ object.device.pk }}&module={{ object.pk }}&return_url={% url 'dcim:device_frontports' pk=object.device.pk %}">Front Ports</a></li>
         {% endif %}
         {% if perms.dcim.add_rearport %}
-          <li><a class="dropdown-item" href="{% url 'dcim:rearport_add' %}?device={{ object.device.pk }}&module={{ object.pk }}&return_url={% url 'dcim:device_rearports' pk=object.pk %}">Rear Ports</a></li>
+          <li><a class="dropdown-item" href="{% url 'dcim:rearport_add' %}?device={{ object.device.pk }}&module={{ object.pk }}&return_url={% url 'dcim:device_rearports' pk=object.device.pk %}">Rear Ports</a></li>
         {% endif %}
       </ul>
     </div>

+ 4 - 0
netbox/templates/tenancy/tenant.html

@@ -77,6 +77,10 @@
                     <h2><a href="{% url 'ipam:prefix_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.prefix_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.prefix_count }}</a></h2>
                     <p>Prefixes</p>
                 </div>
+                <div class="col col-md-4 text-center">
+                    <h2><a href="{% url 'ipam:iprange_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.iprange_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.iprange_count }}</a></h2>
+                    <p>IP Ranges</p>
+                </div>
                 <div class="col col-md-4 text-center">
                     <h2><a href="{% url 'ipam:ipaddress_list' %}?tenant_id={{ object.pk }}" class="stat-btn btn {% if stats.ipaddress_count %}btn-primary{% else %}btn-outline-dark{% endif %} btn-lg">{{ stats.ipaddress_count }}</a></h2>
                     <p>IP addresses</p>

+ 1 - 1
netbox/tenancy/tables/contacts.py

@@ -18,7 +18,7 @@ class ContactGroupTable(NetBoxTable):
     )
     contact_count = columns.LinkedCountColumn(
         viewname='tenancy:contact_list',
-        url_params={'role_id': 'pk'},
+        url_params={'group_id': 'pk'},
         verbose_name='Contacts'
     )
     tags = columns.TagColumn(

+ 3 - 2
netbox/tenancy/views.py

@@ -3,7 +3,7 @@ from django.shortcuts import get_object_or_404
 
 from circuits.models import Circuit
 from dcim.models import Cable, Device, Location, Rack, RackReservation, Site
-from ipam.models import Aggregate, IPAddress, Prefix, VLAN, VRF, ASN
+from ipam.models import Aggregate, IPAddress, IPRange, Prefix, VLAN, VRF, ASN
 from netbox.views import generic
 from utilities.utils import count_related
 from virtualization.models import VirtualMachine, Cluster
@@ -104,8 +104,9 @@ class TenantView(generic.ObjectView):
             'location_count': Location.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
             'device_count': Device.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
             'vrf_count': VRF.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
-            'prefix_count': Prefix.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
             'aggregate_count': Aggregate.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
+            'prefix_count': Prefix.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
+            'iprange_count': IPRange.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
             'ipaddress_count': IPAddress.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
             'vlan_count': VLAN.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
             'circuit_count': Circuit.objects.restrict(request.user, 'view').filter(tenant=instance).count(),