<% $err %>
% $m->comp('SELF:show_age') if $cmd eq 'age';
<h1 class="pagehead">Report IP Inventory</h1>
<h2 class="subheader">Finds IP Addresses used by nodes</h2>
<FORM ACTION="<%$r->uri%>" METHOD="GET">
<DIV>
<INPUT TYPE="hidden" NAME="cmd" VALUE="age">
<TABLE CLASS="ips-search">
<TR>
    <TH>Show:</TH>
    <TD><INPUT TYPE="radio" name="type" value="all" <%$type eq 'all' ? ' CHECKED' : ''%>>All Entries
        <INPUT TYPE="radio" name="type" value="age" <%$type eq 'age' ? ' CHECKED' : ''%>>Search by Age
    </TD>
</TR>
<TR>
    <TH>Age:</TH>
    <TD>IPs not used in
        <SELECT NAME="age_val">
% for (1..12) {
            <OPTION VALUE="<%$_%>"<%$age_val eq $_ ? ' SELECTED' : ''%>><%$_%>
% }
        </SELECT>
        <SELECT NAME="age_mul">
% for (qw/days months years/) {
            <OPTION VALUE="<%$_%>"<%$age_mul eq $_ ? ' SELECTED' : ''%>><%$_%>
% }
        </SELECT>
    <BR><SPAN CLASS="smaller">To find IP addresses that have never been used in a subnet, select "12 Years" and Check "IPs never discovered"</span>
    </TD>
</TR>
<TR>
    <TH>Sort By:</TH>
    <TD><SELECT NAME="sort" SIZE=1>
%       foreach my $s (sort @sorts){
            <OPTION VALUE="<%$s%>" <% $sort eq $s ? 'SELECTED' : ''%>><%$s%>
%       }
        </SELECT>
    </TD>
</TR>
<TR>
    <TH VALIGN=TOP>Subnet:</TH>
    <TD>
        <INPUT TYPE="text" SIZE=20 NAME="subnet" VALUE="<%$subnet|h%>" TITLE="CIDR Format : 192.168.0.0/24">
        <span class="smaller">(CIDR Format : <tt>192.168.0.0/24</tt>)</span>
        <BR>
% if (@$subnets) {
        <SELECT NAME="subnet_select">
        <OPTION VALUE="">Enter above or select here
%  foreach (@$subnets) {
        <OPTION VALUE="<%$_|h%>"<%$subnet eq $_ ? ' SELECTED' : ''%>><%$_%>
%  }
        </SELECT>
        <!-- <%$s2 - $s1%> seconds -->
        <BR>
% } else {
    <A CLASS="navbutton" HREF="<%$r->uri|h%>?subnet_show=1">Show Subnets</A>
    <SPAN CLASS="smaller">(this can take a <i>long</i> time on big installs)</span>
% }
    </TD>
</TR>
<TR>
    <TH>Options</TH>
    <TD>List IPs never discovered <INPUT TYPE="checkbox" NAME="never" VALUE=1<%$never ? ' CHECKED' : ''%>><BR>
        Resolve IPs
            <INPUT TYPE="radio" NAME="dns" VALUE=1<%$dns ? ' CHECKED': ''%>>On
            <INPUT TYPE="radio" NAME="dns" VALUE=0<%!$dns ? ' CHECKED': ''%>>Off<BR>
        Limit
            <SELECT NAME="max" SIZE=1>
% for (qw/16 48 96 256 512 1024 4096 100000/){
                <OPTION VALUE="<%$_%>"<%$max eq $_ ? ' SELECTED' : ''%>><%$_%>
%}
            </SELECT>
    </TD>
</TR>
<TR>
    <TD>&nbsp;</TD>
    <TD><INPUT TYPE="submit" CLASS="navbutton" VALUE="Find IPs"></TD>
</TR>
</TABLE>
</DIV>
</FORM>
<P>
<h1 class="pagehead">Netbios Inventory</h1>
% $m->comp('SELF:show_nbt') if $cmd eq 'nbt';
<FORM ACTION="<%$r->uri%>" METHOD=GET>
<DIV>
<INPUT TYPE="hidden" NAME="cmd" VALUE="nbt">
<TABLE CLASS="ips-search">
<TR>
    <TH>Domain:</TH>
    <TD><SELECT NAME="domain" MULTIPLE SIZE=8>
        <OPTION VALUE="all"<%!scalar(@domain) ? ' SELECTED' : ''%>>[All]
        <OPTION VALUE="blank" <%grep(/^\Qblank\E$/,@domain)?' SELECTED':''%>>(Blank Domain)
