diff --git a/netbox/dcim/api/urls.py b/netbox/dcim/api/urls.py index 22fd9ee32..00b8eb8f3 100644 --- a/netbox/dcim/api/urls.py +++ b/netbox/dcim/api/urls.py @@ -1,7 +1,7 @@ from django.conf.urls import url from extras.models import GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE -from extras.api.views import GraphListView, TopologyMapperView +from extras.api.views import GraphListView, TopologyMapView from .views import * @@ -62,6 +62,6 @@ urlpatterns = [ # Miscellaneous url(r'^related-connections/$', RelatedConnectionsView.as_view(), name='related_connections'), - url(r'^topology-mapper/$', TopologyMapperView.as_view(), name='topology_mapper'), + url(r'^topology-maps/(?P[\w-]+)/$', TopologyMapView.as_view(), name='topology_map'), ] diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 4c1516ee8..52d451cf8 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -15,6 +15,7 @@ from django.views.generic.edit import CreateView, UpdateView from ipam.models import Prefix, IPAddress, VLAN from circuits.models import Circuit +from extras.models import TopologyMap from utilities.error_handlers import handle_protectederror from utilities.forms import ConfirmationForm from utilities.views import ObjectListView, BulkImportView, BulkEditView, BulkDeleteView @@ -89,10 +90,12 @@ def site(request, slug): 'vlan_count': VLAN.objects.filter(site=site).count(), 'circuit_count': Circuit.objects.filter(site=site).count(), } + topology_maps = TopologyMap.objects.filter(site=site) return render(request, 'dcim/site.html', { 'site': site, 'stats': stats, + 'topology_maps': topology_maps, }) diff --git a/netbox/extras/admin.py b/netbox/extras/admin.py index ac584b048..757891a06 100644 --- a/netbox/extras/admin.py +++ b/netbox/extras/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from .models import Graph, ExportTemplate +from .models import Graph, ExportTemplate, TopologyMap @admin.register(Graph) @@ -11,3 +11,11 @@ class GraphAdmin(admin.ModelAdmin): @admin.register(ExportTemplate) class ExportTemplateAdmin(admin.ModelAdmin): list_display = ['content_type', 'name', 'mime_type', 'file_extension'] + + +@admin.register(TopologyMap) +class TopologyMapAdmin(admin.ModelAdmin): + list_display = ['name', 'slug', 'site'] + prepopulated_fields = { + 'slug': ['name'], + } diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index 0038bed05..381956145 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -10,7 +10,7 @@ from django.shortcuts import get_object_or_404 from circuits.models import Provider from dcim.models import Site, Device, Interface, InterfaceConnection -from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_PROVIDER, GRAPH_TYPE_SITE +from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_PROVIDER, GRAPH_TYPE_SITE from .serializers import GraphSerializer @@ -38,24 +38,23 @@ class GraphListView(generics.ListAPIView): return queryset -class TopologyMapperView(APIView): +class TopologyMapView(APIView): """ Generate a topology diagram """ - def get(self, request): + def get(self, request, slug): - # Glean device sets to map. Each set is represented as a hierarchical tier in the diagram. - device_sets = request.GET.getlist('devices', []) + tmap = get_object_or_404(TopologyMap, slug=slug) # Construct the graph graph = pydot.Dot(graph_type='graph', ranksep='1') - for i, device_set in enumerate(device_sets): + for i, device_set in enumerate(tmap.device_sets): subgraph = pydot.Subgraph('sg{}'.format(i), rank='same') # Add a pseudonode for each device_set to enforce hierarchical layout - subgraph.add_node(pydot.Node('set{}'.format(i), shape='none')) + subgraph.add_node(pydot.Node('set{}'.format(i), shape='none', width='0', label='')) if i: graph.add_edge(pydot.Edge('set{}'.format(i - 1), 'set{}'.format(i), style='invis')) @@ -76,7 +75,7 @@ class TopologyMapperView(APIView): # Compile list of all devices device_superset = Q() - for regex in device_sets: + for regex in tmap.device_sets: device_superset = device_superset | Q(name__regex=regex) # Add all connections to the graph diff --git a/netbox/extras/migrations/0002_topologymap.py b/netbox/extras/migrations/0002_topologymap.py new file mode 100644 index 000000000..066b51b5a --- /dev/null +++ b/netbox/extras/migrations/0002_topologymap.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.1 on 2016-04-08 18:53 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0005_auto_20160328_2135'), + ('extras', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='TopologyMap', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50, unique=True)), + ('slug', models.SlugField(unique=True)), + ('device_patterns', models.TextField()), + ('description', models.CharField(blank=True, max_length=100)), + ('site', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='topology_maps', to='dcim.Site')), + ], + options={ + 'ordering': ['name'], + }, + ), + ] diff --git a/netbox/extras/models.py b/netbox/extras/models.py index 12f09aedd..df5433519 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -4,6 +4,9 @@ from django.http import HttpResponse from django.template import Template, Context +from dcim.models import Site + + GRAPH_TYPE_INTERFACE = 100 GRAPH_TYPE_PROVIDER = 200 GRAPH_TYPE_SITE = 300 @@ -68,3 +71,23 @@ class ExportTemplate(models.Model): filename += '.{}'.format(self.file_extension) response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename) return response + + +class TopologyMap(models.Model): + name = models.CharField(max_length=50, unique=True) + slug = models.SlugField(unique=True) + site = models.ForeignKey(Site, related_name='topology_maps', blank=True, null=True) + device_patterns = models.TextField() + description = models.CharField(max_length=100, blank=True) + + class Meta: + ordering = ['name'] + + def __unicode__(self): + return self.name + + @property + def device_sets(self): + if not self.device_patterns: + return None + return [line.strip() for line in self.device_patterns.split('\n')] diff --git a/netbox/templates/dcim/site.html b/netbox/templates/dcim/site.html index 16d49477d..fbff0895c 100644 --- a/netbox/templates/dcim/site.html +++ b/netbox/templates/dcim/site.html @@ -115,6 +115,25 @@ +
+
+ Topology Maps +
+ {% if topology_maps %} + + {% for tm in topology_maps %} + + + + + {% endfor %} +
{{ tm }}{{ tm.description }}
+ {% else %} +
+ None +
+ {% endif %} +