Device Neighbor Map can have max depth and VLAN filter

This commit is contained in:
Oliver Gorwits
2013-12-29 16:23:46 +00:00
parent aae7880311
commit 6079c7dfed
9 changed files with 99 additions and 44 deletions

View File

@@ -4,6 +4,7 @@
* Add IP Phones discovered through LLDP/CDP report * Add IP Phones discovered through LLDP/CDP report
* Add device/node/vlan/port specific search from Navbar * Add device/node/vlan/port specific search from Navbar
* [#3] [#47] Device Neighbor Map can have max depth and VLAN filter
[ENHANCEMENTS] [ENHANCEMENTS]

View File

@@ -10,11 +10,11 @@ __PACKAGE__->table_class('DBIx::Class::ResultSource::View');
__PACKAGE__->table('device_links'); __PACKAGE__->table('device_links');
__PACKAGE__->result_source_instance->is_virtual(1); __PACKAGE__->result_source_instance->is_virtual(1);
__PACKAGE__->result_source_instance->view_definition(<<ENDSQL __PACKAGE__->result_source_instance->view_definition(<<ENDSQL
SELECT dp.ip AS left_ip, di.ip AS right_ip SELECT dp.ip AS left_ip, dp.port AS left_port, di.ip AS right_ip, dp.remote_port AS right_port
FROM ( SELECT device_port.ip, device_port.remote_ip FROM ( SELECT device_port.ip, device_port.port, device_port.remote_ip, device_port.remote_port
FROM device_port FROM device_port
WHERE device_port.remote_port IS NOT NULL WHERE device_port.remote_port IS NOT NULL
GROUP BY device_port.ip, device_port.remote_ip GROUP BY device_port.ip, device_port.port, device_port.remote_ip, device_port.remote_port
ORDER BY device_port.ip) dp ORDER BY device_port.ip) dp
LEFT JOIN device_ip di ON dp.remote_ip = di.alias LEFT JOIN device_ip di ON dp.remote_ip = di.alias
WHERE di.ip IS NOT NULL WHERE di.ip IS NOT NULL
@@ -26,9 +26,23 @@ __PACKAGE__->add_columns(
'left_ip' => { 'left_ip' => {
data_type => 'inet', data_type => 'inet',
}, },
'left_port' => {
data_type => 'text',
},
'right_ip' => { 'right_ip' => {
data_type => 'inet', data_type => 'inet',
}, },
'right_port' => {
data_type => 'text',
},
); );
__PACKAGE__->has_many('left_vlans', 'App::Netdisco::DB::Result::DevicePortVlan',
{ 'foreign.ip' => 'self.left_ip', 'foreign.port' => 'self.left_port' },
{ join_type => 'INNER' } );
__PACKAGE__->has_many('right_vlans', 'App::Netdisco::DB::Result::DevicePortVlan',
{ 'foreign.ip' => 'self.right_ip', 'foreign.port' => 'self.right_port' },
{ join_type => 'INNER' } );
1; 1;

View File

@@ -23,8 +23,11 @@ sub _get_name {
} }
sub _add_children { sub _add_children {
my ($ptr, $childs) = @_; my ($ptr, $childs, $step, $limit) = @_;
return $step if $limit and $step > $limit;
my @legit = (); my @legit = ();
my $max = $step;
foreach my $c (@$childs) { foreach my $c (@$childs) {
next if exists var('seen')->{$c}; next if exists var('seen')->{$c};
@@ -39,14 +42,24 @@ sub _add_children {
for (my $i = 0; $i < @legit; $i++) { for (my $i = 0; $i < @legit; $i++) {
$ptr->[$i]->{children} = []; $ptr->[$i]->{children} = [];
_add_children($ptr->[$i]->{children}, var('links')->{$legit[$i]}); my $nm = _add_children($ptr->[$i]->{children}, var('links')->{$legit[$i]},
($step + 1), $limit);
$max = $nm if $nm > $max;
} }
return $max;
} }
# d3 seems not to use proper ajax semantics, so get instead of ajax # d3 seems not to use proper ajax semantics, so get instead of ajax
get '/ajax/data/device/netmap' => require_login sub { get '/ajax/data/device/netmap' => require_login sub {
my $q = param('q'); my $q = param('q');
my $vlan = param('vlan');
undef $vlan if (defined $vlan and $vlan !~ m/^\d+$/);
my $depth = param('depth');
undef $depth if (defined $depth and $depth !~ m/^\d+$/);
my $device = schema('netdisco')->resultset('Device') my $device = schema('netdisco')->resultset('Device')
->search_for_device($q) or send_error('Bad device', 400); ->search_for_device($q) or send_error('Bad device', 400);
my $start = $device->ip; my $start = $device->ip;
@@ -59,9 +72,19 @@ get '/ajax/data/device/netmap' => require_login sub {
var(links => {}); var(links => {});
my $rs = schema('netdisco')->resultset('Virtual::DeviceLinks')->search({}, { my $rs = schema('netdisco')->resultset('Virtual::DeviceLinks')->search({}, {
columns => [qw/left_ip right_ip/],
result_class => 'DBIx::Class::ResultClass::HashRefInflator', result_class => 'DBIx::Class::ResultClass::HashRefInflator',
}); });
if ($vlan) {
$rs = $rs->search({
'left_vlans.vlan' => $vlan,
'right_vlans.vlan' => $vlan,
}, {
join => [qw/left_vlans right_vlans/],
});
}
while (my $l = $rs->next) { while (my $l = $rs->next) {
var('links')->{ $l->{left_ip} } ||= []; var('links')->{ $l->{left_ip} } ||= [];
push @{ var('links')->{ $l->{left_ip} } }, $l->{right_ip}; push @{ var('links')->{ $l->{left_ip} } }, $l->{right_ip};
@@ -75,7 +98,8 @@ get '/ajax/data/device/netmap' => require_login sub {
); );
var(seen => {$start => 1}); var(seen => {$start => 1});
_add_children($tree{children}, var('links')->{$start}); my $max = _add_children($tree{children}, var('links')->{$start}, 1, $depth);
$tree{scale} = $max;
content_type('application/json'); content_type('application/json');
to_json(\%tree); to_json(\%tree);

View File

@@ -395,18 +395,6 @@ td > form.nd_inline-form {
display: none; display: none;
} }
/* question mark image with popover for netmap instructions */
#nd_netmap-help {
position: fixed;
top: 160px;
right: 7px;
z-index: 1;
color: #555;
font-size: 20px;
cursor: pointer;
display: none;
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/* style customization for many items which appear in the sidebar */ /* style customization for many items which appear in the sidebar */
@@ -416,6 +404,11 @@ td > form.nd_inline-form {
margin-bottom: 12px; margin-bottom: 12px;
} }
/* labels in simple sidebars */
.nd_sidebar-label {
margin-left: 7px;
}
/* fixup for prepended checkbox in sidebar */ /* fixup for prepended checkbox in sidebar */
.nd_searchcheckbox { .nd_searchcheckbox {
width: 121px; width: 121px;
@@ -567,6 +560,11 @@ form .clearfix.success input {
padding-top: 3px !important; padding-top: 3px !important;
} }
.nd_netmap-sidebar {
margin-top: 40px;
margin-left: -9px;
}
.icons-ul { .icons-ul {
margin-left: 22px; margin-left: 22px;
} }

View File

@@ -3,10 +3,6 @@
var winHeight = window.innerHeight; var winHeight = window.innerHeight;
var winWidth = window.innerWidth; var winWidth = window.innerWidth;
var tree = d3.layout.tree()
.size([360, winHeight])
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
// links in the initial tree drawing use this generator // links in the initial tree drawing use this generator
var treeLink = d3.svg.diagonal.radial() var treeLink = d3.svg.diagonal.radial()
.projection(function(d) { return [d.y, d.x / 180 * Math.PI]; }); .projection(function(d) { return [d.y, d.x / 180 * Math.PI]; });
@@ -60,7 +56,10 @@ function to_class(name) { return 'nd_' + name.replace(/\./g, "_") }
// handler for clicking on a circle - redirect to that device's netmap // handler for clicking on a circle - redirect to that device's netmap
function circleClick(d) { function circleClick(d) {
window.location = '[% uri_for('/device') %]?tab=netmap&q=' + d.fullname; window.location = '[% uri_for('/device') %]?tab=netmap'
+ '&q=' + d.fullname
+ '&depth=[% params.depth | uri %]'
+ '&vlan=[% params.vlan | uri %]';
} }
// handler for mouseover on a circle - show that device's real neighbors // handler for mouseover on a circle - show that device's real neighbors
@@ -92,7 +91,15 @@ $.getJSON('[% uri_for('/ajax/data/device/alldevicelinks') %]', function(data) {
neighbors_data = data; neighbors_data = data;
// draw the tree // draw the tree
d3.json("[% uri_for('/ajax/data/device/netmap') %]?&q=[% params.q | uri %]", function(error, root) { 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), var nodes = tree.nodes(root),
links = tree.links(nodes); links = tree.links(nodes);
@@ -171,4 +178,5 @@ $.getJSON('[% uri_for('/ajax/data/device/alldevicelinks') %]', function(data) {
}); // jquery getJSON for all connections }); // jquery getJSON for all connections
// vim: ft=javascript
</script> </script>

View File

@@ -1,16 +1,5 @@
<i class="nd_sidebar-toggle icon-wrench icon-large" id="nd_sidebar-toggle-img-out" <i class="nd_sidebar-toggle icon-wrench icon-large" id="nd_sidebar-toggle-img-out"
rel="tooltip" data-placement="left" data-offset="5" data-title="Show Sidebar"></i> rel="tooltip" data-placement="left" data-offset="5" data-title="Show Sidebar"></i>
<i class="icon-question-sign icon-large" id="nd_netmap-help" rel="popover"
data-title="Neighbor Map Controls"
data-html="true"
data-content="
<ul>
<li>Click and drag to pan</li>
<li>Mouse-wheel scroll to zoom</li>
<li>Hover to hightlight neighbors</li>
<li>Click to view device's map</li>
</ul>"
data-placement='left' data-trigger='click'></i>
<div class="container-fluid"> <div class="container-fluid">
<div class="nd_sidebar nd_sidebar-pinned"> <div class="nd_sidebar nd_sidebar-pinned">
<div class="well"> <div class="well">

View File

@@ -95,7 +95,12 @@
$('#nd_sidebar-reset-link').attr('href', uri_base + '/device?tab=[% tab.tag %]&reset=on&' + $('#nd_sidebar-reset-link').attr('href', uri_base + '/device?tab=[% tab.tag %]&reset=on&' +
$('#ports_form') $('#ports_form')
.find('input[name="q"],input[name="f"],input[name="partial"],input[name="invert"]') .find('input[name="q"],input[name="f"],input[name="partial"],input[name="invert"]')
.serialize()) .serialize());
[% ELSIF tab.tag == 'netmap' %]
// form reset icon on netmap tab
$('#nd_sidebar-reset-link').attr('href', uri_base + '/device?tab=[% tab.tag %]&reset=on&' +
$('#netmap_form').find('input[name="q"]').serialize());
[% END %] [% END %]
do_search(event, '[% tab.tag %]'); do_search(event, '[% tab.tag %]');

View File

@@ -19,14 +19,6 @@
// changes, and only reset when they submit or cancel the change // changes, and only reset when they submit or cancel the change
var dirty = false; var dirty = false;
// show or hide netmap help button
if (tab == 'netmap') {
$('#nd_netmap-help').show();
}
else {
$('#nd_netmap-help').hide();
}
// activate modals, tooltips and popovers // activate modals, tooltips and popovers
$('.nd_modal').modal({show: false}); $('.nd_modal').modal({show: false});
$("[rel=tooltip]").tooltip({live: true}); $("[rel=tooltip]").tooltip({live: true});

View File

@@ -0,0 +1,24 @@
<span class="nd_sidebar-title"><em>Neighbor Map Controls</em></span>
<input name="q" value="[% params.q | html_entity %]" type="hidden"/>
<div class="clearfix nd_netmap-sidebar">
<ul class="muted">
<li>Click and drag to pan</li>
<li>Scroll to zoom</li>
<li>Hover shows neighbors</li>
<li>Click to center device</li>
</ul>
<em class="muted nd_sidebar-label">Draw to Depth:</em><br/>
<input id="nd_port-query" placeholder="" type="number"
name="depth" value="[% params.depth | html_entity %]" type="text"
rel="tooltip" data-placement="left" data-offset="5" data-title="Max Depth"/>
<em class="muted nd_sidebar-label">Filter by VLAN:</em><br/>
<input id="nd_port-query" placeholder="" type="number"
name="vlan" value="[% params.vlan | html_entity %]" type="text"
rel="tooltip" data-placement="left" data-offset="5" data-title="VLAN"/>
</div>
<button id="[% tab.id %]_submit" type="submit" class="btn btn-info">
<i class="icon-pencil icon-large pull-left nd_navbar-icon"></i> Redraw Map</button>