<FORM ACTION="<%$r->uri%>" METHOD="get">
<& SELF:matches &>
<h1 class="pagehead">Device Search</h1>

<TABLE CLASS="devs-search">
<TR>
    <TD COLSPAN=4><h2 class="subheader">Search All</h2></TD>
</TR>
<TR>
    <TD colspan=4>
        <INPUT TYPE="text" size=30 maxlength=255 NAME="text" VALUE="<%$text%>">
        <INPUT TYPE="submit" CLASS="navbutton" VALUE="Search All">
        <span class="small">(non-exact)</span>
    </TD>
</TR> 
<TR>
    <TD COLSPAN=4><h2 class="subheader">Advanced Search</h2></TD>
</TR>
<TR>
    <TH>IP:</TH>
    <TD><INPUT TYPE="text" size=30 maxlength=255 NAME="ip" VALUE="<%$ip%>"></TD>
    <TH>Location:</TH>
    <TD><INPUT TYPE="text" size=30 maxlength=255 NAME="loc" VALUE="<%$loc%>"></TD>
</TR>
<TR>
    <TH>DNS:</TH>
    <TD><INPUT TYPE="text" size=30 maxlength=255 NAME="dns" VALUE="<%$dns%>"> </TD>
    <TH>Description:</TH>
    <TD><INPUT TYPE="text" size=30 maxlength=255 NAME="desc" VALUE="<%$desc%>"> </TD>
</TR>
<TR>
    <TH>Vendor:</TH>
    <TD><SELECT MULTIPLE NAME="vendors" size=<%scalar(keys %$vendors) > $winsize ? $winsize : scalar(keys %$vendors)%>>
%       foreach my $vendor (sort keys %$vendors){
            <OPTION VALUE="<%$vendor%>" <% grep(/^$vendor$/,@vendors) ? 'SELECTED' : ''%>><%$vendor%>
%       } 
        </SELECT></TD>
    <TH>Model:</TH>
    <TD><SELECT MULTIPLE NAME="models" SIZE=5>
%       foreach my $model (sort {lc($a) cmp lc($b)} keys %$models){
            <OPTION VALUE="<%$model%>" <% grep(/^$model$/,@models) ? 'SELECTED': ''%>><%$model%>
%       } 
        </SELECT></TD>
</TR>
<TR>
    <TH>Layer:</TH>
    <TD><SPAN CLASS="small">
%   foreach (1..7){ my $num=$_;
        <INPUT NAME="layers" TYPE="checkbox" VALUE="<%$num%>" <% grep(/^$num$/,@layers) ? 'CHECKED' : ''%>><%$num%>
%   }
    </SPAN></TD>
    <TH>OS:</TH>
    <TD><SELECT MULTIPLE NAME="os" SIZE=<%scalar(keys %$oses) > $winsize ? $winsize : scalar(keys %$oses)%>>
% foreach my $this_os (sort keys %$oses) {
            <OPTION VALUE="<%$this_os%>" <% grep(/^$this_os$/,@os) ? 'SELECTED' : ''%>><%$this_os%>
% }
        </SELECT>
        <SELECT MULTIPLE NAME="os_ver" SIZE=<%scalar(keys %$os_vers) > $winsize ? $winsize : scalar(keys %$os_vers)%>>
% foreach my $this_os_ver (sort keys %$os_vers) {
            <OPTION VALUE="<%$this_os_ver%>" <% grep(/^\Q$this_os_ver\E$/,@os_ver) ? 'SELECTED' : ''%>><%$this_os_ver%>
% }
        </SELECT>
    </TD>
</TR>
<TR>
    <TH>sysName:</TH>
    <TD><INPUT TYPE="text" size=30 maxlength=255 NAME="name" VALUE="<%$name%>"></TD>
    <TH>Exact Match:</TH>
    <TD><INPUT TYPE="radio" NAME="exact" VALUE="1"<% $exact ? 'CHECKED' : ''%>>On
        <INPUT TYPE="radio" NAME="exact" VALUE="0"<% !$exact ? 'CHECKED' : ''%>>Off
    </TD>
