Skip to content

Commit 12948d3

Browse files
committed
Refactor topology visualization for multi-topology support
- Enable rendering multiple topologies (segment and multiple service paths) per circuit or segment - Replace single topology_data with topologies dict in views and templates - Update topology_visualization.js to support dynamic container-based initialization - Extract visualization styles and reusable card structure into includes - Refactor segment and service path templates to use shared topology components - Add logging for debugging topology generation - Bump plugin version to 5.2.1b5
1 parent 14fd533 commit 12948d3

File tree

9 files changed

+250
-175
lines changed

9 files changed

+250
-175
lines changed

cesnet_service_path_plugin/template_content.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@
77
from netbox.plugins import PluginTemplateExtension
88
from utilities.tables import register_table_column
99

10-
from cesnet_service_path_plugin.models import Segment, ServicePathSegmentMapping
10+
from cesnet_service_path_plugin.models import Segment, ServicePathSegmentMapping, ServicePath
1111
from cesnet_service_path_plugin.utils import build_service_path_topology, build_segment_topology
12+
import logging
1213

14+
logging.basicConfig(level=logging.DEBUG)
15+
logger = logging.getLogger(__name__)
1316

1417
plugin_settings = settings.PLUGINS_CONFIG.get("cesnet_service_path_plugin", {})
1518

19+
1620
# Extra Views
1721

1822

@@ -34,23 +38,26 @@ def full_width_page(self):
3438

3539
if segment:
3640
# Check if segment is part of any service path
37-
service_path_mapping = ServicePathSegmentMapping.objects.filter(segment=segment).first()
38-
39-
if service_path_mapping:
40-
# Build service path topology
41-
topology_title = f"Service Path Topology: {service_path_mapping.service_path.name}"
42-
topology_data = build_service_path_topology(service_path_mapping.service_path)
41+
service_paths = ServicePathSegmentMapping.objects.filter(segment=segment).all()
42+
topologies = {}
43+
if service_paths:
44+
for sp in service_paths:
45+
# get data from service path model
46+
sp_data = ServicePath.objects.filter(id=sp.service_path_id).first()
47+
48+
topology_title = f"Service Path {sp_data.name}"
49+
topology_data = build_service_path_topology(sp_data)
50+
topologies[topology_title] = json.dumps(topology_data)
4351
else:
44-
# Build segment topology only
45-
topology_title = f"Segment Topology: {segment.name}"
46-
topology_data = build_segment_topology(segment)
52+
topology_title = f"Segment {segment.name}"
53+
topology_data = build_segment_topology(segment=segment)
54+
topologies[topology_title] = json.dumps(topology_data)
4755

4856
return self.render(
4957
"cesnet_service_path_plugin/circuit_segments_extension.html",
5058
extra_context={
5159
"segment": segment,
52-
"topology_data": json.dumps(topology_data) if topology_data else None,
53-
"topology_title": topology_title,
60+
"topologies": topologies,
5461
},
5562
)
5663

cesnet_service_path_plugin/templates/cesnet_service_path_plugin/circuit_segments_extension.html

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,35 +17,8 @@ <h5 class="card-header">
1717
</div>
1818

1919
<!-- Topology Visualization Card -->
20-
{% if topology_data %}
21-
<div class="col col-md-12 mt-3">
22-
<div class="card">
23-
<h5 class="card-header">
24-
{{ topology_title }}
25-
<div class="card-actions">
26-
<button id="fit-topology" class="btn btn-ghost-primary btn-sm" title="Fit to screen">
27-
<i class="mdi mdi-fit-to-screen" aria-hidden="true"></i>
28-
</button>
29-
<button id="reset-topology" class="btn btn-ghost-primary btn-sm" title="Reset layout">
30-
<i class="mdi mdi-refresh" aria-hidden="true"></i>
31-
</button>
32-
</div>
33-
</h5>
34-
<div class="card-body">
35-
<div id="cy-topology" style="width: 100%; height: 600px; border: 1px solid rgba(59, 130, 246, 0.08); border-radius: 20px;"></div>
36-
</div>
37-
</div>
38-
</div>
39-
40-
<!-- Include Topology Visualization -->
41-
{% include 'cesnet_service_path_plugin/inc/topology_visualization.html' %}
20+
{% if topologies %}
21+
{% include 'cesnet_service_path_plugin/inc/topology_visualization.html' %}
22+
{% include 'cesnet_service_path_plugin/inc/topology_segment_card.html' %}
4223

