index.html 13.3 KB
<!DOCTYPE html>
<!--
  ~ Copyright 2014 Open Networking Laboratory
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->
<html>
<head>
    <title>Topology Viewer</title>
    <script src="libs/d3.v3.min.js"></script>
    <script src="libs/jquery-1.11.1.min.js"></script>
    <style>
        .link {
        }

        .node {
            stroke-width: 3px;
        }

        .textClass {
            stroke: #323232;
            font-family: "Lucida Grande", "Droid Sans", Arial, Helvetica, sans-serif;
            font-weight: normal;
            stroke-width: .5;
            font-size: 14px;
        }
    </style>
</head>
<body>
    <script>
        // Adapted from sample code @ ericcoopey’s block #6c602d7cb14b25c179a4
        // by Thomas Vachuska

        var graph,
                topo = {vertexes:{}, edges:{}},
                paths = [],
                currentPath = 0;

        // Set up the canvas
        var w = 2048,
                h = 2048;

        // Allocate colors predictably
        var color = d3.scale.category10(),
                deviceColor = color(2),
                sourceColor = color(0),
                targetColor = color(1),
                dummy1Color = color(9),
                dummy2Color = color(8),
                dummy3Color = color(7),
                dummy4Color = color(6),
                offlineColor = color(5),
                dummy5Color = color(4),
                hostColor = color(3);

        var selectedNode,
                sourceNode,
                targetNode,
                pathRequested;


        function fillColor(d) {
            return ((targetNode && targetNode.id == d.id) ? targetColor :
                    ((sourceNode && sourceNode.id == d.id) ? sourceColor :
                            d.online ? color(d.group) : offlineColor));
        }

        function strokeColor(d) {
            return selectedNode && d.id == selectedNode.id ? "#f00" : "#aaa";
        }

        function linkColor(d) {
            if (!paths || paths.length == 0) {
                return "#666";
            }

            var path = paths[currentPath];
            if (path) {
                for (var i = 0, n = path.length; i < n; i++) {
                    var link = path[i];
                    if ((link.src == d.source.id || link.dst == d.source.id) &&
                            (link.dst == d.target.id || link.src == d.target.id)) {
                        return "#f00";
                    }
                }
            }
            return "#666";
        }

        function linkKey(link) {
            return link.source.id + "-" + link.target.id;

        };

        function toggleNode(node) {
            pathRequested = false;
            return selectedNode && selectedNode != node ? selectedNode : null;

        };

        function refreshPaths() {
            d3.selectAll("line").attr("stroke", linkColor);
        }

        function fetchPaths() {
            if (!pathRequested && sourceNode && targetNode) {
                pathRequested = true;
                d3.json("rs/topology/paths/" + sourceNode.id + "/" + targetNode.id, function(error, data) {
                    currentPath = 0;
                    paths = data.paths;
                    refreshPaths();
                });
            }
        }

        function resetSelections() {
            selectedNode = null;
            sourceNode = null;
            targetNode = null;
            paths = [];
            currentPath = 0;
            refreshPaths();
            d3.selectAll(".nodeStrokeClass").attr("stroke", strokeColor);
        }

        function nextPath() {
            currentPath = paths && currentPath < paths.length - 1 ? currentPath + 1 : 0
            console.log("Showing path: " + currentPath);
            refreshPaths();
        }

        function dblclick(d) {
            d3.select(this).classed("fixed", d.fixed = false);
        }

        function dragstart(d) {
            // d3.select(this).classed("fixed", d.fixed = true);
        }


        function topoGraph() {
            // Add and remove elements on the graph object
            this.addNode = function (vertex, stamp) {
                var node = topo.vertexes[vertex.name];
                if (node) {
                    var oldState = node.online;
                    node.online = vertex.online;
                    node.stamp = stamp;
                    if (oldState != node.online) {
                        update();
                        return true;
                    }
                    return false;
                }
                node = {"id": vertex.name, "label": vertex.label,
                    "group": vertex.group, "online": vertex.online, "stamp": stamp};
                nodes.push(node);
                topo.vertexes[vertex.name] = node;
                update();
                return true;
            };

            this.addLink = function (edge, stamp) {
                var key = edge.source + "-" + edge.target;
                var link = topo.edges[key];
                if (link) {
                    var oldValue = link.value;
                    link.value = edge.value;
                    link.stamp = stamp;
                    if (oldValue != link.value) {
                        update();
                        return true;
                    }
                    return false;
                }
                link = {"source": findNode(edge.source), "target": findNode(edge.target),
                        "value": edge.value, "stamp": stamp};
                links.push(link);
                topo.edges[key] = link;
                update();
                return true;
            };

            this.prune = function (stamp) {
                var linksChanged = pruneArray(links, stamp, topo.edges, linkKey);
                var nodesChanged = pruneArray(nodes, stamp, topo.vertexes,
                        function(node) { return node.id; });
                if (linksChanged || nodesChanged) {
                    update();
                    return true;
                }
                return false;
            };

            var pruneArray = function(array, stamp, map, key) {
                var changed = false;
                for (var i = 0; i < array.length; i++) {
                    if (array[i].stamp < stamp) {
                        changed = true;
                        map[key(array[i])] = null;
                        array.splice(i, 1);
                        i--;
                    }
                }
                return changed;
            };

            var findNode = function (id) {
                for (var i in nodes) {
                    if (nodes[i]["id"] === id) return nodes[i];
                }
            };

            var force = d3.layout.force();

            var drag = force.drag()
                    .on("dragstart", dragstart);

            var nodes = force.nodes(),
                    links = force.links();

            var vis = d3.select("body")
                    .append("svg:svg")
                    .attr("width", w)
                    .attr("height", h)
                    .attr("id", "svg")
                    .attr("pointer-events", "all")
                    .attr("viewBox", "0 0 " + w + " " + h)
                    .attr("perserveAspectRatio", "xMinYMid")
                    .append('svg:g');

            d3.select("body")
                    .on("keydown", function(d) {
                        console.log(d3.event.keyCode);
                        if (d3.event.keyCode == 27) {
                            resetSelections();
                        } else if (d3.event.keyCode == 83) {
                            sourceNode = toggleNode(sourceNode);
                        } else if (d3.event.keyCode == 68) {
                            targetNode = toggleNode(targetNode);
                        } else if (d3.event.keyCode == 65) {
                            var aux = sourceNode;
                            sourceNode = targetNode;
                            targetNode = aux;
                        } else if (d3.event.keyCode == 70) {
                            nextPath();
                        } else if (d3.event.keyCode == 67 && selectedNode) {
                            selectedNode.fixed = !selectedNode.fixed;
                        }

                        d3.selectAll(".nodeStrokeClass").attr("fill", fillColor);
                        fetchPaths();
                    });


            var update = function () {
                var link = vis.selectAll("line")
                        .data(links, linkKey);

                link.enter().append("line")
                        .attr("id", linkKey)
                        .attr("class", "link")
                        .attr("stroke-width", function (d) { return d.value; })
                        .attr("stroke", linkColor);
                link.append("title").text(function (d) { return d.id; });
                link.exit().remove();

                var node = vis.selectAll("g.node")
                        .data(nodes, function (d) { return d.id; })
                        .on("click", function(d) {
                            selectedNode = d;
                            d3.selectAll(".nodeStrokeClass").attr("stroke", strokeColor);
                        });

                var nodeEnter = node.enter().append("g")
                        .attr("class", "node")
                        .on("dblclick", dblclick)
                        .call(force.drag);

                nodeEnter.append("svg:circle")
                        .attr("r", function(d) { return 28 / 2; })
                        .attr("id", function (d) { return "n-" + d.id.replace(/[.:]/g, ""); })
                        .attr("class", "nodeStrokeClass")
                        .attr("fill", fillColor)
                        .attr("stroke", strokeColor);

                nodeEnter.append("image")
                        .attr("xlink:href", function(d) { return d.group == 2 ? "images/switch.png" : "images/server.png"; })
                        .attr("x", -12)
                        .attr("y", -12)
                        .attr("width", 24)
                        .attr("height", 24);

                nodeEnter.append("svg:text")
                        .attr("class", "textClass")
                        .attr("x", 20)
                        .attr("y", ".31em")
                        .text(function (d) { return d.label; });

                node.exit().remove();

                d3.selectAll("nodeStrokeClass").attr("stroke", strokeColor);

                force.on("tick", function () {
                    node.attr("transform", function (d) {
                        return "translate(" + d.x + "," + d.y + ")";
                    });

                    link.attr("x1", function (d) { return d.source.x; })
                            .attr("y1", function (d) { return d.source.y; })
                            .attr("x2", function (d) { return d.target.x; })
                            .attr("y2", function (d) { return d.target.y; });
                });

                // Restart the force layout.
                force
                        .gravity(0.3)
                        .charge(-15000)
                        .friction(0.1)
                        .linkDistance(function(d) { return d.value * 30; })
                        .linkStrength(function(d) { return d.value * 0.6; })
                        .size([w, h])
                        .start();
            };

            // Make it all go
            update();
        }

        function drawGraph() {
            graph = new topoGraph("#svgdiv");
            bringNodesToFront();
        }

        function bringNodesToFront() {
            $(".nodeStrokeClass").each(function( index ) {
                var gnode = this.parentNode;
                gnode.parentNode.appendChild(gnode);
            });
        }

        function addNodes() {
            d3.select("svg")
                    .remove();
            drawGraph();
        }

        function fetchData() {
            var stamp = new Date().getTime();
                d3.json("rs/topology/graph", function(error, data) {
                var changed = false;
                data.vertexes.forEach(function(vertex) { changed = graph.addNode(vertex, stamp) || changed; });
                data.edges.forEach(function(edge) { changed = graph.addLink(edge, stamp) || changed; });

                changed = graph.prune(stamp) || changed;
                if (changed) {
                    bringNodesToFront();
                    // Update node and links styles
                    d3.selectAll(".nodeStrokeClass").attr("fill", fillColor);
                    d3.selectAll(".line").attr("stroke-width", function (d) { return d.value; })
                }

                setTimeout(fetchData, 1000);
            });
        };

        drawGraph();
        setTimeout(fetchData, 500);

    </script>
</body>
</html>