The reason for this is that DataTables keys local data on the page path, so each tab should have its own path. We're already doing this for reports and admin tasks, so it also makes sense to have consistency with search and device tabs. Squashed commit of the following: commit 4ad33a23a81122496adfe561ad14f039e6255eff Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 25 14:46:17 2014 +0100 fix search preference selection commit 363e094935d386961e8773f787af41c46b83129a Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 25 14:36:45 2014 +0100 fix css selector to match begins with /search commit 43c972ee0d9401f74dcc3bd30052dba130b0d068 Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 25 14:18:24 2014 +0100 fix history push commit 84f83eb46874b0222c0484014389713e4f027c8a Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 25 14:06:44 2014 +0100 update sidebar form for tab-path, remove hidden tab name field commit 344d4679a83f714c998cd475c041f8effab0c696 Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 25 14:05:49 2014 +0100 update template links for tab path commit 9cf370d7eb4413aac6fc19c2c13a9bf670600965 Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 25 14:04:57 2014 +0100 move tab-specific JS from common into specific includes files commit c2d8592a18e389535368d1e74fed29fe5a0eabd8 Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 25 14:02:46 2014 +0100 fix mode commit 52487cea47eaaea7f5c74536ad6d4bb2a8d6ba4c Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 25 14:01:15 2014 +0100 move from tab param to tabname template var commit b5a2424631a0050d5de3bc658746a40cd822e869 Merge:531782bd8102bfAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 25 10:50:00 2014 +0100 Merge branch 'master' into em-device-ports-json
188 lines
6.2 KiB
JavaScript
188 lines
6.2 KiB
JavaScript
<script>
|
|
|
|
var winHeight = window.innerHeight;
|
|
var winWidth = window.innerWidth;
|
|
|
|
// links in the initial tree drawing use this generator
|
|
var treeLink = d3.svg.diagonal.radial()
|
|
.projection(function(d) { return [d.y, d.x / 180 * Math.PI]; });
|
|
|
|
// actual device neighbor links use this generator
|
|
var neighLink = d3.svg.diagonal.radial();
|
|
|
|
// store x,y for all circles on the map
|
|
var loc = {};
|
|
// store actual links between all nodes
|
|
var neighbors_data = {};
|
|
|
|
// main SVG background, with support for pan/zoom
|
|
var svg = d3.select("#netmap_pane").append("svg")
|
|
.attr("width", winWidth - 50)
|
|
.attr("height", winHeight - 100)
|
|
.attr("pointer-events", "all")
|
|
.append('g')
|
|
.call(d3.behavior.zoom().on("zoom", redraw))
|
|
.append("g")
|
|
.attr("transform", "translate(" + winHeight / 2 + "," + winHeight / 2 + ")");
|
|
|
|
// this is the image background
|
|
// XXX there must be a way to discover the radial tree's size?
|
|
svg.append('rect')
|
|
.attr("x", (0 - (winHeight * 2)))
|
|
.attr('width', "400%")
|
|
.attr("y", (0 - (winHeight * 2)))
|
|
.attr('height', "400%")
|
|
.attr('fill', 'white');
|
|
|
|
// handle pan and zoom
|
|
function redraw() {
|
|
svg.attr("transform",
|
|
"translate(" + d3.event.translate + ")"
|
|
+ "scale(" + d3.event.scale + ")"
|
|
+ "translate(" + (winHeight / 2) + "," + (winHeight / 2) + ")");
|
|
}
|
|
|
|
// save the x,y of an element into the loc dictionary
|
|
function recordLocation(d,i) {
|
|
var rect = this.getBoundingClientRect();
|
|
loc[d.name] = {
|
|
'x': (rect.left + ((rect.right - rect.left) / 2))
|
|
,'y': (rect.top + ((rect.bottom - rect.top) / 2))
|
|
};
|
|
}
|
|
|
|
// convert a device name to a valid CSS class name
|
|
function to_class(name) { return 'nd_' + name.replace(/\./g, "_") }
|
|
|
|
// handler for clicking on a circle - redirect to that device's netmap
|
|
function circleClick(d) {
|
|
window.location = '[% uri_for('/device/netmap') %]'
|
|
+ '?q=' + d.fullname
|
|
+ '&depth=[% params.depth | uri %]'
|
|
+ '&vlan=[% params.vlan | uri %]';
|
|
}
|
|
|
|
// handler for mouseover on a circle - show that device's real neighbors
|
|
function circleOver(d) {
|
|
$('.link').hide();
|
|
$('path.' + to_class(d.name)).show();
|
|
$(this).css('cursor', 'pointer');
|
|
|
|
$.each(neighbors_data[d.name], function(idx, target) {
|
|
if (! (target in loc)) { return true }
|
|
$('circle.' + to_class(target)).css('fill', '#e96cfa');
|
|
});
|
|
}
|
|
|
|
// handler for mouseout on a circle - hide real neighbours and show treeLinks
|
|
function circleOut(d) {
|
|
$.each(neighbors_data[d.name], function(idx, target) {
|
|
if (! (target in loc)) { return true }
|
|
$('circle.' + to_class(target)).css('fill', '#fff');
|
|
});
|
|
|
|
$(this).css('cursor', 'auto');
|
|
$('path.' + to_class(d.name)).hide();
|
|
$('.link').show();
|
|
}
|
|
|
|
// load all device connections into neighbors_data dictionary
|
|
$.getJSON('[% uri_for('/ajax/data/device/alldevicelinks') %]', function(data) {
|
|
neighbors_data = data;
|
|
|
|
// draw the tree
|
|
d3.json("[% uri_for('/ajax/data/device/netmap') %]?"
|
|
+ '&q=[% params.q | uri %]'
|
|
+ '&depth=[% params.depth | uri %]'
|
|
+ '&vlan=[% params.vlan | uri %]', function(error, root) {
|
|
var tree = d3.layout.tree()
|
|
// magic number "8" for scaling (network depth). seems to make things look right for me.
|
|
.size([360, (winHeight / 8 * (root['scale'] || 0))])
|
|
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
|
|
|
|
var nodes = tree.nodes(root),
|
|
links = tree.links(nodes);
|
|
|
|
var link = svg.selectAll(".link")
|
|
.data(links)
|
|
.enter().append("path")
|
|
.attr("class", "link")
|
|
.attr("d", treeLink);
|
|
|
|
var node = svg.selectAll(".node")
|
|
.data(nodes)
|
|
.enter().append("g")
|
|
.attr("class", "node")
|
|
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; });
|
|
|
|
// begin to draw...
|
|
d3.select("#nd_waiting").remove();
|
|
|
|
node.append("circle")
|
|
.attr("r", 4.5)
|
|
// circle has class name of its device, so we can show/hide it
|
|
.attr("class", function(d) { return to_class(d.name) })
|
|
// store the x,y of every circle we've just drawn
|
|
.each(recordLocation)
|
|
// handlers for mouse interaction with the circles
|
|
.on("click", circleClick)
|
|
.on("mouseover", circleOver)
|
|
.on("mouseout", circleOut);
|
|
|
|
node.append("text")
|
|
.attr("dy", ".31em")
|
|
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
|
|
.attr("transform", function(d) { return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)"; })
|
|
.text(function(d) { return d.name; });
|
|
|
|
// reorient text on the root node
|
|
svg.select(".node")
|
|
.attr("transform", function(d) {
|
|
return d.x < 180 ? "rotate(0)translate(0)" : "rotate(180)translate(0)"; });
|
|
|
|
// key (name) of the root node in our locations store
|
|
var rootname = svg.select(".node").data()[0].name;
|
|
// reformatted neighbors_data for the real neighbor links
|
|
var neighbors = [];
|
|
|
|
// need to build neighbors array only after we have built loc dictionary,
|
|
// after drawing the circles and storing their x,y
|
|
$.each(neighbors_data, function(key, val) {
|
|
if (! (key in loc)) { return true }
|
|
|
|
$.each(val, function(idx, name) {
|
|
if (! (name in loc)) { return true }
|
|
|
|
neighbors.push({
|
|
'source': {
|
|
'name': key
|
|
,'x': loc[key]['x']
|
|
,'y': loc[key]['y']
|
|
}
|
|
,'target': {
|
|
'name': name
|
|
,'x': loc[name]['x']
|
|
,'y': loc[name]['y']
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
// insert Netdisco neighbor links below circles but above tree links
|
|
svg.selectAll(".neighbor")
|
|
.data(neighbors)
|
|
.enter().insert("path", ".node")
|
|
// add class name of source device, so we can show/hide the link
|
|
// (also "neighbor" class)
|
|
.attr("class", function(d) { return ("neighbor " + to_class( d.source.name )) })
|
|
.attr("d", neighLink)
|
|
.attr("transform", "translate(-" + loc[rootname]['x'] + ",-" + loc[rootname]['y'] + ")");
|
|
});
|
|
|
|
}); // jquery getJSON for all connections
|
|
|
|
// vim: ft=javascript
|
|
</script>
|
|
|
|
<div id="nd_waiting" class="span2 alert"><i class="icon-spinner icon-spin"></i> Waiting for results...</div>
|