@@ -127,3 +127,5 @@ dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
node_modules
|
||||
@@ -1 +1,17 @@
|
||||
# netbox-topology-views
|
||||
# netbox-topology-views
|
||||
|
||||
## preview
|
||||
|
||||

|
||||
|
||||
## install
|
||||
|
||||
Add 'netbox_topology_views' to the PLUGINS array in your django configuration file.
|
||||
Then run python3 manage.py collectstatic --no-input
|
||||
|
||||
There is also support for custom fiels.
|
||||
If you create a custom field "coordinates" for "dcim > device" with type "text" and name "coordinates" you will see the same layout every time.
|
||||
|
||||
## use
|
||||
|
||||
go to /plugins/topology-views/ view your topologies
|
||||
|
After Width: | Height: | Size: 53 KiB |
@@ -0,0 +1,78 @@
|
||||
var gulp = require('gulp');
|
||||
var sass = require('gulp-sass');
|
||||
var concat = require('gulp-concat');
|
||||
var uglify = require('gulp-uglify');
|
||||
var cleanCss = require('gulp-clean-css');
|
||||
|
||||
|
||||
var jsVendorFiles = [
|
||||
'node_modules/vis-network/standalone/umd/vis-network.min.js',
|
||||
];
|
||||
|
||||
var cssVendorFiles = [
|
||||
'node_modules/vis-network/styles/vis-network.min.css',
|
||||
];
|
||||
var sassFiles = './netbox_topology_views/static_dev/css/*.scss',
|
||||
cssDest = './netbox_topology_views/static/netbox_topology_views/css/';
|
||||
|
||||
var jsLocalResourceFiles = './netbox_topology_views/static_dev/js/*.js',
|
||||
jsDest = './netbox_topology_views/static/netbox_topology_views/js/';
|
||||
|
||||
|
||||
function styles_local() {
|
||||
return gulp.src(sassFiles)
|
||||
.pipe(sass().on('error', sass.logError))
|
||||
.pipe(concat('app.css'))
|
||||
.pipe(cleanCss())
|
||||
.pipe(gulp.dest(cssDest));
|
||||
|
||||
}
|
||||
exports.styles_local = styles_local
|
||||
|
||||
function styles_vendor() {
|
||||
return gulp.src(cssVendorFiles)
|
||||
.pipe(concat('vendor.css'))
|
||||
.pipe(cleanCss())
|
||||
.pipe(gulp.dest(cssDest))
|
||||
}
|
||||
exports.styles_vendor = styles_vendor
|
||||
|
||||
function js_local() {
|
||||
return gulp.src(jsLocalResourceFiles)
|
||||
.pipe(concat('app.js'))
|
||||
.pipe(uglify())
|
||||
.pipe(gulp.dest(jsDest))
|
||||
}
|
||||
exports.js_local = js_local
|
||||
|
||||
function js_vendor() {
|
||||
return gulp.src(jsVendorFiles)
|
||||
.pipe(concat('vendor.js'))
|
||||
.pipe(uglify())
|
||||
.pipe(gulp.dest(jsDest))
|
||||
|
||||
}
|
||||
exports.js_vendor = js_vendor
|
||||
|
||||
function js_local_dev() {
|
||||
return gulp.src(jsLocalResourceFiles)
|
||||
.pipe(concat('app.js'))
|
||||
.pipe(gulp.dest(jsDest))
|
||||
}
|
||||
exports.js_local_dev = js_local_dev
|
||||
|
||||
function js_vendor_dev() {
|
||||
return gulp.src(jsVendorFiles)
|
||||
.pipe(concat('vendor.js'))
|
||||
.pipe(gulp.dest(jsDest))
|
||||
|
||||
}
|
||||
exports.js_vendor_dev = js_vendor_dev
|
||||
|
||||
exports.css = gulp.series(styles_local, styles_vendor);
|
||||
|
||||
exports.js = gulp.series(js_local, js_vendor);
|
||||
exports.js_dev = gulp.series(js_local_dev, js_vendor_dev);
|
||||
|
||||
exports.build = gulp.series(exports.css, exports.js);
|
||||
exports.build_dev = gulp.series(exports.css, exports.js_dev);
|
||||
@@ -0,0 +1,18 @@
|
||||
from extras.plugins import PluginConfig
|
||||
|
||||
class TopologyViewsConfig(PluginConfig):
|
||||
name = 'netbox_topology_views'
|
||||
verbose_name = 'Topology views'
|
||||
description = 'An plugin to render toplogoy maps'
|
||||
version = '0.1'
|
||||
author = 'Mattijs Vanhaverbeke'
|
||||
author_email = 'author@example.com'
|
||||
base_url = 'topology-views'
|
||||
required_settings = []
|
||||
default_settings = {
|
||||
'preselected_device_roles': 'Firewall,Router,Distribution Switch,Core Switch,Internal Switch,Access Switch,Server,Storage,Backup,Wireless AP',
|
||||
'IGNORE_CABLE_TYPE': 'dcim.poweroutlet,dcim.powerport',
|
||||
'device_img': 'access-switch,core-switch,firewall,router,distribution-switch,backup,storage,wan-network,wireless-ap,server,internal-switch,isp-cpe-material,non-racked-devices,power-units'
|
||||
}
|
||||
|
||||
config = TopologyViewsConfig
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
#no models yet
|
||||
@@ -0,0 +1,10 @@
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
|
||||
from dcim.models import DeviceRole
|
||||
|
||||
class PreDeviceRoleSerializer(ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = DeviceRole
|
||||
fields = ('id', 'name')
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
from rest_framework import routers
|
||||
|
||||
from .views import PreSelectDeviceRolesViewSet, SearchViewSet
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register('preselectdeviceroles', PreSelectDeviceRolesViewSet)
|
||||
router.register('search', SearchViewSet, basename='search')
|
||||
|
||||
urlpatterns = router.urls
|
||||
@@ -0,0 +1,89 @@
|
||||
from rest_framework.viewsets import ModelViewSet, ViewSet, ReadOnlyModelViewSet
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
|
||||
from .serializers import PreDeviceRoleSerializer
|
||||
from django.conf import settings
|
||||
|
||||
from utilities.api import IsAuthenticatedOrLoginNotRequired
|
||||
|
||||
from dcim.models import DeviceRole, Device, Cable
|
||||
|
||||
|
||||
class PreSelectDeviceRolesViewSet(ReadOnlyModelViewSet):
|
||||
preselected_device_roles_raw = settings.PLUGINS_CONFIG["netbox_topology_views"]["preselected_device_roles"]
|
||||
preselected_device_roles = preselected_device_roles_raw.split(",")
|
||||
queryset = DeviceRole.objects.filter(name__in=preselected_device_roles)
|
||||
serializer_class = PreDeviceRoleSerializer
|
||||
|
||||
class SearchViewSet(ViewSet):
|
||||
_ignore_model_permissions = True
|
||||
permission_classes = [IsAuthenticatedOrLoginNotRequired]
|
||||
|
||||
def _filter(self, site, role, name):
|
||||
filter_devices = Device.objects.all()
|
||||
if name is not None:
|
||||
filter_devices = filter_devices.filter(name__contains=name)
|
||||
if site is not None:
|
||||
filter_devices = filter_devices.filter(site__id__in=site)
|
||||
if role is not None:
|
||||
filter_devices = filter_devices.filter(device_role__id__in=role)
|
||||
return filter_devices
|
||||
|
||||
|
||||
@action(detail=False, methods=['post'])
|
||||
def search(self, request):
|
||||
name = None
|
||||
sites = None
|
||||
devicerole = None
|
||||
if "devicerole" in request.data:
|
||||
if request.data["devicerole"]:
|
||||
devicerole = request.data["devicerole"]
|
||||
if "sites" in request.data:
|
||||
if request.data["sites"]:
|
||||
sites = request.data["sites"]
|
||||
if "name" in request.data:
|
||||
if request.data["name"]:
|
||||
name = request.data["name"]
|
||||
|
||||
devices = self._filter(sites, devicerole, name)
|
||||
nodes = []
|
||||
edges = []
|
||||
edge_ids = 0
|
||||
cable_ids = []
|
||||
for device in devices:
|
||||
cables = device.get_cables()
|
||||
for cable in cables:
|
||||
if cable.id not in cable_ids:
|
||||
cable_ids.append(cable.id)
|
||||
edge_ids += 1
|
||||
edge = {}
|
||||
edge["id"] = edge_ids
|
||||
edge["from"] = cable.termination_a.device.id
|
||||
edge["to"] = cable.termination_b.device.id
|
||||
edge["title"] = "Connection between <br> " + cable.termination_a.device.name + " [" + cable.termination_a.name + "]<br>" + cable.termination_b.device.name + " [" + cable.termination_b.name + "]"
|
||||
edges.append(edge)
|
||||
node = {}
|
||||
node["id"] = device.id
|
||||
node["name"] = device.name
|
||||
node["label"] = device.name + device.device_type.display_name
|
||||
node["shape"] = 'image'
|
||||
if device.device_role.slug in settings.PLUGINS_CONFIG["netbox_topology_views"]["device_img"]:
|
||||
node["image"] = '/static/netbox_topology_views/img/' + device.device_role.slug + ".png"
|
||||
else:
|
||||
node["image"] = "/static/netbox_topology_views/img/role-unknown.png"
|
||||
|
||||
for device_custom_field in device.custom_field_values.all():
|
||||
if device_custom_field.field.name == "coordinates":
|
||||
cords = device_custom_field.serialized_value.split(";")
|
||||
node["x"] = int(cords[0])
|
||||
node["y"] = int(cords[1])
|
||||
node["physics"] = False
|
||||
|
||||
nodes.append(node)
|
||||
|
||||
results = {}
|
||||
results["nodes"] = nodes
|
||||
results["edges"] = edges
|
||||
|
||||
return Response(results)
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# no modeles yet
|
||||
@@ -0,0 +1 @@
|
||||
#visgraph{height:70vh}
|
||||
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
@@ -0,0 +1 @@
|
||||
var graph=null,container=null,csrftoken=null,nodes=new vis.DataSet,edges=new vis.DataSet,options={interaction:{hover:!0,hoverConnectedEdges:!0,multiselect:!0},nodes:{shape:"image",brokenImage:"/static/netbox_topology_views/img/role-unknown.png",size:35,font:{multi:"md",face:"helvetica"}},edges:{length:100,width:2,font:{face:"helvetica"}},physics:{solver:"forceAtlas2Based"}};function iniPlotboxIndex(){document.addEventListener("DOMContentLoaded",function(){container=document.getElementById("visgraph"),csrftoken=jQuery("[name=csrfmiddlewaretoken]").val(),startLoadSearchBar(),handleButtonPress()},!1)}function startLoadSearchBar(){$("#device-roles").select2({allowClear:!0,placeholder:"---------",theme:"bootstrap",multiple:!0,ajax:{url:"/api/dcim/device-roles/?brief=true",dataType:"json",type:"GET",data:function(e){return{term:e.term}},processResults:function(e){return{results:$.map(e.results,function(e){return{text:e.name,id:e.id}})}}}}),$("#sites").select2({allowClear:!0,placeholder:"---------",theme:"bootstrap",multiple:!0,ajax:{url:"/api/dcim/sites/?brief=true",dataType:"json",type:"GET",data:function(e){return{term:e.term}},processResults:function(e){return{results:$.map(e.results,function(e){return{text:e.name,id:e.id}})}}}});var n=$("#device-roles");$.ajax({type:"GET",url:"/api/plugins/topology-views/preselectdeviceroles/"}).then(function(e){$.each(e.results,function(e,t){var a=new Option(t.name,t.id,!0,!0);n.append(a).trigger("change"),n.trigger({type:"select2:select",params:{data:t}})})})}function handleButtonPress(){$("#search-form").submit(function(e){$("#status").html('<span class="badge badge-pill badge-info">Loading data</span>'),e.preventDefault();var t=$("#name").val(),a=$("#device-roles").val(),n=$("#sites").val();$.ajax({type:"POST",url:"/api/plugins/topology-views/search/search/",data:JSON.stringify({name:t,devicerole:a,sites:n}),headers:{"X-CSRFToken":csrftoken},contentType:"application/json; charset=utf-8",dataType:"json",success:function(e){$("#status").html('<span class="badge badge-pill badge-info">Drawing network</span>'),graph=null,nodes=new vis.DataSet,edges=new vis.DataSet,graph=new vis.Network(container,{nodes:nodes,edges:edges},options),$.each(e.nodes,function(e,t){nodes.add(t)}),$.each(e.edges,function(e,t){edges.add(t)}),graph.fit(),graph.on("afterDrawing",function(){$("#status").html('<span class="badge badge-pill badge-success">Ready</span>')}),graph.on("dragEnd",function(e){dragged=this.getPositions(e.nodes),$.each(dragged,function(e,t){$("#coordstatus").html(""),$("#checkSaveCoordinates").is(":checked")&&(nodes.update({id:e,physics:!1}),$.ajax({url:"/api/save_coords",type:"POST",dataType:"json",headers:{"X-CSRFToken":csrftoken},contentType:"application/json; charset=utf-8",data:JSON.stringify({node_id:e,x:t.x,y:t.y}),error:function(e){$("#coordstatus").html('<span class="badge badge-pill badge-warning">Failed to update coordinates</span>')},success:function(e){$("#coordstatus").html('<span class="badge badge-pill badge-success">Updated coordinates</span>'),$.ajax({url:"/api/reload_devices",type:"GET",headers:{"X-CSRFToken":csrftoken},success:function(e){}})}}))})})},error:function(e){$("#status").html('<span class="badge badge-pill badge-warning">Something went wrong</span>')}})})}
|
||||
@@ -0,0 +1,3 @@
|
||||
#visgraph {
|
||||
height: 70vh;
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
var graph = null;
|
||||
var container = null;
|
||||
var csrftoken = null;
|
||||
var nodes = new vis.DataSet();
|
||||
var edges = new vis.DataSet();
|
||||
var options = {
|
||||
interaction: {
|
||||
hover: true,
|
||||
hoverConnectedEdges: true,
|
||||
multiselect: true
|
||||
},
|
||||
nodes: {
|
||||
shape: 'image',
|
||||
brokenImage: '/static/netbox_topology_views/img/role-unknown.png',
|
||||
size: 35,
|
||||
font: {
|
||||
multi: 'md',
|
||||
face: 'helvetica',
|
||||
},
|
||||
},
|
||||
edges: {
|
||||
length: 100,
|
||||
width: 2,
|
||||
font: {
|
||||
face: 'helvetica',
|
||||
},
|
||||
},
|
||||
physics: {
|
||||
solver: 'forceAtlas2Based'
|
||||
}
|
||||
};
|
||||
|
||||
function iniPlotboxIndex() {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
container = document.getElementById('visgraph');
|
||||
csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
|
||||
startLoadSearchBar();
|
||||
handleButtonPress();
|
||||
}, false);
|
||||
}
|
||||
|
||||
function startLoadSearchBar() {
|
||||
$('#device-roles').select2({
|
||||
allowClear: true,
|
||||
placeholder: "---------",
|
||||
theme: "bootstrap",
|
||||
multiple: true,
|
||||
ajax: {
|
||||
url: "/api/dcim/device-roles/?brief=true",
|
||||
dataType: "json",
|
||||
type: "GET",
|
||||
data: function (params) {
|
||||
var queryParameters = {
|
||||
term: params.term
|
||||
}
|
||||
return queryParameters;
|
||||
},
|
||||
processResults: function (data) {
|
||||
return {
|
||||
results: $.map(data.results, function (item) {
|
||||
return {
|
||||
text: item.name,
|
||||
id: item.id
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
$('#sites').select2({
|
||||
allowClear: true,
|
||||
placeholder: "---------",
|
||||
theme: "bootstrap",
|
||||
multiple: true,
|
||||
ajax: {
|
||||
url: "/api/dcim/sites/?brief=true",
|
||||
dataType: "json",
|
||||
type: "GET",
|
||||
data: function (params) {
|
||||
|
||||
var queryParameters = {
|
||||
term: params.term
|
||||
}
|
||||
return queryParameters;
|
||||
},
|
||||
processResults: function (data) {
|
||||
return {
|
||||
results: $.map(data.results, function (item) {
|
||||
return {
|
||||
text: item.name,
|
||||
id: item.id
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var deviceRolesSelect = $('#device-roles');
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: '/api/plugins/topology-views/preselectdeviceroles/'
|
||||
}).then(function (data) {
|
||||
$.each(data.results, function (index, device_role_to_preload) {
|
||||
var option = new Option(device_role_to_preload.name, device_role_to_preload.id, true, true);
|
||||
deviceRolesSelect.append(option).trigger('change');
|
||||
deviceRolesSelect.trigger({
|
||||
type: 'select2:select',
|
||||
params: {
|
||||
data: device_role_to_preload
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleButtonPress() {
|
||||
$("#search-form").submit(function (event) {
|
||||
$("#status").html('<span class="badge badge-pill badge-info">Loading data</span>');
|
||||
event.preventDefault();
|
||||
var value = $("#name").val();
|
||||
var value2 = $("#device-roles").val();
|
||||
var value3 = $("#sites").val();
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/api/plugins/topology-views/search/search/",
|
||||
data: JSON.stringify({
|
||||
'name': value,
|
||||
'devicerole': value2,
|
||||
'sites': value3
|
||||
}),
|
||||
headers: { "X-CSRFToken": csrftoken },
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
success: function (data_result) {
|
||||
$("#status").html('<span class="badge badge-pill badge-info">Drawing network</span>');
|
||||
graph = null;
|
||||
nodes = new vis.DataSet();
|
||||
edges = new vis.DataSet();
|
||||
graph = new vis.Network(container, { nodes: nodes, edges: edges }, options);
|
||||
$.each(data_result["nodes"], function (index, device) {
|
||||
nodes.add(device);
|
||||
});
|
||||
$.each(data_result["edges"], function (index, edge) {
|
||||
edges.add(edge);
|
||||
});
|
||||
graph.fit();
|
||||
|
||||
graph.on('afterDrawing', function () {
|
||||
$("#status").html('<span class="badge badge-pill badge-success">Ready</span>');
|
||||
});
|
||||
|
||||
graph.on("dragEnd", function (params) {
|
||||
dragged = this.getPositions(params.nodes);
|
||||
$.each(dragged, function (node_id, coordinates) {
|
||||
$("#coordstatus").html('');
|
||||
if ($('#checkSaveCoordinates').is(":checked")) {
|
||||
nodes.update({ id: node_id, physics: false });
|
||||
$.ajax({
|
||||
url: "/api/save_coords",
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
headers: { "X-CSRFToken": csrftoken },
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify({
|
||||
'node_id': node_id,
|
||||
'x': coordinates.x,
|
||||
'y': coordinates.y
|
||||
}),
|
||||
error: function (error_result) {
|
||||
$("#coordstatus").html('<span class="badge badge-pill badge-warning">Failed to update coordinates</span>');
|
||||
},
|
||||
success: function (data_result) {
|
||||
$("#coordstatus").html('<span class="badge badge-pill badge-success">Updated coordinates</span>');
|
||||
$.ajax({
|
||||
url: '/api/reload_devices',
|
||||
type: 'GET',
|
||||
headers: { "X-CSRFToken": csrftoken },
|
||||
success: function (data) {
|
||||
//nothing
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
error: function (error_result) {
|
||||
$("#status").html('<span class="badge badge-pill badge-warning">Something went wrong</span>');
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load helpers %}
|
||||
|
||||
{% block content %}
|
||||
{% with config=settings.PLUGINS_CONFIG.netbox_topology_views %}
|
||||
<link rel="stylesheet" href="{% static 'netbox_topology_views/css/vendor.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'netbox_topology_views/css/app.css' %}">
|
||||
|
||||
<div class="row ">
|
||||
<div class="col-md-9">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Network</strong> <span id="status"></span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div id="visgraph" class=""></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Search</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form id="search-form" class="form">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input id="name" class="form-control" type="text">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="device-roles">Device roles</label>
|
||||
<select class="form-control" multiple="multiple" id="device-roles"></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="sites">Sites</label>
|
||||
<select class="form-control" multiple="multiple" id="sites"></select>
|
||||
</div>
|
||||
<hr class="mb-4">
|
||||
<button class="btn btn-primary btn-lg btn-block" type="submit" id="start-search">Search</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default mt-3">
|
||||
<div class="panel-heading">
|
||||
<strong>Settings</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="checkSaveCoordinates">
|
||||
<label class="custom-control-label" for="checkSaveCoordinates">Save coordinates</label>
|
||||
</div>
|
||||
<div>
|
||||
<span id="coordstatus"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
|
||||
<script src="{% static 'netbox_topology_views/js/vendor.js' %}"></script>
|
||||
<script src="{% static 'netbox_topology_views/js/app.js' %}"></script>
|
||||
|
||||
<script type="application/javascript"> iniPlotboxIndex()</script>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,10 @@
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
# Define a list of URL patterns to be imported by NetBox. Each pattern maps a URL to
|
||||
# a specific view so that it can be accessed by users.
|
||||
urlpatterns = (
|
||||
path('', views.TopologyHomeView.as_view(), name='home'),
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.views.generic import View
|
||||
|
||||
class TopologyHomeView(View):
|
||||
"""
|
||||
Show the home page
|
||||
"""
|
||||
def get(self, request):
|
||||
return render(request, 'netbox_topology_views/index.html')
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "netbox_topology_views",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"resources": "gulp build",
|
||||
"resources_dev": "gulp build_dev"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-clean-css": "^4.2.0",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-sass": "^4.0.2",
|
||||
"gulp-uglify": "^3.0.2",
|
||||
"vis-data": "^6.2.3",
|
||||
"vis-util": "^1.1.8",
|
||||
"vis-network": "^6.4.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
|
||||
setup(
|
||||
name='netbox-topology-views',
|
||||
version='0.1',
|
||||
description='An NetBox plugin to create Topology maps',
|
||||
url='https://github.com/mattieserver/netbox-topology-views',
|
||||
author='Mattijs Vanhaverbeke',
|
||||
license='Apache 2.0',
|
||||
install_requires=[],
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
)
|
||||