</TR>
<TR>
    <TH>VLAN:</TH>
    <TD><INPUT TYPE="text" size=3 maxlength=5 NAME="vlan" VALUE="<%$vlan%>"></TD>
    <TH>Match Scope:</TH>
    <TD><INPUT TYPE="radio" NAME="boolean" VALUE="or"<% $boolean eq 'or' ? 'CHECKED' : ''%>>Match Any
        <INPUT TYPE="radio" NAME="boolean" VALUE="and"<% $boolean eq 'and' ? 'CHECKED' : ''%>>Match All
        <BR>
        <span class="small"><B>*</B> and <B>?</B> are wildcards.  Non-exact assumes you want *<i>search</i>*</span>
    </TD>
</TR>
<TR>
    <TD>&nbsp;</TD>
    <TD>
        <INPUT TYPE="submit" CLASS="navbutton" VALUE="Search">
        <A class="navbutton" href="<%$r->uri%>">Reset</A>
    </TD>
    <TD>&nbsp;</TD>
    <TD>&nbsp;</TD>
</TR>
</TABLE>
</FORM>
<h2 class="subheader">Hints</h2>
<UL class="square">
    <LI>Matches on <span class="u">DNS</span>,<span class="u">IP</span>, or All may be matching addresses <i>inside</i> those devices.
        Check inside the device for the matching entries.
    <LI>If you want to find blank entries, search on "is null" or conversely "is not null"
    <LI>You can select multiple for Model,Vendor,Layer, and OS by holding down CTRL as you click.
</UL>
<h1 class="pagehead"><A NAME="reports">Device Reports</A></h1>
<FORM ACTION="<%$r->uri%>" METHOD="get">
<DL>
    <DT>
    <DD><INPUT TYPE="submit" CLASS="navbutton" name="specific" VALUE="Broken Topology Links">
    <DD><INPUT TYPE="submit" CLASS="navbutton" name="specific" VALUE="IP Phones discovered through SNMP">
    <DD><INPUT TYPE="submit" CLASS="navbutton" name="specific" VALUE="Devices without DNS entries">
    <DD><INPUT TYPE="submit" CLASS="navbutton" name="specific" VALUE="Device Aliases (IPs) without DNS entries">
    <DD><INPUT TYPE="submit" CLASS="navbutton" name="specific" VALUE="Devices orphaned by missing topology info">
    <DD><INPUT TYPE="submit" CLASS="navbutton" name="specific" VALUE="Device Ports with multiple nodes attached.">
    <DD><INPUT TYPE="submit" CLASS="navbutton" name="specific" VALUE="Device Ports that are blocking.">
    <DD><INPUT TYPE="submit" CLASS="navbutton" name="specific" VALUE="Ports Administratively disabled">
</DL>
</FORM>
<FORM ACTION="<%$r->uri%>" METHOD="get">
<DL>
    <DT>
    <DD><INPUT TYPE="submit" CLASS="navbutton" name="specific" VALUE="SSID Search">
        <B>SSID:</B><INPUT TYPE="text" size=30 maxlength=255 NAME="ssid" VALUE="<%$ssid%>">
        <INPUT TYPE="radio" NAME="ssidbcast" value="1" <% defined $ssidbcast && $ssidbcast  ? ' CHECKED' : ''%>>Broadcast
        <INPUT TYPE="radio" NAME="ssidbcast" value="0" <% defined $ssidbcast && !$ssidbcast ? ' CHECKED' : ''%>>Stealth
        <INPUT TYPE="radio" NAME="ssidbcast" value="" <% !defined $ssidbcast ? ' CHECKED' : ''%>>Either
</DL>
</FORM>

<!-- took <%$time2 - $time1%> seconds to load -->
<%args>
@models   => ()
@layers   => ()
@vendors  => ()
@os       => ()
@os_ver   => ()
$ip       => ''
$text     => ''
$dns      => ''
$name     => ''
$desc     => ''
$boolean  => 'and'
$exact    => 0
$loc      => ''
$specific => undef
$vlan     => undef
$ssid     => undef
$ssidbcast => undef
</%args>
<%shared>
my $matches = undef;
my $where = {};
my $awhere = {};
my $arg_exact;
my $winsize = 5;    # max size for select boxes.
my $title = 'Search Results';     # Search Result Title
</%shared>
<%init>
my $time1= time;

