Device Neighbor Map can have max depth and VLAN filter
This commit is contained in:
		| @@ -4,6 +4,7 @@ | ||||
|  | ||||
|   * Add IP Phones discovered through LLDP/CDP report | ||||
|   * Add device/node/vlan/port specific search from Navbar | ||||
|   * [#3] [#47] Device Neighbor Map can have max depth and VLAN filter | ||||
|  | ||||
|   [ENHANCEMENTS] | ||||
|  | ||||
|   | ||||
| @@ -10,11 +10,11 @@ __PACKAGE__->table_class('DBIx::Class::ResultSource::View'); | ||||
| __PACKAGE__->table('device_links'); | ||||
| __PACKAGE__->result_source_instance->is_virtual(1); | ||||
| __PACKAGE__->result_source_instance->view_definition(<<ENDSQL | ||||
|  SELECT dp.ip AS left_ip, di.ip AS right_ip | ||||
|    FROM ( SELECT device_port.ip, device_port.remote_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.port, device_port.remote_ip, device_port.remote_port | ||||
|            FROM device_port | ||||
|           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 | ||||
|    LEFT JOIN device_ip di ON dp.remote_ip = di.alias | ||||
|   WHERE di.ip IS NOT NULL | ||||
| @@ -26,9 +26,23 @@ __PACKAGE__->add_columns( | ||||
|   'left_ip' => { | ||||
|     data_type => 'inet', | ||||
|   }, | ||||
|   'left_port' => { | ||||
|     data_type => 'text', | ||||
|   }, | ||||
|   'right_ip' => { | ||||
|     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; | ||||
|   | ||||
| @@ -23,8 +23,11 @@ sub _get_name { | ||||
| } | ||||
|  | ||||
| sub _add_children { | ||||
|     my ($ptr, $childs) = @_; | ||||
|     my ($ptr, $childs, $step, $limit) = @_; | ||||
|  | ||||
|     return $step if $limit and $step > $limit; | ||||
|     my @legit = (); | ||||
|     my $max = $step; | ||||
|  | ||||
|     foreach my $c (@$childs) { | ||||
|         next if exists var('seen')->{$c}; | ||||
| @@ -39,14 +42,24 @@ sub _add_children { | ||||
|  | ||||
|     for (my $i = 0; $i < @legit; $i++) { | ||||
|         $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 | ||||
| get '/ajax/data/device/netmap' => require_login sub { | ||||
|     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') | ||||
|       ->search_for_device($q) or send_error('Bad device', 400); | ||||
|     my $start = $device->ip; | ||||
| @@ -59,9 +72,19 @@ get '/ajax/data/device/netmap' => require_login sub { | ||||
|  | ||||
|     var(links => {}); | ||||
|     my $rs = schema('netdisco')->resultset('Virtual::DeviceLinks')->search({}, { | ||||
|       columns => [qw/left_ip right_ip/], | ||||
|       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) { | ||||
|         var('links')->{ $l->{left_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}); | ||||
|     _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'); | ||||
|     to_json(\%tree); | ||||
|   | ||||
| @@ -395,18 +395,6 @@ td > form.nd_inline-form { | ||||
|   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 */ | ||||
|  | ||||
| @@ -416,6 +404,11 @@ td > form.nd_inline-form { | ||||
|   margin-bottom: 12px; | ||||
| } | ||||
|  | ||||
| /* labels in simple sidebars */ | ||||
| .nd_sidebar-label { | ||||
|   margin-left: 7px; | ||||
| } | ||||
|  | ||||
| /* fixup for prepended checkbox in sidebar */ | ||||
| .nd_searchcheckbox { | ||||
|   width: 121px; | ||||
| @@ -567,6 +560,11 @@ form .clearfix.success input { | ||||
|   padding-top: 3px !important; | ||||
| } | ||||
|  | ||||
| .nd_netmap-sidebar { | ||||
|   margin-top: 40px; | ||||
|   margin-left: -9px; | ||||
| } | ||||
|  | ||||
| .icons-ul { | ||||
|   margin-left: 22px; | ||||
| } | ||||
|   | ||||
| @@ -3,10 +3,6 @@ | ||||
| var winHeight = window.innerHeight; | ||||
| 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 | ||||
| var treeLink = d3.svg.diagonal.radial() | ||||
|     .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 | ||||
| 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 | ||||
| @@ -92,7 +91,15 @@ $.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 %]", 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), | ||||
|         links = tree.links(nodes); | ||||
|  | ||||
| @@ -171,4 +178,5 @@ $.getJSON('[% uri_for('/ajax/data/device/alldevicelinks') %]', function(data) { | ||||
|  | ||||
| }); // jquery getJSON for all connections | ||||
|  | ||||
| // vim: ft=javascript | ||||
| </script> | ||||
|   | ||||
| @@ -1,16 +1,5 @@ | ||||
| <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> | ||||
| <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="nd_sidebar nd_sidebar-pinned"> | ||||
|     <div class="well"> | ||||
|   | ||||
| @@ -95,7 +95,12 @@ | ||||
|       $('#nd_sidebar-reset-link').attr('href', uri_base + '/device?tab=[% tab.tag %]&reset=on&' + | ||||
|         $('#ports_form') | ||||
|           .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 %] | ||||
|  | ||||
|       do_search(event, '[% tab.tag %]'); | ||||
|   | ||||
| @@ -19,14 +19,6 @@ | ||||
|     // changes, and only reset when they submit or cancel the change | ||||
|     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 | ||||
|     $('.nd_modal').modal({show: false}); | ||||
|     $("[rel=tooltip]").tooltip({live: true}); | ||||
|   | ||||
							
								
								
									
										24
									
								
								Netdisco/share/views/sidebar/device/netmap.tt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								Netdisco/share/views/sidebar/device/netmap.tt
									
									
									
									
									
										Normal 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> | ||||
|  | ||||
		Reference in New Issue
	
	Block a user