structure and basic setup
@@ -127,3 +127,5 @@ dmypy.json
|
|||||||
|
|
||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
|
node_modules
|
||||||
@@ -1 +1,8 @@
|
|||||||
# netbox-topology-views
|
# netbox-topology-views
|
||||||
|
|
||||||
|
## install
|
||||||
|
|
||||||
|
|
||||||
|
## use
|
||||||
|
|
||||||
|
go to /plugins/topology-views/
|
||||||
@@ -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);
|
||||||
@@ -10,7 +10,9 @@ class TopologyViewsConfig(PluginConfig):
|
|||||||
base_url = 'topology-views'
|
base_url = 'topology-views'
|
||||||
required_settings = []
|
required_settings = []
|
||||||
default_settings = {
|
default_settings = {
|
||||||
'loud': False
|
'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
|
config = TopologyViewsConfig
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
#no models yet
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
from rest_framework.serializers import ModelSerializer, ValidatedModelSerializer
|
||||||
|
|
||||||
|
from dcim.models import DeviceRole
|
||||||
|
|
||||||
|
class PreDeviceRoleSerializer(ValidatedModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = DeviceRole
|
||||||
|
fields = ('id', 'name', 'slug', 'color', 'vm_role', 'description')
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
from rest_framework import routers
|
||||||
|
|
||||||
|
from .views import PreSelectDeviceRolesViewSet
|
||||||
|
|
||||||
|
router = routers.DefaultRouter()
|
||||||
|
#router.register('device_roles', AnimalViewSet)
|
||||||
|
#router.register('sites', AnimalViewSet)
|
||||||
|
#router.register('search', AnimalViewSet)
|
||||||
|
#router.register('save_coords', AnimalViewSet)
|
||||||
|
router.register('preselectdeviceroles', PreSelectDeviceRolesViewSet)
|
||||||
|
|
||||||
|
urlpatterns = router.urls
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
|
from .serializers import PreDeviceRoleSerializer
|
||||||
|
|
||||||
|
from dcim.models import DeviceRole
|
||||||
|
|
||||||
|
class PreSelectDeviceRolesViewSet(ModelViewSet):
|
||||||
|
queryset = DeviceRole.objects.all()
|
||||||
|
serializer_class = PreDeviceRoleSerializer
|
||||||
|
|||||||
@@ -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/assets/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/device_roles",dataType:"json",type:"GET",data:function(e){return{term:e.term}},processResults:function(e){return{results:$.map(e,function(e){return{text:e.name,id:e.id}})}}}}),$("#sites").select2({allowClear:!0,placeholder:"---------",theme:"bootstrap",multiple:!0,ajax:{url:"/api/sites",dataType:"json",type:"GET",data:function(e){return{term:e.term}},processResults:function(e){return{results:$.map(e,function(e){return{text:e.name,id:e.id}})}}}});var n=$("#device-roles");$.ajax({type:"GET",url:"/api/preselect_device_roles"}).then(function(e){$.each(e.data,function(e,a){var t=new Option(a.name,a.id,!0,!0);n.append(t).trigger("change"),n.trigger({type:"select2:select",params:{data:a}})})})}function handleButtonPress(){$("#search-form").submit(function(e){$("#status").html('<span class="badge badge-pill badge-info">Loading data</span>'),e.preventDefault();var a=$("#name").val(),t=$("#device-roles").val(),n=$("#sites").val();$.ajax({type:"POST",url:"/api/search",data:JSON.stringify({name:a,devicerole:t,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,a){nodes.add(a)}),$.each(e.edges,function(e,a){edges.add(a)}),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,a){$("#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:a.x,y:a.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/assets/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/device_roles",
|
||||||
|
dataType: "json",
|
||||||
|
type: "GET",
|
||||||
|
data: function (params) {
|
||||||
|
var queryParameters = {
|
||||||
|
term: params.term
|
||||||
|
}
|
||||||
|
return queryParameters;
|
||||||
|
},
|
||||||
|
processResults: function (data) {
|
||||||
|
return {
|
||||||
|
results: $.map(data, function (item) {
|
||||||
|
return {
|
||||||
|
text: item.name,
|
||||||
|
id: item.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('#sites').select2({
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: "---------",
|
||||||
|
theme: "bootstrap",
|
||||||
|
multiple: true,
|
||||||
|
ajax: {
|
||||||
|
url: "/api/sites",
|
||||||
|
dataType: "json",
|
||||||
|
type: "GET",
|
||||||
|
data: function (params) {
|
||||||
|
|
||||||
|
var queryParameters = {
|
||||||
|
term: params.term
|
||||||
|
}
|
||||||
|
return queryParameters;
|
||||||
|
},
|
||||||
|
processResults: function (data) {
|
||||||
|
return {
|
||||||
|
results: $.map(data, function (item) {
|
||||||
|
return {
|
||||||
|
text: item.name,
|
||||||
|
id: item.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var deviceRolesSelect = $('#device-roles');
|
||||||
|
$.ajax({
|
||||||
|
type: 'GET',
|
||||||
|
url: '/api/preselect_device_roles'
|
||||||
|
}).then(function (data) {
|
||||||
|
$.each(data.data, 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/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,74 @@
|
|||||||
|
{% 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>
|
||||||
|
|
||||||
|
{% 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,10 @@
|
|||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||