Repository: openwisp/openwisp-network-topology (language: python)

Issue:
[bug] Visualizer is currently broken on master
The graph visualizer from the topology admin detail is currently broken on master, clicking on view topology graph raises a JS error:

```
visualize.js:28 Uncaught TypeError: Cannot read properties of undefined (reading 'render')
    at Object.success (visualize.js:28:26)
    at fire (jquery.js:3500:31)
    at Object.fireWith [as resolveWith] (jquery.js:3630:7)
    at done (jquery.js:9796:14)
    at XMLHttpRequest.<anonymous> (jquery.js:10057:9)
(anonymous) @ visualize.js:28
fire @ jquery.js:3500
fireWith @ jquery.js:3630
done @ jquery.js:9796
(anonymous) @ jquery.js:10057
load (async)
send @ jquery.js:10076
ajax @ jquery.js:9690
jQuery.<computed> @ jquery.js:9846
openOverlay @ visualize.js:18
(anonymous) @ visualize.js:41
dispatch @ jquery.js:5430
elemData.handle @ jquery.js:5234
```

Also the visualizer view outside of the admin is broken.
[bug] Visualizer is currently broken on master
The graph visualizer from the topology admin detail is currently broken on master, clicking on view topology graph raises a JS error:

```
visualize.js:28 Uncaught TypeError: Cannot read properties of undefined (reading 'render')
    at Object.success (visualize.js:28:26)
    at fire (jquery.js:3500:31)
    at Object.fireWith [as resolveWith] (jquery.js:3630:7)
    at done (jquery.js:9796:14)
    at XMLHttpRequest.<anonymous> (jquery.js:10057:9)
(anonymous) @ visualize.js:28
fire @ jquery.js:3500
fireWith @ jquery.js:3630
done @ jquery.js:9796
(anonymous) @ jquery.js:10057
load (async)
send @ jquery.js:10076
ajax @ jquery.js:9690
jQuery.<computed> @ jquery.js:9846
openOverlay @ visualize.js:18
(anonymous) @ visualize.js:41
dispatch @ jquery.js:5430
elemData.handle @ jquery.js:5234
```

Also the visualizer view outside of the admin is broken.


Discussion / hints:

