diff --git a/Netdisco/Changes b/Netdisco/Changes index b3ea66bc..61348991 100644 --- a/Netdisco/Changes +++ b/Netdisco/Changes @@ -4,6 +4,7 @@ * Add IP Phones discovered through LLDP/CDP report * Add device/node/vlan/port specific search from Navbar + * Add Undiscovered Neighbors admin report * [#3] [#47] Device Neighbor Map can have max depth and VLAN filter * [#31] get_community now supported * [#19] Ask for Reason when changing Port up/down Status, or VLAN diff --git a/Netdisco/lib/App/Netdisco/DB/Result/Virtual/UndiscoveredNeighbors.pm b/Netdisco/lib/App/Netdisco/DB/Result/Virtual/UndiscoveredNeighbors.pm new file mode 100644 index 00000000..94d14c18 --- /dev/null +++ b/Netdisco/lib/App/Netdisco/DB/Result/Virtual/UndiscoveredNeighbors.pm @@ -0,0 +1,58 @@ +package App::Netdisco::DB::Result::Virtual::UndiscoveredNeighbors; + +use strict; +use warnings; + +use utf8; +use base 'DBIx::Class::Core'; + +__PACKAGE__->table_class('DBIx::Class::ResultSource::View'); + +__PACKAGE__->table('undiscovered_neighbors'); +__PACKAGE__->result_source_instance->is_virtual(1); +__PACKAGE__->result_source_instance->view_definition(<<'ENDSQL'); + SELECT DISTINCT ON (p.remote_ip) d.ip, + d.name, + d.dns, + p.port, + p.remote_ip, + p.remote_id, + p.remote_type, + p.remote_port, + a.log, + a.finished + FROM device_port p + JOIN device d ON d.ip = p.ip + JOIN ADMIN a ON p.remote_ip = a.device + WHERE p.remote_ip NOT IN + (SELECT ALIAS + FROM device_ip) + AND a.action = 'discover' + ORDER BY p.remote_ip, + a.finished DESC +ENDSQL + +__PACKAGE__->add_columns( + "ip", + { data_type => "inet", is_nullable => 0 }, + "name", + { data_type => "text", is_nullable => 1 }, + "dns", + { data_type => "text", is_nullable => 1 }, + "port", + { data_type => "text", is_nullable => 0 }, + "remote_ip", + { data_type => "inet", is_nullable => 1 }, + "remote_port", + { data_type => "text", is_nullable => 1 }, + "remote_type", + { data_type => "text", is_nullable => 1 }, + "remote_id", + { data_type => "text", is_nullable => 1 }, + "log", + { data_type => "text", is_nullable => 1 }, + "finished", + { data_type => "timestamp", is_nullable => 1 }, +); + +1; diff --git a/Netdisco/lib/App/Netdisco/Web/Plugin/AdminTask/UndiscoveredNeighbors.pm b/Netdisco/lib/App/Netdisco/Web/Plugin/AdminTask/UndiscoveredNeighbors.pm new file mode 100644 index 00000000..36c10667 --- /dev/null +++ b/Netdisco/lib/App/Netdisco/Web/Plugin/AdminTask/UndiscoveredNeighbors.pm @@ -0,0 +1,47 @@ +package App::Netdisco::Web::Plugin::AdminTask::UndiscoveredNeighbors; + +use strict; +use warnings; +use Dancer ':syntax'; +use Dancer::Plugin::DBIC; +use Dancer::Plugin::Auth::Extensible; +use App::Netdisco::Util::Device qw/is_discoverable/; + +use App::Netdisco::Web::Plugin; + +register_admin_task( + { tag => 'undiscoveredneighbors', + label => 'Undiscovered Neighbors', + provides_csv => 1, + } +); + +get '/ajax/content/admin/undiscoveredneighbors' => require_role admin => sub { + my @devices + = schema('netdisco')->resultset('Virtual::UndiscoveredNeighbors') + ->order_by('ip')->hri->all; + + return unless scalar @devices; + + # Don't include devices excluded from discovery by config + my @results = grep { + is_discoverable( $_->{'ports'}->{remote_ip}, + $_->{'ports'}->{'remote_type'} ) + } @devices; + + return unless scalar @results; + + if ( request->is_ajax ) { + template 'ajax/admintask/undiscoveredneighbors.tt', + { results => \@results, }, + { layout => undef }; + } + else { + header( 'Content-Type' => 'text/comma-separated-values' ); + template 'ajax/admintask/undiscoveredneighbors_csv.tt', + { results => \@results, }, + { layout => undef }; + } +}; + +1; diff --git a/Netdisco/share/config.yml b/Netdisco/share/config.yml index 9a4efaaa..9616402f 100644 --- a/Netdisco/share/config.yml +++ b/Netdisco/share/config.yml @@ -59,6 +59,7 @@ web_plugins: - AdminTask::UserLog - AdminTask::Users - AdminTask::PortLog + - AdminTask::UndiscoveredNeighbors - Search::Device - Search::Node - Search::VLAN diff --git a/Netdisco/share/views/ajax/admintask/undiscoveredneighbors.tt b/Netdisco/share/views/ajax/admintask/undiscoveredneighbors.tt new file mode 100644 index 00000000..e3322589 --- /dev/null +++ b/Netdisco/share/views/ajax/admintask/undiscoveredneighbors.tt @@ -0,0 +1,26 @@ + + + + + + + + + + + [% FOREACH row IN results %] + + + + + + + [% END %] + +
Device Location Triggering
Last Discovery Attempt
Undiscovered NeighborLast Discovery AttemptLast Discovery Log
+ [% row.dns || row.name || row.ip | html_entity %] ( [% row.port | html_entity %] ) + [% row.remote_ip | html_entity %] + ([% row.remote_port | html_entity %]) + [% ' id: '_ row.remote_id IF row.remote_id %] + [% ' type: '_ row.remote_type IF row.remote_type %][% row.finished | html_entity %][% row.log | html_entity %]
+ diff --git a/Netdisco/share/views/ajax/admintask/undiscoveredneighbors_csv.tt b/Netdisco/share/views/ajax/admintask/undiscoveredneighbors_csv.tt new file mode 100644 index 00000000..78cef5fb --- /dev/null +++ b/Netdisco/share/views/ajax/admintask/undiscoveredneighbors_csv.tt @@ -0,0 +1,18 @@ +[% USE CSV -%] +[% CSV.dump([ 'Device Triggering Last Discovery Attempt' 'Device Port' + 'Remote IP' 'Remote Port' 'Remote ID' 'Remote Type' + 'Last Discovery Attempt' 'Discovery Log']) %] + +[% FOREACH row IN results %] + [% mylist = [] %] + [% mylist.push(row.dns || row.name || row.ip) %] + [% mylist.push(row.port) %] + [% mylist.push(row.remote_ip) %] + [% mylist.push(row.remote_port) %] + [% mylist.push(row.remote_id) %] + [% mylist.push(row.remote_type) %] + [% mylist.push(row.finished) %] + [% mylist.push(row.log) %] + [% CSV.dump(mylist) %] + +[% END %]