add rest of POD and fixes for Device helper renaming

This commit is contained in:
Oliver Gorwits
2012-02-11 16:38:19 +00:00
parent 0f03ea82c2
commit 33ff11fa71
10 changed files with 467 additions and 103 deletions

View File

@@ -82,9 +82,44 @@ __PACKAGE__->set_primary_key("ip");
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:671/XuuvsO2aMB1+IRWFjg
=head1 RELATIONSHIPS
=head2 device_ips
Returns rows from the C<device_ip> table which relate to this Device. That is,
all the interface IP aliases configured on the Device.
=cut
__PACKAGE__->has_many( device_ips => 'Netdisco::DB::Result::DeviceIp', 'ip' );
=head2 vlans
Returns the C<device_vlan> entries for this Device. That is, the list of VLANs
configured on or known by this Device.
=cut
__PACKAGE__->has_many( vlans => 'Netdisco::DB::Result::DeviceVlan', 'ip' );
=head2 ports
Returns the set of ports on this Device.
=cut
__PACKAGE__->has_many( ports => 'Netdisco::DB::Result::DevicePort', 'ip' );
=head2 port_vlans
Returns the set of VLANs known to be configured on Ports on this Device,
either tagged or untagged.
The JOIN is of type "RIGHT" meaning that the results are constrained to VLANs
only on Ports on this Device.
=cut
__PACKAGE__->has_many(
port_vlans => 'Netdisco::DB::Result::DevicePortVlan',
'ip', { join_type => 'RIGHT' }
@@ -93,4 +128,57 @@ __PACKAGE__->has_many(
# helper which assumes we've just RIGHT JOINed to Vlans table
sub vlan { return (shift)->vlans->first }
=head1 ADDITIONAL COLUMNS
=head2 uptime_age
Formatted version of the C<uptime> field.
The format is in "X days/months/years" style, similar to:
1 year 4 months 05:46:00
=cut
sub uptime_age { return (shift)->get_column('uptime_age') }
=head2 last_discover_stamp
Formatted version of the C<last_discover> field, accurate to the minute.
The format is somewhat like ISO 8601 or RFC3339 but without the middle C<T>
between the date stamp and time stamp. That is:
2012-02-06 12:49
=cut
sub last_discover_stamp { return (shift)->get_column('last_discover_stamp') }
=head2 last_macsuck_stamp
Formatted version of the C<last_macsuck> field, accurate to the minute.
The format is somewhat like ISO 8601 or RFC3339 but without the middle C<T>
between the date stamp and time stamp. That is:
2012-02-06 12:49
=cut
sub last_macsuck_stamp { return (shift)->get_column('last_macsuck_stamp') }
=head2 last_arpnip_stamp
Formatted version of the C<last_arpnip> field, accurate to the minute.
The format is somewhat like ISO 8601 or RFC3339 but without the middle C<T>
between the date stamp and time stamp. That is:
2012-02-06 12:49
=cut
sub last_arpnip_stamp { return (shift)->get_column('last_arpnip_stamp') }
1;

View File

@@ -34,7 +34,23 @@ __PACKAGE__->set_primary_key("ip", "alias");
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:/ugGtBSGyrJ7s6yqJ9bclQ
=head1 RELATIONSHIPS
=head2 device
Returns the entry from the C<device> table to which this IP alias relates.
=cut
__PACKAGE__->belongs_to( device => 'Netdisco::DB::Result::Device', 'ip' );
=head2 device_port
Returns the Port on which this IP address is configured (typically a loopback,
routed port or virtual interface).
=cut
__PACKAGE__->belongs_to( device_port => 'Netdisco::DB::Result::DevicePort',
{ 'foreign.port' => 'self.port', 'foreign.ip' => 'self.ip' } );

View File

@@ -39,10 +39,34 @@ __PACKAGE__->set_primary_key("ip", "port", "vlan");
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:/3KLjJ3D18pGaPEaw9EU5w
=head1 RELATIONSHIPS
=head2 device
Returns the entry from the C<device> table which hosts the Port on which this
VLAN is configured.
=cut
__PACKAGE__->belongs_to( device => 'Netdisco::DB::Result::Device', 'ip' );
=head2 port
Returns the entry from the C<port> table on which this VLAN is configured.
=cut
__PACKAGE__->belongs_to( port => 'Netdisco::DB::Result::DevicePort', {
'foreign.ip' => 'self.ip', 'foreign.port' => 'self.port',
});
=head2 vlan
Returns the entry from the C<device_vlan> table describing this VLAN in
detail, typically in order that the C<name> can be retrieved.
=cut
__PACKAGE__->belongs_to( vlan => 'Netdisco::DB::Result::DeviceVlan', {
'foreign.ip' => 'self.ip', 'foreign.vlan' => 'self.vlan',
});

View File

@@ -37,7 +37,22 @@ __PACKAGE__->set_primary_key("ip", "vlan");
# Created by DBIx::Class::Schema::Loader v0.07015 @ 2012-01-07 14:20:02
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:hBJRcdzOic4d3u4pD1m8iA
=head1 RELATIONSHIPS
=head2 device
Returns the entry from the C<device> table on which this VLAN entry was discovered.
=cut
__PACKAGE__->belongs_to( device => 'Netdisco::DB::Result::Device', 'ip' );
=head2 port_vlans_tagged
Link relationship for C<tagging_ports>, see below.
=cut
__PACKAGE__->has_many( port_vlans_tagged => 'Netdisco::DB::Result::DevicePortVlan',
{
'foreign.ip' => 'self.ip',
@@ -47,6 +62,13 @@ __PACKAGE__->has_many( port_vlans_tagged => 'Netdisco::DB::Result::DevicePortVla
where => { -not_bool => 'me.native' },
}
);
=head2 port_vlans_native
Link relationship to support C<native_ports>, see below.
=cut
__PACKAGE__->has_many( port_vlans_native => 'Netdisco::DB::Result::DevicePortVlan',
{
'foreign.ip' => 'self.ip',
@@ -56,7 +78,22 @@ __PACKAGE__->has_many( port_vlans_native => 'Netdisco::DB::Result::DevicePortVla
where => { -bool => 'me.native' },
}
);
=head2 tagging_ports
Returns the set of Device Ports on which this VLAN is configured to be tagged.
=cut
__PACKAGE__->many_to_many( tagging_ports => 'port_vlans_tagged', 'port' );
=head2 native_ports
Returns the set of Device Ports on which this VLAN is the native VLAN (that
is, untagged).
=cut
__PACKAGE__->many_to_many( native_ports => 'port_vlans_native', 'port' );
1;

View File

@@ -5,52 +5,132 @@ use strict;
use warnings FATAL => 'all';
use NetAddr::IP::Lite ':lower';
# override the built-in so we can munge some columns
sub find {
my ($set, $ip, $attr) = @_;
$attr ||= {};
=head1 ADDITIONAL METHODS
return $set->SUPER::find($ip,
{
%$attr,
'+select' => [
\"replace(age(timestamp 'epoch' + uptime / 100 * interval '1 second', timestamp '1970-01-01 00:00:00-00')::text, 'mon', 'month')",
\"to_char(last_discover, 'YYYY-MM-DD HH24:MI')",
\"to_char(last_macsuck, 'YYYY-MM-DD HH24:MI')",
\"to_char(last_arpnip, 'YYYY-MM-DD HH24:MI')",
],
'+as' => [qw/ uptime last_discover last_macsuck last_arpnip /],
}
);
=head2 with_times
This is a modifier for any C<search()> (including the helpers below) which
will add the following additional synthesized columns to the result set:
=over 4
=item uptime_age
=item last_discover_stamp
=item last_macsuck_stamp
=item last_arpnip_stamp
=back
=cut
sub with_times {
my ($rs, $cond, $attrs) = @_;
$cond ||= {};
$attrs ||= {};
return $rs
->search_rs($cond, $attrs)
->search({},
{
'+select' => [
\"replace(age(timestamp 'epoch' + uptime / 100 * interval '1 second',
timestamp '1970-01-01 00:00:00-00')::text, 'mon', 'month')",
\"to_char(last_discover, 'YYYY-MM-DD HH24:MI')",
\"to_char(last_macsuck, 'YYYY-MM-DD HH24:MI')",
\"to_char(last_arpnip, 'YYYY-MM-DD HH24:MI')",
],
'+as' => [qw/
uptime_age
last_discover_stamp
last_macsuck_stamp
last_arpnip_stamp
/],
});
}
# finds distinct values of a col for use in form selections
sub get_distinct {
my ($set, $col) = @_;
return $set unless $col;
=head2 search_by_field( \%cond, \%attrs? )
return $set->search({},
{
columns => [$col],
order_by => $col,
distinct => 1
}
)->get_column($col)->all;
}
This variant of the standard C<search()> method returns a ResultSet of Device
entries. It is written to support web forms which accept fields that match and
locate Devices in the database.
The hashref parameter should contain fields from the Device table which will
be intelligently used in a search query.
In addition, you can provide the key C<matchall> which, given a True or False
value, controls whether fields must all match or whether any can match, to
select a row.
Supported keys:
=over 4
=item matchall
If a True value, fields must all match to return a given row of the Device
table, otherwise any field matching will cause the row to be included in
results.
=item name
Can match the C<name> field as a substring.
=item location
Can match the C<location> field as a substring.
=item description
Can match the C<description> field as a substring (usually this field contains
a description of the vendor operating system).
=item model
Will match exactly the C<model> field.
=item os_ver
Will match exactly the C<os_ver> field, which is the operating sytem software version.
=item vendor
Will match exactly the C<vendor> (manufacturer).
=item dns
Can match any of the Device IP address aliases as a substring.
=item ip
Can be a string IP or a NetAddr::IP object, either way being treated as an
IPv4 or IPv6 prefix within which the device must have one IP address alias.
=back
=cut
sub search_by_field {
my ($rs, $p, $attrs) = @_;
die "condition parameter to search_by_field must be hashref\n"
if ref {} ne ref $p or 0 == scalar keys %$p;
$attrs ||= {};
sub by_field {
my ($set, $p) = @_;
return $set unless ref {} eq ref $p;
my $op = $p->{matchall} ? '-and' : '-or';
# this is a bit of an ungraceful backflip to catch junk entry
# whilst avoiding returning all devices in the DB
my $ip = ($p->{ip} ?
(NetAddr::IP::Lite->new($p->{ip}) || NetAddr::IP::Lite->new('255.255.255.255'))
: undef);
# this is a bit of an inelegant trick to catch junk data entry,
# whilst avoiding returning *all* entries in the table
if (exists $p->{ip} and defined $p->{ip} and 'NetAddr::IP::Lite' ne ref $p->{ip}) {
$p->{ip} = ( NetAddr::IP::Lite->new($p->{ip})
|| NetAddr::IP::Lite->new('255.255.255.255') );
}
return $set->search(
{
return $rs
->search_rs({}, $attrs)
->search({
$op => [
($p->{name} ? ('me.name' =>
{ '-ilike' => "\%$p->{name}\%" }) : ()),
@@ -58,22 +138,25 @@ sub by_field {
{ '-ilike' => "\%$p->{location}\%" }) : ()),
($p->{description} ? ('me.description' =>
{ '-ilike' => "\%$p->{description}\%" }) : ()),
($p->{model} ? ('me.model' =>
{ '-in' => $p->{model} }) : ()),
($p->{os_ver} ? ('me.os_ver' =>
{ '-in' => $p->{os_ver} }) : ()),
($p->{vendor} ? ('me.vendor' =>
{ '-in' => $p->{vendor} }) : ()),
($p->{dns} ? (
-or => [
'me.dns' => { '-ilike' => "\%$p->{dns}\%" },
'device_ips.dns' => { '-ilike' => "\%$p->{dns}\%" },
]) : ()),
($ip ? (
-or => [
'me.ip' => { '<<=' => $ip->cidr },
'device_ips.alias' => { '<<=' => $ip->cidr },
]) : ()),
-or => [
'me.dns' => { '-ilike' => "\%$p->{dns}\%" },
'device_ips.dns' => { '-ilike' => "\%$p->{dns}\%" },
]) : ()),
($p->{ip} ? (
-or => [
'me.ip' => { '<<=' => $p->{ip}->cidr },
'device_ips.alias' => { '<<=' => $p->{ip}->cidr },
]) : ()),
],
},
{
@@ -84,9 +167,38 @@ sub by_field {
);
}
sub by_any {
my ($set, $q) = @_;
return $set unless $q;
=head2 search_fuzzy( $value )
This method accepts a single parameter only and returns a ResultSet of rows
from the Device table where one field matches the passed parameter.
The following fields are inspected for a match:
=over 4
=item contact
=item serial
=item location
=item name
=item description
=item dns
=item ip (including aliases)
=back
=cut
sub search_fuzzy {
my ($rs, $q) = @_;
die "missing param to search_by_field\n"
unless $q;
$q = "\%$q\%" if $q !~ m/\%/;
# basic IP check is a string match
@@ -104,7 +216,7 @@ sub by_any {
];
}
return $set->search(
return $rs->search(
{
-or => [
'me.contact' => { '-ilike' => $q },
@@ -127,39 +239,123 @@ sub by_any {
);
}
sub carrying_vlan {
my ($set, $vlan) = @_;
return $set unless $vlan and $vlan =~ m/^\d+$/;
=head2 carrying_vlan( \%cond, \%attrs? )
return $set->search(
{
'vlans.vlan' => $vlan,
'port_vlans.vlan' => $vlan,
},
{
order_by => [qw/ me.dns me.ip /],
columns => [qw/ me.ip me.dns me.model me.os me.vendor /],
join => 'port_vlans',
prefetch => 'vlans',
},
);
my $set = $rs->carrying_vlan({ vlan => 123 });
Like C<search()>, this returns a ResultSet of matching rows from the Device
table.
The returned devices each are aware of the given Vlan and have at least one
Port configured in the Vlan (either tagged, or not).
=over 4
=item *
The C<cond> parameter must be a hashref containing a key C<vlan> with
the value to search for.
=item *
Results are ordered by the Device DNS and IP fields.
=item *
Related rows from the C<device_vlan> table will be prefetched.
=back
=cut
sub carrying_vlan {
my ($rs, $cond, $attrs) = @_;
die "vlan number required for carrying_vlan\n"
if ref {} ne ref $cond or !exists $cond->{vlan};
$cond->{'vlans.vlan'} = $cond->{vlan};
$cond->{'port_vlans.vlan'} = delete $cond->{vlan};
$attrs ||= {};
return $rs
->search_rs($cond,
{
order_by => [qw/ me.dns me.ip /],
columns => [qw/ me.ip me.dns me.model me.os me.vendor /],
join => 'port_vlans',
prefetch => 'vlans',
})
->search({}, $attrs);
}
sub carrying_vlan_name {
my ($set, $name) = @_;
return $set unless $name;
$name = "\%$name\%" if $name !~ m/\%/;
=head2 carrying_vlan_name( \%cond, \%attrs? )
return $set->search(
{
'vlans.description' => { '-ilike' => $name },
},
{
my $set = $rs->carrying_vlan_name({ name => 'Branch Office' });
Like C<search()>, this returns a ResultSet of matching rows from the Device
table.
The returned devices each are aware of the named Vlan and have at least one
Port configured in the Vlan (either tagged, or not).
=over 4
=item *
The C<cond> parameter must be a hashref containing a key C<name> with
the value to search for. The value may optionally include SQL wildcard
characters.
=item *
Results are ordered by the Device DNS and IP fields.
=item *
Related rows from the C<device_vlan> table will be prefetched.
=back
=cut
sub carrying_vlan_name {
my ($rs, $cond, $attrs) = @_;
die "vlan name required for carrying_vlan_name\n"
if ref {} ne ref $cond or !exists $cond->{name};
$cond->{'vlans.description'} = { '-ilike' => $cond->{name} };
$attrs ||= {};
return $rs
->search_rs({}, {
order_by => [qw/ me.dns me.ip /],
columns => [qw/ me.ip me.dns me.model me.os me.vendor /],
prefetch => 'vlans',
},
);
})
->search($cond, $attrs);
}
=head2 get_distinct( $column )
Returns an asciibetical sorted list of the distinct values in the given column
of the Device table. This is useful for web forms when you want to provide a
drop-down list of possible options.
=cut
sub get_distinct {
my ($rs, $col) = @_;
return $rs unless $col;
return $rs->search({},
{
columns => [$col],
order_by => $col,
distinct => 1
}
)->get_column($col)->all;
}
1;

View File

@@ -8,7 +8,7 @@ use warnings FATAL => 'all';
my $set = $rs->search_by_mac({mac => '00:11:22:33:44:55'});
Like C<search()>, this returns a C<$resultset> of matching rows from the
Like C<search()>, this returns a ResultSet of matching rows from the
DevicePort table.
=over 4
@@ -55,7 +55,7 @@ sub search_by_mac {
my $set = $rs->search_by_ip({ip => '192.0.2.1'});
Like C<search()>, this returns a C<$resultset> of matching rows from the
Like C<search()>, this returns a ResultSet of matching rows from the
DevicePort table.
=over 4
@@ -103,7 +103,7 @@ sub search_by_ip {
my $set = $rs->search_by_name({name => 'sniffer'});
Like C<search()>, this returns a C<$resultset> of matching rows from the
Like C<search()>, this returns a ResultSet of matching rows from the
DevicePort table.
=over 4
@@ -141,7 +141,7 @@ sub search_by_name {
my $set = $rs->search_by_vlan({vlan => 123});
Like C<search()>, this returns a C<$resultset> of matching rows from the
Like C<search()>, this returns a ResultSet of matching rows from the
DevicePort table.
=over 4
@@ -179,7 +179,7 @@ sub search_by_vlan {
my $set = $rs->search_by_port({port => 'FastEthernet0/23'});
Like C<search()>, this returns a C<$resultset> of matching rows from the
Like C<search()>, this returns a ResultSet of matching rows from the
DevicePort table.
=over 4

View File

@@ -8,7 +8,7 @@ use warnings FATAL => 'all';
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
Like C<search()>, this returns a ResultSet of matching rows from the Node
table.
=over 4

View File

@@ -18,8 +18,8 @@ column.
=cut
sub has_dns_col {
my $set = shift;
return $set->result_source->has_column('dns');
my $rs = shift;
return $rs->result_source->has_column('dns');
}
my $search_attr = {
@@ -37,7 +37,7 @@ my $search_attr = {
my $set = $rs->search_by_ip({ip => '192.0.2.1', active => 1});
Like C<search()>, this returns a C<$resultset> of matching rows from the
Like C<search()>, this returns a ResultSet of matching rows from the
NodeIp table.
=over 4
@@ -96,7 +96,7 @@ sub search_by_ip {
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
Like C<search()>, this returns a ResultSet of matching rows from the
NodeIp table.
=over 4
@@ -152,7 +152,7 @@ sub search_by_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
Like C<search()>, this returns a ResultSet of matching rows from the
NodeIp table.
=over 4

View File

@@ -57,7 +57,8 @@ ajax '/ajax/content/device/addresses' => sub {
my $ip = param('q');
return unless $ip;
my $set = schema('netdisco')->resultset('DeviceIp')->search({ip => $ip}, { order_by => 'alias' });
my $set = schema('netdisco')->resultset('DeviceIp')
->search({ip => $ip}, { order_by => 'alias' });
return unless $set->count;
content_type('text/html');
@@ -71,17 +72,19 @@ ajax '/ajax/content/device/ports' => sub {
my $ip = param('q');
return unless $ip;
my $set = schema('netdisco')->resultset('DevicePort')->search_by_ip({ip => $ip});
my $set = schema('netdisco')->resultset('DevicePort')
->search_by_ip({ip => $ip});
# refine by ports if requested
my $q = param('f');
if ($q) {
if ($q =~ m/^\d+$/) {
$set = $set->by_vlan($q);
$set = $set->search_by_vlan({vlan => $q});
}
else {
my $c = schema('netdisco')->resultset('DevicePort')
->search_by_ip({ip => $ip})->search_by_port({port => $q});
->search_by_ip({ip => $ip})
->search_by_port({port => $q});
if ($c->count) {
$set = $set->search_by_port({port => $q});
}
@@ -115,7 +118,8 @@ ajax '/ajax/content/device/details' => sub {
my $ip = param('q');
return unless $ip;
my $device = schema('netdisco')->resultset('Device')->find($ip);
my $device = schema('netdisco')->resultset('Device')
->with_times()->find($ip);
return unless $device;
content_type('text/html');

View File

@@ -42,13 +42,13 @@ ajax '/ajax/content/search/device' => sub {
my $set;
if ($has_opt) {
$set = schema('netdisco')->resultset('Device')->by_field(scalar params);
$set = schema('netdisco')->resultset('Device')->search_by_field(scalar params);
}
else {
my $q = param('q');
return unless $q;
$set = schema('netdisco')->resultset('Device')->by_any($q);
$set = schema('netdisco')->resultset('Device')->search_fuzzy($q);
}
return unless $set->count;
@@ -136,15 +136,15 @@ ajax '/ajax/content/search/node' => sub {
# devices carrying vlan xxx
ajax '/ajax/content/search/vlan' => sub {
my $vlan = param('q');
return unless $vlan;
my $q = param('q');
return unless $q;
my $set;
if ($vlan =~ m/^\d+$/) {
$set = schema('netdisco')->resultset('Device')->carrying_vlan($vlan);
if ($q =~ m/^\d+$/) {
$set = schema('netdisco')->resultset('Device')->carrying_vlan({vlan => $q});
}
else {
$set = schema('netdisco')->resultset('Device')->carrying_vlan_name($vlan);
$set = schema('netdisco')->resultset('Device')->carrying_vlan_name({name => $q});
}
return unless $set->count;
@@ -208,9 +208,8 @@ get '/search' => sub {
}
}
else {
if ($s->resultset('Device')->search({
dns => { '-ilike' => "\%$q\%" },
})->count) {
if ($s->resultset('Device')
->search({dns => { '-ilike' => "\%$q\%" }})->count) {
params->{'tab'} = 'device';
}
elsif ($s->resultset('DevicePort')