From 86d35012dbeeb50a85195302951da55d63d919dd Mon Sep 17 00:00:00 2001 From: "Eric A. Miller" Date: Tue, 5 Aug 2014 23:16:53 -0400 Subject: [PATCH] Initial version of json based device port view TODO: plugins and sidebar modifications --- .../App/Netdisco/Web/Plugin/Device/Ports.pm | 212 ++--- Netdisco/share/views/ajax/device/ports.tt | 873 ++++++++++-------- 2 files changed, 594 insertions(+), 491 deletions(-) diff --git a/Netdisco/lib/App/Netdisco/Web/Plugin/Device/Ports.pm b/Netdisco/lib/App/Netdisco/Web/Plugin/Device/Ports.pm index cd6636d4..39eaca63 100644 --- a/Netdisco/lib/App/Netdisco/Web/Plugin/Device/Ports.pm +++ b/Netdisco/lib/App/Netdisco/Web/Plugin/Device/Ports.pm @@ -4,177 +4,163 @@ 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 }); +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 $q = param('q'); my $prefer = param('prefer'); $prefer = '' - unless defined $prefer and $prefer =~ m/^(?:port|name|vlan)$/; + 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; + my $device + = schema('netdisco')->resultset('Device')->search_for_device($q) + or send_error( 'Bad device', 400 ); + my $rs = $device->ports_flattened; # refine by ports if requested my $f = param('f'); if ($f) { - if (($prefer eq 'vlan') or not $prefer and $f =~ m/^\d+$/) { - if (param('invert')) { - $set = $set->search({ - 'me.vlan' => { '!=' => $f }, - 'port_vlans.vlan' => [ - '-or' => { '!=' => $f }, { '=' => undef } - ], - }, { join => 'port_vlans' }); + if ( ( $prefer eq 'vlan' ) or not $prefer and $f =~ m/^\d+$/ ) { + if ( param('invert') ) { + $rs = $rs->search( + { 'me.vlan' => { '!=' => $f }, + '-or' => [ + -not_bool => { + 'me.vlan_membership' => + { '@>' => { -value => [$f] } } + }, + 'me.vlan_membership' => { '=' => undef }, + ] + } + ); } else { - $set = $set->search({ - -or => { - 'me.vlan' => $f, - 'port_vlans.vlan' => $f, - }, - }, { join => 'port_vlans' }); + $rs = $rs->search( + { -or => { + 'me.vlan' => $f, + 'me.vlan_membership' => + { '@>' => { -value => [$f] } }, + }, + } + ); } - return unless $set->count; + return unless $rs->count; } else { - if (param('partial')) { + if ( param('partial') ) { + # change wildcard chars to SQL $f =~ s/\*/%/g; $f =~ s/\?/_/g; + # set wilcards at param boundaries - if ($f !~ m/[%_]/) { + if ( $f !~ m/[%_]/ ) { $f =~ s/^\%*/%/; $f =~ s/\%*$/%/; } + # enable ILIKE op - $f = { (param('invert') ? '-not_ilike' : '-ilike') => $f }; + $f = { ( param('invert') ? '-not_ilike' : '-ilike' ) => $f }; } - elsif (param('invert')) { + elsif ( param('invert') ) { $f = { '!=' => $f }; } - if (($prefer eq 'port') or not $prefer and - $set->search({'me.port' => $f})->count) { + if ( ( $prefer eq 'port' ) + or not $prefer and $rs->search( { 'me.port' => $f } )->count ) + { - $set = $set->search({ - -or => [ - 'me.port' => $f, - 'me.slave_of' => $f, - ], - }); + $rs = $rs->search( + { -or => [ + 'me.port' => $f, + 'me.slave_of' => $f, + ], + } + ); } else { - $set = $set->search({'me.name' => $f}); - return unless $set->count; + $rs = $rs->search( { 'me.name' => $f } ); + return unless $rs->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') : ()); + 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') - }); + if ( exists $port_state{free} ) { + my $age_num = ( param('age_num') || 3 ); + my $age_unit = ( param('age_unit') || 'months' ); + my $interval = "$age_num $age_unit"; + if ( scalar keys %port_state == 1 ) { + $rs = $rs->search( + { 'me.up' => { '!=' => 'up' }, }, + { where => \[ + "age(now(), to_timestamp(extract(epoch from me.device_last_discover) " + . "- (me.device_uptime - me.lastchange)/100)) " + . "> ?::interval", + [ {} => $interval ] + ] + } + ); } else { - $set = $set->with_is_free({ - age_num => (param('age_num') || 3), - age_unit => (param('age_unit') || 'months') - }); + $rs = $rs->search( + {}, + { where => \[ + "age(now(), to_timestamp(extract(epoch from me.device_last_discover) " + . "- (me.device_uptime - me.lastchange)/100)) " + . "> ?::interval", + [ {} => $interval ] + ] + } + ); } delete $port_state{free}; } - if (scalar keys %port_state < 3) { + 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}; + 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}); + $rs = $rs->search( { -or => \@combi } ); } - # get aggregate master status - $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'); - - # get vlans on the port - $set = $set->search_rs({}, { prefetch => 'all_port_vlans' })->with_vlan_count - if param('c_vmember'); - - # what kind of nodes are we interested in? - my $nodes_name = (param('n_archived') ? 'nodes' : 'active_nodes'); - $nodes_name .= '_with_age' if param('c_nodes') and param('n_age'); - $set = $set->search_rs({}, { order_by => ["${nodes_name}.vlan", "${nodes_name}.mac", "ips.ip"] }) - if param('c_nodes'); - # retrieve active/all connected nodes, if asked for - $set = $set->search_rs({}, { prefetch => [{$nodes_name => 'ips'}] }) - if param('c_nodes'); + $rs + = $rs->search_rs( {}, + { prefetch => 'nodes', bind => [ $device->ip ] } ) + if param('c_nodes'); - # retrieve wireless SSIDs, if asked for - $set = $set->search_rs({}, { prefetch => [{$nodes_name => 'wireless'}] }) - if param('c_nodes') && param('n_ssid'); + my @results = $rs->hri->all; + return unless scalar @results; - # retrieve NetBIOS, if asked for - $set = $set->search_rs({}, { prefetch => [{$nodes_name => 'netbios'}] }) - if param('c_nodes') && param('n_netbios'); - - # retrieve vendor, if asked for - $set = $set->search_rs({}, { prefetch => [{$nodes_name => 'oui'}] }) - if param('c_nodes') && 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_rs({}, { prefetch => [{neighbor_alias => 'device'}] }) - if param('c_neighbors'); - - # sort ports (empty set would be a 'no records' msg) - my $results = [ sort { &App::Netdisco::Util::Web::sort_port($a->port, $b->port) } $set->all ]; - return unless scalar @$results; - - if (request->is_ajax) { - template 'ajax/device/ports.tt', { - results => $results, - nodes => $nodes_name, - device => $device, - }, { layout => undef }; + if ( request->is_ajax ) { + template 'ajax/device/ports.tt', + { results => to_json( \@results ), }, { layout => undef }; } else { header( 'Content-Type' => 'text/comma-separated-values' ); - template 'ajax/device/ports_csv.tt', { - results => $results, - nodes => $nodes_name, - device => $device, - }, { layout => undef }; + template 'ajax/device/ports_csv.tt', { results => \@results, }, + { layout => undef }; } }; -true; +1; diff --git a/Netdisco/share/views/ajax/device/ports.tt b/Netdisco/share/views/ajax/device/ports.tt index c9053b28..49bb0e72 100644 --- a/Netdisco/share/views/ajax/device/ports.tt +++ b/Netdisco/share/views/ajax/device/ports.tt @@ -6,12 +6,11 @@ [% FOREACH item IN vars.port_columns %] [% NEXT IF item.name == 'c_admin' %] [% NEXT IF item.name == 'c_nodes' AND params.c_nodes AND params.c_neighbors %] - [% NEXT UNLESS params.${item.name} %] + [% NEXT IF item.name == 'c_neighbors' AND params.c_nodes AND !params.c_neighbors %] + [% NEXT IF item.name == 'c_nodes' AND !params.c_nodes AND params.c_neighbors %] [% SET th_class = '' %] [% IF (user_can_port_control AND params.c_admin AND (item.name == 'c_port' OR item.name == 'c_name')) %] - [% th_class = ' class="nd_nudge-for-icon natural"' %] - [% ELSIF (item.name == 'c_port' OR item.name == 'c_descr' OR item.name == 'c_name') %] - [% th_class = ' class="natural"' %] + [% th_class = ' class="nd_nudge-for-icon"' %] [% END %] [% item.label | html_entity %] @@ -19,382 +18,500 @@ [% END %] - - [% FOREACH row IN results %] - - - [% IF row.up_admin != 'up' %] - - [% ELSIF row.up == 'up' AND row.stp == 'blocking' AND row.vlan_count < 2 %] - - [% ELSIF row.has_column_loaded('is_free') AND row.is_free %] - - [% ELSIF row.up_admin == 'up' AND (row.up != 'up' AND row.up != 'dormant') %] - - [% ELSE %] - - [% END %] - [% IF row.slave_of %]
- [% IF row.get_column('agg_master_up_admin') != 'up' %] - - [% ELSIF row.get_column('agg_master_up') == 'up' %] - - [% ELSE %] - - [% END %] - [% END %] - - - [% FOREACH config IN settings._extra_device_port_cols %] - [% NEXT UNLESS config.position == 'left' AND params.${config.name} %] - - [% TRY %] - [% INCLUDE "plugin/${config.name}/device_port_column.tt" %] - [% CATCH %] - - [% END %] - - [% END %] - - [% IF params.c_port %] - [% IF user_can_port_control AND params.c_admin %] - [% IF row.up_admin == 'up' %] - - - [% ELSE %] - - - [% END %] - - - - [% ELSE %] - - [% END %] - - [% IF row.is_master %] -   - [% END %] - [% row.port | html_entity %] - [% IF row.slave_of %]
- - [% row.slave_of | html_entity %] - [% END %] - - [% END %] - - [% FOREACH config IN settings._extra_device_port_cols %] - [% NEXT UNLESS config.position == 'mid' AND params.${config.name} %] - - [% TRY %] - [% INCLUDE "plugin/${config.name}/device_port_column.tt" %] - [% CATCH %] - - [% END %] - - [% END %] - - [% IF params.c_descr %] - [% row.descr | html_entity %] - [% END %] - - [% IF params.c_type %] - [% row.type | html_entity %] - [% END %] - - [% IF params.c_duplex %] - - [% IF row.up == 'up' AND row.duplex %] - [% (row.duplex_admin.ucfirst || 'Auto') | html_entity %] / [% row.duplex.ucfirst | html_entity %] - [% END %] - - [% END %] - - [% IF params.c_lastchange %] - [% row.lastchange_stamp | html_entity %] - [% END %] - - [% IF params.c_name %] - [% IF user_can_port_control AND params.c_admin %] - - - [% ELSE %] - - [% END %] -
- [% row.name | html_entity %] -
- - [% END %] - - [% IF params.c_speed %] - [% row.speed | html_entity %] - [% END %] - - [% IF params.c_mac %] - [% row.mac | html_entity %] - [% END %] - - [% IF params.c_mtu %] - [% row.mtu | html_entity %] - [% END %] - - [% IF params.c_pvid %] - [% IF user_can_port_control AND params.c_admin %] - - -
- [% IF row.vlan %][% row.vlan | html_entity %][% END %] -
- - [% ELSE %] - - - [% row.vlan | html_entity %] - - [% END %] - [% END %] - - [% IF params.c_vmember %] - - [% IF row.vlan_count %] - [% SET output = '' %] - [% SET vlanlist = [] %] - [% FOREACH vlan IN row.all_port_vlans %][% vlanlist.push(vlan.get_column('vlan')) %][% END %] - [% FOREACH vlan IN vlanlist.nsort %] - [% SET output = output _ - '' _ vlan _ '' %] - [% SET output = output _ ', ' IF NOT loop.last %] - [% END %] - [% IF row.vlan_count > 10 %] [%# TODO make this a settable variable %] - [% SET output = '
(' _ row.vlan_count - _ ')
-
Show VLANs
-
' _ output %] - [% SET output = output _ '
' %] - [% END %] - [% output %] - [% END %] - - [% END %] - - [% IF params.c_ssid %] - [% row.ssid.ssid | html_entity %] - [% END %] - - [% IF params.c_power %] - [% IF row.power %] - [% IF row.power.admin == 'true' %] - [% IF user_can_port_control AND params.c_admin %] - - - - [% ELSE %] - - - [% END %] - - [% IF row.power.power > 0 %] - [% row.power.power | html_entity %] mW - [% ELSE %] - ([% row.power.status | html_entity %]) - [% END %] - - [% ELSE %] - [% IF user_can_port_control AND params.c_admin %] - - - - [% ELSE %] - - - [% END %] - [% END %] - - [% ELSE %] - - [% END %] - [% END %] - - [% IF params.c_nodes OR params.c_neighbors %] - - [% IF params.c_neighbors AND (row.remote_ip OR row.is_uplink) %] - [% IF row.neighbor %] - - [% IF row.remote_type AND row.remote_type.match('(?i)ip.phone') %] -   - [% ELSIF row.remote_type AND row.remote_type.match('(cisco\s+AIR-[L|C]?AP|-K9W8-|^AP:\s)') %] -   - [% END %] - - [% row.neighbor.dns.remove(settings.domain_suffix) || row.neighbor.ip | html_entity %] - [% IF row.remote_port %] - - - - [% row.remote_port | html_entity %] - [% END %] -
- [% IF params.neigh_id and (row.remote_id or row.remote_type) %] - ([% 'id: '_ row.remote_id IF row.remote_id %] - [% ' type: '_ row.remote_type IF row.remote_type %])
- [% END %] - [% ELSIF row.remote_ip %] -   - [% IF row.remote_type AND row.remote_type.match('(?i)ip.phone') %] -   - [% ELSIF row.remote_type AND row.remote_type.match('(cisco\s+AIR-[L|C]?AP|-K9W8-|^AP:\s)') %] -   - [% END %] - - [% row.remote_ip | html_entity %] - [% ' - ' IF row.remote_port %][% row.remote_port | html_entity %]
- [% IF params.neigh_id and (row.remote_id or row.remote_type) %] - ([% 'id: '_ row.remote_id IF row.remote_id %] - [% ' type: '_ row.remote_type IF row.remote_type %])
- [% END %] - [% ELSE %] -   (possible uplink) - [% END %] - [% END %] - [% IF params.c_nodes %] - [% FOREACH node IN row.$nodes %] - [% '
' IF (row.remote_ip OR row.is_uplink) OR NOT loop.first %] - [% '  ' IF NOT node.active %] - [% IF row.remote_type AND row.remote_type.match('(?i)ip.phone') %] -   - [% ELSIF node.wireless.defined - OR (row.remote_type AND row.remote_type.match('(cisco\s+AIR-[L|c]?AP|-K9W8-|^AP:\s)')) %] -   - [% END %] - - [% node.net_mac.$mac_format_call | html_entity %] - [% IF (node.vlan > 0) && (node.vlan != row.vlan) %] - (on vlan [% node.vlan | html_entity %]) - [% END %] - [% IF params.n_ssid AND node.wireless.defined %] - (SSID: - [% FOREACH wlan IN node.wireless %] - [% wlan.ssid | html_entity %] - [% END %] - ) - [% END %] - [% IF params.n_vendor AND node.oui.defined %] - (Vendor: - [% FOREACH oui IN node.oui %] - [% oui.abbrev | html_entity %] - [% END %] - ) - [% END %] - [% ' (' _ node.time_last_age _ ')' IF params.n_age %] - [% IF params.n_ip %] - [% FOREACH ip IN node.ips %] -
  [% '  ' IF NOT ip.active %] - [% SET dns = ip.dns %] - [% IF dns %] - [% dns %] ([% ip.ip | html_entity %]) - [% ELSE %] - [% ip.ip | html_entity %] - [% END %] - [% END %] - [% END %] - [% IF params.n_netbios %] - [% FOREACH nbt IN node.netbios %] -
     \\[% nbt.domain | html_entity %]\[% nbt.nbname | html_entity %] -
     [% nbt.nbuser || '[No User]' | html_entity %]@[% nbt.ip | html_entity %] - [% END %] - [% END %] - [% END %] - [% END %] - - [% END %] - - [% IF params.c_stp %] - [% row.stp | html_entity %] - [% END %] - - [% IF params.c_up %] - - [% row.up_admin.ucfirst | html_entity %] / [% row.up.ucfirst | html_entity %] - - [% END %] - - [% FOREACH config IN settings._extra_device_port_cols %] - [% NEXT UNLESS config.position == 'right' AND params.${config.name} %] - - [% TRY %] - [% INCLUDE "plugin/${config.name}/device_port_column.tt" %] - [% CATCH %] - - [% END %] - - [% END %] - - [% END %] - -[% IF user_can_port_control %] - -[% END %] - -