my $models  = sql_column('device',['distinct(model)', 'true']); 
my $vendors = sql_column('device',['distinct(vendor)','true']); 
my $oses    = sql_column('device',['distinct(os)',    'true']);
my $os_vers = sql_column('device',['distinct(os_ver)','true']);

$arg_exact = $exact;

# Hack for VLANs - make it a specific search
if (length $vlan and !defined $specific) {
    $specific = 'vlan_dev';
}

#   Clear non-specific terms
if (defined $specific and length($specific)){
    @models = @layers = @vendors = undef;
    $text = $name = $desc = $loc = undef;
}

if ($specific =~ /alias/i){
    $title = 'Device Aliases (IPs) without DNS Entries';
    # Show these cols
    $where->{location}++;
    $where->{alias}++;
    $where->{name}++;
    $where->{contact}++;
    $matches = sql_rows('device d left join device_ip i on d.ip = i.ip',
                        ['d.ip','d.dns','d.location','i.alias','d.name','d.contact'],
                        {'i.dns' => 'is null','i.alias' => 'is not null'});

} elsif ($specific =~ /DNS entries/){
    $title = 'Devices without DNS entries';
    $where->{vendor}++;
    $where->{model}++;
    $where->{location}++;
    $where->{name}++;
    $where->{contact}++;
    $matches = sql_rows('device',
                        ['ip','vendor','model','location','name','contact'],
                        {'dns' => 'is null'});

} elsif ($specific =~ /orphan/i){
    $title = 'Devices Orphaned by missing topology info';
    # Show these cols
    $where->{vendor}++;
    $where->{model}++;
    $where->{location}++;
    $where->{contact}++;

    my $G = make_graph();
    my @S = $G->connected_components;

    # Find the strongly connected component that is biggest
    my $biggest = 0;
    for (my $sub = 0; $sub < @S; $sub++){
        $biggest = scalar @{$S[$sub]} > scalar @{$S[$biggest]} ?
                          $sub : $biggest;
    }

    my @orphans;
    for (my $j; $j < @S; $j++){
        next if $j == $biggest;
        push (@orphans,@{$S[$j]}); 
    }

    # Add in devices with no topology info at all.
    # make_graph() ignores these but this search shouldn't.
    my $qlist = [\\'not in (select distinct ip from device_port where remote_port is not null)'];
    # Also add in orphans if there were any.
    if (@orphans) {
    $qlist->[1] = \@orphans;
    }
    $matches = sql_rows('device',['ip','dns','vendor','model','location','contact'],
            {'ip'=>$qlist},1);

} elsif ($specific =~ /multiple/i){
    $title = 'Device ports with multiple nodes attached';
    $where->{port}++;
    $where->{count}++;

    $matches = sql_rows('device_port d, node n, device i',
                        ['d.ip','i.dns','d.port','count(distinct(n.mac))'],
                        {'i.ip' => \'d.ip', 'd.ip' => \'n.switch', 'd.port' => \'n.port',
                         'd.remote_ip' => 'is null', 'n.active' => 1}, undef,
                        "group by d.ip,d.port,i.dns having count(distinct(n.mac)) > 1");

} elsif ($specific =~ /disabled/i){
    $title = 'Ports administratively disabled';
    $where->{port}++;
    $where->{up_admin}++;

    $matches = sql_rows('device_port p, device d',
                        ['d.ip','d.dns','p.port','p.up_admin'],
                        {'d.ip' => \'p.ip', 'up_admin' => 'down'}
                       );

} elsif ($specific =~ /vlan_dev/i){
    $vlan =~ s/[^\d]//g; # untaint the vlan passed to us
    $title = "Devices Carrying VLAN $vlan";
    $where->{vendor}++;
    $where->{model}++;
    $where->{os}++;
    $where->{description}++;
    $matches = sql_rows(
        'device d left join device_vlan dv on d.ip = dv.ip right join device_port_vlan dpv on (d.ip = dpv.ip and dv.vlan = dpv.vlan)',
        ['distinct(d.ip)','d.dns','d.vendor','d.model','d.os','dv.description','dv.vlan'],
        {'dv.vlan' => \"$vlan"}
    );

} elsif ($specific =~ /vlan/i){
    $vlan =~ s/[^\d]//g; # untaint the vlan passed to us
    $dns  =~ s/[^-.A-Za-z0-9]//g; # untaint the dns passed to us
    $title = (($ip && $dns) ? "Ports on Device $dns" : "Device Ports") ." Carrying VLAN $vlan";
    $where->{description}++;
    $where->{speed}++;
    $matches = sql_rows(
        'device_port_vlan v, device_vlan dv, device d, device_port p',
        ['d.ip','d.dns','v.port','p.name','p.speed','dv.description'],
        {'d.ip' => \'v.ip', 'v.ip' => \'p.ip', (($ip && $dns) ? ('p.ip' => $ip) : ()),
            'v.port' => \'p.port', 'v.vlan' => \"$vlan", 'dv.ip' => \'p.ip', 'dv.vlan' => \"$vlan"}
    );

} elsif ($specific =~ /blocking/i){
    $title = 'Device ports that are blocking';
    $where->{port}++;
    $where->{stp}++;
    $matches = sql_rows('device_port p, device d',
                        ['d.ip','d.dns','p.port','p.up_admin','stp'],
                        {'d.ip' => \'p.ip', 'stp' => 'broken','up' => 'up'}
                       );
    my $matches_blocking = sql_rows('device_port p, device d',
                        ['d.ip','d.dns','p.port','p.up_admin','stp'],
                        {'d.ip' => \'p.ip', 'stp' => [['blocking','broken']], 'up' => '!down'}
                       );

    push (@$matches,@$matches_blocking);
    
} elsif ($specific =~ /broken/i){
    $title = 'Broken topology links';
    # select d.ip,d.dns,p.port,p.remote_ip,p.remote_port from device_port p , device d
    #        where 
    #           d.ip = p.ip 
    #        and not exists 
    #           (select 1 from device_port q where q.ip = p.remote_ip and q.port = p.remote_port) 
    #        and not exists 
    #           (select 1 from device_ip a, device_port q where a.alias = p.remote_ip and q.ip = a.ip and q.port = p.remote_port) 
    #        and p.remote_ip is not null and p.remote_port is not null 
    #        order by p.ip;
    $matches = sql_rows('device_port p, device d',
                           ['d.ip','d.dns','p.ip','p.port','p.remote_ip','p.remote_port','p.remote_type','p.remote_id'],
                           {'d.ip' => \'p.ip',
                            'not exists' => \\'(select 1 from device_port q where q.ip = p.remote_ip and q.port = p.remote_port)',
                            'not exists ' => \\'(select 1 from device_ip a, device_port q where a.alias = p.remote_ip and q.ip = a.ip and q.port = p.remote_port)',
                            'p.remote_ip' => \\'is not null', 'p.remote_port' => \\'is not null',
                            'p.remote_type' => \\ ' NOT ILIKE \'%ip_phone%\' OR p.remote_type IS NULL',
                           }
                          ); 
    $where->{port}++;
    $where->{remote_port}++;
    $where->{remote_ip}++;
    $where->{remote_id}++;
    $where->{remote_type}++;

} elsif ($specific =~ /ip.phone/i){
    $title = 'IP Phones discovered through SNMP';
    $matches = sql_rows('device_port p, device d',
                           ['d.ip','d.dns','p.ip','p.port','p.remote_ip','p.remote_port','p.remote_type','p.remote_id'],
                           {'d.ip' => \'p.ip',
                            'not exists' => \\'(select 1 from device_port q where q.ip = p.remote_ip and q.port = p.remote_port)',
                            'not exists ' => \\'(select 1 from device_ip a, device_port q where a.alias = p.remote_ip and q.ip = a.ip and q.port = p.remote_port)',
                            'p.remote_ip' => \\'is not null', 'p.remote_port' => \\'is not null',
                            'p.remote_type' => \\ ' ILIKE \'%ip_phone%\'', 
                           }
                          ); 
    $where->{port}++;
    $where->{remote_port}++;
    $where->{remote_ip}++;
    $where->{remote_id}++;
    $where->{remote_type}++;
} elsif ($specific =~ /SSID/) {
    $specific = "SSID";
    $title = 'SSID Port Search - ' . $ssid;
    my %xtrawhere = ();
    if (!defined($ssidbcast) || ($ssidbcast cmp '') == 0) {
        $where->{broadcast}++;
    } else {
        $title .= " " . ($ssidbcast ? 'Broadcast' : 'Stealth');
        $xtrawhere{'broadcast'} = $ssidbcast;
    }
    $matches = sql_rows('device_port_ssid p, device d',
            ['d.ip','d.dns','d.vendor','d.model','d.location','d.contact','p.port','p.broadcast','p.ssid'],
            {'d.ip' => \'p.ip',
             'p.ssid' => sql_match($ssid,$exact),
             %xtrawhere
            }
            );
    if ($where->{broadcast}) {
        foreach my $row (@$matches) {
            $row->{broadcast} = $row->{broadcast} ? 'Broadcast' : 'Stealth';
        }
    }
    $where->{vendor}++;
    $where->{model}++;
    $where->{location}++;
    $where->{contact}++;
    $where->{ssid}++;
}