% foreach my $d (sort keys %$nbt_domains){
        <OPTION VALUE="<%$d|u%>"<%grep(/^\Q$d\E$/,@domain)?' SELECTED':''%>><%$d|h%> (<%$nbt_domains->{$d} |h%>)
% }
        </SELECT>
        <!-- (<% $n2 - $n1 %> seconds.) -->
    </TD>
</TR>
<TR>
    <TH>Archive Data:</TH>
    <TD><INPUT TYPE="checkbox" NAME="archive" VALUE=1<%$archive ? ' CHECKED' : ''%>></TD>
</TR>
<TR>
    <TD COLSPAN=2><INPUT TYPE="submit" CLASS="navbutton" VALUE="Search">
        <A CLASS="navbutton" HREF="<%$r->uri%>">Reset</A>
    </TD>
</TR>
</TABLE>
</DIV>
</FORM>
<%args>
$subnet  => ''
$subnet_select => ''
$subnet_show => 0
$sort    => 'IP'
$age_val => 2
$age_mul => 'months'
$cmd     => ''
$max     => 256
@domain  => ()
$archive => 0
$never   => 0
$dns     => 1
$type    => 'all'
</%args>
<%shared>
my @sorts = qw/IP Age/;
my ($ips,$arg_sort,$odd,$arg_max,$arg_domain,$addrs,$err,$arg_dns);
my $nodes = [];  my $domain_count = {}; my $nbt_domains={};
my $subnets = [];
my $domain = $netdisco::CONFIG{domain} || '';
my $have_nbt_names = 0;
</%shared>
<%init>
$arg_sort = $sort; $arg_max = $max; $arg_domain = \@domain; $arg_dns = $dns;

# If you're just visiting for the first time, set the never checkbox.
# If you've submitted a query, though, leave never alone.  Can't
# default it to 1 in %args since it's a checkbox so no value is
# submitted if it's not checked.
if (%ARGS == 0) {
    $never = 1;
}

# get some form filler
my $n1 = time;
# I admit, this 'group by' is an ugly kludge :) -m
$nbt_domains = sql_column('node_nbt group by domain',['distinct(domain)','count(*)']);
my $n2 = time;
delete $nbt_domains->{''};

# Get the rows from the subnets table that have
#  node or device addresses.
my $s1 = time;
if ($subnet_show) {
    #BROKEN ON NEWNOC maxb 4/27 -- takes too long at 96 seconds
    $subnets = active_subnets();
}
my $s2 = time;

$subnet = $subnet_select if ($subnet_select ne '');
if ($cmd eq 'age' and !$subnet){
    $err = "Subnet Required.";
    $cmd = '';
}

