update Node and NodeIp Result/ResultSet to have smarter value-add subs
This commit is contained in:
		| @@ -50,10 +50,20 @@ __PACKAGE__->set_primary_key("mac", "switch", "port"); | ||||
|  | ||||
| __PACKAGE__->belongs_to( device => 'Netdisco::DB::Result::Device', | ||||
|   { 'foreign.ip' => 'self.switch' }, { join_type => 'LEFT' } ); | ||||
|  | ||||
| # device port may have been deleted (reconfigured modules?) but node remains | ||||
| __PACKAGE__->belongs_to( device_port => 'Netdisco::DB::Result::DevicePort', | ||||
|   { 'foreign.ip' => 'self.switch', 'foreign.port' => 'self.port' }, { join_type => 'LEFT' } ); | ||||
|   { 'foreign.ip' => 'self.switch', 'foreign.port' => 'self.port' }, | ||||
|   { join_type => 'LEFT' } | ||||
| ); | ||||
|  | ||||
| __PACKAGE__->has_many( ips => 'Netdisco::DB::Result::NodeIp', | ||||
|   { 'foreign.mac' => 'self.mac', 'foreign.active' => 'self.active' } ); | ||||
|  | ||||
| __PACKAGE__->belongs_to( oui => 'Netdisco::DB::Result::Oui', 'oui' ); | ||||
|  | ||||
| # accessors for custom formatted columns | ||||
| sub time_first_stamp { return (shift)->get_column('time_first_stamp') } | ||||
| sub time_last_stamp  { return (shift)->get_column('time_last_stamp')  } | ||||
|  | ||||
| 1; | ||||
|   | ||||
| @@ -4,25 +4,62 @@ use base 'DBIx::Class::ResultSet'; | ||||
| use strict; | ||||
| use warnings FATAL => 'all'; | ||||
|  | ||||
| sub by_mac { | ||||
|     my ($set, $archive, $mac) = @_; | ||||
|     return $set unless $mac; | ||||
| =head1 search_by_mac( \%cond, \%attrs? ) | ||||
|  | ||||
|     return $set->search( | ||||
|       { | ||||
|         'me.mac' => $mac, | ||||
|         ($archive ? () : (active => 1)), | ||||
|       }, | ||||
|       { | ||||
|         order_by => {'-desc' => 'time_last'}, | ||||
|         columns => [qw/ mac switch port oui active device.dns /], | ||||
|         '+select' => [ | ||||
|           \"to_char(time_first, 'YYYY-MM-DD HH24:MI')", | ||||
|           \"to_char(time_last, 'YYYY-MM-DD HH24:MI')", | ||||
|         ], | ||||
|         '+as' => [qw/ time_first time_last /], | ||||
|         join => 'device', | ||||
|       }, | ||||
|  my $set = $rs->search_by_mac({mac => '00:11:22:33:44:55', active => 1}); | ||||
|  | ||||
| Like C<search()>, this returns a C<$resultset> of matching rows from the Node | ||||
| table. | ||||
|  | ||||
| =over 4 | ||||
|  | ||||
| =item * | ||||
|  | ||||
| The C<cond> parameter must be a hashref containing a key C<mac> with | ||||
| the value to search for. | ||||
|  | ||||
| =item * | ||||
|  | ||||
| Results are ordered by time last seen. | ||||
|  | ||||
| =item * | ||||
|  | ||||
| Additional columns C<time_first_stamp> and C<time_last_stamp> provide | ||||
| preformatted timestamps of the C<time_first> and C<time_last> fields. | ||||
|  | ||||
| =item * | ||||
|  | ||||
| A JOIN is performed on the Device table and the Device C<dns> column | ||||
| prefetched. | ||||
|  | ||||
| =back | ||||
|  | ||||
| To limit results only to active nodes, set C<< {active => 1} >> in C<cond>. | ||||
|  | ||||
| =cut | ||||
|  | ||||
| sub search_by_mac { | ||||
|     my ($rs, $cond, $attrs) = @_; | ||||
|  | ||||
|     die "mac address required for search_by_mac\n" | ||||
|       if ref {} ne ref $cond or !exists $cond->{mac}; | ||||
|  | ||||
|     $cond->{'me.mac'} = delete $cond->{mac}; | ||||
|     $attrs ||= {}; | ||||
|  | ||||
|     return $rs | ||||
|       ->search_rs($cond, %$attrs) | ||||
|       ->search({}, | ||||
|         { | ||||
|           order_by => {'-desc' => 'time_last'}, | ||||
|           '+columns' => [qw/ device.dns /], | ||||
|           '+select' => [ | ||||
|             \"to_char(time_first, 'YYYY-MM-DD HH24:MI')", | ||||
|             \"to_char(time_last, 'YYYY-MM-DD HH24:MI')", | ||||
|           ], | ||||
|           '+as' => [qw/ time_first_stamp time_last_stamp /], | ||||
|           join => 'device', | ||||
|         }, | ||||
|     ); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -13,69 +13,176 @@ sub has_dns_col { | ||||
|  | ||||
| my $search_attr = { | ||||
|     order_by => {'-desc' => 'time_last'}, | ||||
|     columns => [qw/ mac ip active oui.company /], | ||||
|     '+columns' => [qw/ oui.company /], | ||||
|     '+select' => [ | ||||
|       \"to_char(time_first, 'YYYY-MM-DD HH24:MI')", | ||||
|       \"to_char(time_last, 'YYYY-MM-DD HH24:MI')", | ||||
|     ], | ||||
|     '+as' => [qw/ time_first time_last /], | ||||
|     '+as' => [qw/ time_first_stamp time_last_stamp /], | ||||
|     join => 'oui' | ||||
| }; | ||||
|  | ||||
| sub by_ip { | ||||
|     my ($set, $archive, $ip) = @_; | ||||
|     return $set unless $ip; | ||||
| =head1 search_by_ip( \%cond, \%attrs? ) | ||||
|  | ||||
|     my $op = '='; | ||||
|     if ('NetAddr::IP::Lite' eq ref $ip) { | ||||
|         $op = '<<=' if $ip->num > 1; | ||||
|  my $set = $rs->search_by_ip({ip => '123.123.123.123', active => 1}); | ||||
|  | ||||
| Like C<search()>, this returns a C<$resultset> of matching rows from the | ||||
| NodeIp table. | ||||
|  | ||||
| =over 4 | ||||
|  | ||||
| =item * | ||||
|  | ||||
| The C<cond> parameter must be a hashref containing a key C<ip> with the value | ||||
| to search for. Value can either be a simple string of IPv4 or IPv6, or a | ||||
| NetAddr::IP object in which case all results within the CIDR/Prefix will be | ||||
| retrieved. | ||||
|  | ||||
| =item * | ||||
|  | ||||
| Results are ordered by time last seen. | ||||
|  | ||||
| =item * | ||||
|  | ||||
| Additional columns C<time_first_stamp> and C<time_last_stamp> provide | ||||
| preformatted timestamps of the C<time_first> and C<time_last> fields. | ||||
|  | ||||
| =item * | ||||
|  | ||||
| A JOIN is performed on the OUI table and the OUI C<company> column prefetched. | ||||
|  | ||||
| =back | ||||
|  | ||||
| To limit results only to active IPs, set C<< {active => 1} >> in C<cond>. | ||||
|  | ||||
| =cut | ||||
|  | ||||
| sub search_by_ip { | ||||
|     my ($rs, $cond, $attrs) = @_; | ||||
|  | ||||
|     die "ip address required for search_by_ip\n" | ||||
|       if ref {} ne ref $cond or !exists $cond->{ip}; | ||||
|     $attrs ||= {}; | ||||
|  | ||||
|     # handle either plain text IP or NetAddr::IP (/32 or CIDR) | ||||
|     my ($op, $ip) = ('=', delete $cond->{ip}); | ||||
|  | ||||
|     if ('NetAddr::IP::Lite' eq ref $ip and $ip->num > 1) { | ||||
|         $op = '<<='; | ||||
|         $ip = $ip->cidr; | ||||
|     } | ||||
|     $cond->{ip} = { $op => $ip }; | ||||
|  | ||||
|     return $set->search( | ||||
|       { | ||||
|         ip => { $op => $ip }, | ||||
|         ($archive ? () : (active => 1)), | ||||
|       }, | ||||
|       { | ||||
|         %$search_attr, | ||||
|         ( $set->has_dns_col ? ('+columns' => 'dns') : () ), | ||||
|       } | ||||
|     ); | ||||
|     $rs = $rs->search_rs({}, {'+columns' => 'dns'}) | ||||
|       if $rs->has_dns_col; | ||||
|  | ||||
|     return $rs | ||||
|       ->search_rs($cond, %$attrs) | ||||
|       ->search({}, $search_attr); | ||||
| } | ||||
|  | ||||
| sub by_name { | ||||
|     my ($set, $archive, $name) = @_; | ||||
|     return $set unless $name; | ||||
|     die "do not call by_name unless you have a dns col on the node_ip table." | ||||
|       if not $set->has_dns_col; | ||||
| =head1 search_by_name( \%cond, \%attrs? ) | ||||
|  | ||||
|     return $set->search( | ||||
|       { | ||||
|         dns => { '-ilike' => $name }, | ||||
|         ($archive ? () : (active => 1)), | ||||
|       }, | ||||
|       { | ||||
|         %$search_attr, | ||||
|         '+columns' => 'dns', | ||||
|       } | ||||
|     ); | ||||
|  my $set = $rs->search_by_name({dns => 'foo.example.com', active => 1}); | ||||
|  | ||||
| Like C<search()>, this returns a C<$resultset> of matching rows from the | ||||
| NodeIp table. | ||||
|  | ||||
| =over 4 | ||||
|  | ||||
| =item * | ||||
|  | ||||
| The NodeIp table must have a C<dns> column for this search to work. Typically | ||||
| this column is the IP's DNS PTR record, cached at the time of Netdisco Arpnip. | ||||
|  | ||||
| =item * | ||||
|  | ||||
| The C<cond> parameter must be a hashref containing a key C<dns> with the value | ||||
| to search for. The value may optionally include SQL wildcard characters. | ||||
|  | ||||
| =item * | ||||
|  | ||||
| Results are ordered by time last seen. | ||||
|  | ||||
| =item * | ||||
|  | ||||
| Additional columns C<time_first_stamp> and C<time_last_stamp> provide | ||||
| preformatted timestamps of the C<time_first> and C<time_last> fields. | ||||
|  | ||||
| =item * | ||||
|  | ||||
| A JOIN is performed on the OUI table and the OUI C<company> column prefetched. | ||||
|  | ||||
| =back | ||||
|  | ||||
| To limit results only to active IPs, set C<< {active => 1} >> in C<cond>. | ||||
|  | ||||
| =cut | ||||
|  | ||||
| sub search_by_dns { | ||||
|     my ($rs, $cond, $attrs) = @_; | ||||
|  | ||||
|     die "search_by_dns requires a dns col on the node_ip table.\n" | ||||
|       if not $rs->has_dns_col; | ||||
|  | ||||
|     die "dns field required for search_by_dns\n" | ||||
|       if ref {} ne ref $cond or !exists $cond->{dns}; | ||||
|  | ||||
|     $cond->{dns} = { '-ilike' => delete $cond->{dns} }; | ||||
|     $attrs ||= {}; | ||||
|  | ||||
|     return $rs | ||||
|       ->search_rs($cond, %$attrs) | ||||
|       ->search_rs({}, {'+columns' => 'dns'}) | ||||
|       ->search({}, $search_attr); | ||||
| } | ||||
|  | ||||
| sub by_mac { | ||||
|     my ($set, $archive, $mac) = @_; | ||||
|     return $set unless $mac; | ||||
| =head1 search_by_mac( \%cond, \%attrs? ) | ||||
|  | ||||
|     return $set->search( | ||||
|       { | ||||
|         mac => $mac, | ||||
|         ($archive ? () : (active => 1)), | ||||
|       }, | ||||
|       { | ||||
|         %$search_attr, | ||||
|         ( $set->has_dns_col ? ('+columns' => 'dns') : () ), | ||||
|       } | ||||
|     ); | ||||
|  my $set = $rs->search_by_mac({mac => '00:11:22:33:44:55', active => 1}); | ||||
|  | ||||
| Like C<search()>, this returns a C<$resultset> of matching rows from the | ||||
| NodeIp table. | ||||
|  | ||||
| =over 4 | ||||
|  | ||||
| =item * | ||||
|  | ||||
| The C<cond> parameter must be a hashref containing a key C<mac> with the value | ||||
| to search for. | ||||
|  | ||||
| =item * | ||||
|  | ||||
| Results are ordered by time last seen. | ||||
|  | ||||
| =item * | ||||
|  | ||||
| Additional columns C<time_first_stamp> and C<time_last_stamp> provide | ||||
| preformatted timestamps of the C<time_first> and C<time_last> fields. | ||||
|  | ||||
| =item * | ||||
|  | ||||
| A JOIN is performed on the OUI table and the OUI C<company> column prefetched. | ||||
|  | ||||
| =back | ||||
|  | ||||
| To limit results only to active IPs, set C<< {active => 1} >> in C<cond>. | ||||
|  | ||||
| =cut | ||||
|  | ||||
| sub search_by_mac { | ||||
|     my ($rs, $cond, $attrs) = @_; | ||||
|  | ||||
|     die "mac address required for search_by_mac\n" | ||||
|       if ref {} ne ref $cond or !exists $cond->{mac}; | ||||
|     $attrs ||= {}; | ||||
|  | ||||
|     $rs = $rs->search_rs({}, {'+columns' => 'dns'}) | ||||
|       if $rs->has_dns_col; | ||||
|  | ||||
|     return $rs | ||||
|       ->search_rs($cond, %$attrs) | ||||
|       ->search({}, $search_attr); | ||||
| } | ||||
|  | ||||
| 1; | ||||
|   | ||||
| @@ -65,13 +65,15 @@ ajax '/ajax/content/search/node' => sub { | ||||
|     content_type('text/html'); | ||||
|  | ||||
|     my $mac = Net::MAC->new(mac => $node, 'die' => 0, verbose => 0); | ||||
|     my @active = (param('archived') ? () : (active => 1)); | ||||
|  | ||||
|     if (eval { $mac->as_IEEE }) { | ||||
|  | ||||
|         my $sightings = schema('netdisco')->resultset('Node') | ||||
|           ->by_mac(param('archived'), $mac->as_IEEE); | ||||
|           ->search_by_mac({mac => $mac->as_IEEE, @active}); | ||||
|  | ||||
|         my $ips = schema('netdisco')->resultset('NodeIp') | ||||
|           ->by_mac(param('archived'), $mac->as_IEEE); | ||||
|           ->search_by_mac({mac => $mac->as_IEEE, @active}); | ||||
|  | ||||
|         my $ports = schema('netdisco')->resultset('DevicePort') | ||||
|           ->by_mac($mac->as_IEEE); | ||||
| @@ -90,9 +92,9 @@ ajax '/ajax/content/search/node' => sub { | ||||
|         my $set; | ||||
|  | ||||
|         if (my $ip = NetAddr::IP::Lite->new($node)) { | ||||
|             # by_ip() will extract cidr notation if necessary | ||||
|             # search_by_ip() will extract cidr notation if necessary | ||||
|             $set = schema('netdisco')->resultset('NodeIp') | ||||
|               ->by_ip(param('archived'), $ip); | ||||
|               ->search_by_ip({ip => $ip, @active}); | ||||
|         } | ||||
|         else { | ||||
|             if (schema('netdisco')->resultset('NodeIp')->has_dns_col) { | ||||
| @@ -104,7 +106,7 @@ ajax '/ajax/content/search/node' => sub { | ||||
|                         if index($node, setting('domain_suffix')) == -1; | ||||
|                 } | ||||
|                 $set = schema('netdisco')->resultset('NodeIp') | ||||
|                   ->by_name(param('archived'), $node); | ||||
|                   ->search_by_dns({dns => $node, @active}); | ||||
|             } | ||||
|             elsif (setting('domain_suffix')) { | ||||
|                 $node .= setting('domain_suffix') | ||||
| @@ -120,13 +122,16 @@ ajax '/ajax/content/search/node' => sub { | ||||
|                     return; | ||||
|                 } | ||||
|                 $set = schema('netdisco')->resultset('NodeIp') | ||||
|                   ->by_ip(param('archived'), $node); | ||||
|                   ->search_by_ip({ip => $node, @active}); | ||||
|             } | ||||
|         } | ||||
|         return unless $set and $set->count; | ||||
|  | ||||
|         template 'ajax/search/node_by_ip.tt', { | ||||
|           results => $set, | ||||
|           macs => $set, | ||||
|           # a callback for the templates, which | ||||
|           # allows modification of the DB query before execution | ||||
|           archive_filter => sub { (shift)->search({@active}) }, | ||||
|         }, { layout => undef }; | ||||
|     } | ||||
| }; | ||||
|   | ||||
| @@ -14,7 +14,7 @@ | ||||
|     </tr> | ||||
|   </thead> | ||||
|   </tbody> | ||||
|     [% WHILE (row = results.next) %] | ||||
|     [% WHILE (row = macs.next) %] | ||||
|     <tr> | ||||
|       <td><a class="nd_linkcell" | ||||
|         href="[% uri_for('/search') %]?[% vars.query_defaults.node %]&q=[% row.mac | uri %]">[% row.mac %]</a></td> | ||||
| @@ -27,11 +27,11 @@ | ||||
|         [% ' <span class="label warning">a</span>' IF NOT row.active %] | ||||
|       </td> | ||||
|       [% IF params.stamps %] | ||||
|       <td>[% row.time_first %]</td> | ||||
|       <td>[% row.time_last %]</td> | ||||
|       <td>[% row.time_first_stamp %]</td> | ||||
|       <td>[% row.time_last_stamp %]</td> | ||||
|       [% END %] | ||||
|     </tr> | ||||
|     [% FOREACH node IN row.node_sightings(params.archived) %] | ||||
|     [% FOREACH node IN archive_filter(row.node_sightings) %] | ||||
|     <tr> | ||||
|       <td> </td> | ||||
|       [% IF params.vendor %] | ||||
| @@ -43,12 +43,12 @@ | ||||
|         [% ' <span class="label warning">a</span>' IF NOT node.active %] | ||||
|       </td> | ||||
|       [% IF params.stamps %] | ||||
|       <td>[% node.time_first %]</td> | ||||
|       <td>[% node.time_last %]</td> | ||||
|       <td>[% node.time_first_stamp %]</td> | ||||
|       <td>[% node.time_last_stamp %]</td> | ||||
|       [% END %] | ||||
|     </tr> | ||||
|     [% END %] | ||||
|     [% FOREACH nodeip IN row.ip_aliases(params.archived) %] | ||||
|     [% FOREACH nodeip IN archive_filter(row.ip_aliases) %] | ||||
|     <tr> | ||||
|       <td> </td> | ||||
|       [% IF params.vendor %] | ||||
| @@ -60,8 +60,8 @@ | ||||
|         [% ' <span class="label warning">a</span>' IF NOT nodeip.active %] | ||||
|       </td> | ||||
|       [% IF params.stamps %] | ||||
|       <td>[% nodeip.time_first %]</td> | ||||
|       <td>[% nodeip.time_last %]</td> | ||||
|       <td>[% nodeip.time_first_stamp %]</td> | ||||
|       <td>[% nodeip.time_last_stamp %]</td> | ||||
|       [% END %] | ||||
|     </tr> | ||||
|     [% END %] | ||||
|   | ||||
| @@ -40,8 +40,8 @@ | ||||
|         [% ' <span class="label warning">a</span>' IF NOT row.active %] | ||||
|       </td> | ||||
|       [% IF params.stamps %] | ||||
|       <td>[% row.time_first %]</td> | ||||
|       <td>[% rw.time_last %]</td> | ||||
|       <td>[% row.time_first_stamp %]</td> | ||||
|       <td>[% rw.time_last_stamp %]</td> | ||||
|       [% END %] | ||||
|     </tr> | ||||
|     [% SET first_row = 0 %] | ||||
| @@ -71,8 +71,8 @@ | ||||
|         [% ' <span class="label warning">a</span>' IF NOT node.active %] | ||||
|       </td> | ||||
|       [% IF params.stamps %] | ||||
|       <td>[% node.time_first %]</td> | ||||
|       <td>[% node.time_last %]</td> | ||||
|       <td>[% node.time_first_stamp %]</td> | ||||
|       <td>[% node.time_last_stamp %]</td> | ||||
|       [% END %] | ||||
|     </tr> | ||||
|     [% SET first_row = 0 %] | ||||
|   | ||||
		Reference in New Issue
	
	Block a user