my @show_cols = ();

# Search on all text fields
if (length($text)) {
    my $match = sql_match($text,0);
    push(@{$where->{'d.ip/i.alias'}},  $match);
    push(@{$where->{'d.dns/i.dns'}},  $match);
    push(@{$where->{'d.name'}}, $match);
    push(@{$where->{'d.description'}}, $match);
    push(@{$where->{'d.location'}}, $match);
    push(@{$where->{'d.serial'}}, $match);
    push(@{$where->{'d.contact'}}, $match);
    $boolean = 'or';
}

# Generalized Search Terms

# Search on Model
foreach my $model (@models){
    next unless defined $model and length($model);
    if ($model eq 'show'){
        push (@show_cols,'d.model');
        next;
    }
    push (@{$where->{'d.model'}},$model);
}

# Search on OS
foreach my $this_os (@os){
    next unless defined $this_os and $this_os;
    if ($this_os eq 'show'){
        push (@show_cols,'d.os');
        next;
    }
    push (@{$where->{'d.os'}},$this_os);
}

# Search on OS Version
foreach my $this_ver (@os_ver){
    next unless defined $this_ver and $this_ver;
    if ($this_ver eq 'show'){
        push (@show_cols,'d.os_ver');
        next;
    }
    push (@{$where->{'d.os_ver'}},$this_ver);
}

