Compare commits

...
5 Commits
Author SHA1 Message Date
mattieserver 4dd962f002 Prepare release v3.5.0 (#288)
* bump version

* bump version
2023-04-30 20:13:20 +02:00
Mario 5c58f2a9c2 Closes #64: Show neighbors option (#286)
* Add option to show neighbors

* Updated documentation and help text

* Added newlines at end of files where they where missing

* Updated options screenshot

* fix function vars

---------
2023-04-30 19:09:36 +02:00
Marioandmattieserver 5e9aaecf9c Closes #189, Closes #158: Add draw.io XML export (#277)
* Added export to draw.io XML

* Corrected missing newline at end of file

* init

* moved to api call

---------

Co-authored-by: mattieserver <3049868+mattieserver@users.noreply.github.com>
2023-04-30 15:22:38 +02:00
Mario 7d23cfc7eb Merge pull request #287 from dreng/285_icons_info
Fixes #285: Remove 'Icons info' from Readme
2023-04-29 15:14:44 +02:00
dreng 0af736df0e Removed 'Icons info' section 2023-04-29 13:14:15 +02:00
18 changed files with 715 additions and 288 deletions
+5 -12
View File
@@ -99,15 +99,13 @@ Go to the plugins tab in the navbar and click topology or go to `$NETBOX_URL/plu
Select your options for the topology view:
![selection_options](https://user-images.githubusercontent.com/20901110/220410144-5633d24c-6383-4cba-8d6d-ba77ef3d3cdd.png)
![topology_options](https://user-images.githubusercontent.com/20901110/235302073-234c3ef7-2c2b-4d0c-a674-9909d490d7ea.png)
<dl>
<dt>Save Coordinates</dt>
<dd>Save the coordinates of devices in the topology view.</dd>
<dt>Show Unconnected</dt>
<dd>Show devices that have no connections or for which no connection is displayed. This option depends on other parameters like 'Show Cables' and 'Show Logical Connections'.</dd>
<dt>Show Circuit Terminations</dt>
<dd>Show connections which end at a circuit termination in the topology view. These connections are displayed as blue dashed lines.</dd>
<dt>Show Cables</dt>
<dd>Show connections between interfaces, front / rear ports, etc., that are connected with one or more cables. These connections are displayed as solid lines in the color of the cable.</dd>
<dt>Show Logical Connections</dt>
@@ -116,6 +114,10 @@ Select your options for the topology view:
intermediate front / rear port connections, etc. This is similar to what was referred to as 'end-to-end' connections in previous versions. These connections are displayed as yellow dotted lines.</dd>
<dt>Show redundant Cable and Locigal Connection</dt>
<dd>Shows a logical connection (in addition to a cable), even if a cable is directly connected. Leaving this option disabled prevents that redundant display. This option only has an effect if 'Show Logical Connections' is activated.</dd>
<dt>Show Neighbors</dt>
<dd>Adds neighbors to the filter result set automatically. Link peers will be added if 'Show Cables' is ticked, far-end terminations will be added if 'Show Logical Connections' is ticked.</dd>
<dt>Show Circuit Terminations</dt>
<dd>Show connections which end at a circuit termination in the topology view. These connections are displayed as blue dashed lines.</dd>
<dt>Show Power Feeds</dt>
<dd>Displays connections between power outlets and power ports. These connections are displayed as solid lines in the color of the cable. This option depends on 'Show Cables'.</dd>
<dt>Show Wireless Links</dt>
@@ -150,12 +152,3 @@ To view `/plugins/netbox_topology-views/image`:
To view `/plugins/netbox_topology-views/individualoptions`:
+ netbox topology views | individualoptions | change
## 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)
+1 -1
View File
@@ -5,7 +5,7 @@ class TopologyViewsConfig(PluginConfig):
name = "netbox_topology_views"
verbose_name = "Topology views"
description = "An plugin to render topology maps"
version = "3.4.0"
version = "3.5.0"
author = "Mattijs Vanhaverbeke"
author_email = "author@example.com"
base_url = "netbox_topology_views"
+2 -1
View File
@@ -25,4 +25,5 @@ class DeviceRoleSerializer(ModelSerializer):
class IndividualOptionsSerializer(NetBoxModelSerializer):
class Meta:
model = IndividualOptions
fields = ("ignore_cable_type", "show_unconnected", "show_cables", "show_logical_connections", "show_single_cable_logical_conns", "show_circuit", "show_power", "show_wireless", "draw_default_layout")
fields = ("ignore_cable_type", "show_unconnected", "show_cables", "show_logical_connections", "show_single_cable_logical_conns", "show_neighbors", "show_circuit", "show_power", "show_wireless", "draw_default_layout")
+1
View File
@@ -6,5 +6,6 @@ router = NetBoxRouter()
router.register("save-coords", views.SaveCoordsViewSet)
router.register("images", views.SaveRoleImageViewSet)
router.register("xml-export", views.ExportTopoToXML)
urlpatterns = router.urls
+45 -4
View File
@@ -5,7 +5,7 @@ from dcim.models import Device, DeviceRole, PowerFeed, PowerPanel
from django.conf import settings
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.contenttypes.models import ContentType
from django.http import JsonResponse
from django.http import HttpResponse, JsonResponse
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
@@ -14,9 +14,10 @@ from netbox_topology_views.api.serializers import (
RoleImageSerializer,
TopologyDummySerializer,
)
from netbox_topology_views.models import RoleImage
from netbox_topology_views.utils import get_image_from_url
from netbox_topology_views.models import RoleImage, IndividualOptions
from netbox_topology_views.views import get_topology_data
from netbox_topology_views.utils import get_image_from_url, export_data_to_xml, get_query_settings
from netbox_topology_views.filters import DeviceFilterSet
class SaveCoordsViewSet(ReadOnlyModelViewSet):
queryset = Device.objects.none()
@@ -62,6 +63,46 @@ class SaveCoordsViewSet(ReadOnlyModelViewSet):
return Response({"status": "saved coords"})
class ExportTopoToXML(PermissionRequiredMixin, ViewSet):
queryset = Device.objects.none()
permission_required = ("dcim.view_site", "dcim.view_device")
serializer_class = TopologyDummySerializer
def list(self, request):
self.filterset = DeviceFilterSet
self.queryset = Device.objects.all().select_related(
"device_type", "device_role"
)
self.queryset = self.filterset(request.GET, self.queryset).qs
individualOptions, created = IndividualOptions.objects.get_or_create(
user_id=request.user.id,
)
if request.GET:
save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless, show_neighbors = get_query_settings(request)
topo_data = get_topology_data(
queryset=self.queryset,
individualOptions=individualOptions,
save_coords=save_coords,
show_unconnected=show_unconnected,
show_cables=show_cables,
show_logical_connections=show_logical_connections,
show_single_cable_logical_conns=show_single_cable_logical_conns,
show_neighbors=show_neighbors,
show_circuit=show_circuit,
show_power=show_power,
show_wireless=show_wireless,
)
xml_data = export_data_to_xml(topo_data).decode('utf-8')
return HttpResponse(xml_data, content_type="application/xml; charset=utf-8")
else:
return JsonResponse(
{"status": "Missing or malformed request parameters"}, status=400
)
class SaveRoleImageViewSet(PermissionRequiredMixin, ViewSet):
queryset = DeviceRole.objects.none()
+23 -9
View File
@@ -33,9 +33,10 @@ class DeviceFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
"save_coords",
"show_unconnected",
"show_cables",
"show_circuit",
"show_logical_connections",
"show_single_cable_logical_conns",
"show_neighbors",
"show_circuit",
"show_power",
"show_wireless",
),
@@ -118,17 +119,17 @@ class DeviceFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
show_unconnected = forms.BooleanField(
label=_("Show Unconnected"), required=False, initial=False
)
show_cables = forms.BooleanField(
label =_("Show Cables"), required=False, initial=False
)
show_logical_connections = forms.BooleanField(
label =_("Show Logical Connections"), required=False, initial=False
)
show_single_cable_logical_conns = forms.BooleanField(
label =_("Show redundant Cable and Locigal Connection"), required=False, initial=False
)
show_cables = forms.BooleanField(
label =_("Show Cables"), required=False, initial=False
)
show_wireless = forms.BooleanField(
label =_("Show Wireless Links"), required=False, initial=False
show_neighbors = forms.BooleanField(
label =_("Show Neighbors"), required=False, initial=False
)
show_circuit = forms.BooleanField(
label=_("Show Circuit Terminations"), required=False, initial=False
@@ -136,6 +137,9 @@ class DeviceFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
show_power = forms.BooleanField(
label=_("Show Power Feeds"), required=False, initial=False
)
show_wireless = forms.BooleanField(
label =_("Show Wireless Links"), required=False, initial=False
)
class IndividualOptionsForm(NetBoxModelForm):
fieldsets = (
@@ -148,9 +152,10 @@ class IndividualOptionsForm(NetBoxModelForm):
"preselected_tags",
"show_unconnected",
"show_cables",
"show_circuit",
"show_logical_connections",
"show_single_cable_logical_conns",
"show_neighbors",
"show_circuit",
"show_power",
"show_wireless",
"draw_default_layout",
@@ -215,6 +220,14 @@ class IndividualOptionsForm(NetBoxModelForm):
"disabled prevents that redundant display. This option only "
"has an effect if 'Show Logical Connections' is activated.")
)
show_neighbors = forms.BooleanField(
label =_("Show Neighbors"),
required=False,
initial=False,
help_text=_("Adds neighbors to the filter result set automatically. "
"Link peers will be added if 'Show Cables' is ticked, far-end "
"terminations will be added if 'Show Logical Connections' is ticked.")
)
show_circuit = forms.BooleanField(
label=_("Show Circuit Terminations"),
required=False,
@@ -248,5 +261,6 @@ class IndividualOptionsForm(NetBoxModelForm):
class Meta:
model = IndividualOptions
fields = [
'user_id', 'ignore_cable_type', 'preselected_device_roles', 'preselected_tags', 'show_unconnected', 'show_cables', 'show_logical_connections', 'show_single_cable_logical_conns', 'show_circuit', 'show_power', 'show_wireless', 'draw_default_layout'
]
'user_id', 'ignore_cable_type', 'preselected_device_roles', 'preselected_tags', 'show_unconnected', 'show_cables', 'show_logical_connections', 'show_single_cable_logical_conns', 'show_neighbors', 'show_circuit', 'show_power', 'show_wireless', 'draw_default_layout'
]
@@ -0,0 +1,18 @@
# Generated by Django 4.1.8 on 2023-04-29 09:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('netbox_topology_views', '0002_individualoptions'),
]
operations = [
migrations.AddField(
model_name='individualoptions',
name='show_neighbors',
field=models.BooleanField(default=False),
),
]
+3
View File
@@ -137,6 +137,9 @@ class IndividualOptions(NetBoxModel):
show_single_cable_logical_conns = models.BooleanField(
default=False
)
show_neighbors = models.BooleanField(
default=False
)
show_circuit = models.BooleanField(
default=False
)
File diff suppressed because one or more lines are too long
@@ -133,6 +133,29 @@ function performGraphDownload() {
document.body.removeChild(tempDownloadLink)
}
// Download XML
const downloadXmlButton = document.querySelector('#btnDownloadXml')
downloadXmlButton.addEventListener('click', (e) => {
performXmlDownload()
})
function performXmlDownload() {
const tempDownloadLink = document.createElement('a');
fetch('/api/plugins/netbox_topology_views/xml-export/?' + new URLSearchParams(window.location.search)).then(response => response.text())
.then(data => {
var blob = new Blob([data ], { type: "text/plain" });
tempDownloadLink.setAttribute("href", window.URL.createObjectURL(blob));
tempDownloadLink.setAttribute("download", 'topology.xml');
tempDownloadLink.dataset.downloadurl = ["text/plain", tempDownloadLink.download, tempDownloadLink.href].join(":");
tempDownloadLink.click();
});
}
// Theme switching
const observer = new MutationObserver((mutations) =>
mutations.forEach((mutation) => {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "netbox_topology_views",
"version": "3.4.0",
"version": "3.5.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -1,7 +1,7 @@
{
"private": true,
"name": "netbox_topology_views",
"version": "3.4.0",
"version": "3.5.0",
"scripts": {
"bundle": "node bundle.js",
"bundle:styles": "node bundle.js --styles",
@@ -13,10 +13,10 @@
"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",
"esbuild": "^0.12.24",
"esbuild-sass-plugin": "^1.5.2",
"keycharm": "^0.4.0",
"timsort": "^0.3.0",
"uuid": "^8.0.0"
+317 -189
View File
@@ -3,249 +3,377 @@
"@egjs/hammerjs@^2.0.0":
version "2.0.17"
resolved "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz"
integrity sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==
"integrity" "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A=="
"resolved" "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz"
"version" "2.0.17"
dependencies:
"@types/hammerjs" "^2.0.36"
"@esbuild/linux-loong64@0.14.54":
"integrity" "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw=="
"resolved" "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz"
"version" "0.14.54"
"@types/hammerjs@^2.0.36":
version "2.0.41"
resolved "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz"
integrity sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA==
"integrity" "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA=="
"resolved" "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz"
"version" "2.0.41"
anymatch@~3.1.2:
version "3.1.3"
resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz"
integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
"anymatch@~3.1.2":
"integrity" "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="
"resolved" "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz"
"version" "3.1.3"
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
"normalize-path" "^3.0.0"
"picomatch" "^2.0.4"
binary-extensions@^2.0.0:
version "2.2.0"
resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz"
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
"binary-extensions@^2.0.0":
"integrity" "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA=="
"resolved" "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz"
"version" "2.2.0"
braces@~3.0.2:
version "3.0.2"
resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
"braces@~3.0.2":
"integrity" "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A=="
"resolved" "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz"
"version" "3.0.2"
dependencies:
fill-range "^7.0.1"
"fill-range" "^7.0.1"
"chokidar@>=3.0.0 <4.0.0":
version "3.5.3"
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz"
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
"integrity" "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw=="
"resolved" "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz"
"version" "3.5.3"
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"
"anymatch" "~3.1.2"
"braces" "~3.0.2"
"fsevents" "~2.3.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"
"fsevents" "~2.3.2"
component-emitter@^1.3.0:
version "1.3.0"
resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz"
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
"component-emitter@^1.3.0":
"integrity" "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
"resolved" "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz"
"version" "1.3.0"
esbuild-linux-64@0.14.54:
version "0.14.54"
resolved "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz"
integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==
"esbuild-android-64@0.14.54":
"integrity" "sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ=="
"resolved" "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz"
"version" "0.14.54"
esbuild-sass-plugin@^1.5.2:
version "1.8.2"
resolved "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-1.8.2.tgz"
integrity sha512-ZBjONsRSpmzMKvxSNohnNXCNaBBVlYLqYhyZtN8leGGkJktCvVMKC6g9V5TWIemk1SEaW2XK+YFxz3Whw3+YYw==
"esbuild-android-arm64@0.14.54":
"integrity" "sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg=="
"resolved" "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz"
"version" "0.14.54"
"esbuild-darwin-64@0.14.54":
"integrity" "sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug=="
"resolved" "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz"
"version" "0.14.54"
"esbuild-darwin-arm64@0.14.54":
"integrity" "sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw=="
"resolved" "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz"
"version" "0.14.54"
"esbuild-freebsd-64@0.14.54":
"integrity" "sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg=="
"resolved" "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz"
"version" "0.14.54"
"esbuild-freebsd-arm64@0.14.54":
"integrity" "sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q=="
"resolved" "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz"
"version" "0.14.54"
"esbuild-linux-32@0.14.54":
"integrity" "sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw=="
"resolved" "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz"
"version" "0.14.54"
"esbuild-linux-64@0.14.54":
"integrity" "sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg=="
"resolved" "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz"
"version" "0.14.54"
"esbuild-linux-arm@0.14.54":
"integrity" "sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw=="
"resolved" "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz"
"version" "0.14.54"
"esbuild-linux-arm64@0.14.54":
"integrity" "sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig=="
"resolved" "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz"
"version" "0.14.54"
"esbuild-linux-mips64le@0.14.54":
"integrity" "sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw=="
"resolved" "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz"
"version" "0.14.54"
"esbuild-linux-ppc64le@0.14.54":
"integrity" "sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ=="
"resolved" "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz"
"version" "0.14.54"
"esbuild-linux-riscv64@0.14.54":
"integrity" "sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg=="
"resolved" "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz"
"version" "0.14.54"
"esbuild-linux-s390x@0.14.54":
"integrity" "sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA=="
"resolved" "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz"
"version" "0.14.54"
"esbuild-netbsd-64@0.14.54":
"integrity" "sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w=="
"resolved" "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz"
"version" "0.14.54"
"esbuild-openbsd-64@0.14.54":
"integrity" "sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw=="
"resolved" "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz"
"version" "0.14.54"
"esbuild-sass-plugin@^1.5.2":
"integrity" "sha512-ZBjONsRSpmzMKvxSNohnNXCNaBBVlYLqYhyZtN8leGGkJktCvVMKC6g9V5TWIemk1SEaW2XK+YFxz3Whw3+YYw=="
"resolved" "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-1.8.2.tgz"
"version" "1.8.2"
dependencies:
esbuild "^0.14.5"
picomatch "^2.3.0"
resolve "^1.20.0"
sass "^1.45.0"
"esbuild" "^0.14.5"
"picomatch" "^2.3.0"
"resolve" "^1.20.0"
"sass" "^1.45.0"
esbuild@^0.12.24:
version "0.12.29"
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.12.29.tgz"
integrity sha512-w/XuoBCSwepyiZtIRsKsetiLDUVGPVw1E/R3VTFSecIy8UR7Cq3SOtwKHJMFoVqqVG36aGkzh4e8BvpO1Fdc7g==
"esbuild-sunos-64@0.14.54":
"integrity" "sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw=="
"resolved" "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz"
"version" "0.14.54"
esbuild@^0.14.5:
version "0.14.54"
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.14.54.tgz"
integrity sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==
"esbuild-windows-32@0.14.54":
"integrity" "sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w=="
"resolved" "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz"
"version" "0.14.54"
"esbuild-windows-64@0.14.54":
"integrity" "sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ=="
"resolved" "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz"
"version" "0.14.54"
"esbuild-windows-arm64@0.14.54":
"integrity" "sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg=="
"resolved" "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz"
"version" "0.14.54"
"esbuild@^0.12.24":
"integrity" "sha512-w/XuoBCSwepyiZtIRsKsetiLDUVGPVw1E/R3VTFSecIy8UR7Cq3SOtwKHJMFoVqqVG36aGkzh4e8BvpO1Fdc7g=="
"resolved" "https://registry.npmjs.org/esbuild/-/esbuild-0.12.29.tgz"
"version" "0.12.29"
"esbuild@^0.14.5":
"integrity" "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA=="
"resolved" "https://registry.npmjs.org/esbuild/-/esbuild-0.14.54.tgz"
"version" "0.14.54"
dependencies:
"@esbuild/linux-loong64" "0.14.54"
"esbuild-android-64" "0.14.54"
"esbuild-android-arm64" "0.14.54"
"esbuild-darwin-64" "0.14.54"
"esbuild-darwin-arm64" "0.14.54"
"esbuild-freebsd-64" "0.14.54"
"esbuild-freebsd-arm64" "0.14.54"
"esbuild-linux-32" "0.14.54"
"esbuild-linux-64" "0.14.54"
"esbuild-linux-arm" "0.14.54"
"esbuild-linux-arm64" "0.14.54"
"esbuild-linux-mips64le" "0.14.54"
"esbuild-linux-ppc64le" "0.14.54"
"esbuild-linux-riscv64" "0.14.54"
"esbuild-linux-s390x" "0.14.54"
"esbuild-netbsd-64" "0.14.54"
"esbuild-openbsd-64" "0.14.54"
"esbuild-sunos-64" "0.14.54"
"esbuild-windows-32" "0.14.54"
"esbuild-windows-64" "0.14.54"
"esbuild-windows-arm64" "0.14.54"
optionalDependencies:
"@esbuild/linux-loong64" "0.14.54"
esbuild-android-64 "0.14.54"
esbuild-android-arm64 "0.14.54"
esbuild-darwin-64 "0.14.54"
esbuild-darwin-arm64 "0.14.54"
esbuild-freebsd-64 "0.14.54"
esbuild-freebsd-arm64 "0.14.54"
esbuild-linux-32 "0.14.54"
esbuild-linux-64 "0.14.54"
esbuild-linux-arm "0.14.54"
esbuild-linux-arm64 "0.14.54"
esbuild-linux-mips64le "0.14.54"
esbuild-linux-ppc64le "0.14.54"
esbuild-linux-riscv64 "0.14.54"
esbuild-linux-s390x "0.14.54"
esbuild-netbsd-64 "0.14.54"
esbuild-openbsd-64 "0.14.54"
esbuild-sunos-64 "0.14.54"
esbuild-windows-32 "0.14.54"
esbuild-windows-64 "0.14.54"
esbuild-windows-arm64 "0.14.54"
"esbuild-android-64" "0.14.54"
"esbuild-android-arm64" "0.14.54"
"esbuild-darwin-64" "0.14.54"
"esbuild-darwin-arm64" "0.14.54"
"esbuild-freebsd-64" "0.14.54"
"esbuild-freebsd-arm64" "0.14.54"
"esbuild-linux-32" "0.14.54"
"esbuild-linux-64" "0.14.54"
"esbuild-linux-arm" "0.14.54"
"esbuild-linux-arm64" "0.14.54"
"esbuild-linux-mips64le" "0.14.54"
"esbuild-linux-ppc64le" "0.14.54"
"esbuild-linux-riscv64" "0.14.54"
"esbuild-linux-s390x" "0.14.54"
"esbuild-netbsd-64" "0.14.54"
"esbuild-openbsd-64" "0.14.54"
"esbuild-sunos-64" "0.14.54"
"esbuild-windows-32" "0.14.54"
"esbuild-windows-64" "0.14.54"
"esbuild-windows-arm64" "0.14.54"
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
"fill-range@^7.0.1":
"integrity" "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ=="
"resolved" "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz"
"version" "7.0.1"
dependencies:
to-regex-range "^5.0.1"
"to-regex-range" "^5.0.1"
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
"fsevents@~2.3.2":
"integrity" "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="
"resolved" "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz"
"version" "2.3.2"
glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
"function-bind@^1.1.1":
"integrity" "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
"resolved" "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz"
"version" "1.1.1"
"glob-parent@~5.1.2":
"integrity" "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="
"resolved" "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz"
"version" "5.1.2"
dependencies:
is-glob "^4.0.1"
"is-glob" "^4.0.1"
has@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz"
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
"has@^1.0.3":
"integrity" "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw=="
"resolved" "https://registry.npmjs.org/has/-/has-1.0.3.tgz"
"version" "1.0.3"
dependencies:
function-bind "^1.1.1"
"function-bind" "^1.1.1"
immutable@^4.0.0:
version "4.1.0"
resolved "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz"
integrity sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==
"immutable@^4.0.0":
"integrity" "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ=="
"resolved" "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz"
"version" "4.1.0"
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
"is-binary-path@~2.1.0":
"integrity" "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="
"resolved" "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz"
"version" "2.1.0"
dependencies:
binary-extensions "^2.0.0"
"binary-extensions" "^2.0.0"
is-core-module@^2.9.0:
version "2.11.0"
resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz"
integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==
"is-core-module@^2.9.0":
"integrity" "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw=="
"resolved" "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz"
"version" "2.11.0"
dependencies:
has "^1.0.3"
"has" "^1.0.3"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz"
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
"is-extglob@^2.1.1":
"integrity" "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
"resolved" "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz"
"version" "2.1.1"
is-glob@^4.0.1, is-glob@~4.0.1:
version "4.0.3"
resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
"is-glob@^4.0.1", "is-glob@~4.0.1":
"integrity" "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="
"resolved" "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz"
"version" "4.0.3"
dependencies:
is-extglob "^2.1.1"
"is-extglob" "^2.1.1"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
"is-number@^7.0.0":
"integrity" "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
"resolved" "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"
"version" "7.0.0"
"keycharm@^0.2.0 || ^0.3.0 || ^0.4.0", keycharm@^0.4.0:
version "0.4.0"
resolved "https://registry.npmjs.org/keycharm/-/keycharm-0.4.0.tgz"
integrity sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ==
"keycharm@^0.4.0":
"integrity" "sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ=="
"resolved" "https://registry.npmjs.org/keycharm/-/keycharm-0.4.0.tgz"
"version" "0.4.0"
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
"normalize-path@^3.0.0", "normalize-path@~3.0.0":
"integrity" "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
"resolved" "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz"
"version" "3.0.0"
path-parse@^1.0.7:
version "1.0.7"
resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
"path-parse@^1.0.7":
"integrity" "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
"resolved" "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz"
"version" "1.0.7"
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.0:
version "2.3.1"
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
"picomatch@^2.0.4", "picomatch@^2.2.1", "picomatch@^2.3.0":
"integrity" "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
"resolved" "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
"version" "2.3.1"
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz"
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
"readdirp@~3.6.0":
"integrity" "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="
"resolved" "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz"
"version" "3.6.0"
dependencies:
picomatch "^2.2.1"
"picomatch" "^2.2.1"
resolve@^1.20.0:
version "1.22.1"
resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz"
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
"resolve@^1.20.0":
"integrity" "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw=="
"resolved" "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz"
"version" "1.22.1"
dependencies:
is-core-module "^2.9.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
"is-core-module" "^2.9.0"
"path-parse" "^1.0.7"
"supports-preserve-symlinks-flag" "^1.0.0"
sass@^1.45.0:
version "1.56.2"
resolved "https://registry.npmjs.org/sass/-/sass-1.56.2.tgz"
integrity sha512-ciEJhnyCRwzlBCB+h5cCPM6ie/6f8HrhZMQOf5vlU60Y1bI1rx5Zb0vlDZvaycHsg/MqFfF1Eq2eokAa32iw8w==
"sass@^1.45.0":
"integrity" "sha512-ciEJhnyCRwzlBCB+h5cCPM6ie/6f8HrhZMQOf5vlU60Y1bI1rx5Zb0vlDZvaycHsg/MqFfF1Eq2eokAa32iw8w=="
"resolved" "https://registry.npmjs.org/sass/-/sass-1.56.2.tgz"
"version" "1.56.2"
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"
"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.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
"integrity" "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
"resolved" "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz"
"version" "1.0.2"
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
"supports-preserve-symlinks-flag@^1.0.0":
"integrity" "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
"resolved" "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
"version" "1.0.0"
timsort@^0.3.0:
version "0.3.0"
resolved "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz"
integrity sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==
"timsort@^0.3.0":
"integrity" "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A=="
"resolved" "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz"
"version" "0.3.0"
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
"to-regex-range@^5.0.1":
"integrity" "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="
"resolved" "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz"
"version" "5.0.1"
dependencies:
is-number "^7.0.0"
"is-number" "^7.0.0"
"uuid@^3.4.0 || ^7.0.0 || ^8.0.0", "uuid@^7.0.0 || ^8.0.0", uuid@^8.0.0:
version "8.3.2"
resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
"uuid@^8.0.0":
"integrity" "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
"resolved" "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz"
"version" "8.3.2"
vis-data@^7.0.0, vis-data@^7.1.4:
version "7.1.4"
resolved "https://registry.npmjs.org/vis-data/-/vis-data-7.1.4.tgz"
integrity sha512-usy+ePX1XnArNvJ5BavQod7YRuGQE1pjFl+pu7IS6rCom2EBoG0o1ZzCqf3l5US6MW51kYkLR+efxRbnjxNl7w==
"vis-data@^7.1.4":
"integrity" "sha512-usy+ePX1XnArNvJ5BavQod7YRuGQE1pjFl+pu7IS6rCom2EBoG0o1ZzCqf3l5US6MW51kYkLR+efxRbnjxNl7w=="
"resolved" "https://registry.npmjs.org/vis-data/-/vis-data-7.1.4.tgz"
"version" "7.1.4"
vis-network@^9.1.2:
version "9.1.2"
resolved "https://registry.npmjs.org/vis-network/-/vis-network-9.1.2.tgz"
integrity sha512-BdapguKg7sk3NvdZaDsM7T6rNhOBFz0/F4ZScxctK4klRzQPLQPTEcmbioXaZhMkkgWymzBR3lFCxL1q+eYyAw==
"vis-network@^9.1.2":
"integrity" "sha512-BdapguKg7sk3NvdZaDsM7T6rNhOBFz0/F4ZScxctK4klRzQPLQPTEcmbioXaZhMkkgWymzBR3lFCxL1q+eYyAw=="
"resolved" "https://registry.npmjs.org/vis-network/-/vis-network-9.1.2.tgz"
"version" "9.1.2"
"vis-util@^4.0.0 || ^5.0.0", vis-util@^5.0.1, vis-util@^5.0.3:
version "5.0.3"
resolved "https://registry.npmjs.org/vis-util/-/vis-util-5.0.3.tgz"
integrity sha512-Wf9STUcFrDzK4/Zr7B6epW2Kvm3ORNWF+WiwEz2dpf5RdWkLUXFSbLcuB88n1W6tCdFwVN+v3V4/Xmn9PeL39g==
"vis-util@^5.0.3":
"integrity" "sha512-Wf9STUcFrDzK4/Zr7B6epW2Kvm3ORNWF+WiwEz2dpf5RdWkLUXFSbLcuB88n1W6tCdFwVN+v3V4/Xmn9PeL39g=="
"resolved" "https://registry.npmjs.org/vis-util/-/vis-util-5.0.3.tgz"
"version" "5.0.3"
@@ -8,9 +8,13 @@
<div class="controls">
<div class="control-group">
{% block extra_controls %}{% endblock %}
<a id="btnDownloadXml" class="btn btn-sm btn-info" href="#">
<i class="mdi mdi-download"></i>
Download XML
</a>
<a id="btnDownloadImage" class="btn btn-sm btn-info" href="#">
<i class="mdi mdi-download"></i>
Download
Download PNG
</a>
<a class="btn btn-sm btn-danger" data-bs-dismiss="modal">
<i class="mdi mdi-close-thick"></i>
@@ -36,4 +40,4 @@
const brokenImage = '{{ broken_image }}';
const topologyData = {{ topology_data | safe }};
</script>
<script src="{% static 'netbox_topology_views/js/app.js' %}?ver={{ epoch }} " defer></script>
<script src="{% static 'netbox_topology_views/js/app.js' %}?ver={{ epoch }} " defer></script>
@@ -16,9 +16,13 @@
<div class="controls">
<div class="control-group">
{% block extra_controls %}{% endblock %}
<a id="btnDownloadXml" class="btn btn-sm btn-info" href="#">
<i class="mdi mdi-download"></i>
Download XML
</a>
<a id="btnDownloadImage" class="btn btn-sm btn-info" href="#">
<i class="mdi mdi-download"></i>
Download
Download PNG
</a>
</div>
</div>
+208
View File
@@ -2,6 +2,8 @@ from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path
from typing import Type
import base64
import xml.dom.minidom
import sys
@@ -9,6 +11,7 @@ from django.conf import settings
from django.db.models import Model
from django.templatetags.static import static
from django.utils.text import camel_case_to_spaces, re_camel_case
from django.contrib import messages
IMAGE_DIR = Path(settings.STATIC_ROOT) / "netbox_topology_views/img"
if sys.version_info >= (3,9,0):
@@ -103,3 +106,208 @@ def get_model_role(model: Type[Model]) -> Role:
slug=get_model_slug(model),
name=re_camel_case.sub(r" \1", model.__name__),
)
def get_query_settings(request):
save_coords = False
if "save_coords" in request.GET:
if request.GET["save_coords"] == "on":
save_coords = True
# General options overrides
if save_coords == True and settings.PLUGINS_CONFIG["netbox_topology_views"]["allow_coordinates_saving"] == False:
save_coords = False
messages.warning(request, "Coordinate saving not allowed. Setting has been overridden")
elif settings.PLUGINS_CONFIG["netbox_topology_views"]["always_save_coordinates"] == True:
save_coords = True
# Individual options
show_unconnected = False
if "show_unconnected" in request.GET:
if request.GET["show_unconnected"] == "on":
show_unconnected = True
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
show_logical_connections = False
if "show_logical_connections" in request.GET:
if request.GET["show_logical_connections"] == "on" :
show_logical_connections = True
show_single_cable_logical_conns = False
if "show_single_cable_logical_conns" in request.GET:
if request.GET["show_single_cable_logical_conns"] == "on" :
show_single_cable_logical_conns = True
show_cables = False
if "show_cables" in request.GET:
if request.GET["show_cables"] == "on" :
show_cables = True
show_wireless = False
if "show_wireless" in request.GET:
if request.GET["show_wireless"] == "on" :
show_wireless = True
show_neighbors = False
if "show_neighbors" in request.GET:
if request.GET["show_neighbors"] == "on" :
show_neighbors = True
return save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless, show_neighbors
class LinePattern():
wireless = [2, 10, 2, 10]
power = [5, 5, 3, 3]
logical = [1, 10, 1, 10]
def export_data_to_xml(data: dict):
if data is None:
return ''
# static xml header
doc = xml.dom.minidom.Document()
mxfile = doc.createElement('mxfile')
mxfile.setAttribute('host', 'app.diagrams.net')
mxfile.setAttribute('type', 'device')
diagram = doc.createElement('diagram')
diagram.setAttribute('name', 'topology')
diagram.setAttribute('id', 'someid')
doc.appendChild(mxfile)
mxfile.appendChild(diagram)
# mxGraphModel
mxgraphmodel = doc.createElement('mxGraphModel')
mxgraphmodel.setAttribute('dx', '0')
mxgraphmodel.setAttribute('dy', '0')
mxgraphmodel.setAttribute('grid', '0')
mxgraphmodel.setAttribute('guides', '1')
mxgraphmodel.setAttribute('tooltips', '1')
mxgraphmodel.setAttribute('connect', '1')
mxgraphmodel.setAttribute('arrows', '1')
mxgraphmodel.setAttribute('fold', '1')
mxgraphmodel.setAttribute('page', '1')
mxgraphmodel.setAttribute('pageScale', '1')
mxgraphmodel.setAttribute('pageWidth', '827')
mxgraphmodel.setAttribute('pageHeight', '1169')
mxgraphmodel.setAttribute('background', 'none')
mxgraphmodel.setAttribute('math', '0')
mxgraphmodel.setAttribute('shadow', '0')
diagram.appendChild(mxgraphmodel)
# root
root = doc.createElement('root')
mxgraphmodel.appendChild(root)
# mxCells
# header
mxcell = doc.createElement('mxCell')
mxcell.setAttribute('id', '0')
root.appendChild(mxcell)
mxcell = doc.createElement('mxCell')
mxcell.setAttribute('id', '1')
mxcell.setAttribute('parent', '0')
root.appendChild(mxcell)
# edges
for edge in data['edges']:
dashes = ''
if 'dashes' in edge:
if edge['dashes'] == LinePattern().wireless:
dashes = 'dashed=1;dashPattern=3 6;'
if edge['dashes'] == LinePattern().power:
dashes = 'dashed=1;dashPattern=6 6;'
if edge['dashes'] == LinePattern().logical:
dashes = 'dashed=1;dashPattern=1 4;strokeWidth=2;'
mxcell = doc.createElement('mxCell')
mxcell.setAttribute('id', 'edge_' + str(edge['id']))
mxcell.setAttribute('style', 'rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=none;endFill=0;strokeColor=' + edge['color'] + ';' + dashes)
mxcell.setAttribute('edge', '1')
mxcell.setAttribute('parent', '1')
mxcell.setAttribute('source', 'node_' + str(edge['from']))
mxcell.setAttribute('target', 'node_' + str(edge['to']))
root.appendChild(mxcell)
mxgeometry = doc.createElement('mxGeometry')
mxgeometry.setAttribute('relative', '1')
mxgeometry.setAttribute('as', 'geometry')
mxcell.appendChild(mxgeometry)
# nodes
noPositionX = 0
noPositionY = 1000
for node in data['nodes']:
with open(str(Path(settings.BASE_DIR)) + node['image'], "rb") as img:
svg = base64.b64encode(img.read()).decode('utf-8')
mxcell = doc.createElement('mxCell')
mxcell.setAttribute('id', 'node_' + str(node['id']))
mxcell.setAttribute('value', str(node['label']))
mxcell.setAttribute('style', 'shape=image;verticalLabelPosition=bottom;verticalAlign=top;imageAspect=0;aspect=fixed;image=data:image/svg+xml,' + svg + ';perimeter=elippsePerimeter;fontSize=10;strokeWidth=1;fontColor=#000000;')
mxcell.setAttribute('vertex', '1')
mxcell.setAttribute('parent', '1')
root.appendChild(mxcell)
mxgeometry = doc.createElement('mxGeometry')
# Check if x and y values are stored in the database and set pseudo
# coordinates if not. Otherwise icons will not be exported / KeyError is raised
if 'x' in node:
mxgeometry.setAttribute('x', str(node['x']))
else:
# Set next pseudo coordinates
if noPositionX > 2500:
noPositionX = 100
noPositionY = noPositionY + 100
else:
noPositionX = noPositionX + 100
mxgeometry.setAttribute('x', str(noPositionX))
if 'y' in node:
mxgeometry.setAttribute('y', str(node['y']))
else:
mxgeometry.setAttribute('y', str(noPositionY))
mxgeometry.setAttribute('width', '50')
mxgeometry.setAttribute('height', '50')
mxgeometry.setAttribute('as', 'geometry')
mxcell.appendChild(mxgeometry)
# Place a warning if one or more icons are misplaced because of missing coordinates
if noPositionX > 0:
mxcell = doc.createElement('mxCell')
mxcell.setAttribute('id', 'noPositionWarning')
mxcell.setAttribute('value', 'One or more icons are misplaced. This happens if no coordinates are stored for a device. Turn on coordinates saving and make sure to drag all icons in the topology to a specific position before exporting to XML.')
mxcell.setAttribute('style', 'text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontColor=#FF0000;fontStyle=0;fontSize=35;labelBackgroundColor=#FFFF99;labelBorderColor=none;strokeWidth=20;')
mxcell.setAttribute('vertex', '1')
mxcell.setAttribute('parent', '1')
root.appendChild(mxcell)
mxgeometry = doc.createElement('mxGeometry')
mxgeometry.setAttribute('x', '0')
mxgeometry.setAttribute('y', '0')
mxgeometry.setAttribute('width', '870')
mxgeometry.setAttribute('height', '100')
mxgeometry.setAttribute('as', 'geometry')
mxcell.appendChild(mxgeometry)
# debug output
# print(doc.toprettyxml())
return doc.toxml(encoding='UTF-8')
+50 -61
View File
@@ -2,6 +2,7 @@ import json
from functools import reduce
from typing import DefaultDict, Dict, Optional, Union
import time
from itertools import chain
from utilities.htmx import is_htmx
from circuits.models import Circuit, CircuitTermination
@@ -38,6 +39,8 @@ from netbox_topology_views.utils import (
get_model_role,
get_model_slug,
image_static_url,
LinePattern,
get_query_settings
)
@@ -198,6 +201,7 @@ def create_edge(
edge["id"] = edge_id
edge["from"] = termination_a["device_id"]
edge["to"] = termination_b["device_id"]
edge["color"] = '#2b7ce9'
title = "Cable"
if circuit is not None:
@@ -205,17 +209,17 @@ def create_edge(
title = f"Circuit provider: {circuit['provider_name']}<br>Termination"
elif wireless is not None:
edge["dashes"] = [2, 10, 2, 10]
edge["dashes"] = LinePattern().wireless
title = "Wireless Connection"
elif power is not None:
edge["dashes"] = [5, 5, 3, 3]
edge["dashes"] = LinePattern().power
title = "Power Connection"
elif interface is not None:
title = "Interface Connection"
edge["width"] = 3
edge["dashes"] = [1, 10, 1, 10]
edge["dashes"] = LinePattern().logical
edge["color"] = '#f1c232'
edge["href"] = interface.get_absolute_url() + "trace"
@@ -260,6 +264,7 @@ def get_topology_data(
show_circuit: bool,
show_logical_connections: bool,
show_single_cable_logical_conns: bool,
show_neighbors: bool,
show_power: bool,
show_wireless: bool,
):
@@ -287,6 +292,31 @@ def get_topology_data(
device_ids = [d.pk for d in queryset]
site_ids = [d.site_id for d in queryset]
if show_neighbors:
interfaces = Interface.objects.filter(
Q(device_id__in=device_ids)
)
frontports = FrontPort.objects.filter(
Q(device_id__in=device_ids)
)
rearports = RearPort.objects.filter(
Q(device_id__in=device_ids)
)
ports = chain(interfaces, frontports, rearports)
for port in ports:
for link_peer in port.link_peers:
if link_peer.device.id not in device_ids:
device_ids.append(link_peer.device.id)
if show_logical_connections:
path_complete_interfaces = Interface.objects.filter(
Q(_path__is_complete=True) & Q(device_id__in=device_ids)
)
for path_complete_interface in path_complete_interfaces:
for connected_endpoint in path_complete_interface.connected_endpoints:
device_ids.append(connected_endpoint.device.id)
if show_circuit:
circuit_terminations = CircuitTermination.objects.filter(
Q(site_id__in=site_ids) | Q(provider_network__isnull=False)
@@ -596,65 +626,22 @@ class TopologyHomeView(PermissionRequiredMixin, View):
)
if request.GET:
save_coords = False
if "save_coords" in request.GET:
if request.GET["save_coords"] == "on":
save_coords = True
# General options overrides
if save_coords == True and settings.PLUGINS_CONFIG["netbox_topology_views"]["allow_coordinates_saving"] == False:
save_coords = False
messages.warning(request, "Coordinate saving not allowed. Setting has been overridden")
elif settings.PLUGINS_CONFIG["netbox_topology_views"]["always_save_coordinates"] == True:
save_coords = True
# Individual options
show_unconnected = False
if "show_unconnected" in request.GET:
if request.GET["show_unconnected"] == "on":
show_unconnected = True
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
show_logical_connections = False
if "show_logical_connections" in request.GET:
if request.GET["show_logical_connections"] == "on" :
show_logical_connections = True
show_single_cable_logical_conns = False
if "show_single_cable_logical_conns" in request.GET:
if request.GET["show_single_cable_logical_conns"] == "on" :
show_single_cable_logical_conns = True
show_cables = False
if "show_cables" in request.GET:
if request.GET["show_cables"] == "on" :
show_cables = True
show_wireless = False
if "show_wireless" in request.GET:
if request.GET["show_wireless"] == "on" :
show_wireless = True
save_coords, show_unconnected, show_power, show_circuit, show_logical_connections, show_single_cable_logical_conns, show_cables, show_wireless, show_neighbors = get_query_settings(request)
if not "draw_init" in request.GET or "draw_init" in request.GET and request.GET["draw_init"].lower() == "true":
topo_data = get_topology_data(
self.queryset,
individualOptions,
show_unconnected,
save_coords,
show_cables,
show_circuit,
show_logical_connections,
show_single_cable_logical_conns,
show_power,
show_wireless,
queryset=self.queryset,
individualOptions=individualOptions,
save_coords=save_coords,
show_unconnected=show_unconnected,
show_cables=show_cables,
show_logical_connections=show_logical_connections,
show_single_cable_logical_conns=show_single_cable_logical_conns,
show_neighbors=show_neighbors,
show_circuit=show_circuit,
show_power=show_power,
show_wireless=show_wireless,
)
else:
@@ -670,6 +657,7 @@ class TopologyHomeView(PermissionRequiredMixin, View):
if individualOptions.show_cables: q['show_cables'] = "on"
if individualOptions.show_logical_connections: q['show_logical_connections'] = "on"
if individualOptions.show_single_cable_logical_conns: q['show_single_cable_logical_conns'] = "on"
if individualOptions.show_neighbors: q['show_neighbors'] = "on"
if individualOptions.show_circuit: q['show_circuit'] = "on"
if individualOptions.show_power: q['show_power'] = "on"
if individualOptions.show_wireless: q['show_wireless'] = "on"
@@ -690,7 +678,7 @@ class TopologyHomeView(PermissionRequiredMixin, View):
"filter_form": DeviceFilterForm(request.GET, label_suffix=""),
"topology_data": json.dumps(topo_data),
"broken_image": find_image_url("role-unknown"),
"epoch": int(time.time()),
"epoch": int(time.time())
},
)
@@ -701,7 +689,7 @@ class TopologyHomeView(PermissionRequiredMixin, View):
"filter_form": DeviceFilterForm(request.GET, label_suffix=""),
"topology_data": json.dumps(topo_data),
"broken_image": find_image_url("role-unknown"),
"model": self.model,
"model": self.model
},
)
@@ -792,6 +780,7 @@ class TopologyIndividualOptionsView(PermissionRequiredMixin, View):
'show_cables': queryset.show_cables,
'show_logical_connections': queryset.show_logical_connections,
'show_single_cable_logical_conns': queryset.show_single_cable_logical_conns,
'show_neighbors': queryset.show_neighbors,
'show_circuit': queryset.show_circuit,
'show_power': queryset.show_power,
'show_wireless': queryset.show_wireless,
+1 -1
View File
@@ -7,7 +7,7 @@ long_description = readme.read_text()
setup(
name="netbox-topology-views",
version="3.4.0",
version="3.5.0",
description="An NetBox plugin to create Topology maps",
long_description=long_description,
long_description_content_type="text/markdown",