diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 7390ec1df..57666417a 100644 --- a/.github/workflows/stale.yml +++ b/.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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c01adf4c9..1b4733cbe 100644 --- a/CONTRIBUTING.md +++ b/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 diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index ea5e580b8..339081902 100644 --- a/docs/release-notes/version-3.2.md +++ b/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) diff --git a/netbox/ipam/filtersets.py b/netbox/ipam/filtersets.py index a445022ca..d9cf6eefc 100644 --- a/netbox/ipam/filtersets.py +++ b/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: diff --git a/netbox/ipam/tables/services.py b/netbox/ipam/tables/services.py index 8c81a28c2..58d0a9aff 100644 --- a/netbox/ipam/tables/services.py +++ b/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' diff --git a/netbox/ipam/tests/test_filtersets.py b/netbox/ipam/tests/test_filtersets.py index 198f9d62d..d98fe889e 100644 --- a/netbox/ipam/tests/test_filtersets.py +++ b/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 diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index d89d6a711..6682fc920 100644 --- a/netbox/ipam/views.py +++ b/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, diff --git a/netbox/templates/dcim/module.html b/netbox/templates/dcim/module.html index 130cd046f..f2dac38f2 100644 --- a/netbox/templates/dcim/module.html +++ b/netbox/templates/dcim/module.html @@ -18,25 +18,25 @@ diff --git a/netbox/templates/tenancy/tenant.html b/netbox/templates/tenancy/tenant.html index e4c1db006..52c13e1aa 100644 --- a/netbox/templates/tenancy/tenant.html +++ b/netbox/templates/tenancy/tenant.html @@ -77,6 +77,10 @@

{{ stats.prefix_count }}

Prefixes

+
+

{{ stats.iprange_count }}

+

IP Ranges

+

{{ stats.ipaddress_count }}

IP addresses

diff --git a/netbox/tenancy/tables/contacts.py b/netbox/tenancy/tables/contacts.py index 17abc5a5b..234dc2ad7 100644 --- a/netbox/tenancy/tables/contacts.py +++ b/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( diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index 58ad98e8f..f6f95b123 100644 --- a/netbox/tenancy/views.py +++ b/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(),