From bc47b54f67e744eaced0d4683285e8571b7166f3 Mon Sep 17 00:00:00 2001 From: Oliver Gorwits Date: Thu, 7 Dec 2023 07:49:18 +0000 Subject: [PATCH] improve searchable generic report fields (#1133) * implement links on all v4/v6/mac found in _searchable fields * word boundaries on matching --- Build.PL | 1 + lib/App/Netdisco/Web/GenericReport.pm | 46 +++++++++++++++++++++++ share/config.yml | 6 +-- share/views/ajax/report/generic_report.tt | 2 +- 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/Build.PL b/Build.PL index 72c88c5a..43ed0ddf 100644 --- a/Build.PL +++ b/Build.PL @@ -79,6 +79,7 @@ Module::Build->new( 'Plack::Middleware::ReverseProxy' => '0.15', 'Pod::Usage' => 0, 'Regexp::Common' => 2017060201, + 'Regexp::Common::net::CIDR' => 0, 'Role::Tiny' => '1.002005', 'Scope::Guard' => 0, 'Sereal' => '0', diff --git a/lib/App/Netdisco/Web/GenericReport.pm b/lib/App/Netdisco/Web/GenericReport.pm index 08cc2abb..86eee9f2 100644 --- a/lib/App/Netdisco/Web/GenericReport.pm +++ b/lib/App/Netdisco/Web/GenericReport.pm @@ -9,6 +9,10 @@ use Path::Class 'file'; use Storable 'dclone'; use Safe; +use HTML::Entities 'encode_entities'; +use Regexp::Common qw( RE_net_IPv4 RE_net_IPv6 RE_net_MAC RE_net_domain ); +use Regexp::Common::net::CIDR (); + our ($config, @data); foreach my $report (@{setting('reports')}) { @@ -72,6 +76,48 @@ foreach my $report (@{setting('reports')}) { my @results = ((-f $munger) ? $compartment->rdo( $munger ) : @data); return if $@ or (0 == scalar @results); + # searchable field support.. + + my $recidr4 = $RE{net}{CIDR}{IPv4}{-keep}; #RE_net_CIDR_IPv4(-keep); + my $rev4 = RE_net_IPv4(-keep); + my $rev6 = RE_net_IPv6(-keep); + my $remac = RE_net_MAC(-keep); + #my $redom = RE_net_domain(-keep, -nospace, -rfc1101); + + foreach my $row (@results) { + foreach my $col (@column_order) { + next unless $column_config{$col}->{_searchable}; + my $fields = (ref $row->{$col} ? $row->{$col} : [$row->{$col}]); + + foreach my $f (@$fields) { + # seems too sensitive match to be useful :-( + #$f =~ s!\b${redom}\b!'path_query + # .'">'. encode_entities($1 .($2 ? "/$2" : '')) .''!gex; + + $f =~ s!\b${recidr4}\b!'path_query + .'">'. encode_entities("$1/$2") .''!gex; + + if (not $1 and not $2) { + $f =~ s!\b${rev4}\b!''. encode_entities($1) .''!gex; + } + + $f =~ s!\b${rev6}\b!''. encode_entities($1) .''!gex; + + $f =~ s!\b${remac}\b!''. encode_entities($1) .''!gex; + + $row->{$col} = $f if not ref $row->{$col}; + } + } + } + if (request->is_ajax) { template 'ajax/report/generic_report.tt', { results => \@results, diff --git a/share/config.yml b/share/config.yml index 3e82af9d..c5eb2f0f 100644 --- a/share/config.yml +++ b/share/config.yml @@ -183,7 +183,7 @@ system_reports: label: 'Blocked - Error-Disabled' category: Port columns: - - { ip: Device } + - { ip: Device, _searchable: true } - { dns: DNS } - { port: Port } - { name: Description } @@ -259,12 +259,12 @@ system_reports: HAVING count(vlan) > COALESCE(NULLIF(?,''), '1') ::integer ORDER BY vlans DESC, ip ASC, port ASC - tag: duplicateprivatenetworks - category: Port + category: IP label: 'Duplicate Private Networks' columns: - { subnet: 'Subnet', _searchable: true } - { count: 'Instances' } - - { seen: 'Where Seen' } + - { seen: 'Where Seen', _searchable: true } query: | SELECT subnet, count(subnet), array_agg(host(alias)::text || ' on ' || host(ip)::text) AS seen FROM device_ip diff --git a/share/views/ajax/report/generic_report.tt b/share/views/ajax/report/generic_report.tt index 49d2c686..b08faebe 100644 --- a/share/views/ajax/report/generic_report.tt +++ b/share/views/ajax/report/generic_report.tt @@ -13,7 +13,7 @@ [% FOREACH record IN row.item(col) %] [% IF column_options.$col._searchable %] - [% record | html_entity %] + [% record | none %] [% ELSE %] [% record | html_entity %] [% END %]