diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index 8ac21e04c..800c0fb90 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -39,6 +39,16 @@ PREFIX_LINK_BRIEF = """ """ +IPADDRESS_LINK = """ +{% if record.pk %} + {{ record.address }} +{% elif perms.ipam.add_ipaddress %} + {{ record.0 }} free IP{{ record.0|pluralize }} +{% else %} + {{ record.0 }} +{% endif %} +""" + STATUS_LABEL = """ {% if record.pk %} {{ record.get_status_display }} @@ -169,7 +179,7 @@ class PrefixBriefTable(BaseTable): class IPAddressTable(BaseTable): pk = ToggleColumn() - address = tables.LinkColumn('ipam:ipaddress', args=[Accessor('pk')], verbose_name='IP Address') + address = tables.TemplateColumn(IPADDRESS_LINK, verbose_name='IP Address') vrf = tables.LinkColumn('ipam:vrf', args=[Accessor('vrf.pk')], default='Global', verbose_name='VRF') tenant = tables.TemplateColumn(TENANT_LINK, verbose_name='Tenant') device = tables.LinkColumn('dcim:device', args=[Accessor('interface.device.pk')], orderable=False, diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 95aa33b1e..86fc53c74 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -1,4 +1,4 @@ -from netaddr import IPSet +from netaddr import IPNetwork, IPSet from django_tables2 import RequestConfig from django.contrib.auth.mixins import PermissionRequiredMixin @@ -31,6 +31,41 @@ def add_available_prefixes(parent, prefix_list): return prefix_list +def add_available_ipaddresses(prefix, ipaddress_list): + """ + Create fake IPAddress objects for all unallocated space within a prefix. + """ + + # Find all unallocated space + available_ips = IPSet(prefix) - IPSet([str(ip.address.ip) for ip in ipaddress_list]) + available_ips = [IPAddress(address=IPNetwork('{}/{}'.format(ip, prefix.prefixlen))) for ip in available_ips] + + # Concatenate and sort complete list of children + ipaddress_list = list(ipaddress_list) + available_ips + ipaddress_list.sort(key=lambda ip: ip.address) + if not ipaddress_list: + return [] + + # Summarize free IPs in the list + computed_list = [] + count = 0 + prev_ip = ipaddress_list[0] + for ip in ipaddress_list: + if ip.pk: + if count: + computed_list.append((count, prev_ip)) + count = 0 + computed_list.append(ip) + continue + if not count: + prev_ip = ip + count += 1 + if count: + computed_list.append((count, prev_ip)) + + return computed_list + + # # VRFs # @@ -375,6 +410,7 @@ def prefix_ipaddresses(request, pk): # Find all IPAddresses belonging to this Prefix ipaddresses = IPAddress.objects.filter(vrf=prefix.vrf, address__net_contained_or_equal=str(prefix.prefix))\ .select_related('vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for') + ipaddresses = add_available_ipaddresses(prefix.prefix, ipaddresses) ip_table = tables.IPAddressTable(ipaddresses) ip_table.model = IPAddress