1 // Copyright (c) 2018 Maen Artimy
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
16 const tabObj = Tabs('topology');
20 // Define the div for the tooltip
21 const div = d3.select("body").append("div")
22 .attr("class", "tooltip")
25 function add_prefix(obj) {
26 return String(obj).replace(/^0+/, "Switch_");
29 function trim_zeros(obj) {
30 return String(obj).replace(/^0+/, "");
33 // Takes JSON data and convert to a graph format that D3 understands:
36 // {"id": "0000000000000001", "type": "switch"},
37 // {"id": "0000000000000002", "type": "switch"},
38 // {"id": "16:18:83:a8:4d:1c", "type": "host"},
42 // {"source": "0000000000000002", "target": "0000000000000003",
43 // "port": {"source": "00000003", "target": "00000002"}, "value": 4},
46 function displayMessage(msg) {
47 var $x = $("#snackbar");
49 $x.toggleClass("show");
50 setTimeout(function () { $x.toggleClass("show"); }, 3000);
54 function toGraph(top) {
58 var lst = top.switches;
60 for (var i = 0; i < lst.length; i++) {
61 nodes.push({ "id": lst[i].dpid, "type": "switch" });
64 if (top.links.length > 0) {
66 for (var i = 0; i < lst.length; i++) {
67 if (lst[i].src.dpid < lst[i].dst.dpid) { // prevent duplicate links
69 "source": lst[i].src.dpid, "target": lst[i].dst.dpid, "value": 4,
70 "port": { "source": lst[i].src.port_no, "target": lst[i].dst.port_no }
74 } else if (top.switches.length > 1) { // represent the network with a cloud
75 nodes.push({ "id": 0, "type": "cloud" });
76 for (var i = 0; i < lst.length; i++) {
78 "source": 0, "target": lst[i].dpid, "value": 4,
79 "port": { "source": 0, "target": 0 }
85 console.log(top.hosts);
86 for (var i = 0; i < lst.length; i++) {
87 nodes.push({ "id": lst[i].mac, "type": "host","ip": lst[i].ipv4});
89 "source": lst[i].port.dpid, "target": lst[i].mac, "value": 2,
90 "port": { "source": lst[i].port.port_no, "target": 0 }
94 return { "nodes": nodes, "links": links };
97 // Plot the topology using D3.js
98 // Many online tutorials explain how this works. Example: www.puzzlr.org/force-graphs-with-d3
99 function plotGraph(graph) {
100 var svg = d3.select("svg");
101 var width = +svg.attr("width");
102 var height = +svg.attr("height");
104 //custom force to put everything in a box
105 function box_force() {
107 for (var i = 0, n = graph.nodes.length; i < n; ++i) {
108 curr_node = graph.nodes[i];
109 curr_node.x = Math.max(radius, Math.min(width - radius, curr_node.x));
110 curr_node.y = Math.max(radius, Math.min(height - radius, curr_node.y));
114 // Create a force layout simulation
116 // Link: attraction force
117 // chanrge: repulsion force
118 // x: attracts the nodes to the horizontal centre
119 // y: attracts the hosts the bottom while other nodes are attrcated to the top
120 // centre: atracts to the centre
121 // collision: avoids node collision
122 // box: keeps all nodes inside
123 var simulation = d3.forceSimulation()
125 .force("link", d3.forceLink(graph.links).id(function (d) { return d.id; }).distance(size * 2))
126 .force("charge", d3.forceManyBody().strength(-size * 30))
127 .force("x", d3.forceX(width / 2))
128 .force("y", d3.forceY(function (d) {
129 if (d.type === "host") {
130 return 3 * height / 4
132 return 1 * height / 4
135 .force("centre", d3.forceCenter(width / 2, height / 2))
136 .force("collision", d3.forceCollide().radius(35))
137 .force("box", box_force);
139 // Create nodes with image and text
140 var node = svg.append("g")
141 .attr("class", "nodes")
145 // .attr("id", function (d) { return "N" + d.id; })
146 .attr("class", "node");
149 .attr("xlink:href", function (d) {
150 if (d.type === "switch") {
151 return "img/switch.png"
152 } else if (d.type === "cloud") {
153 return "img/cloud.svg"
158 .on("mouseover", handleMouseOver)
159 .on("mouseout", handleMouseOut)
162 .attr("class", "label")
163 .attr("dy", size + 14)
164 .text(function (d) { return d.id; });
168 .attr("class", "label")
169 .attr("dy", size + 26)
170 .text(function (d) { if (d.type === "host")return (d.ip); });
171 // .text(function (d) { return d.id.replace(/^0+/, ''); });
174 // Create links with lines, circles, and text
175 var link = svg.append("g")
176 .attr("class", "links")
180 .attr("class", "link");
183 .attr("stroke-width", function (d) { return d.value; });
185 link.append("circle")
186 .attr("class", "start")
189 link.append("circle")
190 .attr("class", "end")
194 .attr("class", "start")
195 .text(function (d) { return trim_zeros(d.port.source); })
198 .attr("class", "end")
199 .text(function (d) { return trim_zeros(d.port.target); })
202 function tickActions() {
204 return Math.sqrt((d.target.x - d.source.x) ** 2 + (d.target.y - d.source.y) ** 2);
208 .attr("transform", function (d) { return "translate(" + (d.x - size / 2) + "," + (d.y - size / 2) + ")"; });
210 .attr("transform", function (d) { return "translate(" + d.source.x + "," + d.source.y + ")"; });
212 link.selectAll("line")
213 .attr("x1", function (d) { return (d.target.x - d.source.x) * size / 2 / norm(d); })
214 .attr("y1", function (d) { return (d.target.y - d.source.y) * size / 2 / norm(d); })
215 .attr("x2", function (d) { return (d.target.x - d.source.x) * (1 - size / 2 / norm(d)); })
216 .attr("y2", function (d) { return (d.target.y - d.source.y) * (1 - size / 2 / norm(d)); })
218 // position of the link start port
219 link.selectAll("circle.start")
220 .attr("cx", function (d) { return (d.target.x - d.source.x) * size / 2 / norm(d); })
221 .attr("cy", function (d) { return (d.target.y - d.source.y) * size / 2 / norm(d); })
223 // psotion of the link end port
224 link.selectAll("circle.end")
225 .attr("cx", function (d) { return (d.target.x - d.source.x) * (1 - size / 2 / norm(d)); })
226 .attr("cy", function (d) { return (d.target.y - d.source.y) * (1 - size / 2 / norm(d)); })
228 link.selectAll("text.start")
229 .attr("dx", function (d) { return (d.target.x - d.source.x) * size / 2 / norm(d); })
230 .attr("dy", function (d) { return (d.target.y - d.source.y) * size / 2 / norm(d); })
232 link.selectAll("text.end")
233 .attr("dx", function (d) { return (d.target.x - d.source.x) * (1 - size / 2 / norm(d)); })
234 .attr("dy", function (d) { return (d.target.y - d.source.y) * (1 - size / 2 / norm(d)); })
238 // Handling mouse drag
240 .on("start", drag_start)
241 .on("drag", drag_drag)
242 .on("end", drag_end));
244 function drag_start(d) {
245 if (!d3.event.active) simulation.alphaTarget(0.3).restart();
250 function drag_drag(d) {
255 function drag_end(d) {
256 if (!d3.event.active) simulation.alphaTarget(0);
261 // Handling mouse over
262 function handleMouseOver(d) {
265 .style("opacity", .9);
266 div.html(d.type + ": " + d.id)
267 .style("left", (d3.event.pageX) + "px")
268 .style("top", (d3.event.pageY - 28) + "px");
271 function handleMouseOut(d) {
274 .style("opacity", 0);
277 // drag_handler(node);
279 // run tickActions in every simulation step
280 simulation.on("tick", tickActions);
284 // Display the raw topology data
285 function listTopology(network) {
286 data = "<h1>Switches</h1>" + JSON.stringify(network.switches) + "<br>";
287 data += "<h1>Links</h1>" + JSON.stringify(network.links) + "<br>";
288 data += "<h1>Hosts</h1>" + JSON.stringify(network.hosts) + "<br>";
289 $('#data').html(data);
292 function getTopology() {
293 tabObj.buildTabs("#main", ["Graph", "Tables"], "Nothing to show!");
294 var $svg = $('<svg width="800" height="600"></svg>');
295 var $data = $('<div id="data"></div>');
296 tabObj.buildContent('Graph', $svg);
297 tabObj.buildContent('Tables', $data);
302 // La funcion jsonget fue creada para sustituir el metodo json de D3
305 let xhr = new XMLHttpRequest();
306 xhr.open('GET', "/gettopo" , true);
307 //console.log(xhr); //para ver en la consola
308 xhr.onload = function() {
309 if (xhr.status == 200) { //can use this.status instead
310 //console.log(xhr.responseText);// para ver en la consola
311 if(xhr.response === null || xhr.response === ""){
312 displayMessage("No response from controller")
314 plotGraph(toGraph(JSON.parse(xhr.responseText)));
325 // d3.json(location.hostname+":8080/topology").then(function (data) {
326 // listTopology(data)
327 // plotGraph(toGraph(data));
332 // When the refresh button is clicked, clear the page and start over
333 $('.refresh').on('click', function () {