# Search on Layers
my @layer_search = ('_','_','_','_','_','_','_');
# @layer_search is computer indexed, left->right
foreach my $layer (@layers){
    next unless defined $layer and length($layer);
    next unless ($layer > 0 and $layer < 8);
    $layer_search[$layer-1] = 1;
}
# the database field is in order 87654321
my $layer_string = join('', reverse @layer_search);
if ($layer_string =~ /1/){
    $layer_string = '%'.$layer_string;
    push (@{$where->{'d.layers'}},$layer_string);
}
# Search on Vendor
foreach my $vendor (@vendors){
    next unless defined $vendor and length($vendor);
    if ($vendor eq 'show'){
        push (@show_cols,'d.vendor');
        next;
    }
    push (@{$where->{'d.vendor'}},$vendor);
}


# Search on Location
if (length($loc)){
    my $match = sql_match($loc,$exact);
    push(@{$where->{'d.location'}}, $match);
}

# Search on Description
if (length($desc)){
    my $match = sql_match($desc,$exact);
    push(@{$where->{'d.description'}}, $match);
}

# Search on DNS entry
if (!$specific and length($dns)){
    my $match = sql_match($dns,$exact);
    push(@{$where->{'d.dns/i.dns'}}, $match);
}

# Search on sysName
if (length($name)){
    my $match = sql_match($name,$exact);
    push(@{$where->{'d.name'}}, $match);
}

# Search on IP
if (!$specific and length($ip)){
    my $match = sql_match($ip,$exact);
    push(@{$where->{'d.ip/i.alias'}}, $match);
    push(@show_cols,'d.name');
}

