mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge branch 'develop' into feature
This commit is contained in:
5
.github/workflows/stale.yml
vendored
5
.github/workflows/stale.yml
vendored
@ -27,7 +27,10 @@ jobs:
|
|||||||
This issue has been automatically marked as stale because it has not had
|
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
|
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
|
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-label: 'pending closure'
|
||||||
stale-pr-message: >
|
stale-pr-message: >
|
||||||
This PR has been automatically marked as stale because it has not had
|
This PR has been automatically marked as stale because it has not had
|
||||||
|
@ -160,9 +160,9 @@ to aid in issue management.
|
|||||||
|
|
||||||
It is natural that some new issues get more attention than others. The stale
|
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
|
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
|
been overlooked. **Do not** comment on a stale issue merely to "bump" it in an
|
||||||
an effort to circumvent the bot: Doing so will not remove the stale label.
|
effort to circumvent the bot: This will result in the immediate closure of the
|
||||||
(Stale labels can be removed only by maintainers.)
|
issue, and you may be barred from participating in future discussions.
|
||||||
|
|
||||||
## Maintainer Guidance
|
## Maintainer Guidance
|
||||||
|
|
||||||
|
@ -2,6 +2,18 @@
|
|||||||
|
|
||||||
## v3.2.5 (FUTURE)
|
## 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)
|
## v3.2.4 (2022-05-31)
|
||||||
|
@ -464,7 +464,7 @@ class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
|||||||
field_name='address',
|
field_name='address',
|
||||||
lookup_expr='family'
|
lookup_expr='family'
|
||||||
)
|
)
|
||||||
parent = django_filters.CharFilter(
|
parent = MultiValueCharFilter(
|
||||||
method='search_by_parent',
|
method='search_by_parent',
|
||||||
label='Parent prefix',
|
label='Parent prefix',
|
||||||
)
|
)
|
||||||
@ -571,14 +571,16 @@ class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
|||||||
return queryset.filter(qs_filter)
|
return queryset.filter(qs_filter)
|
||||||
|
|
||||||
def search_by_parent(self, queryset, name, value):
|
def search_by_parent(self, queryset, name, value):
|
||||||
value = value.strip()
|
|
||||||
if not value:
|
if not value:
|
||||||
return queryset
|
return queryset
|
||||||
try:
|
q = Q()
|
||||||
query = str(netaddr.IPNetwork(value.strip()).cidr)
|
for prefix in value:
|
||||||
return queryset.filter(address__net_host_contained=query)
|
try:
|
||||||
except (AddrFormatError, ValueError):
|
query = str(netaddr.IPNetwork(prefix.strip()).cidr)
|
||||||
return queryset.none()
|
q |= Q(address__net_host_contained=query)
|
||||||
|
except (AddrFormatError, ValueError):
|
||||||
|
return queryset.none()
|
||||||
|
return queryset.filter(q)
|
||||||
|
|
||||||
def filter_address(self, queryset, name, value):
|
def filter_address(self, queryset, name, value):
|
||||||
try:
|
try:
|
||||||
|
@ -14,7 +14,8 @@ class ServiceTemplateTable(NetBoxTable):
|
|||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
ports = tables.Column(
|
ports = tables.Column(
|
||||||
accessor=tables.A('port_list')
|
accessor=tables.A('port_list'),
|
||||||
|
order_by=tables.A('ports'),
|
||||||
)
|
)
|
||||||
tags = columns.TagColumn(
|
tags = columns.TagColumn(
|
||||||
url_name='ipam:servicetemplate_list'
|
url_name='ipam:servicetemplate_list'
|
||||||
@ -35,7 +36,8 @@ class ServiceTable(NetBoxTable):
|
|||||||
order_by=('device', 'virtual_machine')
|
order_by=('device', 'virtual_machine')
|
||||||
)
|
)
|
||||||
ports = tables.Column(
|
ports = tables.Column(
|
||||||
accessor=tables.A('port_list')
|
accessor=tables.A('port_list'),
|
||||||
|
order_by=tables.A('ports'),
|
||||||
)
|
)
|
||||||
tags = columns.TagColumn(
|
tags = columns.TagColumn(
|
||||||
url_name='ipam:service_list'
|
url_name='ipam:service_list'
|
||||||
|
@ -823,10 +823,8 @@ class IPAddressTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_parent(self):
|
def test_parent(self):
|
||||||
params = {'parent': '10.0.0.0/24'}
|
params = {'parent': ['10.0.0.0/30', '2001:db8::/126']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8)
|
||||||
params = {'parent': '2001:db8::/64'}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
|
||||||
|
|
||||||
def test_filter_address(self):
|
def test_filter_address(self):
|
||||||
# Check IPv4 and IPv6, with and without a mask
|
# Check IPv4 and IPv6, with and without a mask
|
||||||
|
@ -7,12 +7,12 @@ from django.urls import reverse
|
|||||||
from circuits.models import Provider, Circuit
|
from circuits.models import Provider, Circuit
|
||||||
from circuits.tables import ProviderTable
|
from circuits.tables import ProviderTable
|
||||||
from dcim.filtersets import InterfaceFilterSet
|
from dcim.filtersets import InterfaceFilterSet
|
||||||
from dcim.models import Interface, Site
|
from dcim.models import Interface, Site, Device
|
||||||
from dcim.tables import SiteTable
|
from dcim.tables import SiteTable
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
from virtualization.filtersets import VMInterfaceFilterSet
|
from virtualization.filtersets import VMInterfaceFilterSet
|
||||||
from virtualization.models import VMInterface
|
from virtualization.models import VMInterface, VirtualMachine
|
||||||
from . import filtersets, forms, tables
|
from . import filtersets, forms, tables
|
||||||
from .constants import *
|
from .constants import *
|
||||||
from .models import *
|
from .models import *
|
||||||
@ -676,7 +676,19 @@ class IPAddressView(generic.ObjectView):
|
|||||||
related_ips_table = tables.IPAddressTable(related_ips, orderable=False)
|
related_ips_table = tables.IPAddressTable(related_ips, orderable=False)
|
||||||
related_ips_table.configure(request)
|
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 {
|
return {
|
||||||
'parent_prefixes_table': parent_prefixes_table,
|
'parent_prefixes_table': parent_prefixes_table,
|
||||||
|
@ -18,25 +18,25 @@
|
|||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu" aria-labeled-by="add-components">
|
<ul class="dropdown-menu" aria-labeled-by="add-components">
|
||||||
{% if perms.dcim.add_consoleport %}
|
{% 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 %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_consoleserverport %}
|
{% 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 %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_powerport %}
|
{% 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 %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_poweroutlet %}
|
{% 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 %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_interface %}
|
{% 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 %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_frontport %}
|
{% 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 %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_rearport %}
|
{% 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 %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -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>
|
<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>
|
<p>Prefixes</p>
|
||||||
</div>
|
</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">
|
<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>
|
<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>
|
<p>IP addresses</p>
|
||||||
|
@ -18,7 +18,7 @@ class ContactGroupTable(NetBoxTable):
|
|||||||
)
|
)
|
||||||
contact_count = columns.LinkedCountColumn(
|
contact_count = columns.LinkedCountColumn(
|
||||||
viewname='tenancy:contact_list',
|
viewname='tenancy:contact_list',
|
||||||
url_params={'role_id': 'pk'},
|
url_params={'group_id': 'pk'},
|
||||||
verbose_name='Contacts'
|
verbose_name='Contacts'
|
||||||
)
|
)
|
||||||
tags = columns.TagColumn(
|
tags = columns.TagColumn(
|
||||||
|
@ -3,7 +3,7 @@ from django.shortcuts import get_object_or_404
|
|||||||
|
|
||||||
from circuits.models import Circuit
|
from circuits.models import Circuit
|
||||||
from dcim.models import Cable, Device, Location, Rack, RackReservation, Site
|
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 netbox.views import generic
|
||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
from virtualization.models import VirtualMachine, Cluster
|
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(),
|
'location_count': Location.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
|
||||||
'device_count': Device.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(),
|
'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(),
|
'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(),
|
'ipaddress_count': IPAddress.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
|
||||||
'vlan_count': VLAN.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(),
|
'circuit_count': Circuit.objects.restrict(request.user, 'view').filter(tenant=instance).count(),
|
||||||
|
Reference in New Issue
Block a user