43-
{% elif segment %}
44-
<div class="col col-md-12 mt-3">
45-
<div class="alert alert-info">
46-
<i class="mdi mdi-information-outline" aria-hidden="true"></i>
47-
This circuit is associated with segment <a href="{{ segment.get_absolute_url }}">{{ segment.name }}</a>,
48-
but topology visualization is not available. Visit the segment detail page to view its topology.
49-
</div>
50-
</div>
5124
{% endif %}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
{% load i18n %}
2+
<!-- fallback if there are no topologies-->
3+
{% if not topologies %}
4+
<div class="alert alert-warning">
5+
{% trans "No topologies available." %}
6+
</div>
7+
{% else %}
8+
9+
<div class="row mb-3">
10+
<div class="col col-12">
11+
<div class="card">
12+
<h5 class="card-header">
13+
{% trans "Topologies" %}
14+
{% for topology_title, topology_data in topologies.items %}
15+
{% if topologies|length > 1 %}
16+
<button class="btn btn-ghost-primary btn-sm topology-btn {% if forloop.first %}active{% endif %}"
17+
id="btn-topology-{{ forloop.counter }}"
18+
onclick="showTopologyWithButtons('cy-topology-viewer', 'cy-topology-data-{{ forloop.counter }}', '{{ forloop.counter }}')">
19+
{{ topology_title }}
20+
</button>
21+
{% else %}
22+
{{ topology_title }}
23+
{% endif %}
24+
{% endfor %}
25+
26+
</h5>
27+
28+
<div class="card-body">
29+
<!-- Single Topology Container -->
30+
<div class="cy-topology" id="cy-topology-viewer"></div>
31+
32+
<!-- Hidden topology data containers -->
33+
{% for topology_title, topology_data in topologies.items %}
34+
<div id="cy-topology-data-{{ forloop.counter }}"
35+
data-topology='{{ topology_data|safe }}'
36+
style="display: none;"></div>
37+
{% endfor %}
38+
</div>
39+
</div>
40+
</div>
41+
</div>
42+
43+
44+
45+
<style>
46+
.card-header {
47+
display: flex;
48+
align-items: center;
49+
gap: 15px;
50+
}
51+
52+
.topology-btn.active {
53+
background-color: #2563eb !important;
54+
color: white !important;
55+
}
56+
</style>
57+
58+
<script>
59+
let currentTopologyInstance = null;
60+
61+
function showTopologyWithButtons(viewerContainerId, dataContainerId, buttonNumber) {
62+
// Remove active class from all buttons
63+
document.querySelectorAll('.topology-btn').forEach(function(btn) {
64+
btn.classList.remove('active');
65+
});
66+
67+
// Add active class to clicked button
68+
document.getElementById('btn-topology-' + buttonNumber).classList.add('active');
69+
70+
// Destroy existing topology instance if any
71+
if (currentTopologyInstance) {
72+
currentTopologyInstance.destroy();
73+
}
74+
75+
// Get topology data from hidden container
76+
const dataContainer = document.getElementById(dataContainerId);
77+
const topologyData = JSON.parse(dataContainer.getAttribute('data-topology'));
78+
79+
// Initialize new topology
80+
currentTopologyInstance = initializeTopology(viewerContainerId, topologyData);
81+
}
82+
83+
function showTopology(viewerContainerId, dataContainerId) {
84+
// Destroy existing topology instance if any
85+
if (currentTopologyInstance) {
86+
currentTopologyInstance.destroy();
87+
}
88+
89+
// Get topology data from hidden container
90+
const dataContainer = document.getElementById(dataContainerId);
91+
const topologyData = JSON.parse(dataContainer.getAttribute('data-topology'));
92+
// Initialize new topology
93+
currentTopologyInstance = initializeTopology(viewerContainerId, topologyData);
94+
}
95+
</script>
96+
97+
{% if topologies|length > 1 %}
98+
<script>
99+
// Initialize first topology on page load
100+
document.addEventListener('DOMContentLoaded', function() {
101+
showTopologyWithButtons('cy-topology-viewer', 'cy-topology-data-1', 1);
102+
});
103+
</script>
104+
{% else %}
105+
<script>
106+
// Initialize first topology on page load
107+
document.addEventListener('DOMContentLoaded', function() {
108+
showTopology('cy-topology-viewer', 'cy-topology-data-1');
109+
});
110+
</script>
111+
{% endif %}
112+
{% endif %}

cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/topology_styles.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
<!-- CSS Styles for topology visualization -->
22
<style>
3-
#cy-topology {
3+
.cy-topology {
44
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
55
border-radius: 20px;
66
box-shadow: inset 0 2px 8px rgba(59, 130, 246, 0.06);
77
border: 1px solid rgba(59, 130, 246, 0.08);
8+
width: 100%;
9+
height: 600px;
810
}
911

1012
/* Tooltip styling for node hover */

0 commit comments

Comments
 (0)