if ($cmd eq 'age'){

    # Make new column in sql results 'is_old' that is boolean and
    #   tells if we match the aging characteristic.  Default to all
    #   matching for type eq 'all'.
    my @col = ('true as is_old');

    if ($type eq 'age') {
        $age_val = 2 unless $age_val =~ m/^\d+$/;
        $age_mul =~ s/[^a-z ]+//g;
        @col = ("(age(now(),node_ip.time_last) >= interval '$age_val $age_mul') as is_old");
    }

    $subnet .= '/24' unless $subnet =~ /\//;
    my $safenet = dbh_quote($subnet);

    # Grab all node_ip entries in subnet
    my $nodes = sql_rows('node_ip LEFT JOIN node_nbt ON node_nbt.ip=node_ip.ip',
                    [@col, 'age(now(),node_ip.time_last) as age',
                     'extract(epoch from node_ip.time_first) as time_first',
                     'extract(epoch from node_ip.time_last) as time_last',
                     'node_ip.ip','node_ip.active',
                     'node_nbt.domain','node_nbt.nbname',
                     'node_nbt.active as nbtactive'],
                    { 'node_ip.ip' => \\"<< inet $safenet" }
                   );


    # Add all the device / aliases in subnet
    $col[0] =~ s/node_ip\.time_last/last_discover/;
    my $devs_seen = sql_rows('device', ['age(now(),last_discover) as age',
                                        'extract(epoch from creation) as time_first','true as active',
                                        'extract(epoch from last_discover) as time_last','ip', @col],
                            { 'ip' => \\"<< inet $safenet"});
    $col[0] =~ s/last_discover/d.last_discover/;
    my $aliases = sql_rows('device_ip a left join device d on a.ip=d.ip', ['age(now(),d.last_discover) as age',
                                        'extract(epoch from a.creation) as time_first','true as active',
                                        'extract(epoch from d.last_discover) as time_last','a.alias as ip',@col],
                            { 'a.alias' => \\"<< inet $safenet"});

    # Loop through to find newest entries
    my %seen; my %results;
    foreach my $r (@$devs_seen,@$aliases,@$nodes){
        my $ip = $r->{ip};
        my $is_old = $r->{is_old};

        my $am_newest = 0;

        # First entry
        unless (defined $seen{$ip} ){
           $am_newest++;
        }

        # Active entries, but dont overwrite device entries
        #   note that devs and aliases come first
        elsif ($r->{active} and !$seen{$ip}->{active}) {
           $am_newest++;
        }

        # newest archived data
        elsif ( $r->{time_last} > $seen{$ip}->{time_last} ) {
           $am_newest++;
        }

        next unless $am_newest;
        $have_nbt_names ||= defined($r->{nbname});
        # Mark down IP as the most relevant and seen
        $seen{$ip}=$r;

        next unless $is_old;
        # Mark down the IP as a match
        $results{$ip}=$r;

    }

    push (@$ips,values %results);


    # Look for holes in our IP Space and report on that.
    if ($never){
        # Get all the possible IPs in a subnet
        $addrs = dump_subnet($subnet) || {};

        # Remove all the nodes seen in the subnet
        foreach my $ip (keys %seen) {
            delete $addrs->{$ip};
        }

        # Add our missing IPs to the results from above.
        foreach my $ip (keys %$addrs){
            push @$ips, { 'age' => 'Never', 'time_first' => 'Never',
                          'ip' => $ip, 'active' => 1, 'time_last' => 'Never',
                        };
        }
    }

    # Do DNS lookups on results
    if ($dns){
        foreach my $i (@$ips){
            my $ip = $i->{ip};
            $i->{dns} = hostname($ip);
        }
    }

}

if ($cmd eq 'nbt'){
    my $where = {};
    foreach my $d (@domain) {
        $d = '' if $d eq 'blank';
    }
    if (grep(/^all$/,@domain)){
        @domain = ();
    } else {
        $where = {'domain' => [[@domain]]};
    }
    $where->{active} = 1 unless $archive;
    $nodes = sql_rows('node_nbt',['ip','mac','nbname','domain','server','nbuser','active',
                                  'extract(epoch from time_first) as time_first','extract(epoch from time_last) as time_last'],
                                 $where);
    # count domains
    foreach my $n (@$nodes){
        my $d = $n->{domain};
        $domain_count->{$d}++;
    }
}
</%init>
%#
%# show_nbt() - Show results from Netbios Search
%#
<%method show_nbt>
<h2 class="subheader">Results</h2>
% unless (scalar @$nodes) {
No Results Found.
% return; }
<TABLE CLASS="ips-res">
    <TR>
        <TH>Domain</TH>
        <TH>Count</TH>
        <TH>Domain</TH>
        <TH>Count</TH>
    </TR>
% my $cnt = 0;
% foreach my $d (sort keys %$domain_count){
% $cnt++;
% if ($cnt%2){
<TR CLASS="match-<%++$odd%2%>">
%}
    <TD><A HREF="<%$r->uri%>?domain=<%$d||'blank'|u%>"><%$d || '[Blank]'|h%></A></TD>
    <TD><%$domain_count->{$d}%></TD>
% if (($cnt+1)%2) {
</TR>
%}
%}
</TABLE>
<% scalar(keys %$domain_count) %> Domains.
<P>
<TABLE CLASS="ips-res">
    <TR>
        <TH>Domain</TH>
        <TH>Node</TH>
        <TH>Name</TH>
        <TH>User</TH>
        <TH>First Seen</TH>
        <TH>Last Seen</TH>
    </TR>
