1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Introduced TopologyMap

This commit is contained in:
Jeremy Stretch
2016-04-08 14:57:54 -04:00
parent 42e16db8b4
commit 29fd04026d
7 changed files with 94 additions and 11 deletions

View File

@ -1,7 +1,7 @@
from django.conf.urls import url from django.conf.urls import url
from extras.models import GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE 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 * from .views import *
@ -62,6 +62,6 @@ urlpatterns = [
# Miscellaneous # Miscellaneous
url(r'^related-connections/$', RelatedConnectionsView.as_view(), name='related_connections'), 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<slug>[\w-]+)/$', TopologyMapView.as_view(), name='topology_map'),
] ]

View File

@ -15,6 +15,7 @@ from django.views.generic.edit import CreateView, UpdateView
from ipam.models import Prefix, IPAddress, VLAN from ipam.models import Prefix, IPAddress, VLAN
from circuits.models import Circuit from circuits.models import Circuit
from extras.models import TopologyMap
from utilities.error_handlers import handle_protectederror from utilities.error_handlers import handle_protectederror
from utilities.forms import ConfirmationForm from utilities.forms import ConfirmationForm
from utilities.views import ObjectListView, BulkImportView, BulkEditView, BulkDeleteView from utilities.views import ObjectListView, BulkImportView, BulkEditView, BulkDeleteView
@ -89,10 +90,12 @@ def site(request, slug):
'vlan_count': VLAN.objects.filter(site=site).count(), 'vlan_count': VLAN.objects.filter(site=site).count(),
'circuit_count': Circuit.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', { return render(request, 'dcim/site.html', {
'site': site, 'site': site,
'stats': stats, 'stats': stats,
'topology_maps': topology_maps,
}) })

View File

@ -1,6 +1,6 @@
from django.contrib import admin from django.contrib import admin
from .models import Graph, ExportTemplate from .models import Graph, ExportTemplate, TopologyMap
@admin.register(Graph) @admin.register(Graph)
@ -11,3 +11,11 @@ class GraphAdmin(admin.ModelAdmin):
@admin.register(ExportTemplate) @admin.register(ExportTemplate)
class ExportTemplateAdmin(admin.ModelAdmin): class ExportTemplateAdmin(admin.ModelAdmin):
list_display = ['content_type', 'name', 'mime_type', 'file_extension'] 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'],
}

View File

@ -10,7 +10,7 @@ from django.shortcuts import get_object_or_404
from circuits.models import Provider from circuits.models import Provider
from dcim.models import Site, Device, Interface, InterfaceConnection 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 from .serializers import GraphSerializer
@ -38,24 +38,23 @@ class GraphListView(generics.ListAPIView):
return queryset return queryset
class TopologyMapperView(APIView): class TopologyMapView(APIView):
""" """
Generate a topology diagram 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. tmap = get_object_or_404(TopologyMap, slug=slug)
device_sets = request.GET.getlist('devices', [])
# Construct the graph # Construct the graph
graph = pydot.Dot(graph_type='graph', ranksep='1') 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') subgraph = pydot.Subgraph('sg{}'.format(i), rank='same')
# Add a pseudonode for each device_set to enforce hierarchical layout # 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: if i:
graph.add_edge(pydot.Edge('set{}'.format(i - 1), 'set{}'.format(i), style='invis')) 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 # Compile list of all devices
device_superset = Q() device_superset = Q()
for regex in device_sets: for regex in tmap.device_sets:
device_superset = device_superset | Q(name__regex=regex) device_superset = device_superset | Q(name__regex=regex)
# Add all connections to the graph # Add all connections to the graph

View File

@ -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'],
},
),
]

View File

@ -4,6 +4,9 @@ from django.http import HttpResponse
from django.template import Template, Context from django.template import Template, Context
from dcim.models import Site
GRAPH_TYPE_INTERFACE = 100 GRAPH_TYPE_INTERFACE = 100
GRAPH_TYPE_PROVIDER = 200 GRAPH_TYPE_PROVIDER = 200
GRAPH_TYPE_SITE = 300 GRAPH_TYPE_SITE = 300
@ -68,3 +71,23 @@ class ExportTemplate(models.Model):
filename += '.{}'.format(self.file_extension) filename += '.{}'.format(self.file_extension)
response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename) response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
return response 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')]

View File

@ -115,6 +115,25 @@
</tr> </tr>
</table> </table>
</div> </div>
<div class="panel panel-default">
<div class="panel-heading">
<strong>Topology Maps</strong>
</div>
{% if topology_maps %}
<table class="table table-hover panel-body">
{% for tm in topology_maps %}
<tr>
<td><a href="{% url 'dcim-api:topology_map' slug=tm.slug %}" target="_blank">{{ tm }}</a></td>
<td>{{ tm.description }}</td>
</tr>
{% endfor %}
</table>
{% else %}
<div class="panel-body text-muted">
None
</div>
{% endif %}
</div>
</div> </div>
</div> </div>