Files
netdisco/lib/App/Netdisco/Web/Plugin/Device/SNMP.pm
2022-08-03 17:57:54 +01:00

187 lines
6.3 KiB
Perl
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package App::Netdisco::Web::Plugin::Device::SNMP;
use strict;
use warnings;
use Dancer qw(:syntax);
use Dancer::Plugin::Ajax;
use Dancer::Plugin::DBIC;
use Dancer::Plugin::Swagger;
use Dancer::Plugin::Auth::Extensible;
use App::Netdisco::Web::Plugin;
use App::Netdisco::Util::SNMP qw(%ALL_MUNGERS decode_and_munge);
use Module::Load ();
use Try::Tiny;
register_device_tab({ tag => 'snmp', label => 'SNMP',
render_if => sub { schema('netdisco')->resultset('DeviceBrowser')->count() } });
get '/ajax/content/device/snmp' => require_login sub {
my $device = try { schema('netdisco')->resultset('Device')
->search_for_device( param('q') ) }
or send_error('Bad Device', 404);
template 'ajax/device/snmp.tt', { device => $device->ip },
{ layout => 'noop' };
};
ajax '/ajax/data/device/:ip/snmptree/:base' => require_login sub {
my $device = try { schema('netdisco')->resultset('Device')
->find( param('ip') ) }
or send_error('Bad Device', 404);
my $base = param('base');
$base =~ m/^\.1(\.\d+)*$/ or send_error('Bad OID Base', 404);
my $items = _get_snmp_data($device->ip, $base);
content_type 'application/json';
to_json $items;
};
ajax '/ajax/data/device/:ip/typeahead' => require_login sub {
my $device = try { schema('netdisco')->resultset('Device')
->find( param('ip') ) }
or send_error('Bad Device', 404);
my $term = param('term') or return to_json [];
$term = '%'. $term .'%';
my @found = schema('netdisco')->resultset('DeviceBrowser')
->search({ leaf => { -ilike => $term }, ip => $device->ip },
{ rows => 25, columns => 'leaf' })
->get_column('leaf')->all;
return to_json [] unless scalar @found;
content_type 'application/json';
to_json [ sort @found ];
};
ajax '/ajax/data/device/:ip/snmpnodesearch' => require_login sub {
my $device = try { schema('netdisco')->resultset('Device')
->find( param('ip') ) }
or send_error('Bad Device', 404);
my $to_match = param('str');
my $partial = param('partial');
my $excludeself = param('excludeself');
return to_json [] unless $to_match or length($to_match);
$to_match = $to_match . '%' if $partial;
my $found = undef;
my $op = ($partial ? '-ilike' : '=');
$found = schema('netdisco')->resultset('DeviceBrowser')
->search({ -or => [ oid => { $op => $to_match }, leaf => { $op => $to_match } ], ip => $device->ip },
{ rows => 1, order_by => 'oid_parts' })->first;
return to_json [] unless $found;
$found = $found->oid;
$found =~ s/^\.1\.?//;
my @results = ('.1');
foreach my $part (split m/\./, $found) {
my $last = $results[-1];
push @results, "${last}.${part}";
}
content_type 'application/json';
to_json \@results;
};
ajax '/ajax/content/device/:ip/snmpnode/:oid' => require_login sub {
my $device = try { schema('netdisco')->resultset('Device')
->find( param('ip') ) }
or send_error('Bad Device', 404);
my $oid = param('oid');
$oid =~ m/^\.1(\.\d+)*$/ or send_error('Bad OID', 404);
my $object = schema('netdisco')->resultset('DeviceBrowser')
->with_snmp_object($device->ip)->find({ 'snmp_object.oid' => $oid })
or send_error('Bad OID', 404);
my $munge = (param('munge') and exists $ALL_MUNGERS{param('munge')})
? param('munge') : $object->munge;
my %data = (
$object->get_columns,
snmp_object => { $object->snmp_object->get_columns },
value => decode_and_munge( $munge, $object->value ),
);
template 'ajax/device/snmpnode.tt',
{ node => \%data, munge => $munge, mungers => [sort keys %ALL_MUNGERS] },
{ layout => 'noop' };
};
sub _get_snmp_data {
my ($ip, $base, $recurse) = @_;
my @parts = grep {length} split m/\./, $base;
# psql cannot cope with bind params and group by array element
# so we build a static query instead.
my $next_part = (scalar @parts + 1);
my $child_part = (scalar @parts + 2);
my $query = <<QUERY;
SELECT db.oid_parts[$next_part] AS part,
count(distinct(db.oid_parts[$child_part])) as children
FROM device_browser db
WHERE db.ip = ?
AND db.oid LIKE ? || '.%'
GROUP BY db.oid_parts[$next_part]
QUERY
my $rs = schema('netdisco')->resultset('Virtual::GenericReport')->result_source;
$rs->view_definition($query);
$rs->remove_columns($rs->columns);
$rs->add_columns(qw/part children/);
my %kids = map { ($base .'.'. $_->{part}) => $_ }
schema('netdisco')->resultset('Virtual::GenericReport')
->search(undef, {
result_class => 'DBIx::Class::ResultClass::HashRefInflator',
bind => [$ip, $base],
})->hri->all;
return [{
text => 'No SNMP data for this device.',
children => \0,
state => { disabled => \1 },
icon => 'icon-search',
}] unless scalar keys %kids;
my %meta = map { ('.'. join '.', @{$_->{oid_parts}}) => $_ }
schema('netdisco')->resultset('Virtual::FilteredSNMPObject')
->search({}, { bind => [
$base,
(scalar @parts + 1),
[[ map {$_->{part}} values %kids ]],
(scalar @parts + 1),
] })->hri->all;
my @items = map {{
id => $_,
text => ($meta{$_}->{leaf} .' ('. $kids{$_}->{part} .')'),
# for nodes with only one child, recurse to prefetch...
children => (($kids{$_}->{children} == 1)
? _get_snmp_data($ip, ("${base}.". $kids{$_}->{part}), 1)
: ($kids{$_}->{children} ? \1 : \0)),
# and set the display to open to show the single child
state => { opened => ( ($recurse or $kids{$_}->{children} == 1)
? \1
: \0 ) },
($kids{$_}->{children} ? () : (icon => 'icon-leaf')),
(scalar @{$meta{$_}->{index}} ? (icon => 'icon-th') : ()),
}} sort {$kids{$a}->{part} <=> $kids{$b}->{part}} keys %kids;
return \@items;
}
true;