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