# Run Query
if (scalar keys %$where and ! defined $specific and ! length($specific)){
    #$matches = sql_rows('device',['*'],$where,$boolean eq 'or');

    my @cols = ();
    foreach my $wcol (keys %$where){
        foreach my $col (split('/',$wcol)) {
            # Skip the ones we're already selecting below
            next if ($col =~ /^i\./);
            next if ($col =~ /^d\.ip$/);
            next if ($col =~ /^d\.dns/);
            push(@cols,$col);
        }
    }

    # Add Columns to Grab info but not part of Search Terms
    foreach my $show (@show_cols){
        next if (grep /^$show$/,@cols);
        push @cols,$show;
    }

    $matches = sql_rows('device d left join device_ip i on d.ip = i.ip',
                        ['distinct(d.ip)','d.dns',@cols],
                        $where,
                        $boolean eq 'or');

    # Change col1/col2 to just col1 for display (implicit priority to col1)
    foreach my $wcol (keys %$where){
        if ($wcol =~ /^(.*)\/(.*)/){
            delete $where->{$wcol};
            push(@{$where->{$1}},undef); 
        }
    }
    # Add Info Columns to columns to display
    foreach my $w (@show_cols){
        next if (grep /^$w$/,keys(%$where));
        push(@{$where->{$w}},undef);
    }

}

my $time2 = time;
</%init>
%#
%# matches()  - Display search results stored in @$matches.
%#
<%method matches >
%return unless defined $matches;
<h1 class="pagehead"><%$title%></h1>
% unless (scalar @$matches) {
    No Results Found.
%    return;
% }
<TABLE CLASS="devs-data">
    <TR>
        <TH>Device</TH>
%    foreach my $col (sort keys %$where){
%       my $colview = $col;
%       $colview =~ s/^[^.]+\.//;
%       $colview = $colview eq 'count' ? 'Number of Active<BR>Nodes Connected' : $colview;
        <TH><%ucfirst($colview)%></TH>
%    }
    </TR>
<%perl>
my $count=0;
my @sort_matches = exists $where->{count} ? 
                     sort {$b->{count} <=> $a->{count} } @$matches :
                     sort {$a->{dns} cmp $b->{dns}} @$matches;
foreach my $result (@sort_matches){
   my $ip =  $result->{ip};
   my $dns = $result->{dns};
   my $port = $result->{port};
   my $vlan = $result->{vlan};
   $dns = defined $dns ? $dns : $ip;
   $dns =~ s/\Q$netdisco::CONFIG{domain}\E//;
   if (defined $port && !exists $where->{count}) {
    $dns .= " ($port)" ;
   }
</%perl>
    <TR>
% if (defined $vlan) { # set up by specific=vlan_dev
        <TD CLASS="match-<%$count%2%>"><A HREF="device_search.html?specific=vlan&vlan=<%$vlan|u%>&ip=<%$ip|u%>&dns=<%$dns|u%>"><% $dns %></A></TD>
% }
% elsif (exists $where->{count}) { # set up by specific =~ /multiple/i
        <TD CLASS="match-<%$count%2%>"><A HREF="device.html?ip=<%$ip|u%>"><% $dns %></A></TD>
% }
% else { # general case
        <TD CLASS="match-<%$count%2%>"><A HREF="device.html?ip=<%$ip|u%><% defined $port ? '&port=' : ''%><% $port|u %>"><% $dns %></A></TD>
% }
<%perl>
    foreach my $col (sort keys %$where){
        # Trim off table name for column
        $col =~ s/^[^.]+\.//;
        my $val = $result->{$col};

        # Column Specfic stuff
        if ($col eq 'remote_ip') {
            $val = "<A HREF=\"node.html?node=$val\">$val</A>";
        } elsif ($col eq 'port') {
            $val = "<A HREF=\"device.html?ip=$ip&port=$port\">$val</A>";
        }
</%perl>
       <TD CLASS="match-<%$count%2%>"><%$val%></TD>
%    }
    </TR>
% $count++; }
</TABLE>
<% $count %> matches.
</%method>
%#
%# title()
%#
<%method title>
- Device Search\
</%method>
%# $Id$
%# vim:syntax=mason