Fix patch:
diff --git a/openwisp_network_topology/admin.py b/openwisp_network_topology/admin.py
index 5e30ff1c..d4579603 100644
--- a/openwisp_network_topology/admin.py
+++ b/openwisp_network_topology/admin.py
@@ -102,6 +102,7 @@ class TopologyAdmin(
     receive_url_name = 'receive_topology'
     receive_url_urlconf = app_settings.TOPOLOGY_API_URLCONF
     receive_url_baseurl = app_settings.TOPOLOGY_API_BASEURL
+    change_form_template = 'admin/topology/topology/change_form.html'
 
     def get_actions(self, request):
         """
diff --git a/openwisp_network_topology/static/netjsongraph/css/src/netjsongraph.css b/openwisp_network_topology/static/netjsongraph/css/src/netjsongraph.css
index 284b353f..354e826f 100644
--- a/openwisp_network_topology/static/netjsongraph/css/src/netjsongraph.css
+++ b/openwisp_network_topology/static/netjsongraph/css/src/netjsongraph.css
@@ -1,3 +1,16 @@
+body {
+    margin: 0;
+}
+
+.djnjg-overlay {
+    position: fixed;
+    width: 100%;
+    height: 100%;
+    left: 0;
+    top: 0;
+    background: #000;
+}
+
 .djnjg-overlay h3 {
     display: block;
     font-size: 1.17em;
diff --git a/openwisp_network_topology/static/netjsongraph/js/visualize.js b/openwisp_network_topology/static/netjsongraph/js/visualize.js
index 8ad13982..31860110 100644
--- a/openwisp_network_topology/static/netjsongraph/js/visualize.js
+++ b/openwisp_network_topology/static/netjsongraph/js/visualize.js
@@ -14,18 +14,24 @@ django.jQuery(function ($) {
 
     var openOverlay = function () {
         // show overlay
-        window.__njg_el__ = '.inner';
         $.get(visualizeUrl, function (html) {
             overlay.show();
             inner.html(html);
             body.css('overflow', 'hidden');
             inner.css('overflow', 'hidden');
+
+            const closeBtn = document.createElement('button');
+            closeBtn.setAttribute('class', 'closeBtn');
+            closeBtn.innerHTML = '&times;';
+            overlay.get(0).appendChild(closeBtn);
+
+            window.graph = window.loadNetJsonGraph('.djnjg-overlay .inner');
+
             overlay.find('.closeBtn').click(function (e) {
                 e.preventDefault();
                 window.graph.echarts.dispose();
                 closeOverlay();
             });
-            window.graph.render();
         });
         $(document).keydown(disableArrowKeys);
     };
diff --git a/openwisp_network_topology/templates/admin/topology/change_form.html b/openwisp_network_topology/templates/admin/topology/topology/change_form.html
similarity index 100%
rename from openwisp_network_topology/templates/admin/topology/change_form.html
rename to openwisp_network_topology/templates/admin/topology/topology/change_form.html
diff --git a/openwisp_network_topology/templates/netjsongraph/base.html b/openwisp_network_topology/templates/netjsongraph/base.html
index 415644c0..172ce3d1 100644
--- a/openwisp_network_topology/templates/netjsongraph/base.html
+++ b/openwisp_network_topology/templates/netjsongraph/base.html
@@ -4,7 +4,6 @@
         <title>{% block title %}{% endblock %}</title>
         <meta charset="utf-8" />
         <script src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script>
-
         {% block style %}
         <link href="{% static 'netjsongraph/css/src/netjsongraph.css' %}" rel="stylesheet" />
         <link href="{% static 'netjsongraph/css/lib/jquery-ui.min.css' %}" rel="stylesheet" />
@@ -12,7 +11,8 @@
         {% endblock %}
     </head>
     <body class="frontend">
-        {% block body %}{% endblock %} {% block script %}
+        {% block body %}{% endblock %}
+        {% block script %}
         <script src="{% static 'netjsongraph/js/src/netjsongraph.min.js' %}"></script>
         <script src="{% static 'netjsongraph/js/topology-history.js' %}"></script>
         {% endblock %}
diff --git a/openwisp_network_topology/templates/netjsongraph/detail.html b/openwisp_network_topology/templates/netjsongraph/detail.html
index 204aa898..61bfb46c 100644
--- a/openwisp_network_topology/templates/netjsongraph/detail.html
+++ b/openwisp_network_topology/templates/netjsongraph/detail.html
@@ -1,8 +1,13 @@
 {% extends 'netjsongraph/base.html' %}
+{% load static %}
 {% block title %}{{ topology.label }}{% endblock %}
-{% block body %}
-{% endblock %}
 {% block script %}
     {{ block.super }}
+    <script src="{% static 'admin/js/jquery.init.js' %}"></script>
+    <script src="{% static 'netjsongraph/js/lib/jquery-ui.min.js' %}"></script>
     {% include './netjsongraph-script.html' %}
+    <div class="djnjg-overlay"></div>
+    <script>
+      const graph = window.loadNetJsonGraph('.djnjg-overlay');
+    </script>
 {% endblock %}
diff --git a/openwisp_network_topology/templates/netjsongraph/netjsongraph-script.html b/openwisp_network_topology/templates/netjsongraph/netjsongraph-script.html
index 49343812..8464c825 100644
--- a/openwisp_network_topology/templates/netjsongraph/netjsongraph-script.html
+++ b/openwisp_network_topology/templates/netjsongraph/netjsongraph-script.html
@@ -1,14 +1,9 @@
 <script>
-    // the graph may be loaded elsewhere than body
-    window.__njg_el__ = window.__njg_el__ || 'body';
-    // default API url
-    window.__njg_default_url__ = '{{ graph_url }}';
-
-    (() => {
+    loadNetJsonGraph = (el='body', url='{{ graph_url }}') => {
         const history_url = '{{ history_url }}';
-        const url=`ws://${window.location.host}${window.location.pathname}`;
-        const socket = new WebSocket(url);
-        
+        const wsUrl = `ws://${window.location.host}${window.location.pathname}`;
+        const socket = new WebSocket(wsUrl);
+
         const getDataParseOptions = (data) => {
             return {
                 dateString: data,
@@ -18,8 +13,8 @@
         };
 
         // load graph
-        const graph = new NetJSONGraph(window.__njg_default_url__, {
-            el: window.__njg_el__,
+        const graph = new NetJSONGraph(url, {
+            el: el,
             graphConfig: {
                 series:{
                     zoom:'2',
@@ -127,7 +122,7 @@
             },
         });
 
-        const overlay = document.querySelector(window.__njg_el__);
+        const overlay = document.querySelector(el);
         const createLegend = (key, name) => {
             const legendItem = document.createElement('p');
             const legendIcon = document.createElement('span');
@@ -149,11 +144,6 @@
         legend.appendChild(createLegend('Down', 'link-down'));
         overlay.appendChild(legend);
 
-        const closeBtn = document.createElement('button');
-        closeBtn.setAttribute('class', 'closeBtn');
-        closeBtn.innerHTML = '&times;';
-        overlay.appendChild(closeBtn);
-
         const switcher = document.createElement('div');
         switcher.setAttribute('class', 'njg-switcher');
         const dateLabel = document.createElement('label');
@@ -167,15 +157,17 @@
         switcher.appendChild(datePicker);
         overlay.appendChild(switcher);
 
-        window.graph = graph;
-
         socket.onmessage=function(e){
             const data=JSON.parse(e.data);
             if(data.type==="broadcast_topology"){
                 const topology=JSON.parse(data.topology);
-                window.graph.utils.JSONDataUpdate.call(window.graph, topology);
-            }   
+                window.__njg_graph__.utils.JSONDataUpdate.call(window.__njg_graph__, topology);
+            }
         }
-    })();
-    window.initTopologyHistory(django.jQuery);
+
+        graph.render();
+        window.initTopologyHistory(django.jQuery);
+        return graph
+    };
+
 </script>


Test patch:
diff --git a/openwisp_network_topology/tests/test_admin.py b/openwisp_network_topology/tests/test_admin.py
index 4e151339..bd95dff5 100644
--- a/openwisp_network_topology/tests/test_admin.py
+++ b/openwisp_network_topology/tests/test_admin.py
@@ -201,6 +201,7 @@ def test_topology_change_form(self):
         self.assertContains(
             response, '<div class="readonly">{0}</div>'.format(topology.pk)
         )
+        self.assertContains(response, '<div class="djnjg-overlay">')
 
     def test_topology_visualize_view(self):
         t = self.topology_model.objects.first()