migrate all tabs to be plugins

This commit is contained in:
Oliver Gorwits
2013-02-09 18:40:07 +00:00
parent a684570fbd
commit 10e94aa138
15 changed files with 442 additions and 331 deletions

View File

@@ -12,6 +12,7 @@ use URI::QueryParam (); # part of URI, to add helper methods
use App::Netdisco::Web::AuthN;
use App::Netdisco::Web::Search;
use App::Netdisco::Web::Device;
use App::Netdisco::Web::TypeAhead;
use App::Netdisco::Web::PortControl;
sub _load_web_plugins {

View File

@@ -5,7 +5,6 @@ use Dancer::Plugin::Ajax;
use Dancer::Plugin::DBIC;
use NetAddr::IP::Lite ':lower';
use App::Netdisco::Util::Web (); # for sort_port
hook 'before' => sub {
# list of port detail columns
@@ -87,206 +86,6 @@ hook 'before_template' => sub {
$tokens->{self_options} = $self_uri->query_form_hash;
};
ajax '/ajax/content/device/:thing' => sub {
return "<p>Hello, this is where the ". param('thing') ." content goes.</p>";
};
ajax '/ajax/content/device/netmap' => sub {
content_type('text/html');
template 'ajax/device/netmap.tt', {}, { layout => undef };
};
sub _get_name {
my $ip = shift;
my $domain = quotemeta( setting('domain_suffix') || '' );
(my $dns = (var('devices')->{$ip} || '')) =~ s/$domain$//;
return ($dns || $ip);
}
sub _add_children {
my ($ptr, $childs) = @_;
my @legit = ();
foreach my $c (@$childs) {
next if exists var('seen')->{$c};
var('seen')->{$c}++;
push @legit, $c;
push @{$ptr}, { name => _get_name($c), ip => $c };
}
for (my $i = 0; $i < @legit; $i++) {
$ptr->[$i]->{children} = [];
_add_children($ptr->[$i]->{children}, var('links')->{$legit[$i]});
}
}
# d3 seems not to use proper ajax semantics, so get instead of ajax
get '/ajax/data/device/netmap' => sub {
my $start = param('q');
return unless $start;
my @devices = schema('netdisco')->resultset('Device')->search({}, {
result_class => 'DBIx::Class::ResultClass::HashRefInflator',
columns => ['ip', 'dns'],
})->all;
var(devices => { map { $_->{ip} => $_->{dns} } @devices });
var(links => {});
my $rs = schema('netdisco')->resultset('Virtual::DeviceLinks')->search({}, {
result_class => 'DBIx::Class::ResultClass::HashRefInflator',
});
while (my $l = $rs->next) {
var('links')->{ $l->{left_ip} } ||= [];
push @{ var('links')->{ $l->{left_ip} } }, $l->{right_ip};
}
my %tree = (
ip => $start,
name => _get_name($start),
children => [],
);
var(seen => {$start => 1});
_add_children($tree{children}, var('links')->{$start});
content_type('application/json');
return to_json(\%tree);
};
ajax '/ajax/data/device/alldevicelinks' => sub {
my @devices = schema('netdisco')->resultset('Device')->search({}, {
result_class => 'DBIx::Class::ResultClass::HashRefInflator',
columns => ['ip', 'dns'],
})->all;
var(devices => { map { $_->{ip} => $_->{dns} } @devices });
my $rs = schema('netdisco')->resultset('Virtual::DeviceLinks')->search({}, {
result_class => 'DBIx::Class::ResultClass::HashRefInflator',
});
my %tree = ();
while (my $l = $rs->next) {
push @{ $tree{ _get_name($l->{left_ip} )} },
_get_name($l->{right_ip});
}
content_type('application/json');
return to_json(\%tree);
};
# device interface addresses
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'});
return unless $set->count;
content_type('text/html');
template 'ajax/device/addresses.tt', {
results => $set,
}, { layout => undef };
};
# device ports with a description (er, name) matching
ajax '/ajax/content/device/ports' => sub {
my $ip = param('q');
return unless $ip;
my $set = schema('netdisco')->resultset('DevicePort')
->search({'me.ip' => $ip});
# refine by ports if requested
my $q = param('f');
if ($q) {
if ($q =~ m/^\d+$/) {
$set = $set->search({
-or => {
'me.vlan' => $q,
'port_vlans_tagged.vlan' => $q,
},
}, { join => 'port_vlans_tagged' });
return unless $set->count;
}
else {
$q =~ s/\*/%/g if index($q, '*') >= 0;
$q =~ s/\?/_/g if index($q, '?') >= 0;
$q = { '-ilike' => $q };
if ($set->search({'me.port' => $q})->count) {
$set = $set->search({'me.port' => $q});
}
else {
$set = $set->search({'me.name' => $q});
return unless $set->count;
}
}
}
# filter for free ports if asked
my $free_filter = (param('free') ? 'only_free_ports' : 'with_is_free');
$set = $set->$free_filter({
age_num => (param('age_num') || 3),
age_unit => (param('age_unit') || 'months')
});
# make sure query asks for formatted timestamps when needed
$set = $set->with_times if param('c_lastchange');
# get number of vlans on the port to control whether to list them or not
$set = $set->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');
# retrieve active/all connected nodes, if asked for
$set = $set->search_rs({}, { prefetch => [{$nodes_name => 'ips'}] })
if param('c_nodes');
# 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;
content_type('text/html');
template 'ajax/device/ports.tt', {
results => $results,
nodes => $nodes_name,
device => $ip,
}, { layout => undef };
};
# device details table
ajax '/ajax/content/device/details' => sub {
my $ip = param('q');
return unless $ip;
my $device = schema('netdisco')->resultset('Device')
->with_times()->find($ip);
return unless $device;
content_type('text/html');
template 'ajax/device/details.tt', {
d => $device,
}, { layout => undef };
};
# support typeahead with simple AJAX query for device names
ajax '/ajax/data/device/typeahead' => sub {
my $q = param('query');
my $set = schema('netdisco')->resultset('Device')->search_fuzzy($q);
content_type 'application/json';
return to_json [map {$_->dns || $_->name || $_->ip} $set->all];
};
get '/device' => sub {
my $ip = NetAddr::IP::Lite->new(param('q'));
if (! $ip) {

View File

@@ -0,0 +1,26 @@
package App::Netdisco::Web::Plugin::Device::Addresses;
use Dancer ':syntax';
use Dancer::Plugin::Ajax;
use Dancer::Plugin::DBIC;
use App::Netdisco::Web::Plugin;
register_device_tab({ id => 'addresses', label => 'Addresses' });
# device interface addresses
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'});
return unless $set->count;
content_type('text/html');
template 'ajax/device/addresses.tt', {
results => $set,
}, { layout => undef };
};
true;

View File

@@ -0,0 +1,26 @@
package App::Netdisco::Web::Plugin::Device::Details;
use Dancer ':syntax';
use Dancer::Plugin::Ajax;
use Dancer::Plugin::DBIC;
use App::Netdisco::Web::Plugin;
register_device_tab({ id => 'details', label => 'Details' });
# device details table
ajax '/ajax/content/device/details' => sub {
my $ip = param('q');
return unless $ip;
my $device = schema('netdisco')->resultset('Device')
->with_times()->find($ip);
return unless $device;
content_type('text/html');
template 'ajax/device/details.tt', {
d => $device,
}, { layout => undef };
};
true;

View File

@@ -0,0 +1,14 @@
package App::Netdisco::Web::Plugin::Device::Modules;
use Dancer ':syntax';
use Dancer::Plugin::Ajax;
use App::Netdisco::Web::Plugin;
register_device_tab({ id => 'modules', label => 'Modules' });
ajax '/ajax/content/device/:thing' => sub {
return "<p>Hello, this is where the ". param('thing') ." content goes.</p>";
};
true;

View File

@@ -0,0 +1,96 @@
package App::Netdisco::Web::Plugin::Device::Neighbors;
use Dancer ':syntax';
use Dancer::Plugin::Ajax;
use Dancer::Plugin::DBIC;
use App::Netdisco::Web::Plugin;
register_device_tab({ id => 'netmap', label => 'Neighbors' });
ajax '/ajax/content/device/netmap' => sub {
content_type('text/html');
template 'ajax/device/netmap.tt', {}, { layout => undef };
};
sub _get_name {
my $ip = shift;
my $domain = quotemeta( setting('domain_suffix') || '' );
(my $dns = (var('devices')->{$ip} || '')) =~ s/$domain$//;
return ($dns || $ip);
}
sub _add_children {
my ($ptr, $childs) = @_;
my @legit = ();
foreach my $c (@$childs) {
next if exists var('seen')->{$c};
var('seen')->{$c}++;
push @legit, $c;
push @{$ptr}, { name => _get_name($c), ip => $c };
}
for (my $i = 0; $i < @legit; $i++) {
$ptr->[$i]->{children} = [];
_add_children($ptr->[$i]->{children}, var('links')->{$legit[$i]});
}
}
# d3 seems not to use proper ajax semantics, so get instead of ajax
get '/ajax/data/device/netmap' => sub {
my $start = param('q');
return unless $start;
my @devices = schema('netdisco')->resultset('Device')->search({}, {
result_class => 'DBIx::Class::ResultClass::HashRefInflator',
columns => ['ip', 'dns'],
})->all;
var(devices => { map { $_->{ip} => $_->{dns} } @devices });
var(links => {});
my $rs = schema('netdisco')->resultset('Virtual::DeviceLinks')->search({}, {
result_class => 'DBIx::Class::ResultClass::HashRefInflator',
});
while (my $l = $rs->next) {
var('links')->{ $l->{left_ip} } ||= [];
push @{ var('links')->{ $l->{left_ip} } }, $l->{right_ip};
}
my %tree = (
ip => $start,
name => _get_name($start),
children => [],
);
var(seen => {$start => 1});
_add_children($tree{children}, var('links')->{$start});
content_type('application/json');
return to_json(\%tree);
};
ajax '/ajax/data/device/alldevicelinks' => sub {
my @devices = schema('netdisco')->resultset('Device')->search({}, {
result_class => 'DBIx::Class::ResultClass::HashRefInflator',
columns => ['ip', 'dns'],
})->all;
var(devices => { map { $_->{ip} => $_->{dns} } @devices });
my $rs = schema('netdisco')->resultset('Virtual::DeviceLinks')->search({}, {
result_class => 'DBIx::Class::ResultClass::HashRefInflator',
});
my %tree = ();
while (my $l = $rs->next) {
push @{ $tree{ _get_name($l->{left_ip} )} },
_get_name($l->{right_ip});
}
content_type('application/json');
return to_json(\%tree);
};
true;

View File

@@ -0,0 +1,85 @@
package App::Netdisco::Web::Plugin::Device::Ports;
use Dancer ':syntax';
use Dancer::Plugin::Ajax;
use Dancer::Plugin::DBIC;
use App::Netdisco::Util::Web (); # for sort_port
use App::Netdisco::Web::Plugin;
register_device_tab({ id => 'ports', label => 'Ports' });
# device ports with a description (er, name) matching
ajax '/ajax/content/device/ports' => sub {
my $ip = param('q');
return unless $ip;
my $set = schema('netdisco')->resultset('DevicePort')
->search({'me.ip' => $ip});
# refine by ports if requested
my $q = param('f');
if ($q) {
if ($q =~ m/^\d+$/) {
$set = $set->search({
-or => {
'me.vlan' => $q,
'port_vlans_tagged.vlan' => $q,
},
}, { join => 'port_vlans_tagged' });
return unless $set->count;
}
else {
$q =~ s/\*/%/g if index($q, '*') >= 0;
$q =~ s/\?/_/g if index($q, '?') >= 0;
$q = { '-ilike' => $q };
if ($set->search({'me.port' => $q})->count) {
$set = $set->search({'me.port' => $q});
}
else {
$set = $set->search({'me.name' => $q});
return unless $set->count;
}
}
}
# filter for free ports if asked
my $free_filter = (param('free') ? 'only_free_ports' : 'with_is_free');
$set = $set->$free_filter({
age_num => (param('age_num') || 3),
age_unit => (param('age_unit') || 'months')
});
# make sure query asks for formatted timestamps when needed
$set = $set->with_times if param('c_lastchange');
# get number of vlans on the port to control whether to list them or not
$set = $set->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');
# retrieve active/all connected nodes, if asked for
$set = $set->search_rs({}, { prefetch => [{$nodes_name => 'ips'}] })
if param('c_nodes');
# 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;
content_type('text/html');
template 'ajax/device/ports.tt', {
results => $results,
nodes => $nodes_name,
device => $ip,
}, { layout => undef };
};
true;

View File

@@ -0,0 +1,36 @@
package App::Netdisco::Web::Plugin::Search::Device;
use Dancer ':syntax';
use Dancer::Plugin::Ajax;
use Dancer::Plugin::DBIC;
use List::MoreUtils ();
use App::Netdisco::Web::Plugin;
register_search_tab({id => 'device', label => 'Device'});
# device with various properties or a default match-all
ajax '/ajax/content/search/device' => sub {
my $has_opt = List::MoreUtils::any {param($_)}
qw/name location dns ip description model os_ver vendor/;
my $set;
if ($has_opt) {
$set = schema('netdisco')->resultset('Device')->search_by_field(scalar params);
}
else {
my $q = param('q');
return unless $q;
$set = schema('netdisco')->resultset('Device')->search_fuzzy($q);
}
return unless $set->count;
content_type('text/html');
template 'ajax/search/device.tt', {
results => $set,
}, { layout => undef };
};
true;

View File

@@ -0,0 +1,71 @@
package App::Netdisco::Web::Plugin::Search::Node;
use Dancer ':syntax';
use Dancer::Plugin::Ajax;
use Dancer::Plugin::DBIC;
use NetAddr::IP::Lite ':lower';
use Net::MAC ();
use App::Netdisco::Web::Plugin;
register_search_tab({ id => 'node', label => 'Node' });
# nodes matching the param as an IP or DNS hostname or MAC
ajax '/ajax/content/search/node' => sub {
my $node = param('q');
return unless $node;
content_type('text/html');
my $mac = Net::MAC->new(mac => $node, 'die' => 0, verbose => 0);
my @active = (param('archived') ? () : (-bool => 'active'));
if (! $mac->get_error) {
my $sightings = schema('netdisco')->resultset('Node')
->search_by_mac({mac => $mac->as_IEEE, @active});
my $ips = schema('netdisco')->resultset('NodeIp')
->search_by_mac({mac => $mac->as_IEEE, @active});
my $ports = schema('netdisco')->resultset('DevicePort')
->search({mac => $mac->as_IEEE});
return unless $sightings->count
or $ips->count
or $ports->count;
template 'ajax/search/node_by_mac.tt', {
ips => $ips,
sightings => $sightings,
ports => $ports,
}, { layout => undef };
}
else {
my $set;
if (my $ip = NetAddr::IP::Lite->new($node)) {
# search_by_ip() will extract cidr notation if necessary
$set = schema('netdisco')->resultset('NodeIp')
->search_by_ip({ip => $ip, @active});
}
else {
if (param('partial')) {
$node = "\%$node\%";
}
elsif (setting('domain_suffix')) {
$node .= setting('domain_suffix')
if index($node, setting('domain_suffix')) == -1;
}
$set = schema('netdisco')->resultset('NodeIp')
->search_by_dns({dns => $node, @active});
}
return unless $set and $set->count;
template 'ajax/search/node_by_ip.tt', {
macs => $set,
archive_filter => {@active},
}, { layout => undef };
}
};
true;

View File

@@ -0,0 +1,31 @@
package App::Netdisco::Web::Plugin::Search::Port;
use Dancer ':syntax';
use Dancer::Plugin::Ajax;
use Dancer::Plugin::DBIC;
use App::Netdisco::Web::Plugin;
register_search_tab({ id => 'port', label => 'Port' });
# device ports with a description (er, name) matching
ajax '/ajax/content/search/port' => sub {
my $q = param('q');
return unless $q;
my $set;
if ($q =~ m/^\d+$/) {
$set = schema('netdisco')->resultset('DevicePort')->search({vlan => $q});
}
else {
$set = schema('netdisco')->resultset('DevicePort')->search({name => $q});
}
return unless $set->count;
content_type('text/html');
template 'ajax/search/port.tt', {
results => $set,
}, { layout => undef };
};
true;

View File

@@ -0,0 +1,31 @@
package App::Netdisco::Web::Plugin::Search::VLAN;
use Dancer ':syntax';
use Dancer::Plugin::Ajax;
use Dancer::Plugin::DBIC;
use App::Netdisco::Web::Plugin;
register_search_tab({ id => 'vlan', label => 'VLAN' });
# devices carrying vlan xxx
ajax '/ajax/content/search/vlan' => sub {
my $q = param('q');
return unless $q;
my $set;
if ($q =~ m/^\d+$/) {
$set = schema('netdisco')->resultset('Device')->carrying_vlan({vlan => $q});
}
else {
$set = schema('netdisco')->resultset('Device')->carrying_vlan_name({name => $q});
}
return unless $set->count;
content_type('text/html');
template 'ajax/search/vlan.tt', {
results => $set,
}, { layout => undef };
};
true;

View File

@@ -3,6 +3,7 @@ package App::Netdisco::Web::PortControl;
use Dancer ':syntax';
use Dancer::Plugin::Ajax;
use Dancer::Plugin::DBIC;
use Try::Tiny;
ajax '/ajax/portcontrol' => sub {

View File

@@ -5,8 +5,6 @@ use Dancer::Plugin::Ajax;
use Dancer::Plugin::DBIC;
use NetAddr::IP::Lite ':lower';
use Net::MAC ();
use List::MoreUtils ();
hook 'before' => sub {
# view settings for node options
@@ -63,126 +61,6 @@ hook 'before_template' => sub {
}
};
# device with various properties or a default match-all
ajax '/ajax/content/search/device' => sub {
my $has_opt = List::MoreUtils::any {param($_)}
qw/name location dns ip description model os_ver vendor/;
my $set;
if ($has_opt) {
$set = schema('netdisco')->resultset('Device')->search_by_field(scalar params);
}
else {
my $q = param('q');
return unless $q;
$set = schema('netdisco')->resultset('Device')->search_fuzzy($q);
}
return unless $set->count;
content_type('text/html');
template 'ajax/search/device.tt', {
results => $set,
}, { layout => undef };
};
# nodes matching the param as an IP or DNS hostname or MAC
ajax '/ajax/content/search/node' => sub {
my $node = param('q');
return unless $node;
content_type('text/html');
my $mac = Net::MAC->new(mac => $node, 'die' => 0, verbose => 0);
my @active = (param('archived') ? () : (-bool => 'active'));
if (! $mac->get_error) {
my $sightings = schema('netdisco')->resultset('Node')
->search_by_mac({mac => $mac->as_IEEE, @active});
my $ips = schema('netdisco')->resultset('NodeIp')
->search_by_mac({mac => $mac->as_IEEE, @active});
my $ports = schema('netdisco')->resultset('DevicePort')
->search({mac => $mac->as_IEEE});
return unless $sightings->count
or $ips->count
or $ports->count;
template 'ajax/search/node_by_mac.tt', {
ips => $ips,
sightings => $sightings,
ports => $ports,
}, { layout => undef };
}
else {
my $set;
if (my $ip = NetAddr::IP::Lite->new($node)) {
# search_by_ip() will extract cidr notation if necessary
$set = schema('netdisco')->resultset('NodeIp')
->search_by_ip({ip => $ip, @active});
}
else {
if (param('partial')) {
$node = "\%$node\%";
}
elsif (setting('domain_suffix')) {
$node .= setting('domain_suffix')
if index($node, setting('domain_suffix')) == -1;
}
$set = schema('netdisco')->resultset('NodeIp')
->search_by_dns({dns => $node, @active});
}
return unless $set and $set->count;
template 'ajax/search/node_by_ip.tt', {
macs => $set,
archive_filter => {@active},
}, { layout => undef };
}
};
# devices carrying vlan xxx
ajax '/ajax/content/search/vlan' => sub {
my $q = param('q');
return unless $q;
my $set;
if ($q =~ m/^\d+$/) {
$set = schema('netdisco')->resultset('Device')->carrying_vlan({vlan => $q});
}
else {
$set = schema('netdisco')->resultset('Device')->carrying_vlan_name({name => $q});
}
return unless $set->count;
content_type('text/html');
template 'ajax/search/vlan.tt', {
results => $set,
}, { layout => undef };
};
# device ports with a description (er, name) matching
ajax '/ajax/content/search/port' => sub {
my $q = param('q');
return unless $q;
my $set;
if ($q =~ m/^\d+$/) {
$set = schema('netdisco')->resultset('DevicePort')->search({vlan => $q});
}
else {
$set = schema('netdisco')->resultset('DevicePort')->search({name => $q});
}
return unless $set->count;
content_type('text/html');
template 'ajax/search/port.tt', {
results => $set,
}, { layout => undef };
};
get '/search' => sub {
my $q = param('q');
if (not param('tab')) {

View File

@@ -0,0 +1,16 @@
package App::Netdisco::Web::TypeAhead;
use Dancer ':syntax';
use Dancer::Plugin::Ajax;
use Dancer::Plugin::DBIC;
# support typeahead with simple AJAX query for device names
ajax '/ajax/data/device/typeahead' => sub {
my $q = param('query');
my $set = schema('netdisco')->resultset('Device')->search_fuzzy($q);
content_type 'application/json';
return to_json [map {$_->dns || $_->name || $_->ip} $set->all];
};
true;