[#75] Device module inventory report / search
This commit is contained in:
		
							
								
								
									
										107
									
								
								Netdisco/lib/App/Netdisco/DB/ResultSet/DeviceModule.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								Netdisco/lib/App/Netdisco/DB/ResultSet/DeviceModule.pm
									
									
									
									
									
										Normal file
									
								
							| @@ -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<search()> 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<matchall> 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<description> field as a substring. | ||||
|  | ||||
| =item name | ||||
|  | ||||
| Can match the C<name> field as a substring. | ||||
|  | ||||
| =item type | ||||
|  | ||||
| Can match the C<type> field as a substring. | ||||
|  | ||||
| =item model | ||||
|  | ||||
| Can match the C<model> field as a substring. | ||||
|  | ||||
| =item serial | ||||
|  | ||||
| Can match the C<serial> field as a substring. | ||||
|  | ||||
| =item class | ||||
|  | ||||
| Will match exactly the C<class> 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; | ||||
							
								
								
									
										109
									
								
								Netdisco/lib/App/Netdisco/Web/Plugin/Report/ModuleInventory.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								Netdisco/lib/App/Netdisco/Web/Plugin/Report/ModuleInventory.pm
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
| @@ -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, | ||||
|         }; | ||||
| }; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user