diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index 6859472a6..922ce6a5c 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -6,6 +6,25 @@ from utilities.tables import BaseTable, ToggleColumn from .models import Aggregate, IPAddress, Prefix, RIR, Role, VLAN, VLANGroup, VRF +RIR_UTILIZATION = """ +
+ {% if record.stats.total %} +
+ {{ record.stats.percentages.active }}% +
+
+ {{ record.stats.percentages.reserved }}% +
+
+ {{ record.stats.percentages.deprecated }}% +
+
+ {{ record.stats.percentages.available }}% +
+ {% endif %} +
+""" + RIR_ACTIONS = """ {% if perms.ipam.change_rir %} @@ -108,12 +127,17 @@ class RIRTable(BaseTable): pk = ToggleColumn() name = tables.LinkColumn(verbose_name='Name') aggregate_count = tables.Column(verbose_name='Aggregates') - slug = tables.Column(verbose_name='Slug') + stats_total = tables.Column(accessor='stats.total', verbose_name='Total') + stats_active = tables.Column(accessor='stats.active', verbose_name='Active') + stats_reserved = tables.Column(accessor='stats.reserved', verbose_name='Reserved') + stats_deprecated = tables.Column(accessor='stats.deprecated', verbose_name='Deprecated') + stats_available = tables.Column(accessor='stats.available', verbose_name='Available') + utilization = tables.TemplateColumn(template_code=RIR_UTILIZATION, verbose_name='Utilization') actions = tables.TemplateColumn(template_code=RIR_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name='') class Meta(BaseTable.Meta): model = RIR - fields = ('pk', 'name', 'aggregate_count', 'slug', 'actions') + fields = ('pk', 'name', 'aggregate_count', 'stats_total', 'stats_active', 'stats_reserved', 'stats_deprecated', 'stats_available', 'utilization', 'actions') # diff --git a/netbox/ipam/urls.py b/netbox/ipam/urls.py index 71017be26..dc5fcc964 100644 --- a/netbox/ipam/urls.py +++ b/netbox/ipam/urls.py @@ -19,8 +19,6 @@ urlpatterns = [ url(r'^rirs/$', views.RIRListView.as_view(), name='rir_list'), url(r'^rirs/add/$', views.RIREditView.as_view(), name='rir_add'), url(r'^rirs/delete/$', views.RIRBulkDeleteView.as_view(), name='rir_bulk_delete'), - url(r'^rirs/stats/$', views.rir_stats, name='rir_stats'), - url(r'^rirs/stats/ipv6/$', views.rir_stats, kwargs={'family': 6}, name='rir_stats_ipv6'), url(r'^rirs/(?P[\w-]+)/edit/$', views.RIREditView.as_view(), name='rir_edit'), # Aggregates diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 6beb22f3e..f804e565e 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -158,6 +158,79 @@ class RIRListView(ObjectListView): edit_permissions = ['ipam.change_rir', 'ipam.delete_rir'] template_name = 'ipam/rir_list.html' + def alter_queryset(self, request): + + # Count /64s for IPv6 rather than individual IPs + family = 4 + denominator = 2 ** 64 if family == 6 else 1 + + rirs = [] + for rir in self.queryset: + + stats = { + 'total': 0, + 'active': 0, + 'reserved': 0, + 'deprecated': 0, + 'available': 0, + } + aggregate_list = Aggregate.objects.filter(family=family, rir=rir) + for aggregate in aggregate_list: + + queryset = Prefix.objects.filter(prefix__net_contained_or_equal=str(aggregate.prefix)) + + # Find all consumed space for each prefix status (we ignore containers for this purpose). + active_prefixes = netaddr.cidr_merge([p.prefix for p in queryset.filter(status=PREFIX_STATUS_ACTIVE)]) + reserved_prefixes = netaddr.cidr_merge([p.prefix for p in queryset.filter(status=PREFIX_STATUS_RESERVED)]) + deprecated_prefixes = netaddr.cidr_merge([p.prefix for p in queryset.filter(status=PREFIX_STATUS_DEPRECATED)]) + + # Find all available prefixes by subtracting each of the existing prefix sets from the aggregate prefix. + available_prefixes = ( + netaddr.IPSet([aggregate.prefix]) + - netaddr.IPSet(active_prefixes) + - netaddr.IPSet(reserved_prefixes) + - netaddr.IPSet(deprecated_prefixes) + ) + + # Add the size of each metric to the RIR total. + stats['total'] += aggregate.prefix.size / denominator + stats['active'] += netaddr.IPSet(active_prefixes).size / denominator + stats['reserved'] += netaddr.IPSet(reserved_prefixes).size / denominator + stats['deprecated'] += netaddr.IPSet(deprecated_prefixes).size / denominator + stats['available'] += available_prefixes.size / denominator + + # Calculate the percentage of total space for each prefix status. + total = float(stats['total']) + stats['percentages'] = { + 'active': float('{:.2f}'.format(stats['active'] / total * 100)) if total else 0, + 'reserved': float('{:.2f}'.format(stats['reserved'] / total * 100)) if total else 0, + 'deprecated': float('{:.2f}'.format(stats['deprecated'] / total * 100)) if total else 0, + } + stats['percentages']['available'] = ( + 100 + - stats['percentages']['active'] + - stats['percentages']['reserved'] + - stats['percentages']['deprecated'] + ) + rir.stats = stats + rirs.append(rir) + + return rirs + + def extra_context(self): + + totals = { + 'total': sum([rir.stats['total'] for rir in self.queryset]), + 'active': sum([rir.stats['active'] for rir in self.queryset]), + 'reserved': sum([rir.stats['reserved'] for rir in self.queryset]), + 'deprecated': sum([rir.stats['deprecated'] for rir in self.queryset]), + 'available': sum([rir.stats['available'] for rir in self.queryset]), + } + + return { + 'totals': totals, + } + class RIREditView(PermissionRequiredMixin, ObjectEditView): permission_required = 'ipam.change_rir' @@ -656,76 +729,3 @@ class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'ipam.delete_vlan' cls = VLAN default_redirect_url = 'ipam:vlan_list' - - -# -# Miscellaneous -# - -def rir_stats(request, family=4): - - denominator = 2 ** 64 if family == 6 else 1 - - stats = OrderedDict() - for rir in RIR.objects.all(): - - stats[rir] = { - 'total': 0, - 'active': 0, - 'reserved': 0, - 'deprecated': 0, - 'available': 0, - } - aggregate_list = Aggregate.objects.filter(family=family, rir=rir) - for aggregate in aggregate_list: - - queryset = Prefix.objects.filter(prefix__net_contained_or_equal=str(aggregate.prefix)) - - # Find all consumed space for each prefix status (we ignore containers for this purpose). - active_prefixes = netaddr.cidr_merge([p.prefix for p in queryset.filter(status=PREFIX_STATUS_ACTIVE)]) - reserved_prefixes = netaddr.cidr_merge([p.prefix for p in queryset.filter(status=PREFIX_STATUS_RESERVED)]) - deprecated_prefixes = netaddr.cidr_merge([p.prefix for p in queryset.filter(status=PREFIX_STATUS_DEPRECATED)]) - - # Find all available prefixes by subtracting each of the existing prefix sets from the aggregate prefix. - available_prefixes = ( - netaddr.IPSet([aggregate.prefix]) - - netaddr.IPSet(active_prefixes) - - netaddr.IPSet(reserved_prefixes) - - netaddr.IPSet(deprecated_prefixes) - ) - - # Add the size of each metric to the RIR total. - stats[rir]['total'] += aggregate.prefix.size / denominator - stats[rir]['active'] += netaddr.IPSet(active_prefixes).size / denominator - stats[rir]['reserved'] += netaddr.IPSet(reserved_prefixes).size / denominator - stats[rir]['deprecated'] += netaddr.IPSet(deprecated_prefixes).size / denominator - stats[rir]['available'] += available_prefixes.size / denominator - - # Calculate the percentage of total space for each prefix status. - total = float(stats[rir]['total']) - stats[rir]['percentages'] = { - 'active': float('{:.2f}'.format(stats[rir]['active'] / total * 100)) if total else 0, - 'reserved': float('{:.2f}'.format(stats[rir]['reserved'] / total * 100)) if total else 0, - 'deprecated': float('{:.2f}'.format(stats[rir]['deprecated'] / total * 100)) if total else 0, - 'available': float('{:.2f}'.format(stats[rir]['available'] / total * 100)) if total else 0, - } - stats[rir]['percentages']['available'] = ( - 100 - - stats[rir]['percentages']['active'] - - stats[rir]['percentages']['reserved'] - - stats[rir]['percentages']['deprecated'] - ) - - totals = { - 'total': sum([counts['total'] for rir, counts in stats.items()]), - 'active': sum([counts['active'] for rir, counts in stats.items()]), - 'reserved': sum([counts['reserved'] for rir, counts in stats.items()]), - 'deprecated': sum([counts['deprecated'] for rir, counts in stats.items()]), - 'available': sum([counts['available'] for rir, counts in stats.items()]), - } - - return render(request, 'ipam/stats.html', { - 'stats': stats, - 'totals': totals, - 'family': family, - }) diff --git a/netbox/templates/ipam/rir_list.html b/netbox/templates/ipam/rir_list.html index 51d63f4c2..f0b38ac7f 100644 --- a/netbox/templates/ipam/rir_list.html +++ b/netbox/templates/ipam/rir_list.html @@ -1,4 +1,5 @@ {% extends '_base.html' %} +{% load humanize %} {% load helpers %} {% block title %}RIRs{% endblock %} @@ -18,4 +19,29 @@ {% include 'utilities/obj_table.html' with bulk_delete_url='ipam:rir_bulk_delete' %} +
+
+

Totals

+
+
+

{{ totals.total|intcomma }}

+ All IPv4 space +
+
+

{{ totals.active|intcomma }}

+ Active +
+
+

{{ totals.reserved|intcomma }}

+ Reserved +
+
+

{{ totals.deprecated|intcomma }}

+ Deprecated +
+
+

{{ totals.available|intcomma }}

+ Available +
+
{% endblock %} diff --git a/netbox/templates/ipam/stats.html b/netbox/templates/ipam/stats.html deleted file mode 100644 index e4056e224..000000000 --- a/netbox/templates/ipam/stats.html +++ /dev/null @@ -1,90 +0,0 @@ -{% extends '_base.html' %} -{% load humanize %} -{% load render_table from django_tables2 %} - -{% block title %}RIR Statistics{% endblock %} - -{% block content %} -

RIR Statistics

-
-
- - {% if family == 6 %} - - {% endif %} - {% for rir, counts in stats.items %} -

{{ rir }}

-
- {% if counts.total %} -
- {{ counts.percentages.active }}% -
-
- {{ counts.percentages.reserved }}% -
-
- {{ counts.percentages.deprecated }}% -
-
- {{ counts.percentages.available }}% -
- {% endif %} -
-
-
-

{{ counts.total|intcomma }}

- Total -
-
-

{{ counts.active|intcomma }}

- Active -
-
-

{{ counts.reserved|intcomma }}

- Reserved -
-
-

{{ counts.deprecated|intcomma }}

- Deprecated -
-
-

{{ counts.available|intcomma }}

- Available -
-
- {% endfor %} -
-
-
-

Totals

-
-
-

{{ totals.total|intcomma }}

- All IPv{{ family }} space -
-
-

{{ totals.active|intcomma }}

- Active -
-
-

{{ totals.reserved|intcomma }}

- Reserved -
-
-

{{ totals.deprecated|intcomma }}

- Deprecated -
-
-

{{ totals.available|intcomma }}

- Available -
-
-
-
-{% endblock %}