From ad8bcc3dfc9c6a3c7d6aa9d415a227444a6aa10f Mon Sep 17 00:00:00 2001 From: "Eric A. Miller" Date: Sat, 15 Feb 2014 22:07:55 -0500 Subject: [PATCH] [#75] Device module inventory report / search --- Netdisco/Changes | 4 + .../App/Netdisco/DB/ResultSet/DeviceModule.pm | 107 +++++++++++++++++ .../Web/Plugin/Report/ModuleInventory.pm | 109 ++++++++++++++++++ Netdisco/lib/App/Netdisco/Web/Report.pm | 3 + Netdisco/share/config.yml | 1 + .../views/ajax/report/moduleinventory.tt | 86 ++++++++++++++ .../views/ajax/report/moduleinventory_csv.tt | 27 +++++ Netdisco/share/views/js/report.js | 12 +- .../views/sidebar/report/moduleinventory.tt | 72 ++++++++++++ 9 files changed, 419 insertions(+), 2 deletions(-) create mode 100644 Netdisco/lib/App/Netdisco/DB/ResultSet/DeviceModule.pm create mode 100644 Netdisco/lib/App/Netdisco/Web/Plugin/Report/ModuleInventory.pm create mode 100644 Netdisco/share/views/ajax/report/moduleinventory.tt create mode 100644 Netdisco/share/views/ajax/report/moduleinventory_csv.tt create mode 100644 Netdisco/share/views/sidebar/report/moduleinventory.tt diff --git a/Netdisco/Changes b/Netdisco/Changes index be98efa7..0455a6b4 100644 --- a/Netdisco/Changes +++ b/Netdisco/Changes @@ -1,5 +1,9 @@ 2.023002 - + [NEW FEATURES] + + * [#75] Device module inventory report / search + [ENHANCEMENTS] * Kwalitee fixes diff --git a/Netdisco/lib/App/Netdisco/DB/ResultSet/DeviceModule.pm b/Netdisco/lib/App/Netdisco/DB/ResultSet/DeviceModule.pm new file mode 100644 index 00000000..3d14a19d --- /dev/null +++ b/Netdisco/lib/App/Netdisco/DB/ResultSet/DeviceModule.pm @@ -0,0 +1,107 @@ +package App::Netdisco::DB::ResultSet::DeviceModule; +use base 'App::Netdisco::DB::ResultSet'; + +use strict; +use warnings FATAL => 'all'; + +=head1 ADDITIONAL METHODS + +=head2 search_by_field( \%cond, \%attrs? ) + +This variant of the standard C method returns a ResultSet of Device +Module entries. It is written to support web forms which accept fields that +match and locate Device Modules in the database. + +The hashref parameter should contain fields from the Device Module table +which will be intelligently used in a search query. + +In addition, you can provide the key C which, given a True or False +value, controls whether fields must all match or whether any can match, to +select a row. + +Supported keys: + +=over 4 + +=item matchall + +If a True value, fields must all match to return a given row of the Device +table, otherwise any field matching will cause the row to be included in +results. + +=item description + +Can match the C field as a substring. + +=item name + +Can match the C field as a substring. + +=item type + +Can match the C field as a substring. + +=item model + +Can match the C field as a substring. + +=item serial + +Can match the C field as a substring. + +=item class + +Will match exactly the C field. + +=item ips + +List of Device IPs containing modules. + +=back + +=cut + +sub search_by_field { + my ( $rs, $p, $attrs ) = @_; + + die "condition parameter to search_by_field must be hashref\n" + if ref {} ne ref $p + or 0 == scalar keys %$p; + + my $op = $p->{matchall} ? '-and' : '-or'; + + return $rs->search_rs( {}, $attrs )->search( + { $op => [ + ( $p->{description} + ? ( 'me.description' => + { '-ilike' => "\%$p->{description}\%" } ) + : () + ), + ( $p->{name} + ? ( 'me.name' => { '-ilike' => "\%$p->{name}\%" } ) + : () + ), + ( $p->{type} + ? ( 'me.type' => { '-ilike' => "\%$p->{type}\%" } ) + : () + ), + ( $p->{model} + ? ( 'me.model' => { '-ilike' => "\%$p->{model}\%" } ) + : () + ), + ( $p->{serial} + ? ( 'me.serial' => { '-ilike' => "\%$p->{serial}\%" } ) + : () + ), + + ( $p->{class} + ? ( 'me.class' => { '-in' => $p->{class} } ) + : () + ), + ( $p->{ips} ? ( 'me.ip' => { '-in' => $p->{ips} } ) : () ), + ], + } + ); +} + +1; diff --git a/Netdisco/lib/App/Netdisco/Web/Plugin/Report/ModuleInventory.pm b/Netdisco/lib/App/Netdisco/Web/Plugin/Report/ModuleInventory.pm new file mode 100644 index 00000000..ce730ff8 --- /dev/null +++ b/Netdisco/lib/App/Netdisco/Web/Plugin/Report/ModuleInventory.pm @@ -0,0 +1,109 @@ +package App::Netdisco::Web::Plugin::Report::ModuleInventory; + +use Dancer ':syntax'; +use Dancer::Plugin::DBIC; +use Dancer::Plugin::Auth::Extensible; + +use App::Netdisco::Web::Plugin; +use List::MoreUtils (); + +register_report( + { category => 'Device', + tag => 'moduleinventory', + label => 'Module Inventory', + provides_csv => 1, + } +); + +hook 'before' => sub { + + # view settings + var('module_options' => [ + { name => 'matchall', + label => 'Match All Options', + default => 'on' + }, + ] + ); + + return + unless ( + request->path eq uri_for('/report/moduleinventory')->path + or index( request->path, + uri_for('/ajax/content/report/moduleinventory')->path ) == 0 + ); + + params->{'limit'} ||= 1024; + + foreach my $col ( @{ var('module_options') } ) { + next unless $col->{default} eq 'on'; + params->{ $col->{name} } = 'checked'; + } + +}; + +hook 'before_template' => sub { + my $tokens = shift; + + return + unless ( + request->path eq uri_for('/report/moduleinventory')->path + or index( request->path, + uri_for('/ajax/content/report/moduleinventory')->path ) == 0 + ); + + # used in the search sidebar template to set selected items + foreach my $opt (qw/class/) { + my $p = ( + ref [] eq ref param($opt) + ? param($opt) + : ( param($opt) ? [ param($opt) ] : [] ) + ); + $tokens->{"${opt}_lkp"} = { map { $_ => 1 } @$p }; + } +}; + +get '/ajax/content/report/moduleinventory' => require_login sub { + + my $has_opt = List::MoreUtils::any { param($_) } + qw/device description name type model serial class/; + + my $rs = schema('netdisco')->resultset('DeviceModule'); + + if ($has_opt) { + + if ( param('device') ) { + my @ips = schema('netdisco')->resultset('Device') + ->search_fuzzy( param('device') )->get_column('ip')->all; + + params->{'ips'} = \@ips; + } + + $rs = $rs->search_by_field( scalar params )->prefetch('device') + ->limit( param('limit') )->hri; + + } + else { + $rs = $rs->search( + {}, + { select => [ 'class', { count => 'class' } ], + as => [qw/ class count /], + group_by => [qw/ class /] + } + )->order_by( { -desc => 'count' } )->hri; + + } + + return unless $rs->has_rows; + if ( request->is_ajax ) { + template 'ajax/report/moduleinventory.tt', { results => $rs, }, + { layout => undef }; + } + else { + header( 'Content-Type' => 'text/comma-separated-values' ); + template 'ajax/report/moduleinventory_csv.tt', { results => $rs, }, + { layout => undef }; + } +}; + +1; diff --git a/Netdisco/lib/App/Netdisco/Web/Report.pm b/Netdisco/lib/App/Netdisco/Web/Report.pm index b94ddb9c..800f9295 100644 --- a/Netdisco/lib/App/Netdisco/Web/Report.pm +++ b/Netdisco/lib/App/Netdisco/Web/Report.pm @@ -12,6 +12,8 @@ get '/report/*' => require_login sub { = [ schema('netdisco')->resultset('NodeNbt')->get_distinct_col('domain') ]; + my $class_list = [ schema('netdisco')->resultset('DeviceModule') + ->get_distinct_col('class') ]; # trick the ajax into working as if this were a tabbed page params->{tab} = $tag; @@ -21,6 +23,7 @@ get '/report/*' => require_login sub { { report => setting('_reports')->{$tag}, domain_list => $domain_list, + class_list => $class_list, }; }; diff --git a/Netdisco/share/config.yml b/Netdisco/share/config.yml index 7f3d2303..de401b7d 100644 --- a/Netdisco/share/config.yml +++ b/Netdisco/share/config.yml @@ -47,6 +47,7 @@ web_plugins: - Report::DevicePoeStatus - Report::DuplexMismatch - Report::IpInventory + - Report::ModuleInventory - Report::Netbios - Report::NodeMultiIPs - Report::PhonesDiscovered diff --git a/Netdisco/share/views/ajax/report/moduleinventory.tt b/Netdisco/share/views/ajax/report/moduleinventory.tt new file mode 100644 index 00000000..dbbda590 --- /dev/null +++ b/Netdisco/share/views/ajax/report/moduleinventory.tt @@ -0,0 +1,86 @@ +[% USE Number.Format %] +[% IF results.first.ip %] + [% row = results.reset %] + + + + + + + + + + + + + + + + + [% WHILE (row = results.next) %] + + + + + + + + + + + + + [% END %] + +
DeviceDescriptionNameClassTypeModelSerialHW VersionSW VersionFW Version
+ + [% row.device.dns || row.device.name || row.device.ip | html_entity %] + + + [% row.description | html %] + + + [% row.name | html %] + + + [% row.class.ucfirst | html %] + + + [% row.type | html %] + + + [% row.model | html %] + + + [% row.serial | html %] + [% row.hw_ver | html_entity %][% row.sw_ver | html_entity %][% row.fw_ver | html_entity %]
+[% ELSE %] + [% row = results.reset %] + + + + + + + + + [% WHILE (row = results.next) %] + + + + + [% END %] + +
ClassCount
+ + [% row.class.ucfirst | html %] + [% row.count | format_number %]
+[% END %] diff --git a/Netdisco/share/views/ajax/report/moduleinventory_csv.tt b/Netdisco/share/views/ajax/report/moduleinventory_csv.tt new file mode 100644 index 00000000..96a4d8b7 --- /dev/null +++ b/Netdisco/share/views/ajax/report/moduleinventory_csv.tt @@ -0,0 +1,27 @@ +[% USE CSV -%] +[% IF results.first.ip %] + [% row = results.reset %] + [% CSV.dump(['Device' 'Description' 'Name' 'Class' 'Type' 'Model' 'Serial' 'HW Version' 'SW Version' 'FW Version']) %] + + [% WHILE (row = results.next) %] + [% mylist = [] %] + [% device = row.device.dns || row.device.name || row.device.ip %] + [% FOREACH col IN [ device row.description row.name row.class.ucfirst row.type row.model row.serial row.hw_ver row.sw_ver row.fw_ver ] %] + [% mylist.push(col) %] + [% END %] + [% CSV.dump(mylist) %] + + [% END %] +[% ELSE %] + [% row = results.reset %] + [% CSV.dump(['Class' 'Count']) %] + + [% WHILE (row = results.next) %] + [% mylist = [] %] + [% FOREACH col IN [ row.class.ucfirst row.count ] %] + [% mylist.push(col) %] + [% END %] + [% CSV.dump(mylist) %] + + [% END %] +[% END %] \ No newline at end of file diff --git a/Netdisco/share/views/js/report.js b/Netdisco/share/views/js/report.js index fbf6898f..f4dd9200 100644 --- a/Netdisco/share/views/js/report.js +++ b/Netdisco/share/views/js/report.js @@ -2,7 +2,7 @@ // ajax content is loaded var path = 'report'; - // fields in the IP Inventory Report form + // colored input fields in the Report Options sidebar forms var form_inputs = $(".nd_colored-input"); // this is called by do_search to support local code @@ -15,7 +15,7 @@ $("[rel=popover]").popover({live: true}); } - // on load, check initial Device Search Options form state, + // on load, check initial Report Options form state, // and on each change to the form fields $(document).ready(function() { var tab = '[% report.tag %]' @@ -25,6 +25,14 @@ form_inputs.each(function() {device_form_state($(this))}); form_inputs.change(function() {device_form_state($(this))}); + // handler for bin icon in search forms + $('.nd_field-clear-icon').click(function() { + var name = $(this).data('btn-for'); + var input = $('[name=' + name + ']'); + input.val(''); + device_form_state(input); // reset input field + }); + $('#nd_ipinventory-subnet').on('input', function(event) { if ($(this).val().indexOf(':') != -1) { $('#never').attr('disabled', 'disabled'); diff --git a/Netdisco/share/views/sidebar/report/moduleinventory.tt b/Netdisco/share/views/sidebar/report/moduleinventory.tt new file mode 100644 index 00000000..5156a086 --- /dev/null +++ b/Netdisco/share/views/sidebar/report/moduleinventory.tt @@ -0,0 +1,72 @@ + +

Module Search Options

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ Limit:
+ +
+
+ + +
+