Compare commits
5
Commits
v2.0.3
...
v3.0.0-beta.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2e2a4c781 | ||
|
|
318b8495b7 | ||
|
|
5e1d5d2937 | ||
|
|
0b6df3bd28 | ||
|
|
af1666b3ba |
+2
-1
@@ -128,4 +128,5 @@ dmypy.json
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
node_modules
|
||||
node_modules
|
||||
app.js.map
|
||||
@@ -35,6 +35,7 @@ Then run `python3 manage.py collectstatic --no-input`
|
||||
|
||||
| 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,7 +46,7 @@ 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"
|
||||
|
||||
@@ -67,11 +68,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
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
-78
@@ -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);
|
||||
@@ -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.2'
|
||||
version = '3.0.0-beta.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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -15,17 +15,16 @@ 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, ('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', 'intermediate_dev_role_id')),
|
||||
(None, ('device_role_id', )),
|
||||
(None, ('tag', 'status')),
|
||||
)
|
||||
|
||||
@@ -39,17 +38,6 @@ class DeviceFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
required=False,
|
||||
label=_('Device Role')
|
||||
)
|
||||
end2end_connections = forms.BooleanField(
|
||||
label=_("Display end-to-end connections"),
|
||||
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'
|
||||
)
|
||||
site_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
@@ -72,11 +60,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 panel/feed"),
|
||||
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';
|
||||
@@ -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({
|
||||
@@ -149,13 +149,39 @@ function handleLoadData() {
|
||||
});
|
||||
|
||||
graph.on("doubleClick", function (params) {
|
||||
console.log(params.nodes);
|
||||
selected_devices = params.nodes;
|
||||
for (selected_device in selected_devices) {
|
||||
url = "/dcim/devices/" + selected_devices[selected_device] + "/";
|
||||
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"
|
||||
}
|
||||
}
|
||||
@@ -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 %}
|
||||
|
||||
+261
-196
@@ -1,3 +1,5 @@
|
||||
from platform import node
|
||||
from tkinter.messagebox import NO
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.db.models import Q
|
||||
from django.views.generic import View
|
||||
@@ -6,59 +8,101 @@ from django.conf import settings
|
||||
from django.http import QueryDict
|
||||
from django.http import HttpResponseRedirect
|
||||
|
||||
|
||||
from .forms import DeviceFilterForm
|
||||
from .filters import DeviceFilterSet
|
||||
|
||||
import json
|
||||
|
||||
from dcim.models import Device, Cable, DeviceRole, PathEndpoint
|
||||
from dcim.models import Device, Cable, CableTermination, DeviceRole, PathEndpoint, Interface, FrontPort, RearPort, PowerPanel, PowerFeed
|
||||
from circuits.models import CircuitTermination, ProviderNetwork
|
||||
from wireless.models import WirelessLink
|
||||
from extras.models import Tag
|
||||
|
||||
supported_termination_types = ["interface", "front port", "rear port", "power outlet", "power 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>"
|
||||
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>"
|
||||
|
||||
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"]:
|
||||
@@ -66,193 +110,209 @@ 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]
|
||||
|
||||
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:
|
||||
site_ids = [d.site.id for d in queryset]
|
||||
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
|
||||
|
||||
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
|
||||
else:
|
||||
nodes_powerfeed[power_feed.id] = power_feed
|
||||
|
||||
edge_ids += 1
|
||||
termination_a = { "termination_name": power_feed.power_panel.name, "termination_device_name": str(power_feed.power_panel.id), "device_id": "p{}".format(power_feed.power_panel.id) }
|
||||
termination_b = { "termination_name": power_feed.name, "termination_device_name": str(termination.id), "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
|
||||
|
||||
if not hasattr(link.termination_b, 'device'):
|
||||
#CircuitTermination B Missing Device
|
||||
continue
|
||||
|
||||
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 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 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))
|
||||
|
||||
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 }
|
||||
|
||||
# variables needed for trace() browsing
|
||||
origin = None
|
||||
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))
|
||||
|
||||
# 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 = []
|
||||
|
||||
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
|
||||
|
||||
@@ -269,40 +329,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)
|
||||
|
||||
|
||||
Generated
-4781
File diff suppressed because it is too large
Load Diff
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "netbox_topology_views",
|
||||
"version": "2.0.2",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ with open(path.join(top_level_directory, 'README.md'), encoding='utf-8') as file
|
||||
|
||||
setup(
|
||||
name='netbox-topology-views',
|
||||
version='2.0.2',
|
||||
version='3.0.0-beta.1',
|
||||
description='An NetBox plugin to create Topology maps',
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
|
||||
Reference in New Issue
Block a user