Compare commits

..
29 Commits
Author SHA1 Message Date
Mattijs Vanhaverbeke 9ce13cd1d2 bump version 2022-09-16 16:14:42 +02:00
mattieserver b4e2f2418e added extra checks (#166) 2022-09-16 16:13:32 +02:00
mattieserver 5563e3c084 added device filter option (#165) 2022-09-16 16:07:14 +02:00
mattieserver 77a8a2c0d6 Removed print 2022-09-16 14:52:00 +02:00
mattieserver 010179742c 159 fix power filter (#164)
* fix site_ids var

* fixed power cable name + change power filter name
2022-09-16 14:50:24 +02:00
mattieserver f2bbaed317 added console cable support (#157)
* added console cable support

* removed print
2022-09-14 14:50:10 +02:00
mattieserver 8c56f21f16 Update README.md 2022-09-13 08:46:05 +02:00
mattieserver eb627bdedc Update README.md 2022-09-12 18:36:27 +02:00
mattieserver 291c033e34 Update README.md 2022-09-12 18:23:33 +02:00
mattieserver 1f389b3faa Update README.md 2022-09-12 18:20:18 +02:00
Mattijs Vanhaverbeke 3d3ddaa7c4 bump version 2022-09-08 12:29:29 +02:00
mattieserver 4ea2097429 bump version 2022-09-02 15:13:22 +02:00
mattieserver cfaba462d5 removed unused import (#149) 2022-09-02 15:12:36 +02:00
Mattijs Vanhaverbeke 5e2f1cca57 added info about new icons 2022-09-01 19:10:15 +02:00
Mattijs Vanhaverbeke b2e2a4c781 bump version 2022-09-01 18:48:22 +02:00
mattieserver 318b8495b7 Netbox v3.3 Support (#145)
* init

* added circuit (or at least part of)

* refactor circuits + fix js

* small changes

* small changes

* added power + rework settings

* small changes
2022-09-01 18:44:03 +02:00
mattieserver 5e1d5d2937 added always_save_coordinates (#144) 2022-08-24 14:07:26 +02:00
Andrea DaineseandMattijs Vanhaverbeke 0b6df3bd28 disable physics if not saving coords (#139)
* initial commit

* keeps x/y when not saving coords

Co-authored-by: Mattijs Vanhaverbeke <mattijs.vanhaverbeke@ebo-enterprises.com>
2022-08-05 17:53:15 +02:00
mattieserver af1666b3ba move to esbuild & yarn (#138)
* move to esbuild & yarn

* remove gulpfile

* cleanup unused ref

* updated gitignore
2022-08-03 17:29:15 +02:00
Mattijs Vanhaverbeke 31a8c756e2 package info 2022-08-03 14:07:51 +02:00
Mattijs Vanhaverbeke ce39c98396 bump version 2022-08-03 12:07:58 +02:00
mattieserver faf156a6b2 133 enhancement bubbletext (#136)
* added extra fields to popup

* open new tab with device info
2022-08-03 11:46:37 +02:00
mattieserver 1b36b9e7df added extra check (#135) 2022-08-03 10:59:02 +02:00
mattieserver 652a44437e Create SECURITY.md 2022-07-29 09:33:15 +02:00
mattieserver 76141e70ce update gh actions 2022-06-30 22:47:55 +02:00
mattieserver 76b4f6ac9a Create docker-publish.yml 2022-06-30 22:40:18 +02:00
mattieserver 953d7480af add dockerfile (#130) 2022-06-30 22:39:18 +02:00
Mattijs Vanhaverbeke f323278f00 bump version 2022-06-30 22:22:47 +02:00
mattieserver 56a6f22bf6 added site to device tooltip (#128) 2022-06-30 22:18:35 +02:00
31 changed files with 1125 additions and 5155 deletions
+93
View File
@@ -0,0 +1,93 @@
name: Docker
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
on:
schedule:
- cron: '35 10 * * *'
push:
branches: [ "master" ]
# Publish semver tags as releases.
tags: [ 'v*.*.*' ]
pull_request:
branches: [ "master" ]
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Install the cosign tool except on PR
# https://github.com/sigstore/cosign-installer
- name: Install cosign
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@7e0881f8fe90b25e305bbf0309761e9314607e25
with:
cosign-release: 'v1.9.0'
# Workaround: https://github.com/docker/build-push-action/issues/461
- name: Setup Docker buildx
uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# Sign the resulting Docker image digest except on PRs.
# This will only write to the public Rekor transparency log when the Docker
# repository is public to avoid leaking data. If you would like to publish
# transparency data even for private images, pass --force to cosign below.
# https://github.com/sigstore/cosign
- name: Sign the published Docker image
if: ${{ github.event_name != 'pull_request' }}
env:
COSIGN_EXPERIMENTAL: "true"
# This step uses the identity token to provision an ephemeral certificate
# against the sigstore community Fulcio instance.
run: cosign sign ${{ steps.meta.outputs.tags }}@${{ steps.build-and-push.outputs.digest }}
+11 -11
View File
@@ -5,7 +5,7 @@ name: Upload Python Package
on:
release:
types: [created]
types: [published]
jobs:
deploy:
@@ -13,19 +13,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v1
uses: actions/setup-python@v3
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python setup.py sdist bdist_wheel
twine upload dist/*
pip install build
- name: Build package
run: python -m build
- name: Publish package
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
+2 -1
View File
@@ -128,4 +128,5 @@ dmypy.json
# Pyre type checker
.pyre/
node_modules
node_modules
app.js.map
+4
View File
@@ -0,0 +1,4 @@
FROM netboxcommunity/netbox:latest
COPY ./docker/plugin_requirements.txt /
RUN /opt/netbox/venv/bin/pip install --no-warn-script-location -r /plugin_requirements.txt
+26 -6
View File
@@ -29,12 +29,21 @@ Once installed, the plugin needs to be enabled in your `configuration.py`
PLUGINS = ["netbox_topology_views"]
```
Then run `python3 manage.py collectstatic --no-input`
First run `source /opt/netbox/venv/bin/activate` to enter the Python virtual environment.
Then run
```bash
cd /opt/netbox/netbox
pip3 install netbox-topology-views
python3 manage.py collectstatic --no-input
```
### Versions
| netbox version | netbox-topology-views version |
| ------------- |-------------|
| >= 3.3.0 | >= v3.0.0 |
| >= 3.2.0 | >= v1.1.0 |
| >= 3.1.8 | >= v1.0.0 |
| >= 2.11.1 | >= v0.5.3 |
@@ -45,9 +54,12 @@ Then run `python3 manage.py collectstatic --no-input`
There is also support for custom fields.
If you create a custom field "coordinates" for "dcim > device" with type "text" and name "coordinates" you will see the same layout every time.
If you create a custom field "coordinates" for "dcim > device" and "Circuits > circuit" with type "text" and name "coordinates" you will see the same layout every time.
The coordinates can then be provided as: "X;Y"
The coordinates can then be provided as: "X;Y".
Please read the "Configure" chapter to set the `allow_coordinates_saving` option to True.
You might also set the `always_save_coordinates` option to True.
## Configure
@@ -67,11 +79,10 @@ PLUGINS_CONFIG = {
| ------------- |-------------| -----|
| 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'] | The slug of the device roles that you have a image for. |
| preselected_device_roles | ['Firewall', 'Router', 'Distribution Switch', 'Core Switch', 'Internal Switch', 'Access Switch', 'Server', 'Storage', 'Backup', 'Wireless AP'] | The full name of the device roles you want to pre select in the global view. Note that this is case sensitive|
| preselected_intermediate_dev_roles | ['Patch-panel'] | The full name of the device roles you want to display in the global view when using end-to-end connections mode. Note that this is case sensitive|
| allow_coordinates_saving | False | (bool) Set to true if you use the custom coordinates fields and want to save the coordinates |
| ignore_cable_type | ['power outlet', 'power port'] | The cable types that you want to ignore in the views |
| always_save_coordinates | False | (bool) Set if you want to enable the option to save coordinates by default |
| ignore_cable_type | [] | The cable types that you want to ignore in the views |
| preselected_tags | '[]' | The name of tags you want to preload |
| enable_circuit_terminations | False | (bool) Set to true if you want to see circuit terminations in the topology |
| draw_default_layout | False | (bool) Set to True if you want to load draw the topology on the initial load (when you go to the topology plugin page) |
### Custom Images
@@ -101,3 +112,12 @@ To view `/plugins/topology-views/` you need the following permissions:
+ dcim | site | can view site
+ extras | tag | can view tag
+ dcim | device role | can view device role
## Icons info
Power icons created by [Freepik - Flaticon](https://www.flaticon.com/free-icons/power).
Power icons created by [Flat Icons - Flaticon](https://www.flaticon.com/free-icons/power)
Provider icons created by [Good Ware - Flaticon](https://www.flaticon.com/free-icons/provider)
+13
View File
@@ -0,0 +1,13 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| > 2.0 | :white_check_mark: |
| < 2.0 | :x: |
## Reporting a Vulnerability
Please create an issue when you encounter a Security issue.
+2 -1
View File
@@ -2,6 +2,7 @@
+ install docker
+ install docker compose
+ install npm
+ enable yarn
+ clone netbox repo
+ clone netbox_topology_views repo
+ create venv: `/usr/bin/python3 -m venv venv`
@@ -12,7 +13,7 @@
# Build
+ build the package: `python3 setup.py develop`
+ build the resources `npm run resources`
+ build the resources `yarn run bundle`
# Test
+ copy configuration.py from dev_setup to the netbox/netbox/ folder
+1
View File
@@ -0,0 +1 @@
netbox-topology-views
-78
View File
@@ -1,78 +0,0 @@
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/dist/dist/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);
+3 -5
View File
@@ -4,20 +4,18 @@ class TopologyViewsConfig(PluginConfig):
name = 'netbox_topology_views'
verbose_name = 'Topology views'
description = 'An plugin to render topology maps'
version = '2.0.0'
version = '3.0.1'
author = 'Mattijs Vanhaverbeke'
author_email = 'author@example.com'
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'],
'preselected_intermediate_dev_roles' : [],
'end2end_connections': False,
'ignore_cable_type': ['power outlet','power port'],
'ignore_cable_type': [],
'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'],
'allow_coordinates_saving': False,
'always_save_coordinates': False,
'preselected_tags' : [],
'enable_circuit_terminations': False,
'draw_default_layout': False
}
+13 -3
View File
@@ -7,7 +7,7 @@ from rest_framework.routers import APIRootView
from .serializers import TopologyDummySerializer
from django.conf import settings
from dcim.models import DeviceRole, Device, Cable
from dcim.models import DeviceRole, Device, Cable , PowerPanel, PowerFeed
from circuits.models import Circuit
from extras.models import Tag
@@ -37,8 +37,18 @@ class SaveCoordsViewSet(ReadOnlyModelViewSet):
if "y" in request.data:
if request.data["y"]:
y_coord = request.data["y"]
actual_device= Device.objects.get(id=device_id)
if device_id.startswith("c"):
device_id = device_id.lstrip('c')
actual_device= Circuit.objects.get(id=device_id)
elif device_id.startswith("p"):
device_id = device_id.lstrip('p')
actual_device= PowerPanel.objects.get(id=device_id)
elif device_id.startswith("f"):
device_id = device_id.lstrip('f')
actual_device= PowerFeed.objects.get(id=device_id)
else:
actual_device= Device.objects.get(id=device_id)
if "coordinates" in actual_device.custom_field_data:
actual_device.custom_field_data["coordinates"] = "%s;%s" % (x_coord,y_coord)
+2 -3
View File
@@ -41,10 +41,9 @@ class DeviceFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
)
status = django_filters.MultipleChoiceFilter(
choices=DeviceStatusChoices,
null_value=None
null_value=None,
)
class Meta:
model = Device
fields = ['id', 'name']
+26 -18
View File
@@ -15,18 +15,17 @@ from django.conf import settings
from netbox.forms import NetBoxModelFilterSetForm
from utilities.forms import (TagFilterField, DynamicModelMultipleChoiceField, MultipleChoiceField)
allow_coordinates_saving = settings.PLUGINS_CONFIG["netbox_topology_views"]["allow_coordinates_saving"]
end2end = settings.PLUGINS_CONFIG["netbox_topology_views"]["end2end_connections"]
allow_coordinates_saving = bool(settings.PLUGINS_CONFIG["netbox_topology_views"]["allow_coordinates_saving"])
class DeviceFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
model = Device
fieldsets = (
(None, ('q', 'hide_unconnected', 'save_coords', 'end2end_connections')),
(None, ('tenant_group_id', 'tenant_id')),
(None, ('region_id', 'site_id', 'location_id')),
(None, ('device_role_id', 'intermediate_dev_role_id')),
(None, ('tag', 'status')),
(None, ('q', 'hide_unconnected', 'save_coords', 'show_circuit', 'show_power' ,)),
(None, ('tenant_group_id', 'tenant_id',)),
(None, ('region_id', 'site_id', 'location_id',)),
(None, ('device_role_id','id','status', )),
(None, ('tag', )),
)
region_id = DynamicModelMultipleChoiceField(
@@ -39,16 +38,16 @@ class DeviceFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
required=False,
label=_('Device Role')
)
end2end_connections = forms.BooleanField(
label=_("Display end-to-end connections"),
id = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
required=False,
initial=end2end
)
intermediate_dev_role_id = DynamicModelMultipleChoiceField(
queryset=DeviceRole.objects.all(),
required=False,
label=_('Intermediate Devices Role'),
help_text='Intermediate devices to display when using end-to-end connections mode, even if they do not match the query'
label=_('Device'),
query_params={
'location_id' : '$location_id',
'region_id': '$region_id',
'site_id': '$site_id',
'role_id': '$device_role_id',
},
)
site_id = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
@@ -72,11 +71,20 @@ class DeviceFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
required=False,
initial=False
)
show_circuit = forms.BooleanField(
label=_("Show Circuit Terminations"),
required=False,
initial=False
)
show_power = forms.BooleanField(
label=_("Show Power Feeds"),
required=False,
initial=False
)
save_coords = forms.BooleanField(
label=_("Save Coordinates"),
required=False,
disabled=(not allow_coordinates_saving),
initial=False
disabled=(not allow_coordinates_saving)
)
status = MultipleChoiceField(
choices=DeviceStatusChoices,
@@ -1 +1 @@
#visgraph{height:70vh}html[data-netbox-color-mode=dark] #visgraph{background-color:#212529}
#visgraph{height:70vh}html[data-netbox-color-mode=dark] #visgraph{background-color:#212529}
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,87 @@
const esbuild = require('esbuild');
const { sassPlugin } = require('esbuild-sass-plugin');
const options = {
bundle: true,
minify: true,
sourcemap: 'external',
sourcesContent: false,
logLevel: 'error',
};
const ARGS = process.argv.slice(2);
async function bundleScripts() {
const entryPoints = {
'app': 'js/home.js'
};
try {
const result = await esbuild.build({
...options,
outdir: '../static/netbox_topology_views/js/',
entryPoints,
target: 'es2016',
});
if (result.errors.length === 0) {
for (const [targetName, sourceName] of Object.entries(entryPoints)) {
const source = sourceName.split('/')[1];
console.log(`✅ Bundled source file '${source}' to '${targetName}.js'`);
}
}
} catch (err) {
console.error(err);
}
}
async function bundleStyles() {
try {
const entryPoints = {
'vendor': 'css/_external.scss',
'app': 'css/app.scss',
};
const pluginOptions = { outputStyle: 'compressed' };
// Allow cache disabling.
if (ARGS.includes('--no-cache')) {
pluginOptions.cache = false;
}
let result = await esbuild.build({
...options,
outdir: '../static/netbox_topology_views/css/',
// Disable sourcemaps for CSS/SCSS files, see #7068
sourcemap: false,
entryPoints,
plugins: [sassPlugin(pluginOptions)],
loader: {
'.eot': 'file',
'.woff': 'file',
'.woff2': 'file',
'.svg': 'file',
'.ttf': 'file',
},
});
if (result.errors.length === 0) {
for (const [targetName, sourceName] of Object.entries(entryPoints)) {
const source = sourceName.split('/')[1];
console.log(`✅ Bundled source file '${source}' to '${targetName}.css'`);
}
}
} catch (err) {
console.error(err);
}
}
async function bundleAll() {
if (ARGS.includes('--styles')) {
// Only run style jobs.
return await bundleStyles();
} else if (ARGS.includes('--scripts')) {
// Only run script jobs.
return await bundleScripts();
}
await bundleStyles();
await bundleScripts();
}
bundleAll();
@@ -0,0 +1 @@
@import '../node_modules/vis-network/dist/dist/vis-network.min.css';
+69 -33
View File
@@ -1,11 +1,16 @@
import { DataSet } from "vis-data/esnext";
import { Network } from "vis-network/esnext";
//import 'vis-util';
var graph = null;
var container = null;
var downloadButton = null;
var MIME_TYPE = "image/png";
const MIME_TYPE = "image/png";
var canvas = null;
var csrftoken = null;
var nodes = new vis.DataSet();
var edges = new vis.DataSet();
var nodes = new DataSet();
var edges = new DataSet();
var options = {
interaction: {
hover: true,
@@ -35,12 +40,10 @@ var options = {
solver: 'forceAtlas2Based'
}
};
var selected_regions = [];
var selected_sites = [];
var coord_save_checkbox = null;
var htmlElement = null;
function getCookie(name) {
export function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
@@ -54,38 +57,36 @@ function getCookie(name) {
}
}
return cookieValue;
}
};
function htmlTitle(html) {
export function htmlTitle(html) {
container = document.createElement("div");
container.innerHTML = html;
return container;
}
};
function addEdge(item) {
item.title = htmlTitle( item.title );
export function addEdge(item) {
item.title = htmlTitle(item.title);
edges.add(item);
}
};
function addNode(item) {
item.title = htmlTitle( item.title );
export function addNode(item) {
item.title = htmlTitle(item.title);
nodes.add(item);
}
function iniPlotboxIndex() {
document.addEventListener('DOMContentLoaded', function () {
csrftoken = getCookie('csrftoken');
container = document.getElementById('visgraph');
htmlElement = document.getElementsByTagName("html")[0];
downloadButton = document.getElementById('btnDownloadImage');
handleLoadData();
btnFullView = document.getElementById('btnFullView');
coord_save_checkbox = document.getElementById('id_save_coords');
}, false);
}
export function iniPlotboxIndex() {
csrftoken = getCookie('csrftoken');
container = document.getElementById('visgraph');
htmlElement = document.getElementsByTagName("html")[0];
downloadButton = document.getElementById('btnDownloadImage');
handleLoadData();
btnFullView = document.getElementById('btnFullView');
coord_save_checkbox = document.getElementById('id_save_coords');
};
function performGraphDownload() {
export function performGraphDownload() {
var tempDownloadLink = document.createElement('a');
var generatedImageUrl = canvas.toDataURL(MIME_TYPE);
@@ -94,9 +95,9 @@ function performGraphDownload() {
document.body.appendChild(tempDownloadLink);
tempDownloadLink.click();
document.body.removeChild(tempDownloadLink);
}
};
function handleLoadData() {
export function handleLoadData() {
if (topology_data !== null) {
if (htmlElement.dataset.netboxColorMode == "dark") {
@@ -104,9 +105,9 @@ function handleLoadData() {
}
graph = null;
nodes = new vis.DataSet();
edges = new vis.DataSet();
graph = new vis.Network(container, { nodes: nodes, edges: edges }, options);
nodes = new DataSet();
edges = new DataSet();
graph = new Network(container, { nodes: nodes, edges: edges }, options);
topology_data.edges.forEach(addEdge);
topology_data.nodes.forEach(addNode);
@@ -134,7 +135,6 @@ function handleLoadData() {
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
console.log(xhr.status);
console.log(xhr.responseText);
}};
var data = JSON.stringify({
@@ -147,5 +147,41 @@ function handleLoadData() {
}
}
});
graph.on("doubleClick", function (params) {
let selected_devices = params.nodes;
for (let selected_device in selected_devices) {
let url = ""
if(String(selected_devices[selected_device]).startsWith("c")) {
cid = selected_devices[selected_device].substring(1);
url = "/circuits/circuits/" + cid + "/";
}
else if (String(selected_devices[selected_device]).startsWith("p")) {
cid = selected_devices[selected_device].substring(1);
url = "/dcim/power-panels/" + cid + "/";
}
else if (String(selected_devices[selected_device]).startsWith("f")) {
cid = selected_devices[selected_device].substring(1);
url = "/dcim/power-feeds/" + cid + "/";
}
else {
url = "/dcim/devices/" + selected_devices[selected_device] + "/";
}
window.open(url, "_blank");
}
});
}
}
};
export function load_doc() {
if (document.readyState !== 'loading') {
iniPlotboxIndex();
} else {
document.addEventListener('DOMContentLoaded', iniPlotboxIndex);
}
};
load_doc();
@@ -0,0 +1,25 @@
{
"private": true,
"name": "netbox_topology_views",
"version": "3.0.0-beta.1",
"scripts": {
"bundle": "node bundle.js",
"bundle:styles": "node bundle.js --styles",
"bundle:scripts": "node bundle.js --scripts"
},
"dependencies": {
"vis-data": "^7.1.4",
"vis-network": "^9.1.2",
"vis-util": "^5.0.3"
},
"devDependencies": {
"esbuild": "^0.12.24",
"esbuild-sass-plugin": "^1.5.2",
"@egjs/hammerjs": "^2.0.0",
"component-emitter": "^1.3.0",
"keycharm": "^0.4.0",
"timsort": "^0.3.0",
"uuid": "^8.0.0"
}
}
+356
View File
@@ -0,0 +1,356 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@egjs/hammerjs@^2.0.0":
version "2.0.17"
resolved "https://registry.yarnpkg.com/@egjs/hammerjs/-/hammerjs-2.0.17.tgz#5dc02af75a6a06e4c2db0202cae38c9263895124"
integrity sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==
dependencies:
"@types/hammerjs" "^2.0.36"
"@esbuild/linux-loong64@0.14.53":
version "0.14.53"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.53.tgz#251b4cd6760fadb4d68a05815e6dc5e432d69cd6"
integrity sha512-W2dAL6Bnyn4xa/QRSU3ilIK4EzD5wgYXKXJiS1HDF5vU3675qc2bvFyLwbUcdmssDveyndy7FbitrCoiV/eMLg==
"@types/hammerjs@^2.0.36":
version "2.0.41"
resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.41.tgz#f6ecf57d1b12d2befcce00e928a6a097c22980aa"
integrity sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA==
anymatch@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
binary-extensions@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
dependencies:
fill-range "^7.0.1"
"chokidar@>=3.0.0 <4.0.0":
version "3.5.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
dependencies:
anymatch "~3.1.2"
braces "~3.0.2"
glob-parent "~5.1.2"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.6.0"
optionalDependencies:
fsevents "~2.3.2"
component-emitter@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
esbuild-android-64@0.14.53:
version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.53.tgz#259bc3ef1399a3cad8f4f67c40ee20779c4de675"
integrity sha512-fIL93sOTnEU+NrTAVMIKiAw0YH22HWCAgg4N4Z6zov2t0kY9RAJ50zY9ZMCQ+RT6bnOfDt8gCTnt/RaSNA2yRA==
esbuild-android-arm64@0.14.53:
version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.53.tgz#2158253d4e8f9fdd2a081bbb4f73b8806178841e"
integrity sha512-PC7KaF1v0h/nWpvlU1UMN7dzB54cBH8qSsm7S9mkwFA1BXpaEOufCg8hdoEI1jep0KeO/rjZVWrsH8+q28T77A==
esbuild-darwin-64@0.14.53:
version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.53.tgz#b4681831fd8f8d06feb5048acbe90d742074cc2a"
integrity sha512-gE7P5wlnkX4d4PKvLBUgmhZXvL7lzGRLri17/+CmmCzfncIgq8lOBvxGMiQ4xazplhxq+72TEohyFMZLFxuWvg==
esbuild-darwin-arm64@0.14.53:
version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.53.tgz#d267d957852d121b261b3f76ead86e5b5463acc9"
integrity sha512-otJwDU3hnI15Q98PX4MJbknSZ/WSR1I45il7gcxcECXzfN4Mrpft5hBDHXNRnCh+5858uPXBXA1Vaz2jVWLaIA==
esbuild-freebsd-64@0.14.53:
version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.53.tgz#aca2af6d72b537fe66a38eb8f374fb66d4c98ca0"
integrity sha512-WkdJa8iyrGHyKiPF4lk0MiOF87Q2SkE+i+8D4Cazq3/iqmGPJ6u49je300MFi5I2eUsQCkaOWhpCVQMTKGww2w==
esbuild-freebsd-arm64@0.14.53:
version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.53.tgz#76282e19312d914c34343c8a7da6cc5f051580b9"
integrity sha512-9T7WwCuV30NAx0SyQpw8edbKvbKELnnm1FHg7gbSYaatH+c8WJW10g/OdM7JYnv7qkimw2ZTtSA+NokOLd2ydQ==
esbuild-linux-32@0.14.53:
version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.53.tgz#1045d34cf7c5faaf2af3b29cc1573b06580c37e5"
integrity sha512-VGanLBg5en2LfGDgLEUxQko2lqsOS7MTEWUi8x91YmsHNyzJVT/WApbFFx3MQGhkf+XdimVhpyo5/G0PBY91zg==
esbuild-linux-64@0.14.53:
version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.53.tgz#ab3f2ee2ebb5a6930c72d9539cb34b428808cbe4"
integrity sha512-pP/FA55j/fzAV7N9DF31meAyjOH6Bjuo3aSKPh26+RW85ZEtbJv9nhoxmGTd9FOqjx59Tc1ZbrJabuiXlMwuZQ==
esbuild-linux-arm64@0.14.53:
version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.53.tgz#1f5530412f6690949e78297122350488d3266cfe"
integrity sha512-GDmWITT+PMsjCA6/lByYk7NyFssW4Q6in32iPkpjZ/ytSyH+xeEx8q7HG3AhWH6heemEYEWpTll/eui3jwlSnw==
esbuild-linux-arm@0.14.53:
version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.53.tgz#a44ec9b5b42007ab6c0d65a224ccc6bbd97c54cf"
integrity sha512-/u81NGAVZMopbmzd21Nu/wvnKQK3pT4CrvQ8BTje1STXcQAGnfyKgQlj3m0j2BzYbvQxSy+TMck4TNV2onvoPA==
esbuild-linux-mips64le@0.14.53:
version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.53.tgz#a4d0b6b17cfdeea4e41b0b085a5f73d99311be9f"
integrity sha512-d6/XHIQW714gSSp6tOOX2UscedVobELvQlPMkInhx1NPz4ThZI9uNLQ4qQJHGBGKGfu+rtJsxM4NVHLhnNRdWQ==
esbuild-linux-ppc64le@0.14.53:
version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.53.tgz#8c331822c85465434e086e3e6065863770c38139"
integrity sha512-ndnJmniKPCB52m+r6BtHHLAOXw+xBCWIxNnedbIpuREOcbSU/AlyM/2dA3BmUQhsHdb4w3amD5U2s91TJ3MzzA==
esbuild-linux-riscv64@0.14.53:
version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.53.tgz#36fd75543401304bea8a2d63bf8ea18aaa508e00"
integrity sha512-yG2sVH+QSix6ct4lIzJj329iJF3MhloLE6/vKMQAAd26UVPVkhMFqFopY+9kCgYsdeWvXdPgmyOuKa48Y7+/EQ==
esbuild-linux-s390x@0.14.53:
version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.53.tgz#1622677ab6824123f48f75d3afc031cd41936129"
integrity sha512-OCJlgdkB+XPYndHmw6uZT7jcYgzmx9K+28PVdOa/eLjdoYkeAFvH5hTwX4AXGLZLH09tpl4bVsEtvuyUldaNCg==
esbuild-netbsd-64@0.14.53:
version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.53.tgz#e86d0efd0116658be335492ed12e66b26b4baf52"
integrity sha512-gp2SB+Efc7MhMdWV2+pmIs/Ja/Mi5rjw+wlDmmbIn68VGXBleNgiEZG+eV2SRS0kJEUyHNedDtwRIMzaohWedQ==
esbuild-openbsd-64@0.14.53:
version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.53.tgz#9bcbbe6f86304872c6e91f64c8eb73fc29c3588b"
integrity sha512-eKQ30ZWe+WTZmteDYg8S+YjHV5s4iTxeSGhJKJajFfQx9TLZJvsJX0/paqwP51GicOUruFpSUAs2NCc0a4ivQQ==
esbuild-sass-plugin@^1.5.2:
version "1.8.2"
resolved "https://registry.yarnpkg.com/esbuild-sass-plugin/-/esbuild-sass-plugin-1.8.2.tgz#76babbd6d4c87ffd14259fd364d446f857b3f513"
integrity sha512-ZBjONsRSpmzMKvxSNohnNXCNaBBVlYLqYhyZtN8leGGkJktCvVMKC6g9V5TWIemk1SEaW2XK+YFxz3Whw3+YYw==
dependencies:
esbuild "^0.14.5"
picomatch "^2.3.0"
resolve "^1.20.0"
sass "^1.45.0"
esbuild-sunos-64@0.14.53:
version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.53.tgz#f7a872f7460bfb7b131f7188a95fbce3d1c577e8"
integrity sha512-OWLpS7a2FrIRukQqcgQqR1XKn0jSJoOdT+RlhAxUoEQM/IpytS3FXzCJM6xjUYtpO5GMY0EdZJp+ur2pYdm39g==
esbuild-windows-32@0.14.53:
version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.53.tgz#c5e3ca50e2d1439cc2c9fe4defa63bcd474ce709"
integrity sha512-m14XyWQP5rwGW0tbEfp95U6A0wY0DYPInWBB7D69FAXUpBpBObRoGTKRv36lf2RWOdE4YO3TNvj37zhXjVL5xg==
esbuild-windows-64@0.14.53:
version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.53.tgz#ec2ab4a60c5215f092ffe1eab6d01319e88238af"
integrity sha512-s9skQFF0I7zqnQ2K8S1xdLSfZFsPLuOGmSx57h2btSEswv0N0YodYvqLcJMrNMXh6EynOmWD7rz+0rWWbFpIHQ==
esbuild-windows-arm64@0.14.53:
version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.53.tgz#f71d403806bdf9f4a1f9d097db9aec949bd675c8"
integrity sha512-E+5Gvb+ZWts+00T9II6wp2L3KG2r3iGxByqd/a1RmLmYWVsSVUjkvIxZuJ3hYTIbhLkH5PRwpldGTKYqVz0nzQ==
esbuild@^0.12.24:
version "0.12.29"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.29.tgz#be602db7c4dc78944a9dbde0d1ea19d36c1f882d"
integrity sha512-w/XuoBCSwepyiZtIRsKsetiLDUVGPVw1E/R3VTFSecIy8UR7Cq3SOtwKHJMFoVqqVG36aGkzh4e8BvpO1Fdc7g==
esbuild@^0.14.5:
version "0.14.53"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.53.tgz#20b1007f686e8584f2a01a1bec5a37aac9498ce4"
integrity sha512-ohO33pUBQ64q6mmheX1mZ8mIXj8ivQY/L4oVuAshr+aJI+zLl+amrp3EodrUNDNYVrKJXGPfIHFGhO8slGRjuw==
optionalDependencies:
"@esbuild/linux-loong64" "0.14.53"
esbuild-android-64 "0.14.53"
esbuild-android-arm64 "0.14.53"
esbuild-darwin-64 "0.14.53"
esbuild-darwin-arm64 "0.14.53"
esbuild-freebsd-64 "0.14.53"
esbuild-freebsd-arm64 "0.14.53"
esbuild-linux-32 "0.14.53"
esbuild-linux-64 "0.14.53"
esbuild-linux-arm "0.14.53"
esbuild-linux-arm64 "0.14.53"
esbuild-linux-mips64le "0.14.53"
esbuild-linux-ppc64le "0.14.53"
esbuild-linux-riscv64 "0.14.53"
esbuild-linux-s390x "0.14.53"
esbuild-netbsd-64 "0.14.53"
esbuild-openbsd-64 "0.14.53"
esbuild-sunos-64 "0.14.53"
esbuild-windows-32 "0.14.53"
esbuild-windows-64 "0.14.53"
esbuild-windows-arm64 "0.14.53"
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
dependencies:
to-regex-range "^5.0.1"
fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
is-glob "^4.0.1"
has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
dependencies:
function-bind "^1.1.1"
immutable@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef"
integrity sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"
is-core-module@^2.9.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
dependencies:
has "^1.0.3"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
is-glob@^4.0.1, is-glob@~4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
dependencies:
is-extglob "^2.1.1"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
keycharm@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/keycharm/-/keycharm-0.4.0.tgz#8d684ea9cc01379a07fbddee33ff32d97f5ae2a7"
integrity sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ==
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
path-parse@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
dependencies:
picomatch "^2.2.1"
resolve@^1.20.0:
version "1.22.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
dependencies:
is-core-module "^2.9.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
sass@^1.45.0:
version "1.54.1"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.1.tgz#4f72ef57ce2a6c3251f4e2c75eee9a0c19e09eb5"
integrity sha512-GHJJr31Me32RjjUBagyzx8tzjKBUcDwo5239XANIRBq0adDu5iIG0aFO0i/TBb/4I9oyxkEv44nq/kL1DxdDhA==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"
"source-map-js@>=0.6.2 <2.0.0":
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
timsort@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
integrity sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
uuid@^8.0.0:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
vis-data@^7.1.4:
version "7.1.4"
resolved "https://registry.yarnpkg.com/vis-data/-/vis-data-7.1.4.tgz#90e5e796a79e1901de14c0808fb32a1a0735c1dc"
integrity sha512-usy+ePX1XnArNvJ5BavQod7YRuGQE1pjFl+pu7IS6rCom2EBoG0o1ZzCqf3l5US6MW51kYkLR+efxRbnjxNl7w==
vis-network@^9.1.2:
version "9.1.2"
resolved "https://registry.yarnpkg.com/vis-network/-/vis-network-9.1.2.tgz#ddaca300a9764bbf1646a0aa03eb28fe55823ca8"
integrity sha512-BdapguKg7sk3NvdZaDsM7T6rNhOBFz0/F4ZScxctK4klRzQPLQPTEcmbioXaZhMkkgWymzBR3lFCxL1q+eYyAw==
vis-util@^5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/vis-util/-/vis-util-5.0.3.tgz#6a9d67c6ed92f2bfa67e50c20166bb5f59ac07bf"
integrity sha512-Wf9STUcFrDzK4/Zr7B6epW2Kvm3ORNWF+WiwEz2dpf5RdWkLUXFSbLcuB88n1W6tCdFwVN+v3V4/Xmn9PeL39g==
@@ -69,7 +69,5 @@
{% endblock content-wrapper %}
{% 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 javascript %}
+263 -181
View File
@@ -1,4 +1,4 @@
from django.shortcuts import get_object_or_404, render
from django.shortcuts import render
from django.db.models import Q
from django.views.generic import View
from django.contrib.auth.mixins import PermissionRequiredMixin
@@ -11,43 +11,95 @@ from .filters import DeviceFilterSet
import json
from dcim.models import Device, Cable, DeviceRole, PathEndpoint
from circuits.models import CircuitTermination, ProviderNetwork
from dcim.models import Device, CableTermination, DeviceRole, Interface, FrontPort, RearPort, PowerPanel, PowerFeed
from circuits.models import CircuitTermination
from wireless.models import WirelessLink
from extras.models import Tag
supported_termination_types = ["interface", "front port", "rear port", "power outlet", "power port", "console port", "console server port"]
def create_node(device):
dev_name = device.name
if dev_name is None:
dev_name = "device name unknown"
node_content = ""
if device.device_type is not None:
node_content += "<tr><th>Type: </th><td>" + device.device_type.model + "</td></tr>"
if device.device_role.name is not None:
node_content += "<tr><th>Role: </th><td>" + device.device_role.name + "</td></tr>"
if device.serial != "":
node_content += "<tr><th>Serial: </th><td>" + device.serial + "</td></tr>"
if device.primary_ip is not None:
node_content += "<tr><th>IP Address: </th><td>" + str(device.primary_ip.address) + "</td></tr>"
dev_title = "<table> %s </table>" % (node_content)
def create_node(device, save_coords, circuit = None, powerpanel = None, powerfeed= None):
node = {}
node["id"] = device.id
node_content = ""
if circuit:
dev_name = "Circuit " + str(device.cid)
node["image"] = "../../static/netbox_topology_views/img/circuit.png"
node["id"] = "c{}".format(device.id)
if device.provider is not None:
node_content += "<tr><th>Provider: </th><td>" + device.provider.name + "</td></tr>"
if device.type is not None:
node_content += "<tr><th>Type: </th><td>" + device.type.name + "</td></tr>"
elif powerpanel:
dev_name = "Power Panel " + str(device.id)
node["image"] = "../../static/netbox_topology_views/img/power-panel.png"
node["id"] = "p{}".format(device.id)
if device.site is not None:
node_content += "<tr><th>Site: </th><td>" + device.site.name + "</td></tr>"
if device.location is not None:
node_content += "<tr><th>Location: </th><td>" + device.location.name + "</td></tr>"
elif powerfeed:
dev_name = "Power Feed " + str(device.id)
node["image"] = "../../static/netbox_topology_views/img/power-feed.png"
node["id"] = "f{}".format(device.id)
if device.power_panel is not None:
node_content += "<tr><th>Power Panel: </th><td>" + device.power_panel.name + "</td></tr>"
if device.type is not None:
node_content += "<tr><th>Type: </th><td>" + device.type + "</td></tr>"
if device.supply is not None:
node_content += "<tr><th>Supply: </th><td>" + device.supply + "</td></tr>"
if device.phase is not None:
node_content += "<tr><th>Phase: </th><td>" + device.phase + "</td></tr>"
if device.amperage is not None:
node_content += "<tr><th>Amperage: </th><td>" + str(device.amperage )+ "</td></tr>"
if device.voltage is not None:
node_content += "<tr><th>Voltage: </th><td>" + str(device.voltage) + "</td></tr>"
else:
dev_name = device.name
if dev_name is None:
dev_name = "device name unknown"
if device.device_type is not None:
node_content += "<tr><th>Type: </th><td>" + device.device_type.model + "</td></tr>"
if device.device_role.name is not None:
node_content += "<tr><th>Role: </th><td>" + device.device_role.name + "</td></tr>"
if device.serial != "":
node_content += "<tr><th>Serial: </th><td>" + device.serial + "</td></tr>"
if device.primary_ip is not None:
node_content += "<tr><th>IP Address: </th><td>" + str(device.primary_ip.address) + "</td></tr>"
if device.site is not None:
node_content += "<tr><th>Site: </th><td>" + device.site.name + "</td></tr>"
if device.location is not None:
node_content += "<tr><th>Location: </th><td>" + device.location.name + "</td></tr>"
if device.rack is not None:
node_content += "<tr><th>Rack: </th><td>" + device.rack.name + "</td></tr>"
if device.position is not None:
if device.face is not None:
node_content += "<tr><th>Position: </th><td> {} ({}) </td></tr>".format(device.position, device.face)
else:
node_content += "<tr><th>Position: </th><td>" + device.position + "</td></tr>"
node["id"] = device.id
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"
if device.device_role.color != "":
node["color.border"] = "#" + device.device_role.color
dev_title = "<table><tbody> %s</tbody></table>" % (node_content)
node["title"] = dev_title
node["name"] = dev_name
node["label"] = dev_name
node["title"] = dev_title
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"
if device.device_role.color != "":
node["color.border"] = "#" + device.device_role.color
node["physics"] = True
if "coordinates" in device.custom_field_data:
if device.custom_field_data["coordinates"] is not None:
if ";" in device.custom_field_data["coordinates"]:
@@ -55,188 +107,213 @@ def create_node(device):
node["x"] = int(cords[0])
node["y"] = int(cords[1])
node["physics"] = False
else:
if save_coords:
node["physics"] = False
else:
node["physics"] = True
return node
def create_edge(edge_id, cable, termination_a, termination_b, path = None, circuit = None):
cable_a_dev_name = "device A name unknown" if termination_a.device.name is None else termination_a.device.name
cable_a_name = "device A name unknown" if termination_a.name is None else termination_a.name
cable_b_dev_name = "device A name unknown" if termination_b.device.name is None else termination_b.device.name
cable_b_name = "cable B name unknown" if termination_b.name is None else termination_b.name
def create_edge(edge_id, termination_a, termination_b, circuit = None, cable = None, wireless = None, power=None):
cable_a_name = "device A name unknown" if termination_a["termination_name"] is None else termination_a["termination_name"]
cable_a_dev_name = "device A name unknown" if termination_a["termination_device_name"] is None else termination_a["termination_device_name"]
cable_b_name= "device A name unknown" if termination_b["termination_name"] is None else termination_b["termination_name"]
cable_b_dev_name = "cable B name unknown" if termination_b["termination_device_name"] is None else termination_b["termination_device_name"]
edge = {}
edge["id"] = edge_id
edge["from"] = termination_a.device.id
edge["to"] = termination_b.device.id
edge["from"] = termination_a["device_id"]
edge["to"] = termination_b["device_id"]
if circuit is not None:
edge["dashes"] = True
edge["title"] = "Circuit provider: " + circuit.provider.name + "<br>"
edge["title"] = "Circuit provider: " + circuit["provider_name"] + "<br>"
edge["title"] += "Termination between <br>"
edge["title"] += cable_b_dev_name + " [" + cable_b_name + "]<br>"
edge["title"] += cable_a_dev_name + " [" + cable_a_name + "]"
elif wireless is not None:
edge["dashes"] = [2, 10, 2, 10]
edge["title"] = "Wireless Connection between <br> " + cable_a_dev_name + " [" + cable_a_name + "]<br>" + cable_b_dev_name + " [" + cable_b_name + "]"
elif power is not None:
edge["dashes"] = [5, 5, 3, 3]
edge["title"] = "Power Connection between <br> " + cable_a_dev_name + " [" + cable_a_name + "]<br>" + cable_b_dev_name + " [" + cable_b_name + "]"
else:
edge["title"] = "Cable between <br> " + cable_a_dev_name + " [" + cable_a_name + "]<br>" + cable_b_dev_name + " [" + cable_b_name + "]"
if path is not None:
edge["title"] += "" if len(path) <= 0 else "<br>Through " + "/".join(path)
if cable is not None and cable.color != "":
edge["color"] = "#" + cable.color
return edge
def get_topology_data(queryset, hide_unconnected, intermediate_dev_role_ids, end2end_connections):
def create_circuit_termination(termination):
if isinstance(termination, CircuitTermination):
return { "termination_name": termination.circuit.provider.name, "termination_device_name": termination.circuit.cid, "device_id": "c{}".format(termination.circuit.id) }
if isinstance(termination, Interface) or isinstance(termination, FrontPort) or isinstance(termination, RearPort):
return { "termination_name": termination.name, "termination_device_name": termination.device.name, "device_id": termination.device.id }
return None
def get_topology_data(queryset, hide_unconnected, save_coords, show_circuit, show_power):
nodes_devices = {}
edges = []
nodes = []
edge_ids = 0
cable_ids = []
circuit_ids = []
nodes_circuits = {}
nodes_powerpanel = {}
nodes_powerfeed = {}
nodes_provider_networks = {}
cable_ids = {}
if not queryset:
return None
ignore_cable_type = settings.PLUGINS_CONFIG["netbox_topology_views"]["ignore_cable_type"]
enable_circuit_terminations = settings.PLUGINS_CONFIG["netbox_topology_views"]["enable_circuit_terminations"]
device_ids = [d.id for d in queryset]
site_ids = [d.site.id for d in queryset]
links = Cable.objects.filter( Q(_termination_a_device_id__in=device_ids) | Q(_termination_b_device_id__in=device_ids) ) \
.select_related("termination_a_type", "termination_b_type") \
.prefetch_related("termination_a", "termination_b")
if show_circuit:
circuits = CircuitTermination.objects.filter( Q(site_id__in=site_ids) | Q( provider_network__isnull=False) ).prefetch_related("provider_network", "circuit")
for circuit in circuits:
if not hide_unconnected and circuit.circuit.id not in nodes_circuits:
nodes_circuits[circuit.circuit.id] = circuit.circuit
termination_a = {}
termination_b = {}
circuit_model = {}
if circuit.cable is not None:
termination_a = create_circuit_termination(circuit.cable.a_terminations[0])
termination_b = create_circuit_termination(circuit.cable.b_terminations[0])
elif circuit.provider_network is not None:
if circuit.provider_network.id not in nodes_provider_networks:
nodes_provider_networks[circuit.provider_network.id] = circuit.provider_network
if bool(termination_a) and bool(termination_b):
circuit_model = {"provider_name": circuit.circuit.provider.name}
edge_ids += 1
edges.append(create_edge(edge_id=edge_ids,circuit=circuit_model, termination_a=termination_a, termination_b=termination_b))
circuit_has_connections = False
for termination in [circuit.cable.a_terminations[0], circuit.cable.b_terminations[0]]:
if not isinstance(termination, CircuitTermination):
if termination.device.id not in nodes_devices and termination.device.id in device_ids:
nodes_devices[termination.device.id] = termination.device
circuit_has_connections = True
else:
if termination.device.id in device_ids:
circuit_has_connections = True
if circuit_has_connections and hide_unconnected:
if circuit.circuit.id not in nodes_circuits:
nodes_circuits[circuit.circuit.id] = circuit.circuit
for d in nodes_circuits.values():
nodes.append(create_node(d, save_coords, circuit=True))
links = CableTermination.objects.filter( Q(_device_id__in=device_ids) ).select_related("termination_type")
wlan_links = WirelessLink.objects.filter( Q(_interface_a_device_id__in=device_ids) & Q(_interface_b_device_id__in=device_ids))
if show_power:
power_panels = PowerPanel.objects.filter( Q (site_id__in=site_ids))
power_panels_ids = [d.id for d in power_panels]
power_feeds = PowerFeed.objects.filter( Q (power_panel_id__in=power_panels_ids))
for power_feed in power_feeds:
if not hide_unconnected or (hide_unconnected and power_feed.cable_id is not None):
if power_feed.power_panel.id not in nodes_powerpanel:
nodes_powerpanel[power_feed.power_panel.id] = power_feed.power_panel
power_link_name = ""
if power_feed.id not in nodes_powerfeed:
if hide_unconnected:
if power_feed.link_peers[0].device.id in device_ids:
nodes_powerfeed[power_feed.id] = power_feed
power_link_name =power_feed.link_peers[0].name
else:
nodes_powerfeed[power_feed.id] = power_feed
edge_ids += 1
termination_a = { "termination_name": power_feed.power_panel.name, "termination_device_name": "", "device_id": "p{}".format(power_feed.power_panel.id) }
termination_b = { "termination_name": power_feed.name, "termination_device_name": power_link_name, "device_id": "f{}".format(power_feed.id) }
edges.append(create_edge(edge_id=edge_ids, termination_a=termination_a, termination_b=termination_b, power=True))
if power_feed.cable_id is not None:
if power_feed.cable.id not in cable_ids:
cable_ids[power_feed.cable.id] = {}
cable_ids[power_feed.cable.id][power_feed.cable_end] = termination_b
for d in nodes_powerfeed.values():
nodes.append(create_node(d, save_coords, powerfeed = True))
for d in nodes_powerpanel.values():
nodes.append(create_node(d, save_coords, powerpanel = True))
for link in links:
if link.termination_a_type.name in ignore_cable_type or link.termination_b_type.name in ignore_cable_type \
or link.id in cable_ids:
if link.termination_type.name in ignore_cable_type :
continue
a_is_path_endoint = isinstance(link.termination_a, PathEndpoint)
b_is_path_endoint = isinstance(link.termination_b, PathEndpoint)
if not end2end_connections or (not a_is_path_endoint and not b_is_path_endoint):
if isinstance(link.termination_a, CircuitTermination):
if enable_circuit_terminations and link.termination_a.circuit.id not in circuit_ids:
circuit = link.termination_a.circuit
circuit_ids.append(circuit.id)
cable_ids.append(link.id)
path_destination = circuit.termination_z if link.termination_a.term_side == "A" else circuit.termination_a
if path_destination is not None and path_destination.provider_network is None:
# ProviderNetwork not supported at the moment
# $path_destination.cable would be none : there is no cable between a CircuitTermination and ProviderNetwork
origin_device = link.termination_b.device
destination_device = path_destination.cable.termination_b.device
if origin_device.id in device_ids and destination_device.id in device_ids:
if origin_device.id not in nodes_devices:
nodes_devices[origin_device.id] = origin_device
if destination_device.id not in nodes_devices:
nodes_devices[destination_device.id] = destination_device
edge_ids += 1
edges.append(create_edge(edge_ids, link, link.termination_b, path_destination.cable.termination_b, [circuit.cid], circuit))
elif link.termination_a.device.id in device_ids and link.termination_b.device.id in device_ids:
if link.termination_a.device.id not in nodes_devices:
nodes_devices[link.termination_a.device.id] = link.termination_a.device
if link.termination_b.device.id not in nodes_devices:
nodes_devices[link.termination_b.device.id] = link.termination_b.device
cable_ids.append(link.id)
edge_ids += 1
edges.append(create_edge(edge_ids, link, link.termination_a, link.termination_b))
else:
# termination_a can be a CircuitTermination (no trace()) while termination_b is a PathEndpoint
# If so, we swap them so we can follow the path using PathEndpoint#trace()
path_start = link.termination_a if a_is_path_endoint else link.termination_b
if path_start.device is not None and path_start.device.id not in device_ids:
# device not in queryset, skip
continue
# Ignore incomplete path when in end-to-end connection mode
if path_start.path is not None:
if not path_start.path.is_active or path_start.path.is_split:
continue
#Normal device cables
if link.termination_type.name in supported_termination_types:
complete_link = False
if link.cable_end == "A":
if link.cable.id not in cable_ids:
cable_ids[link.cable.id] = {}
else:
if 'B' in cable_ids[link.cable.id]:
if cable_ids[link.cable.id]['B'] is not None:
complete_link = True
elif link.cable_end == "B":
if link.cable.id not in cable_ids:
cable_ids[link.cable.id] = {}
else:
if 'A' in cable_ids[link.cable.id]:
if cable_ids[link.cable.id]['A'] is not None:
complete_link = True
else:
continue
print("Unkown cable end")
cable_ids[link.cable.id][link.cable_end] = link
path_destination = path_start.path.destination
if hasattr(path_destination, "device") and path_destination.device is not None and path_destination.device.id not in device_ids:
# device not in queryset, skip
continue
if complete_link:
edge_ids += 1
if isinstance(cable_ids[link.cable.id]["B"], CableTermination):
if cable_ids[link.cable.id]["B"]._device_id not in nodes_devices:
nodes_devices[cable_ids[link.cable.id]["B"]._device_id] = cable_ids[link.cable.id]["B"].termination.device
termination_b = { "termination_name": cable_ids[link.cable.id]["B"].termination.name, "termination_device_name": cable_ids[link.cable.id]["B"].termination.device.name, "device_id": cable_ids[link.cable.id]["B"].termination.device.id }
else:
termination_b = cable_ids[link.cable.id]["B"]
valid_path = False
if isinstance(cable_ids[link.cable.id]["A"], CableTermination):
if cable_ids[link.cable.id]["A"]._device_id not in nodes_devices:
nodes_devices[cable_ids[link.cable.id]["A"]._device_id] = cable_ids[link.cable.id]["A"].termination.device
termination_a = { "termination_name": cable_ids[link.cable.id]["A"].termination.name, "termination_device_name": cable_ids[link.cable.id]["A"].termination.device.name, "device_id": cable_ids[link.cable.id]["A"].termination.device.id }
else:
termination_a = cable_ids[link.cable.id]["A"]
edges.append(create_edge(edge_id=edge_ids, cable=link.cable, termination_a=termination_a, termination_b=termination_b))
# variables needed for trace() browsing
origin = None
circuit = None
tmp_path = []
for wlan_link in wlan_links:
if wlan_link.interface_a.device.id not in nodes_devices:
nodes_devices[wlan_link.interface_a.device.id] = wlan_link.interface_a.device
if wlan_link.interface_b.device.id not in nodes_devices:
nodes_devices[wlan_link.interface_b.device.id] = wlan_link.interface_b.device
termination_a = {"termination_name": wlan_link.interface_a.name, "termination_device_name": wlan_link.interface_a.device.name, "device_id": wlan_link.interface_a.device.id}
termination_b = {"termination_name": wlan_link.interface_b.name, "termination_device_name": wlan_link.interface_b.device.name, "device_id": wlan_link.interface_b.device.id}
wireless = {"ssid": wlan_link.ssid }
# trace() : Return the path as a list of three-tuples (A termination, cable, B termination)
# TODO device qui ne s"affichent pas si hide_unconnected
for (termination_a, cable, termination_b) in path_start.trace():
if origin is None:
# New part of the link
origin = termination_a
circuit = None
tmp_path = []
edge_ids += 1
edges.append(create_edge(edge_id=edge_ids, termination_a=termination_a, termination_b=termination_b,wireless=wireless))
if isinstance(termination_b, ProviderNetwork):
# ProviderNetwork not supported at the moment
# It would need to manage several kind of nodes (Device, ProviderNetwork)
# and maps javascript nodes ID with Devices IDs and ProviderNetworks IDs
# When $termination_b is a ProviderNetwork instance, $cable will be None
break
if cable is not None and (cable.termination_a_type in ignore_cable_type or cable.termination_b_type in ignore_cable_type):
# Ignore this path
break
if isinstance(termination_b, CircuitTermination):
if enable_circuit_terminations:
circuit = termination_b.circuit
tmp_path.append(circuit.cid)
if circuit.id not in circuit_ids:
circuit_ids.append(circuit.id)
else:
# Ignore this path
break
elif end2end_connections and termination_b != path_destination \
and termination_b.device.device_role.id not in intermediate_dev_role_ids \
and termination_b.device.id not in device_ids:
# Skip this intermediate device, origin device is keep in $origin for next iteration
tmp_path.append(termination_b.device.name)
elif cable is not None and termination_b is not None:
if cable.id not in cable_ids:
# New part of the link we want to display
cable_ids.append(cable.id)
edge_ids += 1
edges.append(create_edge(edge_ids, cable, origin, termination_b, tmp_path, circuit))
if not valid_path:
valid_path = True
if termination_b.device.id not in nodes_devices and \
(termination_b.device.device_role.id in intermediate_dev_role_ids or termination_b.device.id in device_ids) :
nodes_devices[termination_b.device.id] = termination_b.device
# Reset for next iteration
origin = None
# endfor (trace)
if valid_path and path_start.device.id not in nodes_devices:
nodes_devices[path_start.device.id] = path_start.device
# endfor (link)
for qs_device in queryset:
if qs_device.id not in nodes_devices and not hide_unconnected:
nodes_devices[qs_device.id] = qs_device
results = {}
results["nodes"] = [create_node(d) for d in nodes_devices.values()]
for d in nodes_devices.values():
nodes.append(create_node(d, save_coords))
results["nodes"] = nodes
results["edges"] = edges
return results
@@ -253,40 +330,45 @@ class TopologyHomeView(PermissionRequiredMixin, View):
topo_data = None
if request.GET:
save_coords = False
if 'save_coords' in request.GET:
if request.GET["save_coords"] == "on":
save_coords = True
hide_unconnected = False
if "hide_unconnected" in request.GET:
if request.GET["hide_unconnected"] == "on" :
hide_unconnected = True
if "intermediate_dev_role_id" in request.GET:
intermediate_dev_role_ids = list(map(int, request.GET.getlist("intermediate_dev_role_id")))
else:
intermediate_dev_role_ids = []
show_power = False
if "show_power" in request.GET:
if request.GET["show_power"] == "on" :
show_power = True
show_circuit = False
if "show_circuit" in request.GET:
if request.GET["show_circuit"] == "on" :
show_circuit = True
end2end_connections = False
if "end2end_connections" in request.GET:
if request.GET["end2end_connections"] == "on" :
end2end_connections = True
if "draw_init" in request.GET:
if request.GET["draw_init"].lower() == "true":
topo_data = get_topology_data(self.queryset, hide_unconnected, intermediate_dev_role_ids, end2end_connections)
topo_data = get_topology_data(self.queryset, hide_unconnected, save_coords, show_circuit, show_power)
else:
topo_data = get_topology_data(self.queryset, hide_unconnected, intermediate_dev_role_ids, end2end_connections)
topo_data = get_topology_data(self.queryset, hide_unconnected, save_coords, show_circuit, show_power)
else:
preselected_device_roles = settings.PLUGINS_CONFIG["netbox_topology_views"]["preselected_device_roles"]
preselected_intermediate_dev_roles = settings.PLUGINS_CONFIG["netbox_topology_views"]["preselected_intermediate_dev_roles"]
preselected_tags = settings.PLUGINS_CONFIG["netbox_topology_views"]["preselected_tags"]
always_save_coordinates = bool(settings.PLUGINS_CONFIG["netbox_topology_views"]["always_save_coordinates"])
q_device_role_id = DeviceRole.objects.filter(name__in=preselected_device_roles).values_list("id", flat=True)
q_intermediate_dev_role_id = DeviceRole.objects.filter(name__in=preselected_intermediate_dev_roles).values_list("id", flat=True)
q_tags = Tag.objects.filter(name__in=preselected_tags).values_list("name", flat=True)
q = QueryDict(mutable=True)
q.setlist("device_role_id", list(q_device_role_id))
q.setlist("intermediate_dev_role_id", list(q_intermediate_dev_role_id))
q.setlist("tag", list(q_tags))
q["draw_init"] = settings.PLUGINS_CONFIG["netbox_topology_views"]["draw_default_layout"]
if always_save_coordinates:
q["save_coords"] = "on"
query_string = q.urlencode()
return HttpResponseRedirect(request.path + "?" + query_string)
-4781
View File
File diff suppressed because it is too large Load Diff
-27
View File
@@ -1,27 +0,0 @@
{
"private": true,
"name": "netbox_topology_views",
"version": "2.0.0",
"scripts": {
"resources": "gulp build",
"resources_dev": "gulp build_dev"
},
"dependencies": {
"vis-data": "^7.1.2",
"vis-network": "^9.1.0",
"vis-util": "^5.0.2"
},
"devDependencies": {
"gulp": "^4.0.2",
"gulp-clean-css": "^4.3.0",
"gulp-concat": "^2.6.1",
"gulp-sass": "^4.1.0",
"gulp-uglify": "^3.0.2"
},
"peerDependencies": {
"@egjs/hammerjs": "^2.0.0",
"uuid": "^8.0.0",
"keycharm": "^0.4.0",
"timsort": "^0.3.0"
}
}
+8 -1
View File
@@ -1,16 +1,23 @@
from setuptools import setup, find_packages
from os import path
top_level_directory = path.abspath(path.dirname(__file__))
with open(path.join(top_level_directory, 'README.md'), encoding='utf-8') as file:
long_description = file.read()
setup(
name='netbox-topology-views',
version='2.0.0',
version='3.0.1',
description='An NetBox plugin to create Topology maps',
long_description=long_description,
long_description_content_type='text/markdown',
url='https://github.com/mattieserver/netbox-topology-views',
author='Mattijs Vanhaverbeke',
license='Apache 2.0',
install_requires=[],
packages=find_packages(),
include_package_data=True,
keywords=['netbox-plugin'],
classifiers=[
'Programming Language :: Python',
'Programming Language :: Python :: 3',