Files
netdisco/lib/App/Netdisco/Web/Plugin/Device/Ports.pm

228 lines
7.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::Ports;
use Dancer ':syntax';
use Dancer::Plugin::DBIC;
use Dancer::Plugin::Auth::Extensible;
use App::Netdisco::Util::Web (); # for sort_port
use App::Netdisco::Web::Plugin;
register_device_tab({ tag => 'ports', label => 'Ports', provides_csv => 1 });
# device ports with a description (er, name) matching
get '/ajax/content/device/ports' => require_login sub {
my $q = param('q');
my $prefer = param('prefer');
$prefer = ''
unless defined $prefer and $prefer =~ m/^(?:port|name|vlan)$/;
my $device = schema('netdisco')->resultset('Device')
->search_for_device($q) or send_error('Bad device', 400);
my $set = $device->ports->with_properties;
# refine by ports if requested
my $f = param('f');
if ($f) {
if (($prefer eq 'vlan') or (not $prefer and $f =~ m/^\d+$/)) {
return unless $f =~ m/^\d+$/;
}
else {
if (param('partial')) {
# change wildcard chars to SQL
$f =~ s/\*/%/g;
$f =~ s/\?/_/g;
# set wilcards at param boundaries
if ($f !~ m/[%_]/) {
$f =~ s/^\%*/%/;
$f =~ s/\%*$/%/;
}
# enable ILIKE op
$f = { (param('invert') ? '-not_ilike' : '-ilike') => $f };
}
elsif (param('invert')) {
$f = { '!=' => $f };
}
if (($prefer eq 'port') or not $prefer and
$set->search({-or => ['me.port' => $f, 'me.descr' => $f]})->count) {
$set = $set->search({
-or => [
'me.port' => $f,
'me.descr' => $f,
'me.slave_of' => $f,
],
});
}
else {
$set = $set->search({'me.name' => $f});
return unless $set->count;
}
}
}
# filter for port status if asked
my %port_state = map {$_ => 1}
(ref [] eq ref param('port_state') ? @{param('port_state')}
: param('port_state') ? param('port_state') : ());
return unless scalar keys %port_state;
if (exists $port_state{free}) {
if (scalar keys %port_state == 1) {
$set = $set->only_free_ports({
age_num => (param('age_num') || 3),
age_unit => (param('age_unit') || 'months')
});
}
else {
$set = $set->with_is_free({
age_num => (param('age_num') || 3),
age_unit => (param('age_unit') || 'months')
});
}
delete $port_state{free};
}
if (scalar keys %port_state < 3) {
my @combi = ();
push @combi, {'me.up' => 'up'}
if exists $port_state{up};
push @combi, {'me.up_admin' => 'up', 'me.up' => { '!=' => 'up'}}
if exists $port_state{down};
push @combi, {'me.up_admin' => { '!=' => 'up'}}
if exists $port_state{shut};
$set = $set->search({-or => \@combi});
}
# so far only the basic device_port data
# now begin to join tables depending on the selected columns/options
# get vlans on the port
# leave this query dormant (lazy) unless c_vmember is set or vlan filtering
my $vlans = $set->search({}, {
select => [
'port',
{ array_agg => 'port_vlans.vlan', -as => 'vlan_set' },
{ count => 'port_vlans.vlan', -as => 'vlan_count' },
],
join => 'port_vlans',
group_by => 'me.port',
});
if (param('c_vmember') or ($prefer eq 'vlan') or (not $prefer and $f =~ m/^\d+$/)) {
$vlans = { map {(
$_->port => {
# DBIC smart enough to work out this should be an arrayref :)
vlan_set => $_->get_column('vlan_set'),
vlan_count => $_->get_column('vlan_count'),
},
)} $vlans->all };
}
# get aggregate master status (self join)
$set = $set->search({}, {
'join' => 'agg_master',
'+select' => [qw/agg_master.up_admin agg_master.up/],
'+as' => [qw/agg_master_up_admin agg_master_up/],
});
# make sure query asks for formatted timestamps when needed
$set = $set->with_times if param('c_lastchange');
# what kind of nodes are we interested in?
my $nodes_name = (param('n_archived') ? 'nodes' : 'active_nodes');
$nodes_name .= '_with_age' if param('n_age');
my $ips_name = ((param('n_ip4') and param('n_ip6')) ? 'ips'
: param('n_ip4') ? 'ip4s'
: 'ip6s');
if (param('c_nodes')) {
# retrieve active/all connected nodes, if asked for
$set = $set->search({}, { prefetch => [{$nodes_name => $ips_name}] });
$set = $set->search({}, { order_by => ["${nodes_name}.vlan", "${nodes_name}.mac", "${ips_name}.ip"] });
# retrieve wireless SSIDs, if asked for
$set = $set->search({}, { prefetch => [{$nodes_name => 'wireless'}] })
if param('n_ssid');
# retrieve NetBIOS, if asked for
$set = $set->search({}, { prefetch => [{$nodes_name => 'netbios'}] })
if param('n_netbios');
# retrieve vendor, if asked for
$set = $set->search({}, { prefetch => [{$nodes_name => 'oui'}] })
if param('n_vendor');
}
# retrieve SSID, if asked for
$set = $set->search({}, { prefetch => 'ssid' })
if param('c_ssid');
# retrieve neighbor devices, if asked for
#$set = $set->search({}, { prefetch => [{neighbor_alias => 'device'}] })
# if param('c_neighbors');
# retrieve neighbor devices, if asked for
$set = $set->search({}, {
join => 'neighbor_alias',
'+select' => ['neighbor_alias.ip', 'neighbor_alias.dns'],
'+as' => ['neighbor_ip', 'neighbor_dns'],
}) if param('c_neighbors');
# also get remote LLDP inventory if asked for
$set = $set->with_remote_inventory if param('n_inventory');
# run query
my @results = $set->all;
# filter for tagged vlan using existing agg query,
# which is better than join inflation
if (($prefer eq 'vlan') or (not $prefer and $f =~ m/^\d+$/)) {
if (param('invert')) {
@results = grep {
(!defined $_->vlan or $_->vlan ne $f)
and
(0 == scalar grep {defined and $_ ne $f} @{ $vlans->{$_->port}->{vlan_set} })
} @results;
}
else {
@results = grep {
($_->vlan eq $f)
or
(scalar grep {defined and $_ eq $f} @{ $vlans->{$_->port}->{vlan_set} })
} @results;
}
}
# sort ports
@results = sort { &App::Netdisco::Util::Web::sort_port($a->port, $b->port) } @results;
# empty set would be a 'no records' msg
return unless scalar @results;
if (request->is_ajax) {
template 'ajax/device/ports.tt', {
results => \@results,
nodes => $nodes_name,
ips => $ips_name,
device => $device,
vlans => $vlans,
}, { layout => undef };
}
else {
header( 'Content-Type' => 'text/comma-separated-values' );
template 'ajax/device/ports_csv.tt', {
results => \@results,
nodes => $nodes_name,
ips => $ips_name,
device => $device,
vlans => $vlans,
}, { layout => undef };
}
};
true;