% my $last_domain = '';
% foreach my $n (sort {($a->{domain} cmp $b->{domain}) or
%                      (lc($a->{nbname}) cmp lc($b->{nbname}) )}
%                @$nodes){
    <TR CLASS="match-<%++$odd%2%>">
        <TD><%$last_domain ne $n->{domain} ? $m->interp->apply_escapes($n->{domain}, 'h') : '&nbsp;'%></TD>
        <TD><A HREF="node.html?node=<%$n->{mac}|u%>"><%$n->{mac}%></A></TD>
        <TD><%$n->{domain} ? "\\\\$n->{domain}\\" : '' |h%><A HREF="node.html?node=<%$n->{nbname} |u%>"><%$n->{nbname} || '[None]'|h%></A></TD>
        <TD><%$n->{nbuser} || '[No User]' |h%> @
            <A HREF="node.html?node=<%$n->{ip} |u%>"><%$n->{ip}%></A>
        </TD>
        <TD><& 'node.html:format_date', date=>$n->{time_first}&></TD>
        <TD><& 'node.html:format_date', date=>$n->{time_last}&></TD>
    </TR>
%   $last_domain = $n->{domain};
% }
</TABLE>
<% scalar(@$nodes) %> Nodes.
<P>
</%method>
%#
%# show_age() - Show results from IP age inventory
%#
<%method show_age>
<h2 class="subheader">Results</h2>
% unless (scalar @$ips) {
No Results Found.
% return; }
<TABLE CLASS="ips-res">
    <TR>
        <TH>Node</TH>
% if ($arg_dns) {
        <TH>Host Name</TH>
% }
% if ($have_nbt_names) {
        <TH>NetBIOS<BR>Name</TH>
% }
        <TH>Last Used</TH>
        <TH>First<BR>Discovered</TH>
    </TR>
<%perl>
my @rows;
if ($arg_sort eq 'Age'){
    @rows = sort { $a->{time_last} <=> $b->{time_last} } @$ips;
} else {
    @rows = sort sort_ip @$ips;
}

# Truncate the list if too long (too many results)
if (scalar @rows > $arg_max) {
    splice(@rows,$arg_max);
    $m->out("Found ".scalar(@$ips) ." matching IPs.  Only showing $arg_max.<BR>\n");
}
$odd = 1;
foreach my $row (@rows){
    my $ip = $row->{ip};
    my $dns = $row->{dns};
    $dns =~ s/\Q$domain\E//;
    my $age = $m->comp('device_inv.html:.trim_age', age => $row->{age});
    my $time_first = $row->{time_first} eq 'Never' ? '&nbsp;' : scalar(localtime($row->{time_first}));
    my $active = $row->{active};
    my $d = $row->{domain};
    my $nbname = $row->{nbname};
    my $nbtactive = $row->{nbtactive};
    $odd++;
</%perl>
<TR>
    <TD CLASS="match-<%$odd%2%>"><A HREF="node.html?node=<%$ip|u%><%!$active ? '&archive=1' : ''%>"><%$ip%></A><%!$active ? '*' : ''%></TD>
% if ($arg_dns) {
    <TD CLASS="match-<%$odd%2%>">
%  if ($dns) {
<A HREF="node.html?node=<%$dns|u%><%!$active ? '&archive=1' : ''%>"><%$dns%></A><%!$active ? '*' : ''%>
%  } else {
&nbsp;
%  }
    </TD>
% }
% if ($have_nbt_names) {
    <TD CLASS="match-<%$odd%2%>">
%  if ($nbname) {
\\<%$d%>\<%$nbname%><%!$nbtactive ? '*' : ''%>
%  } else {
&nbsp;
%  }
    </TD>
% }
    <TD CLASS="match-<%$odd%2%>"><%$age%><%!$active ? '*' : ''%></TD>
    <TD CLASS="match-<%$odd%2%>"><%$time_first%></TD>
</TR>
%}
</TABLE>
<% scalar @rows %> IPs Shown.
<BR><B>*</B> denotes archived data (not currently in use, but seen).
</%method>
<%method title>
- Node Inventory \
</%method>
%# $Id$
%# vim:syntax=mason
