relocate repo files so ND2 is the only code
This commit is contained in:
71
lib/App/Netdisco/Web/AdminTask.pm
Normal file
71
lib/App/Netdisco/Web/AdminTask.pm
Normal file
@@ -0,0 +1,71 @@
|
||||
package App::Netdisco::Web::AdminTask;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::Ajax;
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use NetAddr::IP qw/:rfc3021 :lower/;
|
||||
use App::Netdisco::JobQueue 'jq_insert';
|
||||
use App::Netdisco::Util::Device 'delete_device';
|
||||
|
||||
sub add_job {
|
||||
my ($action, $device, $subaction) = @_;
|
||||
|
||||
my $net = NetAddr::IP->new($device);
|
||||
return if
|
||||
($device and (!$net or $net->num == 0 or $net->addr eq '0.0.0.0'));
|
||||
|
||||
my @hostlist = defined $device ? ($net->hostenum) : (undef);
|
||||
|
||||
jq_insert([map {{
|
||||
($_ ? (device => $_->addr) : ()),
|
||||
action => $action,
|
||||
($subaction ? (subaction => $subaction) : ()),
|
||||
username => session('logged_in_user'),
|
||||
userip => request->remote_address,
|
||||
}} @hostlist]);
|
||||
|
||||
true;
|
||||
}
|
||||
|
||||
foreach my $action (@{ setting('job_prio')->{high} },
|
||||
@{ setting('job_prio')->{normal} }) {
|
||||
|
||||
ajax "/ajax/control/admin/$action" => require_role admin => sub {
|
||||
add_job($action, param('device'), param('extra'))
|
||||
or send_error('Bad device', 400);
|
||||
};
|
||||
|
||||
post "/admin/$action" => require_role admin => sub {
|
||||
add_job($action, param('device'), param('extra'))
|
||||
? redirect uri_for('/admin/jobqueue')->path
|
||||
: redirect uri_for('/')->path;
|
||||
};
|
||||
}
|
||||
|
||||
ajax '/ajax/control/admin/delete' => require_role admin => sub {
|
||||
send_error('Missing device', 400) unless param('device');
|
||||
|
||||
my $device = NetAddr::IP->new(param('device'));
|
||||
send_error('Bad device', 400)
|
||||
if ! $device or $device->addr eq '0.0.0.0';
|
||||
|
||||
return delete_device(
|
||||
$device->addr, param('archive'), param('log'),
|
||||
);
|
||||
};
|
||||
|
||||
get '/admin/*' => require_role admin => sub {
|
||||
my ($tag) = splat;
|
||||
|
||||
# trick the ajax into working as if this were a tabbed page
|
||||
params->{tab} = $tag;
|
||||
|
||||
var(nav => 'admin');
|
||||
template 'admintask', {
|
||||
task => setting('_admin_tasks')->{ $tag },
|
||||
};
|
||||
};
|
||||
|
||||
true;
|
||||
196
lib/App/Netdisco/Web/Auth/Provider/DBIC.pm
Normal file
196
lib/App/Netdisco/Web/Auth/Provider/DBIC.pm
Normal file
@@ -0,0 +1,196 @@
|
||||
package App::Netdisco::Web::Auth::Provider::DBIC;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'Dancer::Plugin::Auth::Extensible::Provider::Base';
|
||||
|
||||
# with thanks to yanick's patch at
|
||||
# https://github.com/bigpresh/Dancer-Plugin-Auth-Extensible/pull/24
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Passphrase;
|
||||
use Digest::MD5;
|
||||
use Net::LDAP;
|
||||
use Try::Tiny;
|
||||
|
||||
sub authenticate_user {
|
||||
my ($self, $username, $password) = @_;
|
||||
return unless defined $username;
|
||||
|
||||
my $user = $self->get_user_details($username) or return;
|
||||
return unless $user->in_storage;
|
||||
return $self->match_password($password, $user);
|
||||
}
|
||||
|
||||
sub get_user_details {
|
||||
my ($self, $username) = @_;
|
||||
|
||||
my $settings = $self->realm_settings;
|
||||
my $database = schema($settings->{schema_name})
|
||||
or die "No database connection";
|
||||
|
||||
my $users_table = $settings->{users_resultset} || 'User';
|
||||
my $username_column = $settings->{users_username_column} || 'username';
|
||||
|
||||
my $user = try {
|
||||
$database->resultset($users_table)->find({
|
||||
$username_column => $username
|
||||
});
|
||||
};
|
||||
|
||||
# each of these settings permits no user in the database
|
||||
# so create a pseudo user entry instead
|
||||
if (not $user and not setting('validate_remote_user')
|
||||
and (setting('trust_remote_user')
|
||||
or setting('trust_x_remote_user')
|
||||
or setting('no_auth'))) {
|
||||
$user = $database->resultset($users_table)
|
||||
->new_result({username => $username});
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
sub get_user_roles {
|
||||
my ($self, $username) = @_;
|
||||
return unless defined $username;
|
||||
|
||||
my $settings = $self->realm_settings;
|
||||
my $database = schema($settings->{schema_name})
|
||||
or die "No database connection";
|
||||
|
||||
# Get details of the user first; both to check they exist, and so we have
|
||||
# their ID to use.
|
||||
my $user = $self->get_user_details($username)
|
||||
or return;
|
||||
|
||||
my $roles = $settings->{roles_relationship} || 'roles';
|
||||
my $role_column = $settings->{role_column} || 'role';
|
||||
|
||||
return [ try {
|
||||
$user->$roles->get_column( $role_column )->all;
|
||||
} ];
|
||||
}
|
||||
|
||||
sub match_password {
|
||||
my($self, $password, $user) = @_;
|
||||
return unless $user;
|
||||
|
||||
my $settings = $self->realm_settings;
|
||||
my $username_column = $settings->{users_username_column} || 'username';
|
||||
|
||||
return $user->ldap
|
||||
? $self->match_with_ldap($password, $user->$username_column)
|
||||
: $self->match_with_local_pass($password, $user);
|
||||
}
|
||||
|
||||
sub match_with_local_pass {
|
||||
my($self, $password, $user) = @_;
|
||||
|
||||
my $settings = $self->realm_settings;
|
||||
my $password_column = $settings->{users_password_column} || 'password';
|
||||
|
||||
return unless $password and $user->$password_column;
|
||||
|
||||
if ($user->$password_column !~ m/^{[A-Z]+}/) {
|
||||
my $sum = Digest::MD5::md5_hex($password);
|
||||
|
||||
if ($sum eq $user->$password_column) {
|
||||
if (setting('safe_password_store')) {
|
||||
# upgrade password if successful, and permitted
|
||||
$user->update({password => passphrase($password)->generate});
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return passphrase($password)->matches($user->$password_column);
|
||||
}
|
||||
}
|
||||
|
||||
sub match_with_ldap {
|
||||
my($self, $pass, $user) = @_;
|
||||
|
||||
return unless setting('ldap') and ref {} eq ref setting('ldap');
|
||||
my $conf = setting('ldap');
|
||||
|
||||
my $ldapuser = $conf->{user_string};
|
||||
$ldapuser =~ s/\%USER\%?/$user/egi;
|
||||
|
||||
# If we can bind as anonymous or proxy user,
|
||||
# search for user's distinguished name
|
||||
if ($conf->{proxy_user}) {
|
||||
my $user = $conf->{proxy_user};
|
||||
my $pass = $conf->{proxy_pass};
|
||||
my $attrs = ['distinguishedName'];
|
||||
my $result = _ldap_search($ldapuser, $attrs, $user, $pass);
|
||||
$ldapuser = $result->[0] if ($result->[0]);
|
||||
}
|
||||
# otherwise, if we can't search and aren't using AD and then construct DN
|
||||
# by appending base
|
||||
elsif ($ldapuser =~ m/=/) {
|
||||
$ldapuser = "$ldapuser,$conf->{base}";
|
||||
}
|
||||
|
||||
foreach my $server (@{$conf->{servers}}) {
|
||||
my $opts = $conf->{opts} || {};
|
||||
my $ldap = Net::LDAP->new($server, %$opts) or next;
|
||||
my $msg = undef;
|
||||
|
||||
if ($conf->{tls_opts} ) {
|
||||
$msg = $ldap->start_tls(%{$conf->{tls_opts}});
|
||||
}
|
||||
|
||||
$msg = $ldap->bind($ldapuser, password => $pass);
|
||||
$ldap->unbind(); # take down session
|
||||
|
||||
return 1 unless $msg->code();
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub _ldap_search {
|
||||
my ($filter, $attrs, $user, $pass) = @_;
|
||||
my $conf = setting('ldap');
|
||||
|
||||
return undef unless defined($filter);
|
||||
return undef if (defined $attrs and ref [] ne ref $attrs);
|
||||
|
||||
foreach my $server (@{$conf->{servers}}) {
|
||||
my $opts = $conf->{opts} || {};
|
||||
my $ldap = Net::LDAP->new($server, %$opts) or next;
|
||||
my $msg = undef;
|
||||
|
||||
if ($conf->{tls_opts}) {
|
||||
$msg = $ldap->start_tls(%{$conf->{tls_opts}});
|
||||
}
|
||||
|
||||
if ( $user and $user ne 'anonymous' ) {
|
||||
$msg = $ldap->bind($user, password => $pass);
|
||||
}
|
||||
else {
|
||||
$msg = $ldap->bind();
|
||||
}
|
||||
|
||||
$msg = $ldap->search(
|
||||
base => $conf->{base},
|
||||
filter => "($filter)",
|
||||
attrs => $attrs,
|
||||
);
|
||||
|
||||
$ldap->unbind(); # take down session
|
||||
|
||||
my $entries = [$msg->entries];
|
||||
return $entries unless $msg->code();
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
1;
|
||||
112
lib/App/Netdisco/Web/AuthN.pm
Normal file
112
lib/App/Netdisco/Web/AuthN.pm
Normal file
@@ -0,0 +1,112 @@
|
||||
package App::Netdisco::Web::AuthN;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
hook 'before' => sub {
|
||||
params->{return_url} ||= ((request->path ne uri_for('/')->path)
|
||||
? request->uri : uri_for('/inventory')->path);
|
||||
|
||||
# from the internals of Dancer::Plugin::Auth::Extensible
|
||||
my $provider = Dancer::Plugin::Auth::Extensible::auth_provider('users');
|
||||
|
||||
if (! session('logged_in_user') && request->path ne uri_for('/login')->path) {
|
||||
if (setting('trust_x_remote_user')
|
||||
and scalar request->header('X-REMOTE_USER')
|
||||
and length scalar request->header('X-REMOTE_USER')) {
|
||||
|
||||
(my $user = scalar request->header('X-REMOTE_USER')) =~ s/@[^@]*$//;
|
||||
return if setting('validate_remote_user')
|
||||
and not $provider->get_user_details($user);
|
||||
|
||||
session(logged_in_user => $user);
|
||||
session(logged_in_user_realm => 'users');
|
||||
}
|
||||
elsif (setting('trust_remote_user')
|
||||
and defined $ENV{REMOTE_USER}
|
||||
and length $ENV{REMOTE_USER}) {
|
||||
|
||||
(my $user = $ENV{REMOTE_USER}) =~ s/@[^@]*$//;
|
||||
return if setting('validate_remote_user')
|
||||
and not $provider->get_user_details($user);
|
||||
|
||||
session(logged_in_user => $user);
|
||||
session(logged_in_user_realm => 'users');
|
||||
}
|
||||
elsif (setting('no_auth')) {
|
||||
session(logged_in_user => 'guest');
|
||||
session(logged_in_user_realm => 'users');
|
||||
}
|
||||
else {
|
||||
# user has no AuthN - force to handler for '/'
|
||||
request->path_info('/');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
get qr{^/(?:login(?:/denied)?)?} => sub {
|
||||
template 'index', { return_url => param('return_url') };
|
||||
};
|
||||
|
||||
# override default login_handler so we can log access in the database
|
||||
post '/login' => sub {
|
||||
my $mode = (request->is_ajax ? 'API' : 'Web');
|
||||
my ($success, $realm) = authenticate_user(
|
||||
param('username'), param('password')
|
||||
);
|
||||
|
||||
if ($success) {
|
||||
session logged_in_user => param('username');
|
||||
session logged_in_user_realm => $realm;
|
||||
|
||||
schema('netdisco')->resultset('UserLog')->create({
|
||||
username => session('logged_in_user'),
|
||||
userip => request->remote_address,
|
||||
event => "Login ($mode)",
|
||||
details => param('return_url'),
|
||||
});
|
||||
|
||||
schema('netdisco')->resultset('User')
|
||||
->find( session('logged_in_user') )
|
||||
->update({ last_on => \'now()' });
|
||||
|
||||
return if request->is_ajax;
|
||||
redirect param('return_url');
|
||||
}
|
||||
else {
|
||||
session->destroy;
|
||||
|
||||
schema('netdisco')->resultset('UserLog')->create({
|
||||
username => param('username'),
|
||||
userip => request->remote_address,
|
||||
event => "Login Failure ($mode)",
|
||||
details => param('return_url'),
|
||||
});
|
||||
|
||||
if (request->is_ajax) {
|
||||
status('unauthorized');
|
||||
}
|
||||
else {
|
||||
vars->{login_failed}++;
|
||||
forward uri_for('/login'),
|
||||
{ login_failed => 1, return_url => param('return_url') },
|
||||
{ method => 'GET' };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
# we override the default login_handler, so logout has to be handled as well
|
||||
any ['get', 'post'] => '/logout' => sub {
|
||||
schema('netdisco')->resultset('UserLog')->create({
|
||||
username => session('logged_in_user'),
|
||||
userip => request->remote_address,
|
||||
event => "Logout",
|
||||
details => '',
|
||||
});
|
||||
|
||||
session->destroy;
|
||||
redirect uri_for('/inventory')->path;
|
||||
};
|
||||
|
||||
true;
|
||||
186
lib/App/Netdisco/Web/Device.pm
Normal file
186
lib/App/Netdisco/Web/Device.pm
Normal file
@@ -0,0 +1,186 @@
|
||||
package App::Netdisco::Web::Device;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::Ajax;
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
use URL::Encode 'url_params_mixed';
|
||||
|
||||
hook 'before' => sub {
|
||||
my @default_port_columns_left = (
|
||||
{ name => 'c_admin', label => 'Port Controls', default => '' },
|
||||
{ name => 'c_port', label => 'Port', default => 'on' },
|
||||
);
|
||||
|
||||
my @default_port_columns_right = (
|
||||
{ name => 'c_descr', label => 'Description', default => '' },
|
||||
{ name => 'c_comment', label => 'Last Comment', default => '' },
|
||||
{ name => 'c_type', label => 'Type', default => '' },
|
||||
{ name => 'c_duplex', label => 'Duplex', default => '' },
|
||||
{ name => 'c_lastchange', label => 'Last Change', default => '' },
|
||||
{ name => 'c_name', label => 'Name', default => 'on' },
|
||||
{ name => 'c_speed', label => 'Speed', default => '' },
|
||||
{ name => 'c_mac', label => 'Port MAC', default => '' },
|
||||
{ name => 'c_mtu', label => 'MTU', default => '' },
|
||||
{ name => 'c_pvid', label => 'Native VLAN', default => 'on' },
|
||||
{ name => 'c_vmember', label => 'VLAN Membership', default => 'on' },
|
||||
{ name => 'c_power', label => 'PoE', default => '' },
|
||||
{ name => 'c_ssid', label => 'SSID', default => '' },
|
||||
{ name => 'c_nodes', label => 'Connected Nodes', default => '' },
|
||||
{ name => 'c_neighbors', label => 'Connected Devices', default => 'on' },
|
||||
{ name => 'c_stp', label => 'Spanning Tree', default => '' },
|
||||
{ name => 'c_up', label => 'Status', default => '' },
|
||||
);
|
||||
|
||||
# build list of port detail columns
|
||||
my @port_columns = ();
|
||||
|
||||
push @port_columns,
|
||||
grep {$_->{position} eq 'left'} @{ setting('_extra_device_port_cols') };
|
||||
push @port_columns, @default_port_columns_left;
|
||||
push @port_columns,
|
||||
grep {$_->{position} eq 'mid'} @{ setting('_extra_device_port_cols') };
|
||||
push @port_columns, @default_port_columns_right;
|
||||
push @port_columns,
|
||||
grep {$_->{position} eq 'right'} @{ setting('_extra_device_port_cols') };
|
||||
|
||||
var('port_columns' => \@port_columns);
|
||||
|
||||
# view settings for port connected devices
|
||||
var('connected_properties' => [
|
||||
{ name => 'n_age', label => 'Age Stamp', default => '' },
|
||||
{ name => 'n_ip4', label => 'IPv4 Addresses', default => 'on' },
|
||||
{ name => 'n_ip6', label => 'IPv6 Addresses', default => 'on' },
|
||||
{ name => 'n_netbios', label => 'NetBIOS', default => 'on' },
|
||||
{ name => 'n_ssid', label => 'SSID', default => 'on' },
|
||||
{ name => 'n_vendor', label => 'Vendor', default => '' },
|
||||
{ name => 'n_archived', label => 'Archived Data', default => '' },
|
||||
]);
|
||||
|
||||
return unless (request->path eq uri_for('/device')->path
|
||||
or index(request->path, uri_for('/ajax/content/device')->path) == 0);
|
||||
|
||||
# override ports form defaults with cookie settings
|
||||
|
||||
my $cookie = (cookie('nd_ports-form') || '');
|
||||
my $cdata = url_params_mixed($cookie);
|
||||
|
||||
if ($cdata and ref {} eq ref $cdata and not param('reset')) {
|
||||
foreach my $item (@{ var('port_columns') }) {
|
||||
my $key = $item->{name};
|
||||
next unless defined $cdata->{$key}
|
||||
and $cdata->{$key} =~ m/^[[:alnum:]_]+$/;
|
||||
$item->{default} = $cdata->{$key};
|
||||
}
|
||||
|
||||
foreach my $item (@{ var('connected_properties') }) {
|
||||
my $key = $item->{name};
|
||||
next unless defined $cdata->{$key}
|
||||
and $cdata->{$key} =~ m/^[[:alnum:]_]+$/;
|
||||
$item->{default} = $cdata->{$key};
|
||||
}
|
||||
|
||||
foreach my $key (qw/age_num age_unit mac_format/) {
|
||||
params->{$key} ||= $cdata->{$key}
|
||||
if defined $cdata->{$key}
|
||||
and $cdata->{$key} =~ m/^[[:alnum:]_]+$/;
|
||||
}
|
||||
}
|
||||
|
||||
# copy ports form defaults into request query params if this is
|
||||
# a redirect from within the application (tab param is not set)
|
||||
|
||||
if (param('reset') or not param('tab') or param('tab') ne 'ports') {
|
||||
foreach my $col (@{ var('port_columns') }) {
|
||||
delete params->{$col->{name}};
|
||||
params->{$col->{name}} = 'checked'
|
||||
if $col->{default} eq 'on';
|
||||
}
|
||||
|
||||
foreach my $col (@{ var('connected_properties') }) {
|
||||
delete params->{$col->{name}};
|
||||
params->{$col->{name}} = 'checked'
|
||||
if $col->{default} eq 'on';
|
||||
}
|
||||
|
||||
# not stored in the cookie
|
||||
params->{'age_num'} ||= 3;
|
||||
params->{'age_unit'} ||= 'months';
|
||||
params->{'mac_format'} ||= 'IEEE';
|
||||
|
||||
if (param('reset')) {
|
||||
params->{'age_num'} = 3;
|
||||
params->{'age_unit'} = 'months';
|
||||
params->{'mac_format'} = 'IEEE';
|
||||
|
||||
# nuke the port params cookie
|
||||
cookie('nd_ports-form' => '', expires => '-1 day');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
hook 'before_template' => sub {
|
||||
my $tokens = shift;
|
||||
|
||||
# new searches will use these defaults in their sidebars
|
||||
$tokens->{device_ports} = uri_for('/device', { tab => 'ports' });
|
||||
|
||||
# copy ports form defaults into helper values for building template links
|
||||
|
||||
foreach my $key (qw/age_num age_unit mac_format/) {
|
||||
$tokens->{device_ports}->query_param($key, params->{$key});
|
||||
}
|
||||
|
||||
$tokens->{mac_format_call} = 'as_'. lc(params->{'mac_format'})
|
||||
if params->{'mac_format'};
|
||||
|
||||
foreach my $col (@{ var('port_columns') }) {
|
||||
next unless $col->{default} eq 'on';
|
||||
$tokens->{device_ports}->query_param($col->{name}, 'checked');
|
||||
}
|
||||
|
||||
foreach my $col (@{ var('connected_properties') }) {
|
||||
next unless $col->{default} eq 'on';
|
||||
$tokens->{device_ports}->query_param($col->{name}, 'checked');
|
||||
}
|
||||
|
||||
return unless (request->path eq uri_for('/device')->path
|
||||
or index(request->path, uri_for('/ajax/content/device')->path) == 0);
|
||||
|
||||
# for templates to link to same page with modified query but same options
|
||||
my $self_uri = uri_for(request->path, scalar params);
|
||||
$self_uri->query_param_delete('q');
|
||||
$self_uri->query_param_delete('f');
|
||||
$self_uri->query_param_delete('prefer');
|
||||
$tokens->{self_options} = $self_uri->query_form_hash;
|
||||
};
|
||||
|
||||
get '/device' => require_login sub {
|
||||
my $q = param('q');
|
||||
my $devices = schema('netdisco')->resultset('Device');
|
||||
|
||||
# we are passed either dns or ip
|
||||
my $dev = $devices->search({
|
||||
-or => [
|
||||
\[ 'host(me.ip) = ?' => [ bind_value => $q ] ],
|
||||
'me.dns' => $q,
|
||||
],
|
||||
});
|
||||
|
||||
if ($dev->count == 0) {
|
||||
return redirect uri_for('/', {nosuchdevice => 1, device => $q})->path_query;
|
||||
}
|
||||
|
||||
# if passed dns, need to check for duplicates
|
||||
# and use only ip for q param, if there are duplicates.
|
||||
my $first = $dev->first;
|
||||
my $others = ($devices->search({dns => $first->dns})->count() - 1);
|
||||
|
||||
params->{'tab'} ||= 'details';
|
||||
template 'device', {
|
||||
display_name => ($others ? $first->ip : ($first->dns || $first->ip)),
|
||||
device => params->{'tab'},
|
||||
};
|
||||
};
|
||||
|
||||
true;
|
||||
77
lib/App/Netdisco/Web/GenericReport.pm
Normal file
77
lib/App/Netdisco/Web/GenericReport.pm
Normal file
@@ -0,0 +1,77 @@
|
||||
package App::Netdisco::Web::GenericReport;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::Ajax;
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
use Path::Class 'file';
|
||||
use Safe;
|
||||
|
||||
use vars qw/$config @data/;
|
||||
|
||||
foreach my $report (@{setting('reports')}) {
|
||||
my $r = $report->{tag};
|
||||
|
||||
register_report({
|
||||
tag => $r,
|
||||
label => $report->{label},
|
||||
category => ($report->{category} || 'My Reports'),
|
||||
($report->{hidden} ? (hidden => true) : ()),
|
||||
provides_csv => true,
|
||||
});
|
||||
|
||||
get "/ajax/content/report/$r" => require_login sub {
|
||||
# TODO: this should be done by creating a new Virtual Result class on
|
||||
# the fly (package...) and then calling DBIC register_class on it.
|
||||
|
||||
my $schema = ($report->{database} || 'netdisco');
|
||||
my $rs = schema($schema)->resultset('Virtual::GenericReport')->result_source;
|
||||
|
||||
$rs->view_definition($report->{query});
|
||||
$rs->remove_columns($rs->columns);
|
||||
$rs->add_columns( exists $report->{query_columns}
|
||||
? @{ $report->{query_columns} }
|
||||
: (map {keys %{$_}} @{$report->{columns}})
|
||||
);
|
||||
|
||||
my $set = schema($schema)->resultset('Virtual::GenericReport')
|
||||
->search(undef, {
|
||||
result_class => 'DBIx::Class::ResultClass::HashRefInflator',
|
||||
( (exists $report->{bind_params})
|
||||
? (bind => [map { param($_) } @{ $report->{bind_params} }]) : () ),
|
||||
});
|
||||
@data = $set->all;
|
||||
|
||||
# Data Munging support...
|
||||
|
||||
my $compartment = Safe->new;
|
||||
$config = $report; # closure for the config of this report
|
||||
$compartment->share(qw/$config @data/);
|
||||
$compartment->permit_only(qw/:default sort/);
|
||||
|
||||
my $munger = file(($ENV{NETDISCO_HOME} || $ENV{HOME}), 'site_plugins', $r)->stringify;
|
||||
my @results = ((-f $munger) ? $compartment->rdo( $munger ) : @data);
|
||||
return if $@ or (0 == scalar @results);
|
||||
|
||||
if (request->is_ajax) {
|
||||
template 'ajax/report/generic_report.tt',
|
||||
{ results => \@results,
|
||||
is_custom_report => true,
|
||||
headings => [map {values %{$_}} @{$report->{columns}}],
|
||||
columns => [map {keys %{$_}} @{$report->{columns}}] },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/generic_report_csv.tt',
|
||||
{ results => \@results,
|
||||
headings => [map {values %{$_}} @{$report->{columns}}],
|
||||
columns => [map {keys %{$_}} @{$report->{columns}}] },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
true;
|
||||
51
lib/App/Netdisco/Web/Password.pm
Normal file
51
lib/App/Netdisco/Web/Password.pm
Normal file
@@ -0,0 +1,51 @@
|
||||
package App::Netdisco::Web::Password;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
use Dancer::Plugin::Passphrase;
|
||||
|
||||
use Digest::MD5 ();
|
||||
|
||||
sub _make_password {
|
||||
my $pass = (shift || passphrase->generate_random);
|
||||
if (setting('safe_password_store')) {
|
||||
return passphrase($pass)->generate;
|
||||
}
|
||||
else {
|
||||
return Digest::MD5::md5_hex($pass),
|
||||
}
|
||||
}
|
||||
|
||||
sub _bail {
|
||||
var('passchange_failed' => 1);
|
||||
return template 'password.tt';
|
||||
}
|
||||
|
||||
any ['get', 'post'] => '/password' => require_login sub {
|
||||
my $old = param('old');
|
||||
my $new = param('new');
|
||||
my $confirm = param('confirm');
|
||||
|
||||
if (request->is_post) {
|
||||
unless ($old and $new and $confirm and ($new eq $confirm)) {
|
||||
return _bail();
|
||||
}
|
||||
|
||||
my ($success, $realm) = authenticate_user(
|
||||
session('logged_in_user'), $old
|
||||
);
|
||||
return _bail() if not $success;
|
||||
|
||||
my $user = schema('netdisco')->resultset('User')
|
||||
->find({username => session('logged_in_user')});
|
||||
return _bail() if not $user;
|
||||
|
||||
$user->update({password => _make_password($new)});
|
||||
var('passchange_ok' => 1);
|
||||
}
|
||||
|
||||
template 'password.tt';
|
||||
};
|
||||
|
||||
true;
|
||||
295
lib/App/Netdisco/Web/Plugin.pm
Normal file
295
lib/App/Netdisco/Web/Plugin.pm
Normal file
@@ -0,0 +1,295 @@
|
||||
package App::Netdisco::Web::Plugin;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin;
|
||||
|
||||
use Path::Class 'dir';
|
||||
|
||||
set(
|
||||
'_additional_css' => [],
|
||||
'_additional_javascript' => [],
|
||||
'_extra_device_port_cols' => [],
|
||||
'_extra_device_details' => [],
|
||||
'_navbar_items' => [],
|
||||
'_search_tabs' => [],
|
||||
'_device_tabs' => [],
|
||||
'_admin_tasks' => {},
|
||||
'_admin_order' => [],
|
||||
'_reports_menu' => {},
|
||||
'_reports' => {},
|
||||
'_report_order' => [qw/Device Port IP Node VLAN Network Wireless/, 'My Reports'],
|
||||
);
|
||||
|
||||
# this is what Dancer::Template::TemplateToolkit does by default
|
||||
config->{engines}->{netdisco_template_toolkit}->{INCLUDE_PATH} ||= [ setting('views') ];
|
||||
|
||||
register 'register_template_path' => sub {
|
||||
my ($self, $path) = plugin_args(@_);
|
||||
|
||||
if (!$path) {
|
||||
return error "bad template path to register_template_paths";
|
||||
}
|
||||
|
||||
push @{ config->{engines}->{netdisco_template_toolkit}->{INCLUDE_PATH} },
|
||||
dir($path, 'views')->stringify;
|
||||
};
|
||||
|
||||
sub _register_include {
|
||||
my ($type, $plugin) = @_;
|
||||
|
||||
if (!$type) {
|
||||
return error "bad type to _register_include";
|
||||
}
|
||||
|
||||
if (!$plugin) {
|
||||
return error "bad plugin name to register_$type";
|
||||
}
|
||||
|
||||
push @{ setting("_additional_$type") }, $plugin;
|
||||
}
|
||||
|
||||
register 'register_css' => sub {
|
||||
my ($self, $plugin) = plugin_args(@_);
|
||||
_register_include('css', $plugin);
|
||||
};
|
||||
|
||||
register 'register_javascript' => sub {
|
||||
my ($self, $plugin) = plugin_args(@_);
|
||||
_register_include('javascript', $plugin);
|
||||
};
|
||||
|
||||
register 'register_device_port_column' => sub {
|
||||
my ($self, $config) = plugin_args(@_);
|
||||
$config->{default} ||= '';
|
||||
$config->{position} ||= 'right';
|
||||
|
||||
if (!$config->{name} or !$config->{label}) {
|
||||
return error "bad config to register_device_port_column";
|
||||
}
|
||||
|
||||
foreach my $item (@{ setting('_extra_device_port_cols') }) {
|
||||
if ($item->{name} eq $config->{name}) {
|
||||
$item = $config;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
push @{ setting('_extra_device_port_cols') }, $config;
|
||||
};
|
||||
|
||||
register 'register_device_details' => sub {
|
||||
my ($self, $config) = plugin_args(@_);
|
||||
|
||||
if (!$config->{name} or !$config->{label}) {
|
||||
return error "bad config to register_device_details";
|
||||
}
|
||||
|
||||
foreach my $item (@{ setting('_extra_device_details') }) {
|
||||
if ($item->{name} eq $config->{name}) {
|
||||
$item = $config;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
push @{ setting('_extra_device_details') }, $config;
|
||||
};
|
||||
|
||||
register 'register_navbar_item' => sub {
|
||||
my ($self, $config) = plugin_args(@_);
|
||||
|
||||
if (!$config->{tag}
|
||||
or !$config->{path}
|
||||
or !$config->{label}) {
|
||||
|
||||
return error "bad config to register_navbar_item";
|
||||
}
|
||||
|
||||
foreach my $item (@{ setting('_navbar_items') }) {
|
||||
if ($item->{tag} eq $config->{tag}) {
|
||||
$item = $config;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
push @{ setting('_navbar_items') }, $config;
|
||||
};
|
||||
|
||||
register 'register_admin_task' => sub {
|
||||
my ($self, $config) = plugin_args(@_);
|
||||
|
||||
if (!$config->{tag}
|
||||
or !$config->{label}) {
|
||||
|
||||
return error "bad config to register_admin_task";
|
||||
}
|
||||
|
||||
push @{ setting('_admin_order') }, $config->{tag};
|
||||
setting('_admin_tasks')->{ $config->{tag} } = $config;
|
||||
};
|
||||
|
||||
sub _register_tab {
|
||||
my ($nav, $config) = @_;
|
||||
my $stash = setting("_${nav}_tabs");
|
||||
|
||||
if (!$config->{tag}
|
||||
or !$config->{label}) {
|
||||
|
||||
return error "bad config to register_${nav}_item";
|
||||
}
|
||||
|
||||
foreach my $item (@{ $stash }) {
|
||||
if ($item->{tag} eq $config->{tag}) {
|
||||
$item = $config;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
push @{ $stash }, $config;
|
||||
}
|
||||
|
||||
register 'register_search_tab' => sub {
|
||||
my ($self, $config) = plugin_args(@_);
|
||||
_register_tab('search', $config);
|
||||
};
|
||||
|
||||
register 'register_device_tab' => sub {
|
||||
my ($self, $config) = plugin_args(@_);
|
||||
_register_tab('device', $config);
|
||||
};
|
||||
|
||||
register 'register_report' => sub {
|
||||
my ($self, $config) = plugin_args(@_);
|
||||
my @categories = @{ setting('_report_order') };
|
||||
|
||||
if (!$config->{category}
|
||||
or !$config->{tag}
|
||||
or !$config->{label}
|
||||
or 0 == scalar grep {$config->{category} eq $_} @categories) {
|
||||
|
||||
return error "bad config to register_report";
|
||||
}
|
||||
|
||||
if (0 == scalar grep {$_ eq $config->{tag}}
|
||||
@{setting('_reports_menu')->{ $config->{category} }}) {
|
||||
push @{setting('_reports_menu')->{ $config->{category} }}, $config->{tag};
|
||||
}
|
||||
|
||||
foreach my $tag (@{setting('_reports_menu')->{ $config->{category} }}) {
|
||||
if ($config->{tag} eq $tag) {
|
||||
setting('_reports')->{$tag} = $config;
|
||||
|
||||
foreach my $rconfig (@{setting('reports')}) {
|
||||
if ($rconfig->{tag} eq $tag) {
|
||||
setting('_reports')->{$tag}->{'rconfig'} = $rconfig;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
register_plugin;
|
||||
true;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
App::Netdisco::Web::Plugin - Netdisco Web UI components
|
||||
|
||||
=head1 Introduction
|
||||
|
||||
L<App::Netdisco>'s plugin system allows you more control of what Netdisco
|
||||
components are displayed in the web interface. Plugins can be distributed
|
||||
independently from Netdisco and are a better alternative to source code
|
||||
patches.
|
||||
|
||||
The following web interface components are implemented as plugins:
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
Navigation Bar items (e.g. Inventory link)
|
||||
|
||||
=item *
|
||||
|
||||
Tabs for Search and Device pages
|
||||
|
||||
=item *
|
||||
|
||||
Reports (pre-canned searches)
|
||||
|
||||
=item *
|
||||
|
||||
Additional Device Port Columns
|
||||
|
||||
=item *
|
||||
|
||||
Additional Device Details
|
||||
|
||||
=item *
|
||||
|
||||
Admin Menu function (job control, manual topology, pseudo devices)
|
||||
|
||||
=back
|
||||
|
||||
This document explains how to configure which plugins are loaded. See
|
||||
L<App::Netdisco::Manual::WritingPlugins> if you want to develop new plugins.
|
||||
|
||||
=head1 Application Configuration
|
||||
|
||||
Netdisco configuration supports a C<web_plugins> directive along with the
|
||||
similar C<extra_web_plugins>. These list, in YAML format, the set of Perl
|
||||
module names which are the plugins to be loaded. Each item injects one part of
|
||||
the Netdisco web user interface.
|
||||
|
||||
You can override these settings to add, change, or remove entries from the
|
||||
default lists. Here is an example of the C<web_plugins> list:
|
||||
|
||||
web_plugins:
|
||||
- Inventory
|
||||
- Report::DuplexMismatch
|
||||
- Search::Device
|
||||
- Search::Node
|
||||
- Search::Port
|
||||
- Device::Details
|
||||
- Device::Ports
|
||||
|
||||
Any change should go into your local C<deployment.yml> configuration file. If
|
||||
you want to view the default settings, see the C<share/config.yml> file in the
|
||||
C<App::Netdisco> distribution.
|
||||
|
||||
=head1 How to Configure
|
||||
|
||||
The C<extra_web_plugins> setting is empty, and used only if you want to add
|
||||
new plugins but not change the set enabled by default. If you do want to add
|
||||
to or remove from the default set, then create a version of C<web_plugins>
|
||||
instead.
|
||||
|
||||
Netdisco prepends "C<App::Netdisco::Web::Plugin::>" to any entry in the list.
|
||||
For example, "C<Inventory>" will load the
|
||||
C<App::Netdisco::Web::Plugin::Inventory> module.
|
||||
|
||||
Such plugin modules can either ship with the App::Netdisco distribution
|
||||
itself, or be installed separately. Perl uses the standard C<@INC> path
|
||||
searching mechanism to load the plugin modules.
|
||||
|
||||
If an entry in the list starts with a "C<+>" (plus) sign then Netdisco attemps
|
||||
to load the module as-is, without prepending anything to the name. This allows
|
||||
you to have App::Netdiso web UI plugins in other namespaces:
|
||||
|
||||
web_plugins:
|
||||
- Inventory
|
||||
- Search::Device
|
||||
- Device::Details
|
||||
- +My::Other::Netdisco::Web::Component
|
||||
|
||||
The order of the entries is significant. Unsurprisingly, the modules are
|
||||
loaded in order. Therefore Navigation Bar items appear in the order listed,
|
||||
and Tabs appear on the Search and Device pages in the order listed, and so on.
|
||||
|
||||
Finally, you can also prepend module names with "C<X::>", to support the
|
||||
"Netdisco extension" namespace. For example, "C<X::Observium>" will load the
|
||||
L<App::NetdiscoX::Web::Plugin::Observium> module.
|
||||
|
||||
=cut
|
||||
|
||||
32
lib/App/Netdisco/Web/Plugin/AdminTask/JobQueue.pm
Normal file
32
lib/App/Netdisco/Web/Plugin/AdminTask/JobQueue.pm
Normal file
@@ -0,0 +1,32 @@
|
||||
package App::Netdisco::Web::Plugin::AdminTask::JobQueue;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::Ajax;
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
use App::Netdisco::JobQueue qw/jq_log jq_delete/;
|
||||
|
||||
register_admin_task({
|
||||
tag => 'jobqueue',
|
||||
label => 'Job Queue',
|
||||
});
|
||||
|
||||
ajax '/ajax/control/admin/jobqueue/del' => require_role admin => sub {
|
||||
send_error('Missing job', 400) unless param('job');
|
||||
jq_delete( param('job') );
|
||||
};
|
||||
|
||||
ajax '/ajax/control/admin/jobqueue/delall' => require_role admin => sub {
|
||||
jq_delete();
|
||||
};
|
||||
|
||||
ajax '/ajax/content/admin/jobqueue' => require_role admin => sub {
|
||||
content_type('text/html');
|
||||
template 'ajax/admintask/jobqueue.tt', {
|
||||
results => [ jq_log ],
|
||||
}, { layout => undef };
|
||||
};
|
||||
|
||||
true;
|
||||
75
lib/App/Netdisco/Web/Plugin/AdminTask/NodeMonitor.pm
Normal file
75
lib/App/Netdisco/Web/Plugin/AdminTask/NodeMonitor.pm
Normal file
@@ -0,0 +1,75 @@
|
||||
package App::Netdisco::Web::Plugin::AdminTask::NodeMonitor;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::Ajax;
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
use App::Netdisco::Util::Node 'check_mac';
|
||||
|
||||
register_admin_task({
|
||||
tag => 'nodemonitor',
|
||||
label => 'Node Monitor',
|
||||
});
|
||||
|
||||
sub _sanity_ok {
|
||||
return 0 unless param('mac')
|
||||
and check_mac(undef, param('mac'));
|
||||
|
||||
params->{mac} = check_mac(undef, param('mac'));
|
||||
return 1;
|
||||
}
|
||||
|
||||
ajax '/ajax/control/admin/nodemonitor/add' => require_role admin => sub {
|
||||
send_error('Bad Request', 400) unless _sanity_ok();
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $monitor = schema('netdisco')->resultset('NodeMonitor')
|
||||
->create({
|
||||
mac => param('mac'),
|
||||
active => (param('active') ? \'true' : \'false'),
|
||||
why => param('why'),
|
||||
cc => param('cc'),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
ajax '/ajax/control/admin/nodemonitor/del' => require_role admin => sub {
|
||||
send_error('Bad Request', 400) unless _sanity_ok();
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
schema('netdisco')->resultset('NodeMonitor')
|
||||
->find({mac => param('mac')})->delete;
|
||||
});
|
||||
};
|
||||
|
||||
ajax '/ajax/control/admin/nodemonitor/update' => require_role admin => sub {
|
||||
send_error('Bad Request', 400) unless _sanity_ok();
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $monitor = schema('netdisco')->resultset('NodeMonitor')
|
||||
->find({mac => param('mac')});
|
||||
return unless $monitor;
|
||||
|
||||
$monitor->update({
|
||||
mac => param('mac'),
|
||||
active => (param('active') ? \'true' : \'false'),
|
||||
why => param('why'),
|
||||
cc => param('cc'),
|
||||
date => \'now()',
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
ajax '/ajax/content/admin/nodemonitor' => require_role admin => sub {
|
||||
my $set = schema('netdisco')->resultset('NodeMonitor')
|
||||
->search(undef, { order_by => [qw/active date mac/] });
|
||||
|
||||
content_type('text/html');
|
||||
template 'ajax/admintask/nodemonitor.tt', {
|
||||
results => $set,
|
||||
}, { layout => undef };
|
||||
};
|
||||
|
||||
true;
|
||||
82
lib/App/Netdisco/Web/Plugin/AdminTask/OrphanedDevices.pm
Normal file
82
lib/App/Netdisco/Web/Plugin/AdminTask/OrphanedDevices.pm
Normal file
@@ -0,0 +1,82 @@
|
||||
package App::Netdisco::Web::Plugin::AdminTask::OrphanedDevices;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_admin_task(
|
||||
{ tag => 'orphaned',
|
||||
label => 'Orphaned Devices / Networks',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
get '/ajax/content/admin/orphaned' => require_role admin => sub {
|
||||
|
||||
my @tree = schema('netdisco')->resultset('Virtual::UnDirEdgesAgg')
|
||||
->search( undef, { prefetch => 'device' } )->hri->all;
|
||||
|
||||
my @orphans
|
||||
= schema('netdisco')->resultset('Virtual::OrphanedDevices')->search()
|
||||
->order_by('ip')->hri->all;
|
||||
|
||||
return unless ( scalar @tree || scalar @orphans );
|
||||
|
||||
my @ordered;
|
||||
|
||||
if ( scalar @tree ) {
|
||||
my %tree = map { $_->{'left_ip'} => $_ } @tree;
|
||||
|
||||
my $current_graph = 0;
|
||||
my %visited = ();
|
||||
my @to_visit = ();
|
||||
foreach my $node ( keys %tree ) {
|
||||
next if exists $visited{$node};
|
||||
|
||||
$current_graph++;
|
||||
@to_visit = ($node);
|
||||
while (@to_visit) {
|
||||
my $node_to_visit = shift @to_visit;
|
||||
|
||||
$visited{$node_to_visit} = $current_graph;
|
||||
|
||||
push @to_visit,
|
||||
grep { !exists $visited{$_} }
|
||||
@{ $tree{$node_to_visit}->{'links'} };
|
||||
}
|
||||
}
|
||||
|
||||
my @graphs = ();
|
||||
foreach my $key ( keys %visited ) {
|
||||
push @{ $graphs[ $visited{$key} - 1 ] }, $tree{$key}->{'device'};
|
||||
}
|
||||
|
||||
@ordered = sort { scalar @{$b} <=> scalar @{$a} } @graphs;
|
||||
}
|
||||
|
||||
return if ( scalar @ordered < 2 && !scalar @tree );
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
template 'ajax/admintask/orphaned.tt',
|
||||
{
|
||||
orphans => \@orphans,
|
||||
graphs => \@ordered,
|
||||
},
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/admintask/orphaned_csv.tt',
|
||||
{
|
||||
orphans => \@orphans,
|
||||
graphs => \@ordered,
|
||||
},
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
24
lib/App/Netdisco/Web/Plugin/AdminTask/PollerPerformance.pm
Normal file
24
lib/App/Netdisco/Web/Plugin/AdminTask/PollerPerformance.pm
Normal file
@@ -0,0 +1,24 @@
|
||||
package App::Netdisco::Web::Plugin::AdminTask::PollerPerformance;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::Ajax;
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_admin_task({
|
||||
tag => 'performance',
|
||||
label => 'Poller Performance',
|
||||
});
|
||||
|
||||
ajax '/ajax/content/admin/performance' => require_role admin => sub {
|
||||
my $set = schema('netdisco')->resultset('Virtual::PollerPerformance');
|
||||
|
||||
content_type('text/html');
|
||||
template 'ajax/admintask/performance.tt', {
|
||||
results => $set,
|
||||
}, { layout => undef };
|
||||
};
|
||||
|
||||
true;
|
||||
100
lib/App/Netdisco/Web/Plugin/AdminTask/PseudoDevice.pm
Normal file
100
lib/App/Netdisco/Web/Plugin/AdminTask/PseudoDevice.pm
Normal file
@@ -0,0 +1,100 @@
|
||||
package App::Netdisco::Web::Plugin::AdminTask::PseudoDevice;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::Ajax;
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
use NetAddr::IP::Lite ':lower';
|
||||
|
||||
register_admin_task({
|
||||
tag => 'pseudodevice',
|
||||
label => 'Pseudo Devices',
|
||||
});
|
||||
|
||||
sub _sanity_ok {
|
||||
return 0 unless param('dns')
|
||||
and param('dns') =~ m/^[[:print:]]+$/
|
||||
and param('dns') !~ m/[[:space:]]/;
|
||||
|
||||
my $ip = NetAddr::IP::Lite->new(param('ip'));
|
||||
return 0 unless ($ip and $ip->addr ne '0.0.0.0');
|
||||
|
||||
return 0 unless param('ports')
|
||||
and param('ports') =~ m/^[[:digit:]]+$/;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
ajax '/ajax/control/admin/pseudodevice/add' => require_role admin => sub {
|
||||
send_error('Bad Request', 400) unless _sanity_ok();
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $device = schema('netdisco')->resultset('Device')
|
||||
->create({
|
||||
ip => param('ip'),
|
||||
dns => param('dns'),
|
||||
vendor => 'netdisco',
|
||||
last_discover => \'now()',
|
||||
});
|
||||
return unless $device;
|
||||
|
||||
$device->ports->populate([
|
||||
[qw/port type/],
|
||||
map {["Port$_", 'other']} @{[1 .. param('ports')]},
|
||||
]);
|
||||
|
||||
# device_ip table is used to show whether topo is "broken"
|
||||
schema('netdisco')->resultset('DeviceIp')
|
||||
->create({
|
||||
ip => param('ip'),
|
||||
alias => param('ip'),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
ajax '/ajax/control/admin/pseudodevice/del' => require_role admin => sub {
|
||||
send_error('Bad Request', 400) unless _sanity_ok();
|
||||
forward '/ajax/control/admin/delete', { device => param('ip') };
|
||||
};
|
||||
|
||||
ajax '/ajax/control/admin/pseudodevice/update' => require_role admin => sub {
|
||||
send_error('Bad Request', 400) unless _sanity_ok();
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $device = schema('netdisco')->resultset('Device')
|
||||
->with_port_count->find({ip => param('ip')});
|
||||
return unless $device;
|
||||
my $count = $device->port_count;
|
||||
|
||||
if (param('ports') > $count) {
|
||||
my $start = $count + 1;
|
||||
$device->ports->populate([
|
||||
[qw/port type/],
|
||||
map {["Port$_", 'other']} @{[$start .. param('ports')]},
|
||||
]);
|
||||
}
|
||||
elsif (param('ports') < $count) {
|
||||
my $start = param('ports') + 1;
|
||||
$device->ports
|
||||
->single({port => "Port$_"})->delete
|
||||
for ($start .. $count);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ajax '/ajax/content/admin/pseudodevice' => require_role admin => sub {
|
||||
my $set = schema('netdisco')->resultset('Device')
|
||||
->search(
|
||||
{vendor => 'netdisco'},
|
||||
{order_by => { -desc => 'last_discover' }},
|
||||
)->with_port_count;
|
||||
|
||||
content_type('text/html');
|
||||
template 'ajax/admintask/pseudodevice.tt', {
|
||||
results => $set,
|
||||
}, { layout => undef };
|
||||
};
|
||||
|
||||
true;
|
||||
24
lib/App/Netdisco/Web/Plugin/AdminTask/SlowDevices.pm
Normal file
24
lib/App/Netdisco/Web/Plugin/AdminTask/SlowDevices.pm
Normal file
@@ -0,0 +1,24 @@
|
||||
package App::Netdisco::Web::Plugin::AdminTask::SlowDevices;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::Ajax;
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_admin_task({
|
||||
tag => 'slowdevices',
|
||||
label => 'Slowest Devices',
|
||||
});
|
||||
|
||||
ajax '/ajax/content/admin/slowdevices' => require_role admin => sub {
|
||||
my $set = schema('netdisco')->resultset('Virtual::SlowDevices');
|
||||
|
||||
content_type('text/html');
|
||||
template 'ajax/admintask/slowdevices.tt', {
|
||||
results => $set,
|
||||
}, { layout => undef };
|
||||
};
|
||||
|
||||
true;
|
||||
142
lib/App/Netdisco/Web/Plugin/AdminTask/Topology.pm
Normal file
142
lib/App/Netdisco/Web/Plugin/AdminTask/Topology.pm
Normal file
@@ -0,0 +1,142 @@
|
||||
package App::Netdisco::Web::Plugin::AdminTask::Topology;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::Ajax;
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
use App::Netdisco::Util::Device 'get_device';
|
||||
|
||||
use Try::Tiny;
|
||||
use NetAddr::IP::Lite ':lower';
|
||||
|
||||
register_admin_task({
|
||||
tag => 'topology',
|
||||
label => 'Manual Device Topology',
|
||||
});
|
||||
|
||||
sub _sanity_ok {
|
||||
my $dev1 = NetAddr::IP::Lite->new(param('dev1'));
|
||||
return 0 unless ($dev1 and $dev1->addr ne '0.0.0.0');
|
||||
|
||||
my $dev2 = NetAddr::IP::Lite->new(param('dev2'));
|
||||
return 0 unless ($dev2 and $dev2->addr ne '0.0.0.0');
|
||||
|
||||
return 0 unless param('port1');
|
||||
return 0 unless param('port2');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
ajax '/ajax/control/admin/topology/add' => require_role admin => sub {
|
||||
send_error('Bad Request', 400) unless _sanity_ok();
|
||||
|
||||
my $device = schema('netdisco')->resultset('Topology')
|
||||
->create({
|
||||
dev1 => param('dev1'),
|
||||
port1 => param('port1'),
|
||||
dev2 => param('dev2'),
|
||||
port2 => param('port2'),
|
||||
});
|
||||
|
||||
# re-set remote device details in affected ports
|
||||
# could fail for bad device or port names
|
||||
try {
|
||||
schema('netdisco')->txn_do(sub {
|
||||
# only work on root_ips
|
||||
my $left = get_device(param('dev1'));
|
||||
my $right = get_device(param('dev2'));
|
||||
|
||||
# skip bad entries
|
||||
return unless ($left->in_storage and $right->in_storage);
|
||||
|
||||
$left->ports
|
||||
->search({port => param('port1')}, {for => 'update'})
|
||||
->single()
|
||||
->update({
|
||||
remote_ip => param('dev2'),
|
||||
remote_port => param('port2'),
|
||||
remote_type => undef,
|
||||
remote_id => undef,
|
||||
is_uplink => \"true",
|
||||
manual_topo => \"true",
|
||||
});
|
||||
|
||||
$right->ports
|
||||
->search({port => param('port2')}, {for => 'update'})
|
||||
->single()
|
||||
->update({
|
||||
remote_ip => param('dev1'),
|
||||
remote_port => param('port1'),
|
||||
remote_type => undef,
|
||||
remote_id => undef,
|
||||
is_uplink => \"true",
|
||||
manual_topo => \"true",
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
ajax '/ajax/control/admin/topology/del' => require_role admin => sub {
|
||||
send_error('Bad Request', 400) unless _sanity_ok();
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $device = schema('netdisco')->resultset('Topology')
|
||||
->search({
|
||||
dev1 => param('dev1'),
|
||||
port1 => param('port1'),
|
||||
dev2 => param('dev2'),
|
||||
port2 => param('port2'),
|
||||
})->delete;
|
||||
});
|
||||
|
||||
# re-set remote device details in affected ports
|
||||
# could fail for bad device or port names
|
||||
try {
|
||||
schema('netdisco')->txn_do(sub {
|
||||
# only work on root_ips
|
||||
my $left = get_device(param('dev1'));
|
||||
my $right = get_device(param('dev2'));
|
||||
|
||||
# skip bad entries
|
||||
return unless ($left->in_storage and $right->in_storage);
|
||||
|
||||
$left->ports
|
||||
->search({port => param('port1')}, {for => 'update'})
|
||||
->single()
|
||||
->update({
|
||||
remote_ip => undef,
|
||||
remote_port => undef,
|
||||
remote_type => undef,
|
||||
remote_id => undef,
|
||||
is_uplink => \"false",
|
||||
manual_topo => \"false",
|
||||
});
|
||||
|
||||
$right->ports
|
||||
->search({port => param('port2')}, {for => 'update'})
|
||||
->single()
|
||||
->update({
|
||||
remote_ip => undef,
|
||||
remote_port => undef,
|
||||
remote_type => undef,
|
||||
remote_id => undef,
|
||||
is_uplink => \"false",
|
||||
manual_topo => \"false",
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
ajax '/ajax/content/admin/topology' => require_role admin => sub {
|
||||
my $set = schema('netdisco')->resultset('Topology')
|
||||
->search({},{order_by => [qw/dev1 dev2 port1/]});
|
||||
|
||||
content_type('text/html');
|
||||
template 'ajax/admintask/topology.tt', {
|
||||
results => $set,
|
||||
}, { layout => undef };
|
||||
};
|
||||
|
||||
true;
|
||||
@@ -0,0 +1,48 @@
|
||||
package App::Netdisco::Web::Plugin::AdminTask::UndiscoveredNeighbors;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
use App::Netdisco::Util::Device qw/is_discoverable/;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_admin_task(
|
||||
{ tag => 'undiscoveredneighbors',
|
||||
label => 'Undiscovered Neighbors',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
get '/ajax/content/admin/undiscoveredneighbors' => require_role admin => sub {
|
||||
my @results
|
||||
= schema('netdisco')->resultset('Virtual::UndiscoveredNeighbors')
|
||||
->order_by('ip')->hri->all;
|
||||
return unless scalar @results;
|
||||
|
||||
# Don't include devices excluded from discovery by config
|
||||
# but only if the number of devices is small, as it triggers a
|
||||
# SELECT per device to check.
|
||||
if (scalar @results < 50) {
|
||||
@results
|
||||
= grep { is_discoverable( $_->{'remote_ip'}, $_->{'remote_type'} ) }
|
||||
@results;
|
||||
}
|
||||
return unless scalar @results;
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
template 'ajax/admintask/undiscoveredneighbors.tt',
|
||||
{ results => \@results, },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/admintask/undiscoveredneighbors_csv.tt',
|
||||
{ results => \@results, },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
66
lib/App/Netdisco/Web/Plugin/AdminTask/UserLog.pm
Normal file
66
lib/App/Netdisco/Web/Plugin/AdminTask/UserLog.pm
Normal file
@@ -0,0 +1,66 @@
|
||||
package App::Netdisco::Web::Plugin::AdminTask::UserLog;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::Ajax;
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
use App::Netdisco::Util::ExpandParams 'expand_hash';
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_admin_task(
|
||||
{ tag => 'userlog',
|
||||
label => 'User Activity Log',
|
||||
}
|
||||
);
|
||||
|
||||
ajax '/ajax/control/admin/userlog/data' => require_role admin => sub {
|
||||
send_error( 'Missing parameter', 400 )
|
||||
unless ( param('draw') && param('draw') =~ /\d+/ );
|
||||
|
||||
my $rs = schema('netdisco')->resultset('UserLog');
|
||||
|
||||
my $exp_params = expand_hash( scalar params );
|
||||
|
||||
my $recordsTotal = $rs->count;
|
||||
|
||||
my @data = $rs->get_datatables_data($exp_params)->hri->all;
|
||||
|
||||
my $recordsFiltered = $rs->get_datatables_filtered_count($exp_params);
|
||||
|
||||
content_type 'application/json';
|
||||
return to_json(
|
||||
{ draw => int( param('draw') ),
|
||||
recordsTotal => int($recordsTotal),
|
||||
recordsFiltered => int($recordsFiltered),
|
||||
data => \@data,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
ajax '/ajax/control/admin/userlog/del' => require_role admin => sub {
|
||||
send_error( 'Missing entry', 400 ) unless param('entry');
|
||||
|
||||
schema('netdisco')->txn_do(
|
||||
sub {
|
||||
my $device = schema('netdisco')->resultset('UserLog')
|
||||
->search( { entry => param('entry') } )->delete;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
ajax '/ajax/control/admin/userlog/delall' => require_role admin => sub {
|
||||
schema('netdisco')->txn_do(
|
||||
sub {
|
||||
my $device = schema('netdisco')->resultset('UserLog')->delete;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
ajax '/ajax/content/admin/userlog' => require_role admin => sub {
|
||||
|
||||
content_type('text/html');
|
||||
template 'ajax/admintask/userlog.tt', {}, { layout => undef };
|
||||
};
|
||||
|
||||
1;
|
||||
106
lib/App/Netdisco/Web/Plugin/AdminTask/Users.pm
Normal file
106
lib/App/Netdisco/Web/Plugin/AdminTask/Users.pm
Normal file
@@ -0,0 +1,106 @@
|
||||
package App::Netdisco::Web::Plugin::AdminTask::Users;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::Ajax;
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
use Dancer::Plugin::Passphrase;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
use Digest::MD5 ();
|
||||
|
||||
register_admin_task({
|
||||
tag => 'users',
|
||||
label => 'User Management',
|
||||
provides_csv => 1,
|
||||
});
|
||||
|
||||
sub _sanity_ok {
|
||||
return 0 unless param('username')
|
||||
and param('username') =~ m/^[[:print:] ]+$/;
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub _make_password {
|
||||
my $pass = (shift || passphrase->generate_random);
|
||||
if (setting('safe_password_store')) {
|
||||
return passphrase($pass)->generate;
|
||||
}
|
||||
else {
|
||||
return Digest::MD5::md5_hex($pass),
|
||||
}
|
||||
}
|
||||
|
||||
ajax '/ajax/control/admin/users/add' => require_role admin => sub {
|
||||
send_error('Bad Request', 400) unless _sanity_ok();
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $user = schema('netdisco')->resultset('User')
|
||||
->create({
|
||||
username => param('username'),
|
||||
password => _make_password(param('password')),
|
||||
fullname => param('fullname'),
|
||||
ldap => (param('ldap') ? \'true' : \'false'),
|
||||
port_control => (param('port_control') ? \'true' : \'false'),
|
||||
admin => (param('admin') ? \'true' : \'false'),
|
||||
note => param('note'),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
ajax '/ajax/control/admin/users/del' => require_role admin => sub {
|
||||
send_error('Bad Request', 400) unless _sanity_ok();
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
schema('netdisco')->resultset('User')
|
||||
->find({username => param('username')})->delete;
|
||||
});
|
||||
};
|
||||
|
||||
ajax '/ajax/control/admin/users/update' => require_role admin => sub {
|
||||
send_error('Bad Request', 400) unless _sanity_ok();
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $user = schema('netdisco')->resultset('User')
|
||||
->find({username => param('username')});
|
||||
return unless $user;
|
||||
|
||||
$user->update({
|
||||
((param('password') ne '********')
|
||||
? (password => _make_password(param('password')))
|
||||
: ()),
|
||||
fullname => param('fullname'),
|
||||
ldap => (param('ldap') ? \'true' : \'false'),
|
||||
port_control => (param('port_control') ? \'true' : \'false'),
|
||||
admin => (param('admin') ? \'true' : \'false'),
|
||||
note => param('note'),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
get '/ajax/content/admin/users' => require_role admin => sub {
|
||||
my @results = schema('netdisco')->resultset('User')
|
||||
->search(undef, {
|
||||
'+columns' => {
|
||||
created => \"to_char(creation, 'YYYY-MM-DD HH24:MI')",
|
||||
last_seen => \"to_char(last_on, 'YYYY-MM-DD HH24:MI')",
|
||||
},
|
||||
order_by => [qw/fullname username/]
|
||||
})->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
template 'ajax/admintask/users.tt',
|
||||
{ results => \@results, },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/admintask/users_csv.tt',
|
||||
{ results => \@results, },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
true;
|
||||
35
lib/App/Netdisco/Web/Plugin/Device/Addresses.pm
Normal file
35
lib/App/Netdisco/Web/Plugin/Device/Addresses.pm
Normal file
@@ -0,0 +1,35 @@
|
||||
package App::Netdisco::Web::Plugin::Device::Addresses;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_device_tab( { tag => 'addresses', label => 'Addresses', provides_csv => 1 } );
|
||||
|
||||
# device interface addresses
|
||||
get '/ajax/content/device/addresses' => require_login sub {
|
||||
my $q = param('q');
|
||||
|
||||
my $device
|
||||
= schema('netdisco')->resultset('Device')->search_for_device($q)
|
||||
or send_error( 'Bad device', 400 );
|
||||
|
||||
my @results = $device->device_ips->search( {}, { order_by => 'alias' } )->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
|
||||
if (request->is_ajax) {
|
||||
my $json = to_json( \@results );
|
||||
template 'ajax/device/addresses.tt', { results => $json },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/device/addresses_csv.tt', { results => \@results },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
33
lib/App/Netdisco/Web/Plugin/Device/Details.pm
Normal file
33
lib/App/Netdisco/Web/Plugin/Device/Details.pm
Normal file
@@ -0,0 +1,33 @@
|
||||
package App::Netdisco::Web::Plugin::Device::Details;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::Ajax;
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_device_tab({ tag => 'details', label => 'Details' });
|
||||
|
||||
# device details table
|
||||
ajax '/ajax/content/device/details' => require_login sub {
|
||||
my $q = param('q');
|
||||
my $device = schema('netdisco')->resultset('Device')
|
||||
->search_for_device($q) or send_error('Bad device', 400);
|
||||
|
||||
my @results
|
||||
= schema('netdisco')->resultset('Device')
|
||||
->search( { 'me.ip' => $device->ip } )->with_times()
|
||||
->hri->all;
|
||||
|
||||
my @power
|
||||
= schema('netdisco')->resultset('DevicePower')
|
||||
->search( { 'me.ip' => $device->ip } )->with_poestats->hri->all;
|
||||
|
||||
content_type('text/html');
|
||||
template 'ajax/device/details.tt', {
|
||||
d => $results[0], p => \@power
|
||||
}, { layout => undef };
|
||||
};
|
||||
|
||||
1;
|
||||
30
lib/App/Netdisco/Web/Plugin/Device/Modules.pm
Normal file
30
lib/App/Netdisco/Web/Plugin/Device/Modules.pm
Normal file
@@ -0,0 +1,30 @@
|
||||
package App::Netdisco::Web::Plugin::Device::Modules;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::Ajax;
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Util::Web (); # for sort_module
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_device_tab({ tag => 'modules', label => 'Modules' });
|
||||
|
||||
ajax '/ajax/content/device/modules' => require_login sub {
|
||||
my $q = param('q');
|
||||
|
||||
my $device = schema('netdisco')->resultset('Device')
|
||||
->search_for_device($q) or send_error('Bad device', 400);
|
||||
my @set = $device->modules->search({}, {order_by => { -asc => [qw/parent class pos index/] }});
|
||||
|
||||
# sort modules (empty set would be a 'no records' msg)
|
||||
my $results = &App::Netdisco::Util::Web::sort_modules( \@set );
|
||||
return unless scalar %$results;
|
||||
|
||||
content_type('text/html');
|
||||
template 'ajax/device/modules.tt', {
|
||||
nodes => $results,
|
||||
}, { layout => undef };
|
||||
};
|
||||
|
||||
true;
|
||||
129
lib/App/Netdisco/Web/Plugin/Device/Neighbors.pm
Normal file
129
lib/App/Netdisco/Web/Plugin/Device/Neighbors.pm
Normal file
@@ -0,0 +1,129 @@
|
||||
package App::Netdisco::Web::Plugin::Device::Neighbors;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::Ajax;
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_device_tab({ tag => 'netmap', label => 'Neighbors' });
|
||||
|
||||
ajax '/ajax/content/device/netmap' => require_login 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, $step, $limit) = @_;
|
||||
|
||||
return $step if $limit and $step > $limit;
|
||||
my @legit = ();
|
||||
my $max = $step;
|
||||
|
||||
foreach my $c (@$childs) {
|
||||
next if exists var('seen')->{$c};
|
||||
var('seen')->{$c}++;
|
||||
push @legit, $c;
|
||||
push @{$ptr}, {
|
||||
name => _get_name($c),
|
||||
fullname => (var('devices')->{$c} || $c),
|
||||
ip => $c,
|
||||
};
|
||||
}
|
||||
|
||||
for (my $i = 0; $i < @legit; $i++) {
|
||||
$ptr->[$i]->{children} = [];
|
||||
my $nm = _add_children($ptr->[$i]->{children}, var('links')->{$legit[$i]},
|
||||
($step + 1), $limit);
|
||||
$max = $nm if $nm > $max;
|
||||
}
|
||||
|
||||
return $max;
|
||||
}
|
||||
|
||||
# d3 seems not to use proper ajax semantics, so get instead of ajax
|
||||
get '/ajax/data/device/netmap' => require_login sub {
|
||||
my $q = param('q');
|
||||
|
||||
my $vlan = param('vlan');
|
||||
undef $vlan if (defined $vlan and $vlan !~ m/^\d+$/);
|
||||
|
||||
my $depth = (param('depth') || 8);
|
||||
undef $depth if (defined $depth and $depth !~ m/^\d+$/);
|
||||
|
||||
my $device = schema('netdisco')->resultset('Device')
|
||||
->search_for_device($q) or send_error('Bad device', 400);
|
||||
my $start = $device->ip;
|
||||
|
||||
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({}, {
|
||||
columns => [qw/left_ip right_ip/],
|
||||
result_class => 'DBIx::Class::ResultClass::HashRefInflator',
|
||||
});
|
||||
|
||||
if ($vlan) {
|
||||
$rs = $rs->search({
|
||||
'left_vlans.vlan' => $vlan,
|
||||
'right_vlans.vlan' => $vlan,
|
||||
}, {
|
||||
join => [qw/left_vlans right_vlans/],
|
||||
});
|
||||
}
|
||||
|
||||
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),
|
||||
fullname => (var('devices')->{$start} || $start),
|
||||
children => [],
|
||||
);
|
||||
|
||||
var(seen => {$start => 1});
|
||||
my $max = _add_children($tree{children}, var('links')->{$start}, 1, $depth);
|
||||
$tree{scale} = $max;
|
||||
|
||||
content_type('application/json');
|
||||
to_json(\%tree);
|
||||
};
|
||||
|
||||
ajax '/ajax/data/device/alldevicelinks' => require_login 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');
|
||||
to_json(\%tree);
|
||||
};
|
||||
|
||||
true;
|
||||
192
lib/App/Netdisco/Web/Plugin/Device/Ports.pm
Normal file
192
lib/App/Netdisco/Web/Plugin/Device/Ports.pm
Normal file
@@ -0,0 +1,192 @@
|
||||
package App::Netdisco::Web::Plugin::Device::Ports;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Util::Web (); # for sort_port
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_device_tab({ tag => 'ports', label => 'Ports', provides_csv => 1 });
|
||||
|
||||
# device ports with a description (er, name) matching
|
||||
get '/ajax/content/device/ports' => require_login sub {
|
||||
my $q = param('q');
|
||||
my $prefer = param('prefer');
|
||||
$prefer = ''
|
||||
unless defined $prefer and $prefer =~ m/^(?:port|name|vlan)$/;
|
||||
|
||||
my $device = schema('netdisco')->resultset('Device')
|
||||
->search_for_device($q) or send_error('Bad device', 400);
|
||||
my $set = $device->ports;
|
||||
|
||||
# refine by ports if requested
|
||||
my $f = param('f');
|
||||
if ($f) {
|
||||
if (($prefer eq 'vlan') or not $prefer and $f =~ m/^\d+$/) {
|
||||
if (param('invert')) {
|
||||
$set = $set->search({
|
||||
'me.vlan' => { '!=' => $f },
|
||||
'port_vlans.vlan' => [
|
||||
'-or' => { '!=' => $f }, { '=' => undef }
|
||||
],
|
||||
}, { join => 'port_vlans' });
|
||||
}
|
||||
else {
|
||||
$set = $set->search({
|
||||
-or => {
|
||||
'me.vlan' => $f,
|
||||
'port_vlans.vlan' => $f,
|
||||
},
|
||||
}, { join => 'port_vlans' });
|
||||
}
|
||||
|
||||
return unless $set->count;
|
||||
}
|
||||
else {
|
||||
if (param('partial')) {
|
||||
# change wildcard chars to SQL
|
||||
$f =~ s/\*/%/g;
|
||||
$f =~ s/\?/_/g;
|
||||
# set wilcards at param boundaries
|
||||
if ($f !~ m/[%_]/) {
|
||||
$f =~ s/^\%*/%/;
|
||||
$f =~ s/\%*$/%/;
|
||||
}
|
||||
# enable ILIKE op
|
||||
$f = { (param('invert') ? '-not_ilike' : '-ilike') => $f };
|
||||
}
|
||||
elsif (param('invert')) {
|
||||
$f = { '!=' => $f };
|
||||
}
|
||||
|
||||
if (($prefer eq 'port') or not $prefer and
|
||||
$set->search({'me.port' => $f})->count) {
|
||||
|
||||
$set = $set->search({
|
||||
-or => [
|
||||
'me.port' => $f,
|
||||
'me.slave_of' => $f,
|
||||
],
|
||||
});
|
||||
}
|
||||
else {
|
||||
$set = $set->search({'me.name' => $f});
|
||||
return unless $set->count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# filter for port status if asked
|
||||
my %port_state = map {$_ => 1}
|
||||
(ref [] eq ref param('port_state') ? @{param('port_state')}
|
||||
: param('port_state') ? param('port_state') : ());
|
||||
|
||||
return unless scalar keys %port_state;
|
||||
|
||||
if (exists $port_state{free}) {
|
||||
if (scalar keys %port_state == 1) {
|
||||
$set = $set->only_free_ports({
|
||||
age_num => (param('age_num') || 3),
|
||||
age_unit => (param('age_unit') || 'months')
|
||||
});
|
||||
}
|
||||
else {
|
||||
$set = $set->with_is_free({
|
||||
age_num => (param('age_num') || 3),
|
||||
age_unit => (param('age_unit') || 'months')
|
||||
});
|
||||
}
|
||||
delete $port_state{free};
|
||||
}
|
||||
|
||||
if (scalar keys %port_state < 3) {
|
||||
my @combi = ();
|
||||
|
||||
push @combi, {'me.up' => 'up'}
|
||||
if exists $port_state{up};
|
||||
push @combi, {'me.up_admin' => 'up', 'me.up' => { '!=' => 'up'}}
|
||||
if exists $port_state{down};
|
||||
push @combi, {'me.up_admin' => { '!=' => 'up'}}
|
||||
if exists $port_state{shut};
|
||||
|
||||
$set = $set->search({-or => \@combi});
|
||||
}
|
||||
|
||||
# get aggregate master status
|
||||
$set = $set->search({}, {
|
||||
'join' => 'agg_master',
|
||||
'+select' => [qw/agg_master.up_admin agg_master.up/],
|
||||
'+as' => [qw/agg_master_up_admin agg_master_up/],
|
||||
});
|
||||
|
||||
# make sure query asks for formatted timestamps when needed
|
||||
$set = $set->with_times if param('c_lastchange');
|
||||
|
||||
# get vlans on the port, if there aren't too many
|
||||
my $port_cnt = $device->ports->count() || 1;
|
||||
my $vlan_cnt = $device->port_vlans->count() || 1;
|
||||
my $vmember_ok =
|
||||
(($vlan_cnt / $port_cnt) <= setting('devport_vlan_limit'));
|
||||
|
||||
if ($vmember_ok) {
|
||||
$set = $set->search_rs({}, { prefetch => 'all_port_vlans' })->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('n_age');
|
||||
|
||||
if (param('c_nodes')) {
|
||||
my $ips = ((param('n_ip4') and param('n_ip6')) ? 'ips'
|
||||
: param('n_ip4') ? 'ip4s'
|
||||
: 'ip6s');
|
||||
|
||||
# retrieve active/all connected nodes, if asked for
|
||||
$set = $set->search_rs({}, { prefetch => [{$nodes_name => $ips}] });
|
||||
$set = $set->search_rs({}, { order_by => ["${nodes_name}.vlan", "${nodes_name}.mac", "${ips}.ip"] });
|
||||
|
||||
# retrieve wireless SSIDs, if asked for
|
||||
$set = $set->search_rs({}, { prefetch => [{$nodes_name => 'wireless'}] })
|
||||
if param('n_ssid');
|
||||
|
||||
# retrieve NetBIOS, if asked for
|
||||
$set = $set->search_rs({}, { prefetch => [{$nodes_name => 'netbios'}] })
|
||||
if param('n_netbios');
|
||||
|
||||
# retrieve vendor, if asked for
|
||||
$set = $set->search_rs({}, { prefetch => [{$nodes_name => 'oui'}] })
|
||||
if param('n_vendor');
|
||||
}
|
||||
|
||||
# retrieve SSID, if asked for
|
||||
$set = $set->search({}, { prefetch => 'ssid' }) if param('c_ssid');
|
||||
|
||||
# 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;
|
||||
|
||||
if (request->is_ajax) {
|
||||
template 'ajax/device/ports.tt', {
|
||||
results => $results,
|
||||
nodes => $nodes_name,
|
||||
device => $device,
|
||||
vmember_ok => $vmember_ok,
|
||||
}, { layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/device/ports_csv.tt', {
|
||||
results => $results,
|
||||
nodes => $nodes_name,
|
||||
device => $device,
|
||||
}, { layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
true;
|
||||
27
lib/App/Netdisco/Web/Plugin/Inventory.pm
Normal file
27
lib/App/Netdisco/Web/Plugin/Inventory.pm
Normal file
@@ -0,0 +1,27 @@
|
||||
package App::Netdisco::Web::Plugin::Inventory;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_navbar_item({
|
||||
tag => 'inventory',
|
||||
path => '/inventory',
|
||||
label => 'Inventory',
|
||||
});
|
||||
|
||||
get '/inventory' => require_login sub {
|
||||
my $models = schema('netdisco')->resultset('Device')->get_models();
|
||||
my $releases = schema('netdisco')->resultset('Device')->get_releases();
|
||||
|
||||
var(nav => 'inventory');
|
||||
|
||||
template 'inventory', {
|
||||
models => $models,
|
||||
releases => $releases,
|
||||
};
|
||||
};
|
||||
|
||||
true;
|
||||
41
lib/App/Netdisco/Web/Plugin/Report/ApChannelDist.pm
Normal file
41
lib/App/Netdisco/Web/Plugin/Report/ApChannelDist.pm
Normal file
@@ -0,0 +1,41 @@
|
||||
package App::Netdisco::Web::Plugin::Report::ApChannelDist;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report(
|
||||
{ category => 'Wireless',
|
||||
tag => 'apchanneldist',
|
||||
label => 'Access Point Channel Distribution',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
get '/ajax/content/report/apchanneldist' => require_login sub {
|
||||
my @results = schema('netdisco')->resultset('DevicePortWireless')->search(
|
||||
{ channel => { '!=', '0' } },
|
||||
{ select => [ 'channel', { count => 'channel' } ],
|
||||
as => [qw/ channel ch_count /],
|
||||
group_by => [qw/channel/],
|
||||
order_by => { -desc => [qw/count/] },
|
||||
},
|
||||
)->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
my $json = to_json( \@results );
|
||||
template 'ajax/report/apchanneldist.tt', { results => $json },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/apchanneldist_csv.tt', { results => \@results },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
51
lib/App/Netdisco/Web/Plugin/Report/ApClients.pm
Normal file
51
lib/App/Netdisco/Web/Plugin/Report/ApClients.pm
Normal file
@@ -0,0 +1,51 @@
|
||||
package App::Netdisco::Web::Plugin::Report::ApClients;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report(
|
||||
{ category => 'Wireless',
|
||||
tag => 'apclients',
|
||||
label => 'Access Point Client Count',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
get '/ajax/content/report/apclients' => require_login sub {
|
||||
my @results = schema('netdisco')->resultset('Device')->search(
|
||||
{ 'nodes.time_last' => { '>=', \'me.last_macsuck' } },
|
||||
{ select => [ 'ip', 'dns', 'name', 'model', 'location' ],
|
||||
join => { 'ports' => { 'ssid' => 'nodes' } },
|
||||
'+columns' => [
|
||||
{ 'port' => 'ports.port' },
|
||||
{ 'description' => 'ports.name' },
|
||||
{ 'ssid' => 'ssid.ssid' },
|
||||
{ 'mac_count' => { count => 'nodes.mac' } },
|
||||
],
|
||||
group_by => [
|
||||
'me.ip', 'me.dns', 'me.name', 'me.model',
|
||||
'me.location', 'ports.port', 'ports.descr', 'ports.name', 'ssid.ssid',
|
||||
],
|
||||
order_by => { -desc => [qw/count/] },
|
||||
}
|
||||
)->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
my $json = to_json( \@results );
|
||||
template 'ajax/report/apclients.tt', { results => $json },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/apclients_csv.tt',
|
||||
{ results => \@results },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
58
lib/App/Netdisco/Web/Plugin/Report/ApRadioChannelPower.pm
Normal file
58
lib/App/Netdisco/Web/Plugin/Report/ApRadioChannelPower.pm
Normal file
@@ -0,0 +1,58 @@
|
||||
package App::Netdisco::Web::Plugin::Report::ApRadioChannelPower;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
use App::Netdisco::Util::ExpandParams 'expand_hash';
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report(
|
||||
{ category => 'Wireless',
|
||||
tag => 'apradiochannelpower',
|
||||
label => 'Access Point Radios Channel and Power',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
get '/ajax/content/report/apradiochannelpower/data' => require_login sub {
|
||||
send_error( 'Missing parameter', 400 )
|
||||
unless ( param('draw') && param('draw') =~ /\d+/ );
|
||||
|
||||
my $rs = schema('netdisco')->resultset('Virtual::ApRadioChannelPower');
|
||||
my $exp_params = expand_hash( scalar params );
|
||||
my $recordsTotal = $rs->count;
|
||||
my @data = $rs->get_datatables_data($exp_params)->hri->all;
|
||||
my $recordsFiltered = $rs->get_datatables_filtered_count($exp_params);
|
||||
|
||||
content_type 'application/json';
|
||||
return to_json(
|
||||
{ draw => int( param('draw') ),
|
||||
recordsTotal => int($recordsTotal),
|
||||
recordsFiltered => int($recordsFiltered),
|
||||
data => \@data,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
get '/ajax/content/report/apradiochannelpower' => require_login sub {
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
template 'ajax/report/apradiochannelpower.tt', {},
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
my @results
|
||||
= schema('netdisco')->resultset('Virtual::ApRadioChannelPower')
|
||||
->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/apradiochannelpower_csv.tt',
|
||||
{ results => \@results, },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
42
lib/App/Netdisco/Web/Plugin/Report/DeviceAddrNoDNS.pm
Normal file
42
lib/App/Netdisco/Web/Plugin/Report/DeviceAddrNoDNS.pm
Normal file
@@ -0,0 +1,42 @@
|
||||
package App::Netdisco::Web::Plugin::Report::DeviceAddrNoDNS;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report(
|
||||
{ category => 'Device',
|
||||
tag => 'deviceaddrnodns',
|
||||
label => 'Addresses without DNS Entries',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
get '/ajax/content/report/deviceaddrnodns' => require_login sub {
|
||||
my @results = schema('netdisco')->resultset('Device')->search(
|
||||
{ 'device_ips.dns' => undef },
|
||||
{ select => [ 'ip', 'dns', 'name', 'location', 'contact' ],
|
||||
join => [qw/device_ips/],
|
||||
'+columns' => [ { 'alias' => 'device_ips.alias' }, ],
|
||||
order_by => { -asc => [qw/me.ip device_ips.alias/] },
|
||||
}
|
||||
)->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
my $json = to_json (\@results);
|
||||
template 'ajax/report/deviceaddrnodns.tt', { results => $json },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/deviceaddrnodns_csv.tt',
|
||||
{ results => \@results, },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
38
lib/App/Netdisco/Web/Plugin/Report/DeviceByLocation.pm
Normal file
38
lib/App/Netdisco/Web/Plugin/Report/DeviceByLocation.pm
Normal file
@@ -0,0 +1,38 @@
|
||||
package App::Netdisco::Web::Plugin::Report::DeviceByLocation;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report(
|
||||
{ category => 'Device',
|
||||
tag => 'devicebylocation',
|
||||
label => 'By Location',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
get '/ajax/content/report/devicebylocation' => require_login sub {
|
||||
my @results
|
||||
= schema('netdisco')->resultset('Device')
|
||||
->columns( [qw/ ip dns name location vendor model /] )
|
||||
->order_by( [qw/ location name ip vendor model /] )->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
my $json = to_json( \@results );
|
||||
template 'ajax/report/devicebylocation.tt', { results => $json },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/devicebylocation_csv.tt',
|
||||
{ results => \@results },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
41
lib/App/Netdisco/Web/Plugin/Report/DeviceDnsMismatch.pm
Normal file
41
lib/App/Netdisco/Web/Plugin/Report/DeviceDnsMismatch.pm
Normal file
@@ -0,0 +1,41 @@
|
||||
package App::Netdisco::Web::Plugin::Report::DeviceDnsMismatch;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report(
|
||||
{ category => 'Device',
|
||||
tag => 'devicednsmismatch',
|
||||
label => 'Device Name / DNS Mismatches',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
get '/ajax/content/report/devicednsmismatch' => require_login sub {
|
||||
|
||||
my $suffix = setting('domain_suffix') || '';
|
||||
|
||||
my @results
|
||||
= schema('netdisco')->resultset('Virtual::DeviceDnsMismatch')
|
||||
->search( undef, { bind => [ $suffix, $suffix ] } )
|
||||
->columns( [qw/ ip dns name location contact /] )->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
my $json = to_json( \@results );
|
||||
template 'ajax/report/devicednsmismatch.tt', { results => $json },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/devicednsmismatch_csv.tt',
|
||||
{ results => \@results },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
58
lib/App/Netdisco/Web/Plugin/Report/DevicePoeStatus.pm
Normal file
58
lib/App/Netdisco/Web/Plugin/Report/DevicePoeStatus.pm
Normal file
@@ -0,0 +1,58 @@
|
||||
package App::Netdisco::Web::Plugin::Report::DevicePoeStatus;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
use App::Netdisco::Util::ExpandParams 'expand_hash';
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report(
|
||||
{ category => 'Device',
|
||||
tag => 'devicepoestatus',
|
||||
label => 'Power over Ethernet (PoE) Status',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
get '/ajax/content/report/devicepoestatus/data' => require_login sub {
|
||||
send_error( 'Missing parameter', 400 )
|
||||
unless ( param('draw') && param('draw') =~ /\d+/ );
|
||||
|
||||
my $rs = schema('netdisco')->resultset('Virtual::DevicePoeStatus');
|
||||
|
||||
my $exp_params = expand_hash( scalar params );
|
||||
my $recordsTotal = $rs->count;
|
||||
my @data = $rs->get_datatables_data($exp_params)->hri->all;
|
||||
my $recordsFiltered = $rs->get_datatables_filtered_count($exp_params);
|
||||
|
||||
content_type 'application/json';
|
||||
return to_json(
|
||||
{ draw => int( param('draw') ),
|
||||
recordsTotal => int($recordsTotal),
|
||||
recordsFiltered => int($recordsFiltered),
|
||||
data => \@data,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
get '/ajax/content/report/devicepoestatus' => require_login sub {
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
template 'ajax/report/devicepoestatus.tt', {}, { layout => undef };
|
||||
}
|
||||
else {
|
||||
my @results
|
||||
= schema('netdisco')->resultset('Virtual::DevicePoeStatus')
|
||||
->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/devicepoestatus_csv.tt',
|
||||
{ results => \@results, },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
36
lib/App/Netdisco/Web/Plugin/Report/DuplexMismatch.pm
Normal file
36
lib/App/Netdisco/Web/Plugin/Report/DuplexMismatch.pm
Normal file
@@ -0,0 +1,36 @@
|
||||
package App::Netdisco::Web::Plugin::Report::DuplexMismatch;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report(
|
||||
{ category => 'Port',
|
||||
tag => 'duplexmismatch',
|
||||
label => 'Duplex Mismatches Between Devices',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
get '/ajax/content/report/duplexmismatch' => require_login sub {
|
||||
my @results
|
||||
= schema('netdisco')->resultset('Virtual::DuplexMismatch')->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
my $json = to_json( \@results );
|
||||
template 'ajax/report/duplexmismatch.tt', { results => $json, },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/duplexmismatch_csv.tt',
|
||||
{ results => \@results, },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
44
lib/App/Netdisco/Web/Plugin/Report/HalfDuplex.pm
Normal file
44
lib/App/Netdisco/Web/Plugin/Report/HalfDuplex.pm
Normal file
@@ -0,0 +1,44 @@
|
||||
package App::Netdisco::Web::Plugin::Report::HalfDuplex;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report(
|
||||
{ category => 'Port',
|
||||
tag => 'halfduplex',
|
||||
label => 'Ports in Half Duplex Mode',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
get '/ajax/content/report/halfduplex' => require_login sub {
|
||||
my $format = param('format');
|
||||
my @results
|
||||
= schema('netdisco')->resultset('DevicePort')
|
||||
->columns( [qw/ ip port name duplex /] )->search(
|
||||
{ up => 'up', duplex => { '-ilike' => 'half' } },
|
||||
{ '+columns' => [qw/ device.dns device.name /],
|
||||
join => [qw/ device /],
|
||||
collapse => 1,
|
||||
}
|
||||
)->order_by( [qw/ device.dns port /] )->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
my $json = to_json( \@results );
|
||||
template 'ajax/report/halfduplex.tt', { results => $json },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/halfduplex_csv.tt',
|
||||
{ results => \@results },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
30
lib/App/Netdisco/Web/Plugin/Report/InventoryByModelByOS.pm
Normal file
30
lib/App/Netdisco/Web/Plugin/Report/InventoryByModelByOS.pm
Normal file
@@ -0,0 +1,30 @@
|
||||
package App::Netdisco::Web::Plugin::Report::InventoryByModelByOS;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report(
|
||||
{ category => 'Device',
|
||||
tag => 'inventorybymodelbyos',
|
||||
label => 'Inventory by Model by OS',
|
||||
provides_csv => 0,
|
||||
}
|
||||
);
|
||||
|
||||
get '/ajax/content/report/inventorybymodelbyos' => require_login sub {
|
||||
my @results = schema('netdisco')->resultset('Device')->search(undef, {
|
||||
columns => [qw/vendor model os os_ver/],
|
||||
select => [ { count => 'os_ver' } ],
|
||||
as => [qw/ os_ver_count /],
|
||||
group_by => [qw/ vendor model os os_ver /],
|
||||
order_by => ['vendor', 'model', { -desc => 'count' }, 'os_ver'],
|
||||
})->hri->all;
|
||||
|
||||
template 'ajax/report/inventorybymodelbyos.tt', { results => \@results, },
|
||||
{ layout => undef };
|
||||
};
|
||||
|
||||
1;
|
||||
168
lib/App/Netdisco/Web/Plugin/Report/IpInventory.pm
Normal file
168
lib/App/Netdisco/Web/Plugin/Report/IpInventory.pm
Normal file
@@ -0,0 +1,168 @@
|
||||
package App::Netdisco::Web::Plugin::Report::IpInventory;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
use NetAddr::IP::Lite ':lower';
|
||||
|
||||
register_report(
|
||||
{ category => 'IP',
|
||||
tag => 'ipinventory',
|
||||
label => 'IP Inventory',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
get '/ajax/content/report/ipinventory' => require_login sub {
|
||||
|
||||
# Default to something simple with no results to prevent
|
||||
# "Search failed!" error
|
||||
my $subnet = param('subnet') || '0.0.0.0/32';
|
||||
$subnet = NetAddr::IP::Lite->new($subnet);
|
||||
$subnet = NetAddr::IP::Lite->new('0.0.0.0/32')
|
||||
if (! $subnet) or ($subnet->addr eq '0.0.0.0');
|
||||
|
||||
my $agenot = param('age_invert') || '0';
|
||||
my ( $start, $end ) = param('daterange') =~ /(\d+-\d+-\d+)/gmx;
|
||||
|
||||
my $limit = param('limit') || 256;
|
||||
my $never = param('never') || '0';
|
||||
my $order = [{-desc => 'age'}, {-asc => 'ip'}];
|
||||
|
||||
# We need a reasonable limit to prevent a potential DoS, especially if
|
||||
# 'never' is true. TODO: Need better input validation, both JS and
|
||||
# server-side to provide user feedback
|
||||
$limit = 8192 if $limit > 8192;
|
||||
|
||||
my $rs1 = schema('netdisco')->resultset('DeviceIp')->search(
|
||||
undef,
|
||||
{ join => 'device',
|
||||
select => [
|
||||
'alias AS ip',
|
||||
\'NULL as mac',
|
||||
'creation AS time_first',
|
||||
'device.last_discover AS time_last',
|
||||
'dns',
|
||||
\'true AS active',
|
||||
\'false AS node',
|
||||
\qq/replace( date_trunc( 'minute', age( now(), device.last_discover ) ) ::text, 'mon', 'month') AS age/
|
||||
],
|
||||
as => [qw( ip mac time_first time_last dns active node age)],
|
||||
}
|
||||
)->hri;
|
||||
|
||||
my $rs2 = schema('netdisco')->resultset('NodeIp')->search(
|
||||
undef,
|
||||
{ columns => [qw( ip mac time_first time_last dns active)],
|
||||
'+select' => [ \'true AS node',
|
||||
\qq/replace( date_trunc( 'minute', age( now(), time_last ) ) ::text, 'mon', 'month') AS age/
|
||||
],
|
||||
'+as' => [ 'node', 'age' ],
|
||||
}
|
||||
)->hri;
|
||||
|
||||
my $rs3 = schema('netdisco')->resultset('NodeNbt')->search(
|
||||
undef,
|
||||
{ columns => [qw( ip mac time_first time_last )],
|
||||
'+select' => [
|
||||
'nbname AS dns', 'active',
|
||||
\'true AS node',
|
||||
\qq/replace( date_trunc( 'minute', age( now(), time_last ) ) ::text, 'mon', 'month') AS age/
|
||||
],
|
||||
'+as' => [ 'dns', 'active', 'node', 'age' ],
|
||||
}
|
||||
)->hri;
|
||||
|
||||
my $rs_union = $rs1->union( [ $rs2, $rs3 ] );
|
||||
|
||||
if ( $never ) {
|
||||
$subnet = NetAddr::IP::Lite->new('0.0.0.0/32') if ($subnet->bits ne 32);
|
||||
|
||||
my $rs4 = schema('netdisco')->resultset('Virtual::CidrIps')->search(
|
||||
undef,
|
||||
{ bind => [ $subnet->cidr ],
|
||||
columns => [qw( ip mac time_first time_last dns active)],
|
||||
'+select' => [ \'false AS node',
|
||||
\qq/replace( date_trunc( 'minute', age( now(), time_last ) ) ::text, 'mon', 'month') AS age/
|
||||
],
|
||||
'+as' => [ 'node', 'age' ],
|
||||
}
|
||||
)->hri;
|
||||
|
||||
$rs_union = $rs_union->union( [$rs4] );
|
||||
}
|
||||
|
||||
my $rs_sub = $rs_union->search(
|
||||
{ ip => { '<<' => $subnet->cidr } },
|
||||
{ select => [
|
||||
\'DISTINCT ON (ip) ip',
|
||||
'mac',
|
||||
'dns',
|
||||
\qq/date_trunc('second', time_last) AS time_last/,
|
||||
\qq/date_trunc('second', time_first) AS time_first/,
|
||||
'active',
|
||||
'node',
|
||||
'age'
|
||||
],
|
||||
as => [
|
||||
'ip', 'mac', 'dns', 'time_last', 'time_first',
|
||||
'active', 'node', 'age'
|
||||
],
|
||||
order_by => [{-asc => 'ip'}, {-desc => 'active'}],
|
||||
}
|
||||
)->as_query;
|
||||
|
||||
my $rs;
|
||||
if ( $start && $end ) {
|
||||
$start = $start . ' 00:00:00';
|
||||
$end = $end . ' 23:59:59';
|
||||
|
||||
if ( $agenot ) {
|
||||
$rs = $rs_union->search(
|
||||
{ -or => [
|
||||
time_first => [ undef ],
|
||||
time_last => [ { '<', $start }, { '>', $end } ]
|
||||
]
|
||||
},
|
||||
{ from => { me => $rs_sub }, }
|
||||
);
|
||||
}
|
||||
else {
|
||||
$rs = $rs_union->search(
|
||||
{ -or => [
|
||||
-and => [
|
||||
time_first => undef,
|
||||
time_last => undef,
|
||||
],
|
||||
-and => [
|
||||
time_last => { '>=', $start },
|
||||
time_last => { '<=', $end },
|
||||
],
|
||||
],
|
||||
},
|
||||
{ from => { me => $rs_sub }, }
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$rs = $rs_union->search( undef, { from => { me => $rs_sub }, } );
|
||||
}
|
||||
|
||||
my @results = $rs->order_by($order)->limit($limit)->all;
|
||||
return unless scalar @results;
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
my $json = to_json( \@results );
|
||||
template 'ajax/report/ipinventory.tt', { results => $json },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/ipinventory_csv.tt', { results => \@results, },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
166
lib/App/Netdisco/Web/Plugin/Report/ModuleInventory.pm
Normal file
166
lib/App/Netdisco/Web/Plugin/Report/ModuleInventory.pm
Normal file
@@ -0,0 +1,166 @@
|
||||
package App::Netdisco::Web::Plugin::Report::ModuleInventory;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
use App::Netdisco::Util::ExpandParams 'expand_hash';
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
use List::MoreUtils ();
|
||||
|
||||
register_report(
|
||||
{ category => 'Device',
|
||||
tag => 'moduleinventory',
|
||||
label => 'Module Inventory',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
hook 'before' => sub {
|
||||
return
|
||||
unless (
|
||||
request->path eq uri_for('/report/moduleinventory')->path
|
||||
or index( request->path,
|
||||
uri_for('/ajax/content/report/moduleinventory')->path ) == 0
|
||||
);
|
||||
|
||||
# view settings
|
||||
var('module_options' => [
|
||||
{ name => 'fruonly',
|
||||
label => 'FRU Only',
|
||||
default => 'on'
|
||||
},
|
||||
{ name => 'matchall',
|
||||
label => 'Match All Options',
|
||||
default => 'on'
|
||||
},
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
hook 'before_template' => sub {
|
||||
my $tokens = shift;
|
||||
|
||||
return
|
||||
unless (
|
||||
request->path eq uri_for('/report/moduleinventory')->path
|
||||
or index( request->path,
|
||||
uri_for('/ajax/content/report/moduleinventory')->path ) == 0
|
||||
);
|
||||
|
||||
# used in the search sidebar template to set selected items
|
||||
foreach my $opt (qw/class/) {
|
||||
my $p = (
|
||||
ref [] eq ref param($opt)
|
||||
? param($opt)
|
||||
: ( param($opt) ? [ param($opt) ] : [] )
|
||||
);
|
||||
$tokens->{"${opt}_lkp"} = { map { $_ => 1 } @$p };
|
||||
}
|
||||
};
|
||||
|
||||
get '/ajax/content/report/moduleinventory/data' => require_login sub {
|
||||
send_error( 'Missing parameter', 400 )
|
||||
unless ( param('draw') && param('draw') =~ /\d+/ );
|
||||
|
||||
my $rs = schema('netdisco')->resultset('DeviceModule');
|
||||
$rs = $rs->search( { -bool => 'fru' } ) if param('fruonly');
|
||||
|
||||
if ( param('device') ) {
|
||||
my @ips = schema('netdisco')->resultset('Device')
|
||||
->search_fuzzy( param('device') )->get_column('ip')->all;
|
||||
|
||||
params->{'ips'} = \@ips;
|
||||
}
|
||||
|
||||
$rs = $rs->search_by_field( scalar params )->columns(
|
||||
[ 'ip', 'description', 'name', 'class',
|
||||
'type', 'serial', 'hw_ver', 'fw_ver',
|
||||
'sw_ver', 'model'
|
||||
]
|
||||
)->search(
|
||||
{},
|
||||
{ '+columns' => [qw/ device.dns device.name /],
|
||||
join => 'device',
|
||||
collapse => 1,
|
||||
}
|
||||
);
|
||||
|
||||
my $exp_params = expand_hash( scalar params );
|
||||
|
||||
my $recordsTotal = $rs->count;
|
||||
|
||||
my @data = $rs->get_datatables_data($exp_params)->hri->all;
|
||||
|
||||
my $recordsFiltered = $rs->get_datatables_filtered_count($exp_params);
|
||||
|
||||
content_type 'application/json';
|
||||
return to_json(
|
||||
{ draw => int( param('draw') ),
|
||||
recordsTotal => int($recordsTotal),
|
||||
recordsFiltered => int($recordsFiltered),
|
||||
data => \@data,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
get '/ajax/content/report/moduleinventory' => require_login sub {
|
||||
|
||||
my $has_opt = List::MoreUtils::any { param($_) }
|
||||
qw/device description name type model serial class/;
|
||||
|
||||
my $rs = schema('netdisco')->resultset('DeviceModule');
|
||||
$rs = $rs->search( { -bool => 'fru' } ) if param('fruonly');
|
||||
my @results;
|
||||
|
||||
if ( $has_opt && !request->is_ajax ) {
|
||||
|
||||
if ( param('device') ) {
|
||||
my @ips = schema('netdisco')->resultset('Device')
|
||||
->search_fuzzy( param('device') )->get_column('ip')->all;
|
||||
|
||||
params->{'ips'} = \@ips;
|
||||
}
|
||||
|
||||
@results = $rs->search_by_field( scalar params )->columns(
|
||||
[ 'ip', 'description', 'name', 'class',
|
||||
'type', 'serial', 'hw_ver', 'fw_ver',
|
||||
'sw_ver', 'model'
|
||||
]
|
||||
)->search(
|
||||
{},
|
||||
{ '+columns' => [qw/ device.dns device.name /],
|
||||
join => 'device',
|
||||
collapse => 1,
|
||||
}
|
||||
)->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
}
|
||||
elsif ( !$has_opt ) {
|
||||
@results = $rs->search(
|
||||
{ class => { '!=', undef } },
|
||||
{ select => [ 'class', { count => 'class' } ],
|
||||
as => [qw/ class count /],
|
||||
group_by => [qw/ class /]
|
||||
}
|
||||
)->order_by( { -desc => 'count' } )->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
}
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
my $json = to_json( \@results );
|
||||
template 'ajax/report/moduleinventory.tt',
|
||||
{ results => $json, opt => $has_opt },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/moduleinventory_csv.tt',
|
||||
{ results => \@results, opt => $has_opt },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
110
lib/App/Netdisco/Web/Plugin/Report/Netbios.pm
Normal file
110
lib/App/Netdisco/Web/Plugin/Report/Netbios.pm
Normal file
@@ -0,0 +1,110 @@
|
||||
package App::Netdisco::Web::Plugin::Report::Netbios;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
use App::Netdisco::Util::ExpandParams 'expand_hash';
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report(
|
||||
{ category => 'Node',
|
||||
tag => 'netbios',
|
||||
label => 'NetBIOS Inventory',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
hook 'before_template' => sub {
|
||||
my $tokens = shift;
|
||||
|
||||
return
|
||||
unless ( request->path eq uri_for('/report/netbios')->path
|
||||
or
|
||||
index( request->path, uri_for('/ajax/content/report/netbios')->path )
|
||||
== 0 );
|
||||
|
||||
# used in the search sidebar template to set selected items
|
||||
foreach my $opt (qw/domain/) {
|
||||
my $p = (
|
||||
ref [] eq ref param($opt)
|
||||
? param($opt)
|
||||
: ( param($opt) ? [ param($opt) ] : [] )
|
||||
);
|
||||
$tokens->{"${opt}_lkp"} = { map { $_ => 1 } @$p };
|
||||
}
|
||||
};
|
||||
|
||||
get '/ajax/content/report/netbios/data' => require_login sub {
|
||||
send_error( 'Missing parameter', 400 )
|
||||
unless ( param('draw') && param('draw') =~ /\d+/ );
|
||||
|
||||
my $domain = param('domain');
|
||||
|
||||
my $rs = schema('netdisco')->resultset('NodeNbt');
|
||||
|
||||
my $search = $domain eq 'blank' ? '' : $domain;
|
||||
$rs = $rs->search( { domain => $search } )
|
||||
->order_by( [ { -asc => 'domain' }, { -desc => 'time_last' } ] );
|
||||
|
||||
my $exp_params = expand_hash( scalar params );
|
||||
|
||||
my $recordsTotal = $rs->count;
|
||||
|
||||
my @data = $rs->get_datatables_data($exp_params)->hri->all;
|
||||
|
||||
my $recordsFiltered = $rs->get_datatables_filtered_count($exp_params);
|
||||
|
||||
content_type 'application/json';
|
||||
return to_json(
|
||||
{ draw => int( param('draw') ),
|
||||
recordsTotal => int($recordsTotal),
|
||||
recordsFiltered => int($recordsFiltered),
|
||||
data => \@data,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
get '/ajax/content/report/netbios' => require_login sub {
|
||||
|
||||
my $domain = param('domain');
|
||||
|
||||
my $rs = schema('netdisco')->resultset('NodeNbt');
|
||||
my @results;
|
||||
|
||||
if ( defined $domain && !request->is_ajax ) {
|
||||
my $search = $domain eq 'blank' ? '' : $domain;
|
||||
@results
|
||||
= $rs->search( { domain => $search } )
|
||||
->order_by( [ { -asc => 'domain' }, { -desc => 'time_last' } ] )
|
||||
->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
}
|
||||
elsif ( !defined $domain ) {
|
||||
@results = $rs->search(
|
||||
{},
|
||||
{ select => [ 'domain', { count => 'domain' } ],
|
||||
as => [qw/ domain count /],
|
||||
group_by => [qw/ domain /]
|
||||
}
|
||||
)->order_by( { -desc => 'count' } )->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
}
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
my $json = to_json( \@results );
|
||||
template 'ajax/report/netbios.tt',
|
||||
{ results => $json, opt => $domain },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/netbios_csv.tt',
|
||||
{ results => \@results, opt => $domain },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
51
lib/App/Netdisco/Web/Plugin/Report/NodeMultiIPs.pm
Normal file
51
lib/App/Netdisco/Web/Plugin/Report/NodeMultiIPs.pm
Normal file
@@ -0,0 +1,51 @@
|
||||
package App::Netdisco::Web::Plugin::Report::NodeMultiIPs;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report(
|
||||
{ category => 'Node',
|
||||
tag => 'nodemultiips',
|
||||
label => 'Nodes with multiple active IP addresses',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
get '/ajax/content/report/nodemultiips' => require_login sub {
|
||||
my @results = schema('netdisco')->resultset('Node')->search(
|
||||
{},
|
||||
{ select => [ 'mac', 'switch', 'port' ],
|
||||
join => [qw/device ips oui/],
|
||||
'+columns' => [
|
||||
{ 'dns' => 'device.dns' },
|
||||
{ 'name' => 'device.name' },
|
||||
{ 'ip_count' => { count => 'ips.ip' } },
|
||||
{ 'vendor' => 'oui.company' }
|
||||
],
|
||||
group_by => [
|
||||
qw/ me.mac me.switch me.port device.dns device.name oui.company/
|
||||
],
|
||||
having => \[ 'count(ips.ip) > ?', [ count => 1 ] ],
|
||||
order_by => { -desc => [qw/count/] },
|
||||
}
|
||||
)->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
my $json = to_json( \@results );
|
||||
template 'ajax/report/nodemultiips.tt', { results => $json },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/nodemultiips_csv.tt',
|
||||
{ results => \@results },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
133
lib/App/Netdisco/Web/Plugin/Report/NodeVendor.pm
Normal file
133
lib/App/Netdisco/Web/Plugin/Report/NodeVendor.pm
Normal file
@@ -0,0 +1,133 @@
|
||||
package App::Netdisco::Web::Plugin::Report::NodeVendor;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
use App::Netdisco::Util::ExpandParams 'expand_hash';
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report(
|
||||
{ category => 'Node',
|
||||
tag => 'nodevendor',
|
||||
label => 'Node Vendor Inventory',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
hook 'before_template' => sub {
|
||||
my $tokens = shift;
|
||||
|
||||
return
|
||||
unless (
|
||||
request->path eq uri_for('/report/nodevendor')->path
|
||||
or index( request->path,
|
||||
uri_for('/ajax/content/report/nodevendor')->path ) == 0
|
||||
);
|
||||
|
||||
# used in the search sidebar template to set selected items
|
||||
foreach my $opt (qw/vendor/) {
|
||||
my $p = (
|
||||
ref [] eq ref param($opt)
|
||||
? param($opt)
|
||||
: ( param($opt) ? [ param($opt) ] : [] )
|
||||
);
|
||||
$tokens->{"${opt}_lkp"} = { map { $_ => 1 } @$p };
|
||||
}
|
||||
};
|
||||
|
||||
get '/ajax/content/report/nodevendor/data' => require_login sub {
|
||||
send_error( 'Missing parameter', 400 )
|
||||
unless ( param('draw') && param('draw') =~ /\d+/ );
|
||||
|
||||
my $vendor = param('vendor');
|
||||
|
||||
my $rs = schema('netdisco')->resultset('Node');
|
||||
|
||||
my $match = $vendor eq 'blank' ? undef : $vendor;
|
||||
|
||||
$rs = $rs->search( { 'oui.abbrev' => $match },
|
||||
{ '+columns' => [qw/ device.dns device.name oui.abbrev /],
|
||||
join => [qw/ oui device /],
|
||||
collapse => 1,
|
||||
});
|
||||
|
||||
unless ( param('archived') ) {
|
||||
$rs = $rs->search( { -bool => 'me.active' } );
|
||||
}
|
||||
|
||||
my $exp_params = expand_hash( scalar params );
|
||||
|
||||
my $recordsTotal = $rs->count;
|
||||
|
||||
my @data = $rs->get_datatables_data($exp_params)->hri->all;
|
||||
|
||||
my $recordsFiltered = $rs->get_datatables_filtered_count($exp_params);
|
||||
|
||||
content_type 'application/json';
|
||||
return to_json(
|
||||
{ draw => int( param('draw') ),
|
||||
recordsTotal => int($recordsTotal),
|
||||
recordsFiltered => int($recordsFiltered),
|
||||
data => \@data,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
get '/ajax/content/report/nodevendor' => require_login sub {
|
||||
|
||||
my $vendor = param('vendor');
|
||||
|
||||
my $rs = schema('netdisco')->resultset('Node');
|
||||
my @results;
|
||||
|
||||
if ( defined $vendor && !request->is_ajax ) {
|
||||
|
||||
my $match = $vendor eq 'blank' ? undef : $vendor;
|
||||
|
||||
$rs = $rs->search( { 'oui.abbrev' => $match },
|
||||
{ '+columns' => [qw/ device.dns device.name oui.abbrev /],
|
||||
join => [qw/ oui device /],
|
||||
collapse => 1,
|
||||
});
|
||||
|
||||
unless ( param('archived') ) {
|
||||
$rs = $rs->search( { -bool => 'me.active' } );
|
||||
}
|
||||
|
||||
@results = $rs->hri->all;
|
||||
return unless scalar @results;
|
||||
}
|
||||
elsif ( !defined $vendor ) {
|
||||
$rs = $rs->search(
|
||||
{ },
|
||||
{ join => 'oui',
|
||||
select => [ 'oui.abbrev', { count => 'me.mac' } ],
|
||||
as => [qw/ vendor count /],
|
||||
group_by => [qw/ oui.abbrev /]
|
||||
}
|
||||
)->order_by( { -desc => 'count' } );
|
||||
|
||||
unless ( param('archived') ) {
|
||||
$rs = $rs->search( { -bool => 'me.active' } );
|
||||
}
|
||||
|
||||
@results = $rs->hri->all;
|
||||
return unless scalar @results;
|
||||
}
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
my $json = to_json( \@results );
|
||||
template 'ajax/report/nodevendor.tt',
|
||||
{ results => $json, opt => $vendor },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/nodevendor_csv.tt',
|
||||
{ results => \@results, opt => $vendor },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
54
lib/App/Netdisco/Web/Plugin/Report/NodesDiscovered.pm
Normal file
54
lib/App/Netdisco/Web/Plugin/Report/NodesDiscovered.pm
Normal file
@@ -0,0 +1,54 @@
|
||||
package App::Netdisco::Web::Plugin::Report::NodesDiscovered;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
use App::Netdisco::Util::Web 'sql_match';
|
||||
|
||||
register_report(
|
||||
{ category => 'Node',
|
||||
tag => 'nodesdiscovered',
|
||||
label => 'Nodes discovered through LLDP/CDP',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
get '/ajax/content/report/nodesdiscovered' => require_login sub {
|
||||
my $op = param('matchall') ? '-and' : '-or';
|
||||
|
||||
my @results = schema('netdisco')->resultset('Virtual::NodesDiscovered')
|
||||
->search({
|
||||
$op => [
|
||||
(param('aps') ?
|
||||
('me.remote_type' => { -ilike => 'AP:%' }) : ()),
|
||||
(param('phones') ?
|
||||
('me.remote_type' => { -ilike => '%ip_phone%' }) : ()),
|
||||
(param('remote_id') ?
|
||||
('me.remote_id' => { -ilike => scalar sql_match(param('remote_id')) }) : ()),
|
||||
(param('remote_type') ? ('-or' => [
|
||||
map {( 'me.remote_type' => { -ilike => scalar sql_match($_) } )}
|
||||
grep { $_ }
|
||||
(ref param('remote_type') ? @{param('remote_type')} : param('remote_type'))
|
||||
]) : ()),
|
||||
],
|
||||
})
|
||||
->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
my $json = to_json( \@results );
|
||||
template 'ajax/report/nodesdiscovered.tt', { results => $json },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/nodesdiscovered_csv.tt',
|
||||
{ results => \@results },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
45
lib/App/Netdisco/Web/Plugin/Report/PortAdminDown.pm
Normal file
45
lib/App/Netdisco/Web/Plugin/Report/PortAdminDown.pm
Normal file
@@ -0,0 +1,45 @@
|
||||
package App::Netdisco::Web::Plugin::Report::PortAdminDown;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report(
|
||||
{ category => 'Port',
|
||||
tag => 'portadmindown',
|
||||
label => 'Ports administratively disabled',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
get '/ajax/content/report/portadmindown' => require_login sub {
|
||||
my @results = schema('netdisco')->resultset('Device')->search(
|
||||
{ 'up_admin' => 'down' },
|
||||
{ select => [ 'ip', 'dns', 'name' ],
|
||||
join => [ 'ports' ],
|
||||
'+columns' => [
|
||||
{ 'port' => 'ports.port' },
|
||||
{ 'description' => 'ports.name' },
|
||||
{ 'up_admin' => 'ports.up_admin' },
|
||||
]
|
||||
}
|
||||
)->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
my $json = to_json (\@results);
|
||||
template 'ajax/report/portadmindown.tt', { results => $json },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/portadmindown_csv.tt',
|
||||
{ results => \@results, },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
45
lib/App/Netdisco/Web/Plugin/Report/PortBlocking.pm
Normal file
45
lib/App/Netdisco/Web/Plugin/Report/PortBlocking.pm
Normal file
@@ -0,0 +1,45 @@
|
||||
package App::Netdisco::Web::Plugin::Report::PortBlocking;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report(
|
||||
{ category => 'Port',
|
||||
tag => 'portblocking',
|
||||
label => 'Ports that are blocking',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
get '/ajax/content/report/portblocking' => require_login sub {
|
||||
my @results = schema('netdisco')->resultset('Device')->search(
|
||||
{ 'stp' => [ 'blocking', 'broken' ], 'up' => { '!=', 'down' } },
|
||||
{ select => [ 'ip', 'dns', 'name' ],
|
||||
join => ['ports'],
|
||||
'+columns' => [
|
||||
{ 'port' => 'ports.port' },
|
||||
{ 'description' => 'ports.name' },
|
||||
{ 'stp' => 'ports.stp' },
|
||||
]
|
||||
}
|
||||
)->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
my $json = to_json (\@results);
|
||||
template 'ajax/report/portblocking.tt', { results => $json },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/portblocking_csv.tt',
|
||||
{ results => \@results, },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
66
lib/App/Netdisco/Web/Plugin/Report/PortLog.pm
Normal file
66
lib/App/Netdisco/Web/Plugin/Report/PortLog.pm
Normal file
@@ -0,0 +1,66 @@
|
||||
package App::Netdisco::Web::Plugin::Report::PortLog;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::Ajax;
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report({
|
||||
tag => 'portlog',
|
||||
label => 'Port Control Log',
|
||||
category => 'Port', # not used
|
||||
hidden => true,
|
||||
});
|
||||
|
||||
sub _sanity_ok {
|
||||
return 0 unless
|
||||
param('ip') =~ m/^[[:print:]]+$/
|
||||
and param('port') =~ m/^[[:print:]]+$/
|
||||
and param('log') =~ m/^[[:print:]]+$/;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
ajax '/ajax/control/report/portlog/add' => require_login sub {
|
||||
send_error('Bad Request', 400) unless _sanity_ok();
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $user = schema('netdisco')->resultset('DevicePortLog')
|
||||
->create({
|
||||
ip => param('ip'),
|
||||
port => param('port'),
|
||||
reason => 'other',
|
||||
log => param('log'),
|
||||
username => session('logged_in_user'),
|
||||
userip => request->remote_address,
|
||||
action => 'comment',
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
ajax '/ajax/content/report/portlog' => require_login sub {
|
||||
my $device = param('q');
|
||||
my $port = param('f');
|
||||
send_error('Bad Request', 400) unless $device and $port;
|
||||
|
||||
$device = schema('netdisco')->resultset('Device')
|
||||
->search_for_device($device);
|
||||
return unless $device;
|
||||
|
||||
my $set = schema('netdisco')->resultset('DevicePortLog')->search({
|
||||
ip => $device->ip,
|
||||
port => $port,
|
||||
}, {
|
||||
order_by => { -desc => [qw/creation/] },
|
||||
rows => 200,
|
||||
})->with_times;
|
||||
|
||||
content_type('text/html');
|
||||
template 'ajax/report/portlog.tt', {
|
||||
results => $set,
|
||||
}, { layout => undef };
|
||||
};
|
||||
|
||||
true;
|
||||
53
lib/App/Netdisco/Web/Plugin/Report/PortMultiNodes.pm
Normal file
53
lib/App/Netdisco/Web/Plugin/Report/PortMultiNodes.pm
Normal file
@@ -0,0 +1,53 @@
|
||||
package App::Netdisco::Web::Plugin::Report::PortMultiNodes;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report(
|
||||
{ category => 'Port',
|
||||
tag => 'portmultinodes',
|
||||
label => 'Ports with multiple nodes attached',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
get '/ajax/content/report/portmultinodes' => require_login sub {
|
||||
my @results = schema('netdisco')->resultset('Device')->search(
|
||||
{ 'ports.remote_ip' => undef,
|
||||
(param('vlan') ?
|
||||
('ports.vlan' => param('vlan'), 'nodes.vlan' => param('vlan')) : ()),
|
||||
'nodes.active' => 1,
|
||||
'wireless.port' => undef
|
||||
},
|
||||
{ select => [ 'ip', 'dns', 'name' ],
|
||||
join => { 'ports' => [ 'wireless', 'nodes' ] },
|
||||
'+columns' => [
|
||||
{ 'port' => 'ports.port' },
|
||||
{ 'description' => 'ports.name' },
|
||||
{ 'mac_count' => { count => 'nodes.mac' } },
|
||||
],
|
||||
group_by => [qw/me.ip me.dns me.name ports.port ports.name/],
|
||||
having => \[ 'count(nodes.mac) > ?', [ count => 1 ] ],
|
||||
order_by => { -desc => [qw/count/] },
|
||||
}
|
||||
)->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
my $json = to_json (\@results);
|
||||
template 'ajax/report/portmultinodes.tt', { results => $json },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/portmultinodes_csv.tt',
|
||||
{ results => \@results, },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
79
lib/App/Netdisco/Web/Plugin/Report/PortSsid.pm
Normal file
79
lib/App/Netdisco/Web/Plugin/Report/PortSsid.pm
Normal file
@@ -0,0 +1,79 @@
|
||||
package App::Netdisco::Web::Plugin::Report::PortSsid;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report(
|
||||
{ category => 'Port',
|
||||
tag => 'portssid',
|
||||
label => 'Port SSID Inventory',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
hook 'before_template' => sub {
|
||||
my $tokens = shift;
|
||||
|
||||
return
|
||||
unless (
|
||||
request->path eq uri_for('/report/portssid')->path
|
||||
or index(
|
||||
request->path, uri_for('/ajax/content/report/portssid')->path
|
||||
) == 0
|
||||
);
|
||||
|
||||
# used in the search sidebar template to set selected items
|
||||
foreach my $opt (qw/ssid/) {
|
||||
my $p = (
|
||||
ref [] eq ref param($opt)
|
||||
? param($opt)
|
||||
: ( param($opt) ? [ param($opt) ] : [] )
|
||||
);
|
||||
$tokens->{"${opt}_lkp"} = { map { $_ => 1 } @$p };
|
||||
}
|
||||
};
|
||||
|
||||
get '/ajax/content/report/portssid' => require_login sub {
|
||||
|
||||
my $ssid = param('ssid');
|
||||
|
||||
my $rs = schema('netdisco')->resultset('DevicePortSsid');
|
||||
|
||||
if ( defined $ssid ) {
|
||||
|
||||
$rs = $rs->search(
|
||||
{ ssid => $ssid },
|
||||
{ '+columns' => [
|
||||
qw/ device.dns device.name device.model device.vendor port.port/
|
||||
],
|
||||
join => [qw/ device port /],
|
||||
collapse => 1,
|
||||
}
|
||||
)->order_by( [qw/ port.ip port.port /] )->hri;
|
||||
}
|
||||
else {
|
||||
$rs = $rs->get_ssids->hri;
|
||||
|
||||
}
|
||||
|
||||
my @results = $rs->all;
|
||||
return unless scalar @results;
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
my $json = to_json( \@results );
|
||||
template 'ajax/report/portssid.tt',
|
||||
{ results => $json, opt => $ssid },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/portssid_csv.tt',
|
||||
{ results => \@results, opt => $ssid },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
33
lib/App/Netdisco/Web/Plugin/Report/PortUtilization.pm
Normal file
33
lib/App/Netdisco/Web/Plugin/Report/PortUtilization.pm
Normal file
@@ -0,0 +1,33 @@
|
||||
package App::Netdisco::Web::Plugin::Report::PortUtilization;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report(
|
||||
{ category => 'Device',
|
||||
tag => 'portutilization',
|
||||
label => 'Port Utilization',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
get '/ajax/content/report/portutilization' => require_login sub {
|
||||
return unless schema('netdisco')->resultset('Device')->count;
|
||||
my @results = schema('netdisco')->resultset('Virtual::PortUtilization')->hri->all;
|
||||
|
||||
if (request->is_ajax) {
|
||||
my $json = to_json (\@results);
|
||||
template 'ajax/report/portutilization.tt', { results => $json },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/portutilization_csv.tt', { results => \@results, },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
35
lib/App/Netdisco/Web/Plugin/Report/SsidInventory.pm
Normal file
35
lib/App/Netdisco/Web/Plugin/Report/SsidInventory.pm
Normal file
@@ -0,0 +1,35 @@
|
||||
package App::Netdisco::Web::Plugin::Report::SsidInventory;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report(
|
||||
{ category => 'Wireless',
|
||||
tag => 'ssidinventory',
|
||||
label => 'SSID Inventory',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
get '/ajax/content/report/ssidinventory' => require_login sub {
|
||||
my @results = schema('netdisco')->resultset('DevicePortSsid')
|
||||
->get_ssids->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
my $json = to_json( \@results );
|
||||
template 'ajax/report/portssid.tt', { results => $json },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/portssid_csv.tt', { results => \@results },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
42
lib/App/Netdisco/Web/Plugin/Report/SubnetUtilization.pm
Normal file
42
lib/App/Netdisco/Web/Plugin/Report/SubnetUtilization.pm
Normal file
@@ -0,0 +1,42 @@
|
||||
package App::Netdisco::Web::Plugin::Report::SubnetUtilization;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report({
|
||||
category => 'IP',
|
||||
tag => 'subnets',
|
||||
label => 'Subnet Utilization',
|
||||
provides_csv => 1,
|
||||
});
|
||||
|
||||
get '/ajax/content/report/subnets' => require_login sub {
|
||||
my $subnet = param('subnet') || '0.0.0.0/32';
|
||||
my $agenot = param('age_invert') || '0';
|
||||
my ( $start, $end ) = param('daterange') =~ /(\d+-\d+-\d+)/gmx;
|
||||
|
||||
$start = $start . ' 00:00:00';
|
||||
$end = $end . ' 23:59:59';
|
||||
|
||||
my @results = schema('netdisco')->resultset('Virtual::SubnetUtilization')
|
||||
->search(undef,{
|
||||
bind => [ $subnet, $start, $end, $start, $subnet, $start, $start ],
|
||||
})->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
template 'ajax/report/subnets.tt', { results => \@results },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/subnets_csv.tt', { results => \@results },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
46
lib/App/Netdisco/Web/Plugin/Report/VlanInventory.pm
Normal file
46
lib/App/Netdisco/Web/Plugin/Report/VlanInventory.pm
Normal file
@@ -0,0 +1,46 @@
|
||||
package App::Netdisco::Web::Plugin::Report::VlanInventory;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_report(
|
||||
{ category => 'VLAN',
|
||||
tag => 'vlaninventory',
|
||||
label => 'VLAN Inventory',
|
||||
provides_csv => 1,
|
||||
}
|
||||
);
|
||||
|
||||
get '/ajax/content/report/vlaninventory' => require_login sub {
|
||||
my @results = schema('netdisco')->resultset('DeviceVlan')->search(
|
||||
{ 'me.description' => { '!=', 'NULL' } },
|
||||
{ join => { 'ports' => 'vlan' },
|
||||
select => [
|
||||
'me.vlan',
|
||||
'me.description',
|
||||
{ count => { distinct => 'me.ip' } },
|
||||
{ count => 'ports.vlan' }
|
||||
],
|
||||
as => [qw/ vlan description dcount pcount /],
|
||||
group_by => [qw/ me.vlan me.description /],
|
||||
}
|
||||
)->hri->all;
|
||||
|
||||
return unless scalar @results;
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
my $json = to_json (\@results);
|
||||
template 'ajax/report/vlaninventory.tt', { results => $json },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/report/vlaninventory_csv.tt', { results => \@results },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
true;
|
||||
53
lib/App/Netdisco/Web/Plugin/Search/Device.pm
Normal file
53
lib/App/Netdisco/Web/Plugin/Search/Device.pm
Normal file
@@ -0,0 +1,53 @@
|
||||
package App::Netdisco::Web::Plugin::Search::Device;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use List::MoreUtils ();
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_search_tab(
|
||||
{ tag => 'device', label => 'Device', provides_csv => 1 } );
|
||||
|
||||
# device with various properties or a default match-all
|
||||
get '/ajax/content/search/device' => require_login sub {
|
||||
my $has_opt = List::MoreUtils::any { param($_) }
|
||||
qw/name location dns ip description model os os_ver vendor layers/;
|
||||
my $rs;
|
||||
|
||||
if ($has_opt) {
|
||||
$rs = schema('netdisco')->resultset('Device')->columns(
|
||||
[ "ip", "dns", "name", "contact",
|
||||
"location", "model", "os_ver", "serial"
|
||||
]
|
||||
)->with_times->search_by_field( scalar params );
|
||||
}
|
||||
else {
|
||||
my $q = param('q');
|
||||
send_error( 'Missing query', 400 ) unless $q;
|
||||
|
||||
$rs = schema('netdisco')->resultset('Device')->columns(
|
||||
[ "ip", "dns", "name", "contact",
|
||||
"location", "model", "os_ver", "serial"
|
||||
]
|
||||
)->with_times->search_fuzzy($q);
|
||||
}
|
||||
|
||||
my @results = $rs->hri->all;
|
||||
return unless scalar @results;
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
my $json = to_json( \@results );
|
||||
template 'ajax/search/device.tt', { results => $json },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/search/device_csv.tt', { results => \@results, },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
184
lib/App/Netdisco/Web/Plugin/Search/Node.pm
Normal file
184
lib/App/Netdisco/Web/Plugin/Search/Node.pm
Normal file
@@ -0,0 +1,184 @@
|
||||
package App::Netdisco::Web::Plugin::Search::Node;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::Ajax;
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use NetAddr::IP::Lite ':lower';
|
||||
use NetAddr::MAC ();
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
use App::Netdisco::Util::Web 'sql_match';
|
||||
|
||||
register_search_tab({ tag => 'node', label => 'Node' });
|
||||
|
||||
# nodes matching the param as an IP or DNS hostname or MAC
|
||||
ajax '/ajax/content/search/node' => require_login sub {
|
||||
my $node = param('q');
|
||||
send_error('Missing node', 400) unless $node;
|
||||
content_type('text/html');
|
||||
|
||||
my $agenot = param('age_invert') || '0';
|
||||
my ( $start, $end ) = param('daterange') =~ m/(\d+-\d+-\d+)/gmx;
|
||||
|
||||
my $mac = NetAddr::MAC->new(mac => $node);
|
||||
my @active = (param('archived') ? () : (-bool => 'active'));
|
||||
|
||||
my (@times, @wifitimes, @porttimes);
|
||||
if ($start and $end) {
|
||||
$start = $start . ' 00:00:00';
|
||||
$end = $end . ' 23:59:59';
|
||||
if ($agenot) {
|
||||
@times = (-or => [
|
||||
time_first => [ { '<', $start }, undef ],
|
||||
time_last => { '>', $end },
|
||||
]);
|
||||
@wifitimes = (-or => [
|
||||
time_last => { '<', $start },
|
||||
time_last => { '>', $end },
|
||||
]);
|
||||
@porttimes = (-or => [
|
||||
creation => { '<', $start },
|
||||
creation => { '>', $end },
|
||||
]);
|
||||
}
|
||||
else {
|
||||
@times = (-and => [
|
||||
time_first => { '>=', $start },
|
||||
time_last => { '<=', $end },
|
||||
]);
|
||||
@wifitimes = (-and => [
|
||||
time_last => { '>=', $start },
|
||||
time_last => { '<=', $end },
|
||||
]);
|
||||
@porttimes = (-and => [
|
||||
creation => { '>=', $start },
|
||||
creation => { '<=', $end },
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
my ($likeval, $likeclause) = sql_match($node, not param('partial'));
|
||||
my $using_wildcards = (($likeval ne $node) ? 1 : 0);
|
||||
|
||||
my @where_mac =
|
||||
($using_wildcards ? \['me.mac::text ILIKE ?', $likeval]
|
||||
: ((!defined $mac or $mac->errstr) ? \'0=1' : ('me.mac' => $mac->as_ieee)) );
|
||||
|
||||
my $sightings = schema('netdisco')->resultset('Node')
|
||||
->search({-and => [@where_mac, @active, @times]}, {
|
||||
order_by => {'-desc' => 'time_last'},
|
||||
'+columns' => [
|
||||
'device.dns',
|
||||
{ time_first_stamp => \"to_char(time_first, 'YYYY-MM-DD HH24:MI')" },
|
||||
{ time_last_stamp => \"to_char(time_last, 'YYYY-MM-DD HH24:MI')" },
|
||||
],
|
||||
join => 'device',
|
||||
});
|
||||
|
||||
my $ips = schema('netdisco')->resultset('NodeIp')
|
||||
->search({-and => [@where_mac, @active, @times]}, {
|
||||
order_by => {'-desc' => 'time_last'},
|
||||
'+columns' => [
|
||||
'oui.company',
|
||||
'oui.abbrev',
|
||||
{ time_first_stamp => \"to_char(time_first, 'YYYY-MM-DD HH24:MI')" },
|
||||
{ time_last_stamp => \"to_char(time_last, 'YYYY-MM-DD HH24:MI')" },
|
||||
],
|
||||
join => 'oui'
|
||||
});
|
||||
|
||||
my $netbios = schema('netdisco')->resultset('NodeNbt')
|
||||
->search({-and => [@where_mac, @active, @times]}, {
|
||||
order_by => {'-desc' => 'time_last'},
|
||||
'+columns' => [
|
||||
'oui.company',
|
||||
'oui.abbrev',
|
||||
{ time_first_stamp => \"to_char(time_first, 'YYYY-MM-DD HH24:MI')" },
|
||||
{ time_last_stamp => \"to_char(time_last, 'YYYY-MM-DD HH24:MI')" },
|
||||
],
|
||||
join => 'oui'
|
||||
});
|
||||
|
||||
my $wireless = schema('netdisco')->resultset('NodeWireless')->search(
|
||||
{ -and => [@where_mac, @wifitimes] },
|
||||
{ order_by => { '-desc' => 'time_last' },
|
||||
'+columns' => [
|
||||
'oui.company',
|
||||
'oui.abbrev',
|
||||
{
|
||||
time_last_stamp => \"to_char(time_last, 'YYYY-MM-DD HH24:MI')"
|
||||
}],
|
||||
join => 'oui'
|
||||
}
|
||||
);
|
||||
|
||||
my $rs_dp = schema('netdisco')->resultset('DevicePort');
|
||||
if ($sightings->has_rows or $ips->has_rows or $netbios->has_rows) {
|
||||
my $ports = param('deviceports')
|
||||
? $rs_dp->search({ -and => [@where_mac] }) : undef;
|
||||
|
||||
return template 'ajax/search/node_by_mac.tt', {
|
||||
ips => $ips,
|
||||
sightings => $sightings,
|
||||
ports => $ports,
|
||||
wireless => $wireless,
|
||||
netbios => $netbios,
|
||||
}, { layout => undef };
|
||||
}
|
||||
else {
|
||||
my $ports = param('deviceports')
|
||||
? $rs_dp->search({ -and => [@where_mac, @porttimes] }) : undef;
|
||||
|
||||
if (defined $ports and $ports->has_rows) {
|
||||
return template 'ajax/search/node_by_mac.tt', {
|
||||
ips => $ips,
|
||||
sightings => $sightings,
|
||||
ports => $ports,
|
||||
wireless => $wireless,
|
||||
netbios => $netbios,
|
||||
}, { layout => undef };
|
||||
}
|
||||
}
|
||||
|
||||
my $set = schema('netdisco')->resultset('NodeNbt')
|
||||
->search_by_name({nbname => $likeval, @active, @times});
|
||||
|
||||
unless ( $set->has_rows ) {
|
||||
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, @times});
|
||||
}
|
||||
else {
|
||||
$likeval .= setting('domain_suffix')
|
||||
if index($node, setting('domain_suffix')) == -1;
|
||||
|
||||
$set = schema('netdisco')->resultset('NodeIp')
|
||||
->search_by_dns({dns => $likeval, @active, @times});
|
||||
|
||||
# if the user selects Vendor search opt, then
|
||||
# we'll try the OUI company name as a fallback
|
||||
|
||||
if (param('show_vendor') and not $set->has_rows) {
|
||||
$set = schema('netdisco')->resultset('NodeIp')
|
||||
->with_times
|
||||
->search(
|
||||
{'oui.company' => { -ilike => ''.sql_match($node)}, @times},
|
||||
{'prefetch' => 'oui'},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return unless $set and $set->has_rows;
|
||||
$set = $set->search_rs({}, { order_by => 'me.mac' });
|
||||
|
||||
template 'ajax/search/node_by_ip.tt', {
|
||||
macs => $set,
|
||||
archive_filter => {@active},
|
||||
}, { layout => undef };
|
||||
};
|
||||
|
||||
true;
|
||||
65
lib/App/Netdisco/Web/Plugin/Search/Port.pm
Normal file
65
lib/App/Netdisco/Web/Plugin/Search/Port.pm
Normal file
@@ -0,0 +1,65 @@
|
||||
package App::Netdisco::Web::Plugin::Search::Port;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
use App::Netdisco::Util::Web 'sql_match';
|
||||
|
||||
register_search_tab( { tag => 'port', label => 'Port', provides_csv => 1 } );
|
||||
|
||||
# device ports with a description (er, name) matching
|
||||
get '/ajax/content/search/port' => require_login sub {
|
||||
my $q = param('q');
|
||||
send_error( 'Missing query', 400 ) unless $q;
|
||||
my $rs;
|
||||
|
||||
if ( $q =~ m/^\d+$/ ) {
|
||||
$rs
|
||||
= schema('netdisco')->resultset('DevicePort')
|
||||
->columns( [qw/ ip port name descr /] )->search(
|
||||
{ "port_vlans.vlan" => $q },
|
||||
{ '+columns' => [qw/ device.dns device.name port_vlans.vlan /],
|
||||
join => [qw/ port_vlans device /]
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
my ( $likeval, $likeclause ) = sql_match($q);
|
||||
|
||||
$rs
|
||||
= schema('netdisco')->resultset('DevicePort')
|
||||
->columns( [qw/ ip port name descr /] )->search(
|
||||
{ -or => [
|
||||
{ "me.name" => ( param('partial') ? $likeclause : $q ) },
|
||||
( length $q == 17
|
||||
? { "me.mac" => $q }
|
||||
: \[ 'me.mac::text ILIKE ?', $likeval ]
|
||||
),
|
||||
{ "me.remote_id" => $likeclause },
|
||||
{ "me.remote_type" => $likeclause },
|
||||
]
|
||||
},
|
||||
{ '+columns' => [qw/ device.dns device.name port_vlans.vlan /],
|
||||
join => [qw/ port_vlans device /]
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
my @results = $rs->hri->all;
|
||||
return unless scalar @results;
|
||||
|
||||
if ( request->is_ajax ) {
|
||||
my $json = to_json( \@results );
|
||||
template 'ajax/search/port.tt', { results => $json },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/search/port_csv.tt', { results => \@results },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
41
lib/App/Netdisco/Web/Plugin/Search/VLAN.pm
Normal file
41
lib/App/Netdisco/Web/Plugin/Search/VLAN.pm
Normal file
@@ -0,0 +1,41 @@
|
||||
package App::Netdisco::Web::Plugin::Search::VLAN;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Web::Plugin;
|
||||
|
||||
register_search_tab( { tag => 'vlan', label => 'VLAN', provides_csv => 1 } );
|
||||
|
||||
# devices carrying vlan xxx
|
||||
get '/ajax/content/search/vlan' => require_login sub {
|
||||
my $q = param('q');
|
||||
send_error( 'Missing query', 400 ) unless $q;
|
||||
my $rs;
|
||||
|
||||
if ( $q =~ m/^\d+$/ ) {
|
||||
$rs = schema('netdisco')->resultset('Device')
|
||||
->carrying_vlan( { vlan => $q } );
|
||||
}
|
||||
else {
|
||||
$rs = schema('netdisco')->resultset('Device')
|
||||
->carrying_vlan_name( { name => $q } );
|
||||
}
|
||||
|
||||
my @results = $rs->hri->all;
|
||||
return unless scalar @results;
|
||||
|
||||
if (request->is_ajax) {
|
||||
my $json = to_json( \@results );
|
||||
template 'ajax/search/vlan.tt', { results => $json },
|
||||
{ layout => undef };
|
||||
}
|
||||
else {
|
||||
header( 'Content-Type' => 'text/comma-separated-values' );
|
||||
template 'ajax/search/vlan_csv.tt', { results => \@results },
|
||||
{ layout => undef };
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
88
lib/App/Netdisco/Web/PortControl.pm
Normal file
88
lib/App/Netdisco/Web/PortControl.pm
Normal file
@@ -0,0 +1,88 @@
|
||||
package App::Netdisco::Web::PortControl;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::Ajax;
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::JobQueue qw/jq_insert jq_userlog/;
|
||||
|
||||
ajax '/ajax/portcontrol' => require_role port_control => sub {
|
||||
send_error('No device/port/field', 400)
|
||||
unless param('device') and (param('port') or param('field'));
|
||||
|
||||
my $log = sprintf 'd:[%s] p:[%s] f:[%s]. a:[%s] v[%s]',
|
||||
param('device'), (param('port') || ''), param('field'),
|
||||
(param('action') || ''), (param('value') || '');
|
||||
|
||||
my %action_map = (
|
||||
'location' => 'location',
|
||||
'contact' => 'contact',
|
||||
'c_port' => 'portcontrol',
|
||||
'c_name' => 'portname',
|
||||
'c_pvid' => 'vlan',
|
||||
'c_power' => 'power',
|
||||
);
|
||||
|
||||
my $action = $action_map{ param('field') };
|
||||
my $subaction = ($action =~ m/^(?:power|portcontrol)/
|
||||
? (param('action') ."-other")
|
||||
: param('value'));
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
if (param('port')) {
|
||||
my $a = "$action $subaction";
|
||||
$a =~ s/-other$//;
|
||||
$a =~ s/^portcontrol/port/;
|
||||
|
||||
schema('netdisco')->resultset('DevicePortLog')->create({
|
||||
ip => param('device'),
|
||||
port => param('port'),
|
||||
action => $a,
|
||||
username => session('logged_in_user'),
|
||||
userip => request->remote_address,
|
||||
reason => (param('reason') || 'other'),
|
||||
log => param('log'),
|
||||
});
|
||||
}
|
||||
|
||||
jq_insert({
|
||||
device => param('device'),
|
||||
port => param('port'),
|
||||
action => $action,
|
||||
subaction => $subaction,
|
||||
username => session('logged_in_user'),
|
||||
userip => request->remote_address,
|
||||
log => $log,
|
||||
});
|
||||
});
|
||||
|
||||
content_type('application/json');
|
||||
to_json({});
|
||||
};
|
||||
|
||||
ajax '/ajax/userlog' => require_login sub {
|
||||
my @jobs = jq_userlog( session('logged_in_user') );
|
||||
|
||||
my %status = (
|
||||
'done' => [
|
||||
map {s/\[\]/<empty>/; $_}
|
||||
map { $_->log }
|
||||
grep { $_->status eq 'done' }
|
||||
grep { defined }
|
||||
@jobs
|
||||
],
|
||||
'error' => [
|
||||
map {s/\[\]/<empty>/; $_}
|
||||
map { $_->log }
|
||||
grep { $_->status eq 'error' }
|
||||
grep { defined }
|
||||
@jobs
|
||||
],
|
||||
);
|
||||
|
||||
content_type('application/json');
|
||||
to_json(\%status);
|
||||
};
|
||||
|
||||
true;
|
||||
66
lib/App/Netdisco/Web/Report.pm
Normal file
66
lib/App/Netdisco/Web/Report.pm
Normal file
@@ -0,0 +1,66 @@
|
||||
package App::Netdisco::Web::Report;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
get '/report/*' => require_login sub {
|
||||
my ($tag) = splat;
|
||||
|
||||
# used in the report search sidebar to populate select inputs
|
||||
my ( $domain_list, $class_list, $ssid_list, $type_list, $vendor_list );
|
||||
|
||||
if ( $tag eq 'netbios' ) {
|
||||
$domain_list = [ schema('netdisco')->resultset('NodeNbt')
|
||||
->get_distinct_col('domain') ];
|
||||
}
|
||||
elsif ( $tag eq 'moduleinventory' ) {
|
||||
$class_list = [ schema('netdisco')->resultset('DeviceModule')
|
||||
->get_distinct_col('class') ];
|
||||
|
||||
# this is a bit fragile... three params currently
|
||||
my %params = request->params();
|
||||
if (3 == scalar keys %params) {
|
||||
foreach my $col ( @{ var('module_options') } ) {
|
||||
next unless $col->{default} eq 'on';
|
||||
$params{ $col->{name} } = 'checked';
|
||||
}
|
||||
}
|
||||
}
|
||||
elsif ( $tag eq 'portssid' ) {
|
||||
$ssid_list = [ schema('netdisco')->resultset('DevicePortSsid')
|
||||
->get_distinct_col('ssid') ];
|
||||
}
|
||||
elsif ( $tag eq 'nodesdiscovered' ) {
|
||||
$type_list = [ schema('netdisco')->resultset('DevicePort')
|
||||
->get_distinct_col('remote_type') ];
|
||||
}
|
||||
elsif ( $tag eq 'nodevendor' ) {
|
||||
$vendor_list = [
|
||||
schema('netdisco')->resultset('Node')->search(
|
||||
{},
|
||||
{ join => 'oui',
|
||||
columns => ['oui.abbrev'],
|
||||
order_by => 'oui.abbrev',
|
||||
group_by => 'oui.abbrev',
|
||||
}
|
||||
)->get_column('abbrev')->all
|
||||
];
|
||||
}
|
||||
|
||||
# trick the ajax into working as if this were a tabbed page
|
||||
params->{tab} = $tag;
|
||||
|
||||
var( nav => 'reports' );
|
||||
template 'report',
|
||||
{
|
||||
report => setting('_reports')->{$tag},
|
||||
domain_list => $domain_list,
|
||||
class_list => $class_list,
|
||||
ssid_list => $ssid_list,
|
||||
type_list => $type_list,
|
||||
vendor_list => $vendor_list,
|
||||
};
|
||||
};
|
||||
|
||||
true;
|
||||
131
lib/App/Netdisco/Web/Search.pm
Normal file
131
lib/App/Netdisco/Web/Search.pm
Normal file
@@ -0,0 +1,131 @@
|
||||
package App::Netdisco::Web::Search;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::Ajax;
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Util::Web 'sql_match';
|
||||
use NetAddr::MAC ();
|
||||
|
||||
hook 'before' => sub {
|
||||
# view settings for node options
|
||||
var('node_options' => [
|
||||
{ name => 'stamps', label => 'Time Stamps', default => 'on' },
|
||||
{ name => 'deviceports', label => 'Device Ports', default => 'on' },
|
||||
]);
|
||||
|
||||
# view settings for device options
|
||||
var('device_options' => [
|
||||
{ name => 'matchall', label => 'Match All Options', default => 'on' },
|
||||
]);
|
||||
|
||||
return unless (request->path eq uri_for('/search')->path
|
||||
or index(request->path, uri_for('/ajax/content/search')->path) == 0);
|
||||
|
||||
foreach my $col (@{ var('node_options') }) {
|
||||
next unless $col->{default} eq 'on';
|
||||
params->{$col->{name}} = 'checked'
|
||||
if not param('tab') or param('tab') ne 'node';
|
||||
}
|
||||
|
||||
foreach my $col (@{ var('device_options') }) {
|
||||
next unless $col->{default} eq 'on';
|
||||
params->{$col->{name}} = 'checked'
|
||||
if not param('tab') or param('tab') ne 'device';
|
||||
}
|
||||
};
|
||||
|
||||
hook 'before_template' => sub {
|
||||
my $tokens = shift;
|
||||
|
||||
# new searches will use these defaults in their sidebars
|
||||
$tokens->{search_node} = uri_for('/search', {tab => 'node'});
|
||||
$tokens->{search_device} = uri_for('/search', {tab => 'device'});
|
||||
|
||||
foreach my $col (@{ var('node_options') }) {
|
||||
next unless $col->{default} eq 'on';
|
||||
$tokens->{search_node}->query_param($col->{name}, 'checked');
|
||||
}
|
||||
|
||||
foreach my $col (@{ var('device_options') }) {
|
||||
next unless $col->{default} eq 'on';
|
||||
$tokens->{search_device}->query_param($col->{name}, 'checked');
|
||||
}
|
||||
|
||||
return unless (request->path eq uri_for('/search')->path
|
||||
or index(request->path, uri_for('/ajax/content/search')->path) == 0);
|
||||
|
||||
# used in the device search sidebar template to set selected items
|
||||
foreach my $opt (qw/model vendor os os_ver/) {
|
||||
my $p = (ref [] eq ref param($opt) ? param($opt)
|
||||
: (param($opt) ? [param($opt)] : []));
|
||||
$tokens->{"${opt}_lkp"} = { map { $_ => 1 } @$p };
|
||||
}
|
||||
};
|
||||
|
||||
get '/search' => require_login sub {
|
||||
my $q = param('q');
|
||||
my $s = schema('netdisco');
|
||||
|
||||
if (not param('tab')) {
|
||||
if (not $q) {
|
||||
return redirect uri_for('/')->path;
|
||||
}
|
||||
|
||||
# pick most likely tab for initial results
|
||||
if ($q =~ m/^\d+$/) {
|
||||
params->{'tab'} = 'vlan';
|
||||
}
|
||||
else {
|
||||
my $nd = $s->resultset('Device')->search_fuzzy($q);
|
||||
my ($likeval, $likeclause) = sql_match($q);
|
||||
my $mac = NetAddr::MAC->new($q);
|
||||
|
||||
if ($nd and $nd->count) {
|
||||
if ($nd->count == 1) {
|
||||
# redirect to device details for the one device
|
||||
return redirect uri_for('/device', {
|
||||
tab => 'details',
|
||||
q => $nd->first->ip,
|
||||
f => '',
|
||||
})->path_query;
|
||||
}
|
||||
|
||||
# multiple devices
|
||||
params->{'tab'} = 'device';
|
||||
}
|
||||
elsif ($s->resultset('DevicePort')
|
||||
->search({
|
||||
-or => [
|
||||
{name => $likeclause},
|
||||
((!defined $mac or $mac->errstr)
|
||||
? \['mac::text ILIKE ?', $likeval]
|
||||
: {mac => $mac->as_ieee}),
|
||||
],
|
||||
})->count) {
|
||||
|
||||
params->{'tab'} = 'port';
|
||||
}
|
||||
}
|
||||
|
||||
# if all else fails
|
||||
params->{'tab'} ||= 'node';
|
||||
}
|
||||
|
||||
# used in the device search sidebar to populate select inputs
|
||||
my $model_list = [ $s->resultset('Device')->get_distinct_col('model') ];
|
||||
my $os_list = [ $s->resultset('Device')->get_distinct_col('os') ];
|
||||
my $os_ver_list = [ $s->resultset('Device')->get_distinct_col('os_ver') ];
|
||||
my $vendor_list = [ $s->resultset('Device')->get_distinct_col('vendor') ];
|
||||
|
||||
template 'search', {
|
||||
search => params->{'tab'},
|
||||
model_list => $model_list,
|
||||
os_list => $os_list,
|
||||
os_ver_list => $os_ver_list,
|
||||
vendor_list => $vendor_list,
|
||||
};
|
||||
};
|
||||
|
||||
true;
|
||||
30
lib/App/Netdisco/Web/Static.pm
Normal file
30
lib/App/Netdisco/Web/Static.pm
Normal file
@@ -0,0 +1,30 @@
|
||||
package App::Netdisco::Web::Static;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Path::Class;
|
||||
|
||||
get '/plugin/*/*.js' => sub {
|
||||
my ($plugin) = splat;
|
||||
|
||||
my $content = template
|
||||
'plugin.tt', { target => "plugin/$plugin/$plugin.js" },
|
||||
{ layout => undef };
|
||||
|
||||
send_file \$content,
|
||||
content_type => 'application/javascript',
|
||||
filename => "$plugin.js";
|
||||
};
|
||||
|
||||
get '/plugin/*/*.css' => sub {
|
||||
my ($plugin) = splat;
|
||||
|
||||
my $content = template
|
||||
'plugin.tt', { target => "plugin/$plugin/$plugin.css" },
|
||||
{ layout => undef };
|
||||
|
||||
send_file \$content,
|
||||
content_type => 'text/css',
|
||||
filename => "$plugin.css";
|
||||
};
|
||||
|
||||
true;
|
||||
85
lib/App/Netdisco/Web/Statistics.pm
Normal file
85
lib/App/Netdisco/Web/Statistics.pm
Normal file
@@ -0,0 +1,85 @@
|
||||
package App::Netdisco::Web::Statistics;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
get '/ajax/content/statistics' => require_login sub {
|
||||
|
||||
my $time1 = time;
|
||||
my $schema = schema('netdisco');
|
||||
my $devices = $schema->resultset('Device');
|
||||
|
||||
# used only to get the PostgreSQL version
|
||||
my $users = $schema->resultset('User')->search(
|
||||
{},
|
||||
{ select => [ { version => '' } ],
|
||||
as => [qw/ version /],
|
||||
}
|
||||
);
|
||||
|
||||
my $device_count = $devices->count;
|
||||
my $device_port_count = $schema->resultset('DevicePort')->count;
|
||||
|
||||
my $device_ip_count = $schema->resultset('DeviceIp')
|
||||
->search( undef, { columns => [qw/ alias /] } )->count;
|
||||
|
||||
my $nodes = $schema->resultset('Node')->search(
|
||||
{},
|
||||
{ columns => [qw/mac/],
|
||||
distinct => 1
|
||||
}
|
||||
);
|
||||
|
||||
my $node_count = $nodes->count;
|
||||
my $node_table_count = $schema->resultset('Node')->count;
|
||||
|
||||
my $nodes_ips = $schema->resultset('NodeIp')->search(
|
||||
{},
|
||||
{ columns => [qw/ip/],
|
||||
distinct => 1
|
||||
}
|
||||
);
|
||||
my $ip_count = $nodes_ips->count;
|
||||
my $ip_table_count
|
||||
= $schema->resultset('NodeIp')->search( {}, { columns => [qw/ip/] } )
|
||||
->count;
|
||||
my $device_links = $schema->resultset('DevicePort')
|
||||
->search( { 'remote_ip' => { '!=', undef } } )->count;
|
||||
my $schema_version = $schema->get_db_version;
|
||||
my $target_version = $schema->schema_version;
|
||||
|
||||
my $time2 = time;
|
||||
my $process_time = $time2 - $time1;
|
||||
|
||||
my $disco_ver = $App::Netdisco::VERSION;
|
||||
my $db_version = $users->next->get_column('version');
|
||||
my $dbi_ver = $DBI::VERSION;
|
||||
my $dbdpg_ver = $DBD::Pg::VERSION;
|
||||
|
||||
eval { require SNMP::Info };
|
||||
my $snmpinfo_ver = ($@ ? 'n/a' : $SNMP::Info::VERSION);
|
||||
|
||||
var( nav => 'statistics' );
|
||||
template 'ajax/statistics.tt',
|
||||
{
|
||||
device_count => $device_count,
|
||||
device_ip_count => $device_ip_count,
|
||||
device_links => $device_links,
|
||||
device_port_count => $device_port_count,
|
||||
ip_count => $ip_count,
|
||||
ip_table_count => $ip_table_count,
|
||||
node_count => $node_count,
|
||||
node_table_count => $node_table_count,
|
||||
process_time => $process_time,
|
||||
disco_ver => $disco_ver,
|
||||
db_version => $db_version,
|
||||
dbi_ver => $dbi_ver,
|
||||
dbdpg_ver => $dbdpg_ver,
|
||||
snmpinfo_ver => $snmpinfo_ver,
|
||||
schema_ver => $schema_version,
|
||||
},
|
||||
{ layout => undef };
|
||||
};
|
||||
|
||||
true;
|
||||
71
lib/App/Netdisco/Web/TypeAhead.pm
Normal file
71
lib/App/Netdisco/Web/TypeAhead.pm
Normal file
@@ -0,0 +1,71 @@
|
||||
package App::Netdisco::Web::TypeAhead;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::Ajax;
|
||||
use Dancer::Plugin::DBIC;
|
||||
use Dancer::Plugin::Auth::Extensible;
|
||||
|
||||
use App::Netdisco::Util::Web (); # for sort_port
|
||||
|
||||
ajax '/ajax/data/devicename/typeahead' => require_login sub {
|
||||
return '[]' unless setting('navbar_autocomplete');
|
||||
|
||||
my $q = param('query') || param('term');
|
||||
my $set = schema('netdisco')->resultset('Device')->search_fuzzy($q);
|
||||
|
||||
content_type 'application/json';
|
||||
to_json [map {$_->dns || $_->name || $_->ip} $set->all];
|
||||
};
|
||||
|
||||
ajax '/ajax/data/deviceip/typeahead' => require_login sub {
|
||||
my $q = param('query') || param('term');
|
||||
my $set = schema('netdisco')->resultset('Device')->search_fuzzy($q);
|
||||
|
||||
my @data = ();
|
||||
while (my $d = $set->next) {
|
||||
my $label = $d->ip;
|
||||
if ($d->dns or $d->name) {
|
||||
$label = sprintf '%s (%s)',
|
||||
($d->dns || $d->name), $d->ip;
|
||||
}
|
||||
push @data, { label => $label, value => $d->ip };
|
||||
}
|
||||
|
||||
content_type 'application/json';
|
||||
to_json \@data;
|
||||
};
|
||||
|
||||
ajax '/ajax/data/port/typeahead' => require_login sub {
|
||||
my $dev = param('dev1') || param('dev2');
|
||||
my $port = param('port1') || param('port2');
|
||||
send_error('Missing device', 400) unless $dev;
|
||||
|
||||
my $device = schema('netdisco')->resultset('Device')
|
||||
->find({ip => $dev});
|
||||
send_error('Bad device', 400) unless $device;
|
||||
|
||||
my $set = $device->ports({},{order_by => 'port'});
|
||||
$set = $set->search({port => { -ilike => "\%$port\%" }})
|
||||
if $port;
|
||||
|
||||
my $results = [
|
||||
map {{ label => (sprintf "%s (%s)", $_->port, $_->name), value => $_->port }}
|
||||
sort { &App::Netdisco::Util::Web::sort_port($a->port, $b->port) } $set->all
|
||||
];
|
||||
|
||||
content_type 'application/json';
|
||||
to_json \@$results;
|
||||
};
|
||||
|
||||
ajax '/ajax/data/subnet/typeahead' => require_login sub {
|
||||
my $q = param('query') || param('term');
|
||||
$q = "$q\%" if $q !~ m/\%/;
|
||||
my $nets = schema('netdisco')->resultset('Subnet')->search(
|
||||
{ 'me.net::text' => { '-ilike' => $q }},
|
||||
{ columns => ['net'], order_by => 'net' } );
|
||||
|
||||
content_type 'application/json';
|
||||
to_json [map {$_->net} $nets->all];
|
||||
};
|
||||
|
||||
true;
|
||||
Reference in New Issue
Block a user