blob: 04abfa4b05a5d326e72393e3dd64f974c56aa26a [file] [log] [blame]
<!DOCTYPE html>
<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, "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();
}
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.id; });
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>