Compare commits

..
Author SHA1 Message Date
Mattijs Vanhaverbeke deedee78bd added coords + bump version 2022-02-21 18:14:39 +01:00
Mattijs Vanhaverbeke 683e125cd5 init for v3.1 2022-02-21 17:41:14 +01:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 3b4209223e Bump lodash from 4.17.20 to 4.17.21 (#66)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.20 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.20...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-12 22:13:26 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 6fc7cce79a Bump hosted-git-info from 2.8.8 to 2.8.9 (#67)
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-12 22:13:11 +02:00
mattieserver fd4a1cd0c7 Create codeql-analysis.yml 2021-05-07 16:20:55 +02:00
mattieserver 2deaed1adc Update README.md 2021-05-03 18:53:25 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 55a61d4d10 Bump ini from 1.3.5 to 1.3.8 (#47)
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-03 18:12:42 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 3a4652543f Bump yargs-parser from 5.0.0 to 5.0.1 (#62)
Bumps [yargs-parser](https://github.com/yargs/yargs-parser) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/yargs/yargs-parser/releases)
- [Changelog](https://github.com/yargs/yargs-parser/blob/v5.0.1/CHANGELOG.md)
- [Commits](https://github.com/yargs/yargs-parser/compare/v5.0.0...v5.0.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-02 22:33:51 +02:00
mattieserver 9b4dd77943 update version 2021-05-01 19:30:18 +02:00
15 changed files with 439 additions and 453 deletions
+71
View File
@@ -0,0 +1,71 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '16 22 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript', 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
+1
View File
@@ -35,6 +35,7 @@ Then run `python3 manage.py collectstatic --no-input`
| netbox version | netbox-topology-views version |
| ------------- |-------------|
| >= 2.11.1 | >= v0.5.3 |
| >= 2.10.0 | >= v0.5.0 |
| < 2.10.0 | =< v0.4.10 |
+2 -2
View File
@@ -4,10 +4,10 @@ class TopologyViewsConfig(PluginConfig):
name = 'netbox_topology_views'
verbose_name = 'Topology views'
description = 'An plugin to render topology maps'
version = '0.5.1'
version = '1.0.0-alpha.1'
author = 'Mattijs Vanhaverbeke'
author_email = 'author@example.com'
base_url = 'topology-views'
base_url = 'netbox_topology_views'
required_settings = []
default_settings = {
'preselected_device_roles': 'Firewall,Router,Distribution Switch,Core Switch,Internal Switch,Access Switch,Server,Storage,Backup,Wireless AP',
+2 -4
View File
@@ -1,9 +1,7 @@
from netbox.api import OrderedDefaultRouter
from rest_framework import routers
from . import views
router = OrderedDefaultRouter()
router.APIRootView = views.TopologyViewsRootView
router = routers.DefaultRouter()
router.register('preselectdeviceroles', views.PreSelectDeviceRolesViewSet)
router.register('preselecttags', views.PreSelectTagsViewSet)
+47
View File
@@ -0,0 +1,47 @@
import django_filters
from extras.filters import TagFilter
from dcim.models import Device, DeviceRole, Region, Site, Location
from utilities.filters import TreeNodeMultipleChoiceFilter
from django.db.models import Q
class DeviceFilterSet(django_filters.FilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
)
tag = TagFilter()
role_id = django_filters.ModelMultipleChoiceFilter(
field_name='device_role_id',
queryset=DeviceRole.objects.all(),
label='Role (ID)',
)
region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(),
field_name='site__region',
lookup_expr='in',
label='Region (ID)',
)
site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(),
label='Site (ID)',
)
location_id = TreeNodeMultipleChoiceFilter(
queryset=Location.objects.all(),
field_name='location',
lookup_expr='in',
label='Location (ID)',
)
class Meta:
model = Device
fields = ['id', 'name', ]
def search(self, queryset, name, value):
"""Perform the filtered search."""
if not value.strip():
return queryset
qs_filter = (
Q(name__icontains=value)
)
return queryset.filter(qs_filter)
+49
View File
@@ -0,0 +1,49 @@
from django import forms
from django.conf import settings
from django.utils.translation import gettext as _
from extras.models import Tag
from dcim.models import Device, Site, Region, DeviceRole, Location
from extras.forms import CustomFieldModelFilterForm
from utilities.forms import (TagFilterField, DynamicModelMultipleChoiceField, BootstrapMixin)
class DeviceFilterForm(CustomFieldModelFilterForm):
model = Device
field_groups = [
['q'],
['region_id', 'site_id', 'location_id'],
['device_role_id'],
['tag'],
]
region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
)
device_role_id = DynamicModelMultipleChoiceField(
queryset=DeviceRole.objects.all(),
required=False,
label=_('Device Role')
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
required=False,
query_params={
'region_id': '$region_id',
},
label=_('Site')
)
location_id = DynamicModelMultipleChoiceField(
queryset=Location.objects.all(),
required=False,
query_params={
'region_id': '$region_id',
'site_id': '$site_id',
},
label=_('Location')
)
tag = TagFilterField(model)
+8 -1
View File
@@ -5,6 +5,13 @@ menu_items = (
PluginMenuItem(
link='plugins:netbox_topology_views:home',
link_text='Topology',
buttons=()
buttons=(
PluginMenuButton(
link='plugins:netbox_topology_views:home',
title='Topology View',
icon_class='mdi mdi-plus-thick',
permissions=[],
),
)
),
)
File diff suppressed because one or more lines are too long
+23 -326
View File
@@ -35,343 +35,40 @@ var options = {
var selected_regions = [];
var selected_sites = [];
function addEdge(item) {
edges.add(item);
}
function addNode(item) {
nodes.add(item);
}
function iniPlotboxIndex() {
document.addEventListener('DOMContentLoaded', function () {
container = document.getElementById('visgraph');
csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
startLoadSearchBar();
handleButtonPress();
handleLoadData();
downloadButton = document.getElementById('btnDownloadImage');
btnFullView = document.getElementById('btnFullView');
}, 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 = {
q: 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: function (params) {
var base_url = "../../api/dcim/sites/?brief=true";
if (selected_regions.length == 0) {
return base_url;
}
else {
for (var i = 0; i < selected_regions.length; i++) {
var tmp = "&region_id=" + selected_regions[i];
base_url = base_url + tmp;
}
return base_url;
}
function handleLoadData() {
},
dataType: "json",
type: "GET",
data: function (params) {
var queryParameters = {
q: params.term
}
return queryParameters;
},
processResults: function (data) {
return {
results: $.map(data.results, function (item) {
return {
text: item.name,
id: item.id
}
})
};
}
}
});
graph = null;
nodes = new vis.DataSet();
edges = new vis.DataSet();
graph = new vis.Network(container, { nodes: nodes, edges: edges }, options);
topology_data.edges.forEach(addEdge);
topology_data.nodes.forEach(addNode);
$('#sites').on('select2:select', function (e) {
var data = e.params.data;
var index = selected_sites.indexOf(data.id.toString());
if (index == -1) {
selected_sites.push(data.id.toString());
}
});
$('#sites').on('select2:unselect', function (e) {
var data = e.params.data;
var index = selected_sites.indexOf(data.id.toString());
if (index > -1) {
selected_sites.splice(index, 1);
}
});
$('#sites').on('select2:clear', function (e) {
selected_sites = [];
});
graph.fit();
canvas = document.getElementById('visgraph').getElementsByTagName('canvas')[0];
$('#tags').select2({
allowClear: true,
placeholder: "---------",
theme: "bootstrap",
multiple: true,
ajax: {
url: "../../api/extras/tags/?brief=true",
dataType: "json",
type: "GET",
data: function (params) {
var queryParameters = {
q: params.term
}
return queryParameters;
},
processResults: function (data) {
return {
results: $.map(data.results, function (item) {
return {
text: item.name,
id: item.id
}
})
};
}
}
});
$('#regions').select2({
allowClear: true,
placeholder: "---------",
theme: "bootstrap",
multiple: true,
ajax: {
url: "../../api/dcim/regions/?brief=true",
dataType: "json",
type: "GET",
data: function (params) {
var queryParameters = {
q: params.term
}
return queryParameters;
},
processResults: function (data) {
return {
results: $.map(data.results, function (item) {
return {
text: item.name,
id: item.id
}
})
};
}
}
});
$('#regions').on('select2:select', function (e) {
var data = e.params.data;
var index = selected_regions.indexOf(data.id.toString());
if (index == -1) {
selected_regions.push(data.id.toString());
}
});
$('#regions').on('select2:unselect', function (e) {
var data = e.params.data;
var index = selected_regions.indexOf(data.id.toString());
if (index > -1) {
selected_regions.splice(index, 1);
}
});
$('#regions').on('select2:clear', function (e) {
selected_regions = [];
});
$('#locations').select2({
allowClear: true,
placeholder: "---------",
theme: "bootstrap",
multiple: true,
ajax: {
url: function (params) {
var base_url = "../../api/dcim/locations/?brief=true";
if (selected_sites.length == 0) {
return base_url;
}
else {
for (var i = 0; i < selected_sites.length; i++) {
var tmp = "&site_id=" + selected_sites[i];
base_url = base_url + tmp;
}
return base_url;
}
},
dataType: "json",
type: "GET",
data: function (params) {
var queryParameters = {
q: 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
}
});
graph.on('afterDrawing', function () {
var image = canvas.toDataURL(MIME_TYPE);
downloadButton.href = image;
downloadButton.download = "topology";
});
});
var tagsSelect = $('#tags');
$.ajax({
type: 'GET',
url: '../../api/plugins/topology-views/preselecttags/'
}).then(function (data) {
$.each(data.results, function (index, tags_to_preload) {
var option = new Option(tags_to_preload.name, tags_to_preload.id, true, true);
tagsSelect.append(option).trigger('change');
tagsSelect.trigger({
type: 'select2:select',
params: {
data: tags_to_preload
}
});
});
});
}
function handleButtonPress() {
$("#search-form").submit(function (event) {
$("#status").html('<span class="label label-info">Loading data</span>');
event.preventDefault();
var value = $("#name").val();
var value2 = $("#device-roles").val();
var value3 = $("#sites").val();
var value4 = $("#tags").val();
var value5 = $("#regions").val();
var value6 = $('#checkHideUnconnected').is(":checked");
var value7 = $("#locations").val();
$.ajax({
type: "GET",
url: "../../api/plugins/topology-views/search/search/",
data: {
'name': value,
'devicerole[]': value2,
'sites[]': value3,
'tags[]': value4,
'regions[]': value5,
'hide_unconnected': value6,
'locations[]': value7
},
headers: { "X-CSRFToken": csrftoken },
contentType: "application/json; charset=utf-8",
dataType: "json",
beforeSend: function(){
new_url = this.url.split("../../api/plugins/topology-views/search/search/?");
new_url = new_url[1];
new_url = "../../plugins/topology-views/full?" + new_url
btnFullView.classList.remove("disabled");
btnFullView.href = new_url;
},
success: function (data_result, status, xhr) {
$("#status").html('<span class="label label-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();
canvas = document.getElementById('visgraph').getElementsByTagName('canvas')[0];
graph.on('afterDrawing', function () {
$("#status").html('<span class="label label-success">Ready</span>');
var image = canvas.toDataURL(MIME_TYPE);
downloadButton.href = image;
downloadButton.download = "topology";
});
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/plugins/topology-views/save-coords/save_coords/",
type: 'PATCH',
dataType: 'json',
headers: { "X-CSRFToken": csrftoken },
contentType: "application/json; charset=utf-8",
processData: false,
data: JSON.stringify({
'node_id': node_id,
'x': coordinates.x,
'y': coordinates.y
}),
error: function (error_result) {
$("#coordstatus").html('<span class="label label-warning">Failed to update coordinates</span>');
},
success: function (data_result) {
$("#coordstatus").html('<span class="label label-success">Updated coordinates</span>');
},
});
}
});
});
},
error: function (error_result) {
$("#status").html('<span class="label label-warning">Something went wrong</span>');
},
});
});
}
@@ -1,103 +1,78 @@
{% extends 'base.html' %}
{% extends 'base/layout.html' %}
{% load buttons %}
{% load render_table from django_tables2 %}
{% load static %}
{% load perms %}
{% load helpers %}
{% block content %}
{% with config=settings.PLUGINS_CONFIG.netbox_topology_views %}
{% block title %}Topology Views{% endblock %}
{% block head %}
<link rel="stylesheet" href="{% static 'netbox_topology_views/css/vendor.css' %}">
<link rel="stylesheet" href="{% static 'netbox_topology_views/css/app.css' %}">
{% endblock %}
<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 class="buttons pull-right">
<a id="btnDownloadImage" class="btn btn-xs btn-info">
<i class="mdi mdi-download"></i>
</a>
<a id="btnFullView" class="btn btn-xs btn-info disabled" target="_blank">
<i class="mdi mdi-share"></i>
</a>
</div>
</div>
{% block controls %}
<div class="controls">
<div class="control-group">
{% block extra_controls %}{% endblock %}
<a id="btnDownloadImage" class="btn btn-sm btn-info">
<i class="mdi mdi-download"></i>
Download
</a>
<a id="btnFullView" class="btn btn-sm btn-info disabled" target="_blank">
<i class="mdi mdi-share"></i>
Share
</a>
</div>
</div>
{% endblock controls %}
{% block tabs %}
<ul class="nav nav-tabs px-3">
{% block tab_items %}
<li class="nav-item" role="presentation">
<button class="nav-link active" id="network-tab" data-bs-toggle="tab" data-bs-target="#networks" type="button" role="tab" aria-controls="filters-form" aria-selected="true">
Network
</button>
</li>
{% if filter_form %}
<li class="nav-item" role="presentation">
<button class="nav-link" id="filters-form-tab" data-bs-toggle="tab" data-bs-target="#filters-form" type="button" role="tab" aria-controls="object-list" aria-selected="false">
Filters
{% if filter_form %}{% badge filter_form.changed_data|length bg_class="primary" %}{% endif %}
</button>
</li>
{% endif %}
{% endblock tab_items %}
</ul>
{% endblock tabs %}
{% block content-wrapper %}
{% with config=settings.PLUGINS_CONFIG.netbox_topology_views %}
<div class="tab-content">
<div class="tab-pane show active" id="networks" role="tabpanel" aria-labelledby="network-tab">
<div class="panel-body">
<div id="visgraph" class=""></div>
</div>
<script type="text/javascript">
var topology_data = {{ topology_data | safe }};
</script>
</div>
{% if filter_form %}
<div class="tab-pane show" id="filters-form" role="tabpanel" aria-labelledby="filters-form-tab">
{% include 'inc/filter_list.html' %}
</div>
{% endif %}
</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" autocomplete="off">
{% 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="regions">Regions</label>
<select class="form-control" multiple="multiple" id="regions"></select>
</div>
<div class="form-group">
<label for="sites">Sites</label>
<select class="form-control" multiple="multiple" id="sites"></select>
</div>
<div class="form-group">
<label for="locations">Locations</label>
<select class="form-control" multiple="multiple" id="locations"></select>
</div>
<div class="form-group">
<label for="tags">Tags</label>
<select class="form-control" multiple="multiple" id="tags"></select>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="checkHideUnconnected">
<label class="custom-control-label" for="checkHideUnconnected">Hide Unconnected</label>
</div>
</div>
<hr class="mb-4">
<button class="btn btn-primary btn-lg btn-block" type="submit" id="start-search">Search</button>
</form>
</div>
</div>
{% if config.allow_coordinates_saving == True %}
<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>
{% endif %}
</div>
</div>
<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>
{% 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 %}
{% endblock content-wrapper %}
-1
View File
@@ -7,5 +7,4 @@ from . import views
# a specific view so that it can be accessed by users.
urlpatterns = (
path('', views.TopologyHomeView.as_view(), name='home'),
path('full', views.TopologyFullView.as_view(), name='full'),
)
+157 -10
View File
@@ -1,6 +1,151 @@
from django.shortcuts import get_object_or_404, render
from django.db.models import Q
from django.views.generic import View
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.conf import settings
from .forms import DeviceFilterForm
from .filters import DeviceFilterSet
import json
from dcim.models import Device, Cable, DeviceRole, DeviceType
def get_topology_data(queryset):
nodes = []
nodes_ids = []
edges = []
edge_ids = 0
cable_ids = []
circuit_ids = []
if not queryset:
return None
for qs_device in queryset:
device_has_connections = False
links_from_device = Cable.objects.filter(Q(_termination_a_device_id=qs_device.id) | Q(_termination_b_device_id=qs_device.id) )
for link_from in links_from_device:
device_has_connections = True
if link_from.termination_a_type.name != "circuit termination" and link_from.termination_b_type.name != "circuit termination":
if link_from.id not in cable_ids:
cable_ids.append(link_from.id)
edge_ids += 1
cable_a_dev_name = link_from.termination_a.device.name
if cable_a_dev_name is None:
cable_a_dev_name = "device A name unknown"
cable_a_name = link_from.termination_a.name
if cable_a_name is None:
cable_a_name = "cable A name unknown"
cable_b_dev_name = link_from.termination_b.device.name
if cable_b_dev_name is None:
cable_b_dev_name = "device B name unknown"
cable_b_name = link_from.termination_b.name
if cable_b_name is None:
cable_b_name = "cable B name unknown"
edge = {}
edge["id"] = edge_ids
edge["from"] = link_from.termination_a.device.id
edge["to"] = link_from.termination_b.device.id
edge["title"] = "Cable between <br> " + cable_a_dev_name + " [" + cable_a_name + "]<br>" + cable_b_dev_name + " [" + cable_b_name + "]"
if link_from.color != "":
edge["color"] = "#" + link_from.color
edges.append(edge)
else:
if link_from.termination_a.circuit.id not in circuit_ids:
circuit_ids.append(link_from.termination_a.circuit.id)
edge_ids += 1
cable_b_dev_name = link_from.termination_b.device.name
if cable_b_dev_name is None:
cable_b_dev_name = "device B name unknown"
cable_b_name = link_from.termination_b.name
if cable_b_name is None:
cable_b_name = "cable B name unknown"
edge = {}
edge["id"] = edge_ids
edge["to"] = link_from.termination_b.device.id
edge["dashes"] = True
title = ""
title += "Circuit provider: " + link_from.termination_a.circuit.provider.name + "<br>"
title += "Termination between <br>"
title += cable_b_dev_name + " [" + cable_b_name + "]<br>"
if link_from.termination_a.circuit.termination_a is not None and link_from.termination_a.circuit.termination_a.cable is not None and link_from.termination_a.circuit.termination_a.cable.id != link_from.id and link_from.termination_a.circuit.termination_a.cable.termination_b is not None and link_from.termination_a.circuit.termination_a.cable.termination_b.device is not None:
edge["from"] = link_from.termination_a.circuit.termination_a.cable.termination_b.device.id
cable_a_dev_name = link_from.termination_a.circuit.termination_a.cable.termination_b.device.name
if cable_a_dev_name is None:
cable_a_dev_name = "device B name unknown"
cable_b_name = link_from.termination_a.circuit.termination_a.cable.termination_b.name
if cable_a_name is None:
cable_a_name = "cable B name unknown"
title += cable_a_dev_name + " [" + cable_a_name + "]<br>"
edge["title"] = title
edges.append(edge)
if link_from.termination_a.circuit.termination_z is not None and link_from.termination_a.circuit.termination_z.cable is not None and link_from.termination_a.circuit.termination_z.cable.id != link_from.id and link_from.termination_a.circuit.termination_z.cable.termination_b is not None and link_from.termination_a.circuit.termination_z.cable.termination_b.device is not None:
edge["from"] = link_from.termination_a.circuit.termination_z.cable.termination_b.device.id
cable_a_dev_name = link_from.termination_a.circuit.termination_z.cable.termination_b.device.name
if cable_a_dev_name is None:
cable_a_dev_name = "device B name unknown"
cable_a_name = link_from.termination_a.circuit.termination_z.cable.termination_b.name
if cable_a_name is None:
cable_a_name = "cable B name unknown"
title += cable_a_dev_name + " [" + cable_a_name + "]<br>"
edge["title"] = title
edges.append(edge)
if qs_device.id not in nodes_ids:
nodes_ids.append(qs_device.id)
dev_name = qs_device.name
if dev_name is None:
dev_name = "device name unknown"
node_content = ""
if qs_device.device_type is not None:
node_content += "<tr><th>Type: </th><td>" + qs_device.device_type.model + "</td></tr>"
if qs_device.device_role.name is not None:
node_content += "<tr><th>Role: </th><td>" + qs_device.device_role.name + "</td></tr>"
if qs_device.serial != "":
node_content += "<tr><th>Serial: </th><td>" + qs_device.serial + "</td></tr>"
if qs_device.primary_ip is not None:
node_content += "<tr><th>IP Address: </th><td>" + str(qs_device.primary_ip.address) + "</td></tr>"
dev_title = "<table> %s </table>" % (node_content)
node = {}
node["id"] = qs_device.id
node["name"] = dev_name
node["label"] = dev_name
node["title"] = dev_title
node["shape"] = 'image'
if qs_device.device_role.slug in settings.PLUGINS_CONFIG["netbox_topology_views"]["device_img"]:
node["image"] = '../../static/netbox_topology_views/img/' + qs_device.device_role.slug + ".png"
else:
node["image"] = "../../static/netbox_topology_views/img/role-unknown.png"
if qs_device.device_role.color != "":
node["color.border"] = "#" + qs_device.device_role.color
if "coordinates" in qs_device.custom_field_data:
print(qs_device.custom_field_data)
if qs_device.custom_field_data["coordinates"] is not None:
cords = qs_device.custom_field_data["coordinates"].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 results
class TopologyHomeView(PermissionRequiredMixin, View):
permission_required = ('dcim.view_site', 'dcim.view_device')
@@ -8,14 +153,16 @@ class TopologyHomeView(PermissionRequiredMixin, View):
"""
Show the home page
"""
def get(self, request):
return render(request, 'netbox_topology_views/index.html')
def get(self, request):
self.filterset = DeviceFilterSet
self.queryset = Device.objects.all()
self.queryset = self.filterset(request.GET, self.queryset).qs
topo_data = None
if request.GET:
topo_data = get_topology_data(self.queryset)
class TopologyFullView(PermissionRequiredMixin, View):
permission_required = ('dcim.view_site', 'dcim.view_device')
"""
Show the full view page
"""
def get(self, request):
return render(request, 'netbox_topology_views/full.html')
return render(request, 'netbox_topology_views/index.html' , {
'filter_form': DeviceFilterForm(request.GET, label_suffix=''),
'topology_data': json.dumps(topo_data)
}
)
+12 -17
View File
@@ -1,6 +1,6 @@
{
"name": "netbox_topology_views",
"version": "0.5.1",
"version": "1.0.0-alpha.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -754,12 +754,6 @@
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
"bundled": true,
"dev": true,
"optional": true
},
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
@@ -2328,9 +2322,9 @@
}
},
"hosted-git-info": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
"dev": true
},
"http-signature": {
@@ -2771,9 +2765,9 @@
}
},
"lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"loud-rejection": {
@@ -4805,12 +4799,13 @@
}
},
"yargs-parser": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz",
"integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=",
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz",
"integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==",
"dev": true,
"requires": {
"camelcase": "^3.0.0"
"camelcase": "^3.0.0",
"object.assign": "^4.1.0"
}
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"private": true,
"name": "netbox_topology_views",
"version": "0.5.1",
"version": "1.0.0-alpha.1",
"scripts": {
"resources": "gulp build",
"resources_dev": "gulp build_dev"
+1 -1
View File
@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
setup(
name='netbox-topology-views',
version='0.5.1',
version='1.0.0-alpha.1',
description='An NetBox plugin to create Topology maps',
url='https://github.com/mattieserver/netbox-topology-views',
author='Mattijs Vanhaverbeke',