Merge branch 'master' into og-multiple-domain-suffix

This commit is contained in:
Oliver Gorwits
2019-09-03 09:45:25 +01:00
64 changed files with 548 additions and 232 deletions

View File

@@ -1,12 +1,33 @@
<!--- Provide a general summary of the issue in the Title above -->
<!---
<!-- stop! If your ticket is about a device not being detected correctly, -->
<!-- see SNMP::Info: https://github.com/netdisco/snmp-info/issues/new -->
READ THIS FIRST!
----------------
<!-- stop! If you have new MIBs to submit, -->
<!-- see netdisco-mibs: https://github.com/netdisco/netdisco-mibs/issues/new -->
STOP! If your ticket is about a device not being detected correctly,
see SNMP::Info: https://github.com/netdisco/snmp-info/issues/new
STOP! If you have new MIBs to submit,
see netdisco-mibs: https://github.com/netdisco/netdisco-mibs/issues/new
STOP! If you are running a netdisco docker setup,
see netdisco-docker: https://github.com/netdisco/netdisco-docker/issues/new
everything else about Netdisco's behaviour is good, here :-D
the more info you can provide, the easier it is for us the help you, so please
fill out as many of the items below as possible.
Provide a general summary of the issue in the Title above
when including netdisco config snippets, whitespace matters since it's a yaml file
for github issues it really helps if you include the relevant config parts in a codeblock (code fencing)
see the "code" subject on https://guides.github.com/features/mastering-markdown/ for that)
this should preserve spaces in the issue tracker and make troubleshooting quicker
-->
<!-- everything else about Netdisco's behaviour is good, here :-D -->
## Expected Behavior
<!--- If you're describing a bug, tell us what should happen -->
@@ -37,6 +58,10 @@
* Netdisco version used:
* SNMP::Info version used:
## Config info (deployment.yml)
<!--- if possible include all options you added to your deployment.yml file, since -->
<!--- some options can change the behaviour in drastic ways -->
## Device information
<!--- if the issue relates to specific devices their info would be usefull -->
<!--- do note that the following command might contain sensitive info, you can -->

4
.gitignore vendored
View File

@@ -15,3 +15,7 @@ _build
blib
!.docker/hooks/*
.idea
node_modules/*
yarn.lock
package.json
.stylelintrc

View File

@@ -2,12 +2,14 @@ language: perl
perl:
- "5.30"
- "5.10"
os: linux
dist: trusty
addons:
apt:
packages:
- libsnmp-dev
- phantomjs
- graphviz
- graphviz
hosts:
- localhost
branches:
@@ -15,7 +17,7 @@ branches:
- /^2\.\d{6}$/
- 'master'
install:
- cpanm --quiet --notest PkgConfig Test::CChecker Alien::zlib::Static Alien::OpenSSL::Static Alien::SNMP
- cpanm --quiet --notest https://github.com/egiles/test-compile/archive/v2.1.2.tar.gz PkgConfig Test::CChecker Alien::zlib::Static Alien::OpenSSL::Static Alien::SNMP
script: |
perl Build.PL && \
./Build && \

View File

@@ -26,6 +26,7 @@ Module::Build->new(
'App::cpanminus' => '1.6108',
'App::local::lib::helper' => '0.07',
'Archive::Extract' => '0',
'Authen::Radius' => '0',
'CGI::Expand' => '2.05',
'Data::Printer' => '0',
'DBD::Pg' => '0',

24
LICENCE
View File

@@ -1,24 +0,0 @@
Copyright (c) 2012, The Netdisco Developer Team
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Netdisco Project nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE NETDISCO DEVELOPER TEAM BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

29
LICENSE.md Normal file
View File

@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2019, The Netdisco Developer Team.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -62,6 +62,7 @@ lib/App/Netdisco/DB/Result/Virtual/DevicePoeStatus.pm
lib/App/Netdisco/DB/Result/Virtual/DevicePortSpeed.pm
lib/App/Netdisco/DB/Result/Virtual/DuplexMismatch.pm
lib/App/Netdisco/DB/Result/Virtual/GenericReport.pm
lib/App/Netdisco/DB/Result/Virtual/LastNode.pm
lib/App/Netdisco/DB/Result/Virtual/NodeIp4.pm
lib/App/Netdisco/DB/Result/Virtual/NodeIp6.pm
lib/App/Netdisco/DB/Result/Virtual/NodeMonitor.pm
@@ -237,7 +238,7 @@ lib/App/Netdisco/Worker/Plugin/Vlan/Core.pm
lib/App/Netdisco/Worker/Runner.pm
lib/App/Netdisco/Worker/Status.pm
lib/Dancer/Template/NetdiscoTemplateToolkit.pm
LICENCE
LICENSE.md
MANIFEST This list of files
META.json
META.yml
@@ -318,6 +319,7 @@ share/public/javascripts/netdisco_portcontrol.js
share/public/javascripts/portsort.js
share/public/javascripts/toastr.js
share/public/javascripts/underscore.min.js
share/radius_dictionaries/TODO
share/schema_versions/App-Netdisco-DB-1-2-PostgreSQL.sql
share/schema_versions/App-Netdisco-DB-10-11-PostgreSQL.sql
share/schema_versions/App-Netdisco-DB-11-12-PostgreSQL.sql
@@ -371,6 +373,7 @@ share/schema_versions/App-Netdisco-DB-54-55-PostgreSQL.sql
share/schema_versions/App-Netdisco-DB-55-56-PostgreSQL.sql
share/schema_versions/App-Netdisco-DB-56-57-PostgreSQL.sql
share/schema_versions/App-Netdisco-DB-57-58-PostgreSQL.sql
share/schema_versions/App-Netdisco-DB-58-59-PostgreSQL.sql
share/schema_versions/App-Netdisco-DB-6-7-PostgreSQL.sql
share/schema_versions/App-Netdisco-DB-7-8-PostgreSQL.sql
share/schema_versions/App-Netdisco-DB-8-9-PostgreSQL.sql
@@ -505,6 +508,7 @@ share/views/sidebar/search/device.tt
share/views/sidebar/search/node.tt
share/views/sidebar/search/port.tt
xt/00-compile.t
xt/01-local-pod.t
xt/10-sort_port.t
xt/11-portsort.t
xt/20-checkacl.t

View File

@@ -4,7 +4,7 @@
"Oliver Gorwits <oliver@cpan.org>"
],
"dynamic_config" : 1,
"generated_by" : "Module::Build version 0.4224",
"generated_by" : "Module::Build version 0.4229",
"license" : [
"bsd"
],
@@ -39,6 +39,7 @@
"App::cpanminus" : "1.6108",
"App::local::lib::helper" : "0.07",
"Archive::Extract" : "0",
"Authen::Radius" : "0",
"CGI::Expand" : "2.05",
"DBD::Pg" : "0",
"DBIx::Class" : "0.082841",
@@ -143,7 +144,7 @@
},
"App::Netdisco::DB" : {
"file" : "lib/App/Netdisco/DB.pm",
"version" : "58"
"version" : "59"
},
"App::Netdisco::DB::ExplicitLocking" : {
"file" : "lib/App/Netdisco/DB/ExplicitLocking.pm"
@@ -268,6 +269,9 @@
"App::Netdisco::DB::Result::Virtual::GenericReport" : {
"file" : "lib/App/Netdisco/DB/Result/Virtual/GenericReport.pm"
},
"App::Netdisco::DB::Result::Virtual::LastNode" : {
"file" : "lib/App/Netdisco/DB/Result/Virtual/LastNode.pm"
},
"App::Netdisco::DB::Result::Virtual::NodeIp4" : {
"file" : "lib/App/Netdisco/DB/Result/Virtual/NodeIp4.pm"
},
@@ -804,7 +808,7 @@
},
"homepage" : "http://netdisco.org/",
"license" : [
"http://opensource.org/licenses/BSD-3-Clause"
"http://opensource.org/licenses/bsd-license.php"
],
"repository" : {
"url" : "https://github.com/netdisco/netdisco"
@@ -813,5 +817,5 @@
"x_MailingList" : "https://lists.sourceforge.net/lists/listinfo/netdisco-users"
},
"version" : "2.042010",
"x_serialization_backend" : "JSON::PP version 2.97001"
"x_serialization_backend" : "JSON::PP version 4.02"
}

View File

@@ -14,7 +14,7 @@ configure_requires:
DBIx::Class: '0.082810'
Module::Build: '0.42'
dynamic_config: 1
generated_by: 'Module::Build version 0.4224, CPAN::Meta::Converter version 2.150010'
generated_by: 'Module::Build version 0.4229, CPAN::Meta::Converter version 2.150010'
license: bsd
meta-spec:
url: http://module-build.sourceforge.net/META-spec-v1.4.html
@@ -38,7 +38,7 @@ provides:
file: lib/App/Netdisco/Configuration.pm
App::Netdisco::DB:
file: lib/App/Netdisco/DB.pm
version: '58'
version: '59'
App::Netdisco::DB::ExplicitLocking:
file: lib/App/Netdisco/DB/ExplicitLocking.pm
App::Netdisco::DB::Result::Admin:
@@ -121,6 +121,8 @@ provides:
file: lib/App/Netdisco/DB/Result/Virtual/DuplexMismatch.pm
App::Netdisco::DB::Result::Virtual::GenericReport:
file: lib/App/Netdisco/DB/Result/Virtual/GenericReport.pm
App::Netdisco::DB::Result::Virtual::LastNode:
file: lib/App/Netdisco/DB/Result/Virtual/LastNode.pm
App::Netdisco::DB::Result::Virtual::NodeIp4:
file: lib/App/Netdisco/DB/Result/Virtual/NodeIp4.pm
App::Netdisco::DB::Result::Virtual::NodeIp6:
@@ -483,6 +485,7 @@ requires:
App::cpanminus: '1.6108'
App::local::lib::helper: '0.07'
Archive::Extract: '0'
Authen::Radius: '0'
CGI::Expand: '2.05'
DBD::Pg: '0'
DBIx::Class: '0.082841'
@@ -556,7 +559,7 @@ resources:
MailingList: https://lists.sourceforge.net/lists/listinfo/netdisco-users
bugtracker: https://github.com/netdisco/netdisco/issues
homepage: http://netdisco.org/
license: http://opensource.org/licenses/BSD-3-Clause
license: http://opensource.org/licenses/bsd-license.php
repository: https://github.com/netdisco/netdisco
version: '2.042010'
x_serialization_backend: 'CPAN::Meta::YAML version 0.018'

View File

@@ -71,7 +71,7 @@ these is an optional service which the user is asked to confirm.
Pre-existing requirements are that there be a database table created and a
user with rights to create tables in that database. Both the table and user
name must match those configured in your environment YAML file (default
C<~/environments/deployment.yml>).
F<~/environments/deployment.yml>).
This script will download the latest MAC address vendor prefix data from the
Internet, and update the OUI table in the database. Hence Internet access is
@@ -80,10 +80,17 @@ required to run the script.
Similarly the latest Netdisco MIB bundle is also downloaded and placed into
the user's home directory (or C<$ENV{NETDISCO_HOME}>).
If you upgrade Netdisco make sure you run this script again to make sure
your config remains compatible.
Before each upgrade also review the
L<Release notes|https://github.com/netdisco/netdisco/wiki/Release-Notes> since
additional steps might be required!
=cut
print color 'bold cyan';
say 'This is the Netdisco II deployment script.';
say 'This is the Netdisco 2 deployment script.';
say '';
say 'Before we continue, the following prerequisites must be in place:';
say ' * Database added to PostgreSQL for Netdisco';
@@ -92,6 +99,11 @@ say ' * "~/environments/deployment.yml" file configured with Database dsn/user/p
say ' * A full backup of any existing Netdisco database data';
say ' * Internet access (for OUIs and MIBs)';
say '';
say 'If you are upgrading Netdisco 2 read the release notes:';
say 'https://github.com/netdisco/netdisco/wiki/Release-Notes';
say 'There you will find required and incompatible changes';
say 'which are not covered by this script.';
say '';
say 'You will be asked to confirm all changes to your system.';
say '';
print color 'reset';
@@ -194,13 +206,13 @@ sub deploy_db {
}
sub get_userpass {
my $term = shift;
my $name = $term->get_reply(prompt => 'Username: ');
my $pass = $term->get_reply(prompt => 'Password: ');
my $upterm = shift;
my $name = $upterm->get_reply(prompt => 'Username: ');
my $pass = $upterm->get_reply(prompt => 'Password: ');
unless ($name and $pass) {
say 'username and password cannot be empty, please try again.';
($name, $pass) = get_userpass($term);
($name, $pass) = get_userpass($upterm);
}
return ($name, $pass);
@@ -257,7 +269,7 @@ sub deploy_oui {
print color 'reset';
}
# This subroutine is from Wireshark's make-manuf
# This subroutine is based on Wireshark's make-manuf
# http://anonsvn.wireshark.org/wireshark/trunk/tools/make-manuf
sub shorten {
my $manuf = shift;
@@ -271,22 +283,27 @@ sub shorten {
# & isn't needed when Standalone
$manuf =~ s/ \& / /g;
# remove junk whitespace
$manuf =~ s/\s+/ /g;
# Remove any "the", "inc", "plc" ...
$manuf
=~ s/\s(the|inc|incorporated|plc|systems|corp|corporation|s\/a|a\/s|ab|ag|kg|gmbh|co|company|limited|ltd|holding|spa)(?= )//gi;
=~ s/\s(?:the|inc|incorporated|plc|systems|corp|corporation|s\/a|a\/s|ab|ag|kg|gmbh|co|company|limited|ltd|holding|spa)(?= )//gi;
# Convert to consistent case
$manuf =~ s/(\w+)/\u\L$1/g;
# Remove all spaces
$manuf =~ s/\s+//g;
# Deviating from make-manuf for HP
$manuf =~ s/Hewlett[-]?Packard/Hp/;
# Truncate all names to a reasonable length, say, 8 characters.
# If the string contains UTF-8, this may be substantially more than 8 bytes.
$manuf = substr( $manuf, 0, 8 );
# Truncate all names to first two words max 20 chars
if (length($manuf) > 21) {
my @twowords = grep {defined} (split ' ', $manuf)[0 .. 1];
$manuf = join ' ', @twowords;
}
# Remove all spaces
$manuf =~ s/\s+//g;
return encode( "utf8", $manuf );
}

View File

@@ -71,7 +71,8 @@ L<Release Notes|https://github.com/netdisco/netdisco/wiki/Release-Notes>.
Netdisco has several Perl library dependencies which will be automatically
installed. However it's required that you first install the following
operating system packages:
operating system packages, if not the installation will most likely fail
further down the road.
On Ubuntu/Debian:
@@ -100,7 +101,7 @@ will take about 250MB including MIB files.
root:~# useradd -m -p x -s /bin/bash netdisco
Netdisco uses the PostgreSQL database server. Install PostgreSQL (at least
version 8.4) and then change to the PostgreSQL superuser (usually
version 9.4) and then change to the PostgreSQL superuser (usually
C<postgres>). Create a new database and PostgreSQL user for the Netdisco
application:
@@ -115,14 +116,25 @@ application:
You may wish to L<amend the PostgreSQL
configuration|https://github.com/netdisco/netdisco/wiki/Install-Tips#enable-md5-authentication-to-postgresql>
so that local connections are working. The default PostgreSQL configuration
also needs tuning for modern server hardware. We recommend that you use the
C<pgtune> Python program to auto-tune your C<postgresql.conf> file:
can also use tuning for modern server hardware. We recommend that you use one of the following
tools to tune your C<postgresql.conf> file:
=over 4
=item *
=item L<postgresqltuner|https://github.com/jfcoz/postgresqltuner>
L<https://github.com/elitwin/pgtune>
Script that will check your operating system resources and settings as well as your
running PostgreSQL database and will make recommendations based on actual load. Works
on new netdisco installs but will make the best suggestions once the database contains
a bigger dataset.
=item L<pgtune (fork)|https://pgtune.leopard.in.ua>
A web based application which will recommend which parameters to change.
=item L<pgtune|https://github.com/elitwin/pgtune>
Program to auto-tune your C<postgresql.conf>, regretfully not updated in a while.
=back
@@ -229,7 +241,7 @@ If you're running a version of Netdisco prior to 2.x then you should follow
the full installation instructions, above. This process is for upgrading
version 2.x only.
Before upgrading please review the latest L<Release Notes|https://github.com/netdisco/netdisco/wiki/Release-Notes>.
Before upgrading always review the latest L<Release Notes|https://github.com/netdisco/netdisco/wiki/Release-Notes>.
Then the process below should be run for each installation:
# upgrade Netdisco
@@ -251,7 +263,7 @@ Then the process below should be run for each installation:
The main black navigation bar has a search box which is smart enough to work
out what you're looking for in most cases. For example device names, node IP
or MAC addreses, VLAN numbers, and so on.
or MAC addresses, VLAN numbers, and so on.
=head2 Command-Line Device and Port Actions
@@ -332,7 +344,7 @@ built upon.
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2011-2018 by The Netdisco Developer Team.
This software is copyright (c) 2011-2019 by The Netdisco Developer Team.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View File

@@ -70,7 +70,7 @@ sub display_name {
=head2 cancel
Log a status and prevent other stages from runnning.
Log a status and prevent other stages from running.
=cut

View File

@@ -11,7 +11,7 @@ __PACKAGE__->load_namespaces(
);
our # try to hide from kwalitee
$VERSION = 58; # schema version used for upgrades, keep as integer
$VERSION = 59; # schema version used for upgrades, keep as integer
use Path::Class;
use File::ShareDir 'dist_dir';

View File

@@ -242,6 +242,23 @@ __PACKAGE__->belongs_to( neighbor_alias => 'App::Netdisco::DB::Result::DeviceIp'
{ join_type => 'LEFT' },
);
=head2 last_node
This relationship will return the last node that was seen on the port.
The JOIN is of type "LEFT" in case there isn't any such node.
=cut
__PACKAGE__->belongs_to(
last_node => 'App::Netdisco::DB::Result::Virtual::LastNode', {
'foreign.switch' => 'self.ip',
'foreign.port' => 'self.port',
}, {
join_type => 'LEFT',
}
);
=head2 vlans
As compared to C<port_vlans>, this relationship returns a set of Device VLAN

View File

@@ -29,6 +29,8 @@ __PACKAGE__->add_columns(
{ data_type => "boolean", default_value => \"false", is_nullable => 1 },
"ldap",
{ data_type => "boolean", default_value => \"false", is_nullable => 1 },
"radius",
{ data_type => "boolean", default_value => \"false", is_nullable => 1 },
"admin",
{ data_type => "boolean", default_value => \"false", is_nullable => 1 },
"fullname",

View File

@@ -0,0 +1,54 @@
package App::Netdisco::DB::Result::Virtual::LastNode;
use strict;
use warnings;
use base 'DBIx::Class::Core';
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
__PACKAGE__->table('last_node');
__PACKAGE__->result_source_instance->is_virtual(1);
__PACKAGE__->result_source_instance->view_definition(<<ENDSQL
SELECT DISTINCT ON (switch, port) * FROM node
ORDER BY switch, port, time_last desc
ENDSQL
);
__PACKAGE__->add_columns(
"mac",
{ data_type => "macaddr", is_nullable => 0 },
"switch",
{ data_type => "inet", is_nullable => 0 },
"port",
{ data_type => "text", is_nullable => 0 },
"active",
{ data_type => "boolean", is_nullable => 1 },
"oui",
{ data_type => "varchar", is_nullable => 1, size => 8 },
"time_first",
{
data_type => "timestamp",
default_value => \"current_timestamp",
is_nullable => 1,
original => { default_value => \"now()" },
},
"time_recent",
{
data_type => "timestamp",
default_value => \"current_timestamp",
is_nullable => 1,
original => { default_value => \"now()" },
},
"time_last",
{
data_type => "timestamp",
default_value => \"current_timestamp",
is_nullable => 1,
original => { default_value => \"now()" },
},
"vlan",
{ data_type => "text", is_nullable => 0, default_value => '0' },
);
1;

View File

@@ -7,6 +7,7 @@ use base 'DBIx::Class::Core';
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
# NOTE this query is in `git grep 'THREE PLACES'`
__PACKAGE__->table('port_utilization');
__PACKAGE__->result_source_instance->is_virtual(1);
__PACKAGE__->result_source_instance->view_definition(<<ENDSQL
@@ -17,11 +18,23 @@ __PACKAGE__->result_source_instance->view_definition(<<ENDSQL
ELSE 0 END) as ports_in_use,
sum(CASE WHEN (dp.type != 'propVirtual' AND dp.up_admin != 'up') THEN 1
ELSE 0 END) as ports_shutdown,
sum(CASE WHEN (dp.type != 'propVirtual' AND dp.up_admin = 'up' AND dp.up != 'up'
AND ( age(now(), to_timestamp(extract(epoch from d.last_discover) - (d.uptime - dp.lastchange)/100)) > ?::interval )) THEN 1
ELSE 0 END) as ports_free
FROM device d LEFT JOIN device_port dp
sum(CASE
WHEN ( dp.type != 'propVirtual' AND dp.up_admin = 'up' AND dp.up != 'up'
AND (age(now(), to_timestamp(extract(epoch from d.last_discover) - (d.uptime/100))) < ?::interval)
AND (last_node.time_last IS NULL OR (age(now(), last_node.time_last)) > ?::interval) )
THEN 1
WHEN ( dp.type != 'propVirtual' AND dp.up_admin = 'up' AND dp.up != 'up'
AND (age(now(), to_timestamp(extract(epoch from d.last_discover) - (d.uptime - dp.lastchange)/100)) > ?::interval) )
THEN 1
ELSE 0
END) as ports_free
FROM device d
LEFT JOIN device_port dp
ON d.ip = dp.ip
LEFT JOIN
( SELECT DISTINCT ON (switch, port) * FROM node
ORDER BY switch, port, time_last desc ) AS last_node
ON dp.port = last_node.port AND dp.ip = last_node.switch
GROUP BY d.dns, d.ip
ORDER BY d.dns, d.ip
ENDSQL

View File

@@ -20,6 +20,9 @@ __PACKAGE__->result_source_instance->view_definition(<<ENDSQL
SELECT username, 'ldap' AS role FROM users
WHERE ldap
UNION
SELECT username, 'radius' AS role FROM users
WHERE radius
UNION
SELECT username, 'api' AS role FROM users
WHERE token IS NOT NULL AND token_from IS NOT NULL
ENDSQL

View File

@@ -38,7 +38,7 @@ sub get_distinct_col {
Returns a ResultSet for DataTables Server-side processing which populates
the displayed table. Evaluates the supplied query parameters for filtering,
paging, and ordering information. Note: query paramters are expected to be
paging, and ordering information. Note: query parameters are expected to be
passed as a reference to an expanded hash of hashes.
Filtering if present, will generate simple LIKE matching conditions for each
@@ -75,7 +75,7 @@ sub get_datatables_data {
Returns the total records, after filtering (i.e. the total number of
records after filtering has been applied - not just the number of records
being returned for this page of data) for a datatables ResultSet and
query parameters. Note: query paramters are expected to be passed as a
query parameters. Note: query parameters are expected to be passed as a
reference to an expanded hash of hashes.
=cut

View File

@@ -12,7 +12,7 @@ __PACKAGE__->load_components(qw/
=head2 skipped( $backend?, $max_deferrals?, $retry_after? )
Retuns a correlated subquery for the set of C<device_skip> entries that apply
Returns a correlated subquery for the set of C<device_skip> entries that apply
to some jobs. They match the device IP, current backend, and job action.
Pass the C<backend> FQDN (or the current host will be used as a default), the

View File

@@ -187,11 +187,11 @@ Will match exactly the C<model> field.
=item os
Will match exactly the C<os> field, which is the operating sytem.
Will match exactly the C<os> field, which is the operating system.
=item os_ver
Will match exactly the C<os_ver> field, which is the operating sytem software version.
Will match exactly the C<os_ver> field, which is the operating system software version.
=item vendor
@@ -226,21 +226,20 @@ sub search_by_field {
}
# For Search on Layers
my @layer_search = ( '_', '_', '_', '_', '_', '_', '_' );
# @layer_search is computer indexed, left->right
my $layers = $p->{layers};
my @layer_select = ();
if ( defined $layers && ref $layers ) {
foreach my $layer (@$layers) {
next unless defined $layer and length($layer);
next if ( $layer < 1 || $layer > 7 );
$layer_search[ $layer - 1 ] = 1;
push @layer_select,
\[ 'substring(me.layers,9-?, 1)::int = 1', $layer ];
}
}
elsif ( defined $layers ) {
$layer_search[ $layers - 1 ] = 1;
push @layer_select,
\[ 'substring(me.layers,9-?, 1)::int = 1', $layers ];
}
# the database field is in order 87654321
my $layer_string = join( '', reverse @layer_search );
return $rs
->search_rs({}, $attrs)
@@ -252,8 +251,6 @@ sub search_by_field {
{ '-ilike' => "\%$p->{location}\%" }) : ()),
($p->{description} ? ('me.description' =>
{ '-ilike' => "\%$p->{description}\%" }) : ()),
($p->{layers} ? ('me.layers' =>
{ '-ilike' => "\%$layer_string" }) : ()),
($p->{model} ? ('me.model' =>
{ '-in' => $p->{model} }) : ()),
@@ -264,6 +261,8 @@ sub search_by_field {
($p->{vendor} ? ('me.vendor' =>
{ '-in' => $p->{vendor} }) : ()),
($p->{layers} ? (-or => \@layer_select) : ()),
($p->{dns} ? (
-or => [
'me.dns' => { '-ilike' => "\%$p->{dns}\%" },

View File

@@ -40,7 +40,7 @@ sub with_times {
});
}
=head2 with_free_ports
=head2 with_is_free
This is a modifier for any C<search()> (including the helpers below) which
will add the following additional synthesized columns to the result set:
@@ -67,12 +67,13 @@ sub with_is_free {
->search({},
{
'+columns' => { is_free =>
\["me.up != 'up' and "
."age(now(), to_timestamp(extract(epoch from device.last_discover) "
."- (device.uptime - me.lastchange)/100)) "
."> ?::interval",
[{} => $interval]] },
join => 'device',
# NOTE this query is in `git grep 'THREE PLACES'`
\["me.up_admin = 'up' AND me.up != 'up' AND me.type != 'propVirtual' AND "
."((age(now(), to_timestamp(extract(epoch from device.last_discover) - (device.uptime/100))) < ?::interval "
."AND (last_node.time_last IS NULL OR age(now(), last_node.time_last) > ?::interval)) "
."OR age(now(), to_timestamp(extract(epoch from device.last_discover) - (device.uptime - me.lastchange)/100)) > ?::interval)",
[{} => $interval],[ {} => $interval],[ {} => $interval]] },
join => [qw/device last_node/],
});
}
@@ -96,14 +97,23 @@ sub only_free_ports {
->search_rs($cond, $attrs)
->search(
{
'me.up' => { '!=' => 'up' },
},{
where =>
\["age(now(), to_timestamp(extract(epoch from device.last_discover) "
."- (device.uptime - me.lastchange)/100)) "
."> ?::interval",
# NOTE this query is in `git grep 'THREE PLACES'`
'me.up_admin' => 'up',
'me.up' => { '!=' => 'up' },
'me.type' => { '!=' => 'propVirtual' },
-or => [
-and => [
\["age(now(), to_timestamp(extract(epoch from device.last_discover) - (device.uptime/100))) < ?::interval",
[{} => $interval]],
-or => [
'last_node.time_last' => undef,
\["age(now(), last_node.time_last) > ?::interval", [{} => $interval]],
]
],
\["age(now(), to_timestamp(extract(epoch from device.last_discover) - (device.uptime - me.lastchange)/100)) > ?::interval",
[{} => $interval]],
join => 'device' },
],
},{ join => [qw/device last_node/] },
);
}

View File

@@ -79,6 +79,19 @@ sub jq_warm_thrusters {
actionset => $actionset{$_},
}, { key => 'primary' }) for keys %actionset;
});
# fix up the pseudo devices which need layer 3
# TODO remove this after next release
schema('netdisco')->txn_do(sub {
my @hosts = grep { defined }
map { schema('netdisco')->resultset('Device')->search_for_device($_->{only}) }
grep { exists $_->{only} and ref '' eq ref $_->{only} }
grep { exists $_->{driver} and $_->{driver} eq 'cli' }
@{ setting('device_auth') };
$_->update({ layers => \[q{overlay(layers placing '1' from 6 for 1)}] })
for @hosts;
});
}
sub jq_getsome {

View File

@@ -1,7 +1,5 @@
package App::Netdisco::SSHCollector::Platform::ACE;
# vim: set expandtab tabstop=8 softtabstop=4 shiftwidth=4:
=head1 NAME
App::Netdisco::SSHCollector::Platform::ACE
@@ -13,7 +11,7 @@ virtual contexts with individual ARP tables. Contexts are enumerated
with C<show context>, afterwards the commands C<changeto CONTEXTNAME> and
C<show arp> must be executed for every context.
The IOS shell does not permit to combine mulitple commands in a single
The IOS shell does not permit to combine multiple commands in a single
line, and Net::OpenSSH uses individual connections for individual commands,
so we need to use Expect to execute the changeto and show commands in
the same context.
@@ -36,7 +34,7 @@ use Moo;
Retrieve ARP entries from device. C<$host> is the hostname or IP address
of the device. C<$ssh> is a Net::OpenSSH connection to the device.
Returns a list of hashrefs in the format C<{ mac => MACADDR, ip => IPADDR }>.
Returns a list of hashrefs in the format C<{ mac =E<gt> MACADDR, ip =E<gt> IPADDR }>.
=back

View File

@@ -16,14 +16,14 @@ C<enable> status after login:
To use an C<enable> password separate from the login password, add an
C<enable_password> under C<device_auth> tag in your configuration file:
device_auth:
- tag: sshasa
driver: cli
platform: ASA
only: '192.0.2.1'
username: oliver
password: letmein
enable_password: myenablepass
device_auth:
- tag: sshasa
driver: cli
platform: ASA
only: '192.0.2.1'
username: oliver
password: letmein
enable_password: myenablepass
=cut
@@ -43,7 +43,7 @@ use Moo;
Retrieve ARP and neighbor entries from device. C<$host> is the hostname or IP
address of the device. C<$ssh> is a Net::OpenSSH connection to the device.
Returns a list of hashrefs in the format C<{ mac => MACADDR, ip => IPADDR }>.
Returns a list of hashrefs in the format C<{ mac =E<gt> MACADDR, ip =E<gt> IPADDR }>.
=back

View File

@@ -34,7 +34,7 @@ use Moo;
Retrieve ARP entries from device. C<$host> is the hostname or IP address
of the device. C<$ssh> is a Net::OpenSSH connection to the device.
Returns a list of hashrefs in the format C<{ mac => MACADDR, ip => IPADDR }>.
Returns a list of hashrefs in the format C<{ mac =E<gt> MACADDR, ip =E<gt> IPADDR }>.
=back

View File

@@ -12,14 +12,14 @@ This collector uses "C<arp>" as the command for the arp utility on your
system. Clish "C<show arp>" does not work correctly in versions prior to R77.30.
Config example:
device_auth:
- tag: sshcpvsx
driver: cli
platform: CPVSX
only: '192.0.2.1'
username: oliver
password: letmein
expert_password: letmein2
device_auth:
- tag: sshcpvsx
driver: cli
platform: CPVSX
only: '192.0.2.1'
username: oliver
password: letmein
expert_password: letmein2
=cut

View File

@@ -12,14 +12,14 @@ This collector uses "C<arp>" as the command for the arp utility on your
system. If you wish to specify an absolute path, then add an C<arp_command>
item to your configuration:
device_auth:
- tag: sshfreebsd
driver: cli
platform: FreeBSD
only: '192.0.2.1'
username: oliver
password: letmein
arp_command: '/usr/sbin/arp'
device_auth:
- tag: sshfreebsd
driver: cli
platform: FreeBSD
only: '192.0.2.1'
username: oliver
password: letmein
arp_command: '/usr/sbin/arp'
=cut
@@ -39,7 +39,7 @@ use Moo;
Retrieve ARP entries from device. C<$host> is the hostname or IP address
of the device. C<$ssh> is a Net::OpenSSH connection to the device.
Returns a list of hashrefs in the format C<{ mac => MACADDR, ip => IPADDR }>.
Returns a list of hashrefs in the format C<{ mac =E<gt> MACADDR, ip =E<gt> IPADDR }>.
=back

View File

@@ -7,21 +7,21 @@ App::Netdisco::SSHCollector::Platform::GAIAEmbedded
=head1 DESCRIPTION
Collect ARP entries from Checkpoint GAIA embedded Systems
To get this Plugin to work you have to add an User like 'netdisco' with
'Network admin' right in the GAIA embedded OS
To get this Plugin to work you have to add a user like 'netdisco' with
'Network admin' rights in the GAIA embedded OS.
This collector uses "C<arp>" as the command for the arp utility on your
system. If you wish to specify an absolute path, then add an C<arp_command>
system. If you wish to specify an absolute path, then add an C<arp_command>
item to your configuration:
device_auth:
- tag: sshgaia
driver: cli
platform: GAIAEmbedded
only: '192.0.2.1'
username: oliver
password: letmein
arp_command: 'arp'
device_auth:
- tag: sshgaia
driver: cli
platform: GAIAEmbedded
only: '192.0.2.1'
username: oliver
password: letmein
arp_command: 'arp'
=cut
@@ -41,7 +41,7 @@ use Moo;
Retrieve ARP entries from device. C<$host> is the hostname or IP address
of the device. C<$ssh> is a Net::OpenSSH connection to the device.
Returns a list of hashrefs in the format C<{ mac => MACADDR, ip => IPADDR }>.
Returns a list of hashrefs in the format C<{ mac =E<gt> MACADDR, ip =E<gt> IPADDR }>.
=back

View File

@@ -27,7 +27,7 @@ use Moo;
Retrieve ARP entries from device. C<$host> is the hostname or IP address
of the device. C<$ssh> is a Net::OpenSSH connection to the device.
Returns a list of hashrefs in the format C<{ mac => MACADDR, ip => IPADDR }>.
Returns a list of hashrefs in the format C<{ mac =E<gt> MACADDR, ip =E<gt> IPADDR }>.
=back

View File

@@ -27,7 +27,7 @@ use Moo;
Retrieve ARP entries from device. C<$host> is the hostname or IP address
of the device. C<$ssh> is a Net::OpenSSH connection to the device.
Returns a list of hashrefs in the format C<{ mac => MACADDR, ip => IPADDR }>.
Returns a list of hashrefs in the format C<{ mac =E<gt> MACADDR, ip =E<gt> IPADDR }>.
=back

View File

@@ -12,14 +12,14 @@ This collector uses "C<arp>" as the command for the arp utility on your
system. If you wish to specify an absolute path, then add an C<arp_command>
item to your configuration:
device_auth:
- tag: sshlinux
driver: cli
platform: Linux
only: '192.0.2.1'
username: oliver
password: letmein
arp_command: '/usr/sbin/arp'
device_auth:
- tag: sshlinux
driver: cli
platform: Linux
only: '192.0.2.1'
username: oliver
password: letmein
arp_command: '/usr/sbin/arp'
=cut
@@ -39,7 +39,7 @@ use Moo;
Retrieve ARP entries from device. C<$host> is the hostname or IP address
of the device. C<$ssh> is a Net::OpenSSH connection to the device.
Returns a list of hashrefs in the format C<{ mac => MACADDR, ip => IPADDR }>.
Returns a list of hashrefs in the format C<{ mac =E<gt> MACADDR, ip =E<gt> IPADDR }>.
=back

View File

@@ -58,8 +58,11 @@ Returns C<undef> if the connection fails.
sub reader_for {
my ($class, $ip, $useclass) = @_;
my $device = get_device($ip) or return undef;
return undef if $device->in_storage and $device->is_pseudo;
my $readers = $class->instance->readers or return undef;
return $readers->{$device->ip} if exists $readers->{$device->ip};
debug sprintf 'snmp reader cache warm: [%s]', $device->ip;
return ($readers->{$device->ip}
= _snmp_connect_generic('read', $device, $useclass));
@@ -104,8 +107,11 @@ Returns C<undef> if the connection fails.
sub writer_for {
my ($class, $ip, $useclass) = @_;
my $device = get_device($ip) or return undef;
return undef if $device->in_storage and $device->is_pseudo;
my $writers = $class->instance->writers or return undef;
return $writers->{$device->ip} if exists $writers->{$device->ip};
debug sprintf 'snmp writer cache warm: [%s]', $device->ip;
return ($writers->{$device->ip}
= _snmp_connect_generic('write', $device, $useclass));

View File

@@ -158,6 +158,8 @@ If C<$device_type> is also given, then C<discover_no_type> will be checked.
Also respects C<discover_phones> and C<discover_waps> if either are set to
false.
Also checks if the device is a pseudo device (vendor is C<netdisco>).
Returns false if the host is not permitted to discover the target device.
=cut
@@ -168,6 +170,9 @@ sub is_discoverable {
$remote_type ||= '';
$remote_cap ||= [];
return _bail_msg("is_discoverable: $device is pseudo-device")
if $device->is_pseudo;
return _bail_msg("is_discoverable: $device matches wap_platforms but discover_waps is not enabled")
if ((not setting('discover_waps')) and
(match_to_setting($remote_type, 'wap_platforms') or
@@ -192,9 +197,8 @@ sub is_discoverable {
=head2 is_discoverable_now( $ip, $device_type? )
Same as C<is_discoverable>, but also checks the last_discover field if the
device is in storage, and returns false if that host has been too recently
discovered.
Same as C<is_discoverable>, but also compares the C<last_discover> field
of the C<device> to the C<discover_min_age> configuration.
Returns false if the host is not permitted to discover the target device.
@@ -222,6 +226,8 @@ the local configuration to arpnip the device.
The configuration items C<arpnip_no> and C<arpnip_only> are checked
against the given IP.
Also checks if the device reports layer 3 capability.
Returns false if the host is not permitted to arpnip the target device.
=cut
@@ -230,6 +236,9 @@ sub is_arpnipable {
my $ip = shift;
my $device = get_device($ip) or return 0;
return _bail_msg("is_arpnipable: $device has no layer 3 capability")
unless $device->has_layer(3);
return _bail_msg("is_arpnipable: $device matched arpnip_no")
if check_acl_no($device, 'arpnip_no');
@@ -241,9 +250,8 @@ sub is_arpnipable {
=head2 is_arpnipable_now( $ip )
Same as C<is_arpnipable>, but also checks the last_arpnip field if the
device is in storage, and returns false if that host has been too recently
arpnipped.
Same as C<is_arpnipable>, but also compares the C<last_arpnip> field
of the C<device> to the C<arpnip_min_age> configuration.
Returns false if the host is not permitted to arpnip the target device.
@@ -271,6 +279,8 @@ the local configuration to macsuck the device.
The configuration items C<macsuck_no> and C<macsuck_only> are checked
against the given IP.
Also checks if the device reports layer 2 capability.
Returns false if the host is not permitted to macsuck the target device.
=cut
@@ -279,6 +289,9 @@ sub is_macsuckable {
my $ip = shift;
my $device = get_device($ip) or return 0;
return _bail_msg("is_macsuckable: $device has no layer 2 capability")
unless $device->has_layer(2);
return _bail_msg("is_macsuckable: $device matched macsuck_no")
if check_acl_no($device, 'macsuck_no');
@@ -290,9 +303,8 @@ sub is_macsuckable {
=head2 is_macsuckable_now( $ip )
Same as C<is_macsuckable>, but also checks the last_macsuck field if the
device is in storage, and returns false if that host has been too recently
macsucked.
Same as C<is_macsuckable>, but also compares the C<last_macsuck> field
of the C<device> to the C<macsuck_min_age> configuration.
Returns false if the host is not permitted to macsuck the target device.

View File

@@ -59,13 +59,20 @@ ajax qr{/ajax/control/admin/(?:\w+/)?delete} => require_role setting('defanged_a
get '/admin/*' => require_role admin => sub {
my ($tag) = splat;
# trick the ajax into working as if this were a tabbed page
params->{tab} = $tag;
if (exists setting('_admin_tasks')->{ $tag }) {
# 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 },
};
var(nav => 'admin');
template 'admintask', {
task => setting('_admin_tasks')->{ $tag },
};
}
else {
var('notfound' => true);
status 'not_found';
template 'index';
}
};
true;

View File

@@ -13,6 +13,7 @@ use Dancer::Plugin::DBIC;
use Dancer::Plugin::Passphrase;
use Digest::MD5;
use Net::LDAP;
use Authen::Radius;
use Try::Tiny;
sub authenticate_user {
@@ -103,9 +104,20 @@ sub match_password {
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);
my $pwmatch_result = 0;
my $username = $user->$username_column;
if ($user->ldap) {
$pwmatch_result = $self->match_with_ldap($password, $username);
}
elsif ($user->radius) {
$pwmatch_result = $self->match_with_radius($password, $username);
}
else {
$pwmatch_result = $self->match_with_local_pass($password, $user);
}
return $pwmatch_result;
}
sub match_with_local_pass {
@@ -215,4 +227,28 @@ sub _ldap_search {
return undef;
}
sub match_with_radius {
my($self, $pass, $user) = @_;
return unless setting('radius') and ref {} eq ref setting('radius');
my $conf = setting('radius');
my $radius = Authen::Radius->new(Host => $conf->{server}, Secret => $conf->{secret});
# my $dict_dir = Path::Class::Dir->new( dist_dir('App-Netdisco') )
#  ->subdir('radius_dictionaries')->stringify;
Authen::Radius->load_dictionary(); # put $dict_dir in here once it's useful
$radius->add_attributes(
{ Name => 'User-Name', Value => $user },
{ Name => 'User-Password', Value => $pass },
{ Name => 'h323-return-code', Value => '0' }, # Cisco AV pair
{ Name => 'Digest-Attributes', Value => { Method => 'REGISTER' } }
);
$radius->send_packet(ACCESS_REQUEST);
my $type = $radius->recv_packet();
my $radius_return = ($type eq ACCESS_ACCEPT) ? 1 : 0;
return $radius_return;
}
1;

View File

@@ -36,7 +36,7 @@ ajax '/ajax/control/admin/pseudodevice/add' => require_role admin => sub {
ip => param('ip'),
dns => param('dns'),
vendor => 'netdisco',
layers => '00000100',
layers => param('layers'),
last_discover => \'now()',
});
return unless $device;
@@ -87,6 +87,9 @@ ajax '/ajax/control/admin/pseudodevice/update' => require_role admin => sub {
})->delete;
}
}
# also set layers
$device->update({layers => param('layers')});
});
};

View File

@@ -41,6 +41,7 @@ ajax '/ajax/control/admin/users/add' => require_role setting('defanged_admin') =
password => _make_password(param('password')),
fullname => param('fullname'),
ldap => (param('ldap') ? \'true' : \'false'),
radius => (param('radius') ? \'true' : \'false'),
port_control => (param('port_control') ? \'true' : \'false'),
admin => (param('admin') ? \'true' : \'false'),
note => param('note'),
@@ -71,6 +72,7 @@ ajax '/ajax/control/admin/users/update' => require_role setting('defanged_admin'
: ()),
fullname => param('fullname'),
ldap => (param('ldap') ? \'true' : \'false'),
radius => (param('radius') ? \'true' : \'false'),
port_control => (param('port_control') ? \'true' : \'false'),
admin => (param('admin') ? \'true' : \'false'),
note => param('note'),

View File

@@ -31,7 +31,7 @@ get '/ajax/content/device/ports' => require_login sub {
# change wildcard chars to SQL
$f =~ s/\*/%/g;
$f =~ s/\?/_/g;
# set wilcards at param boundaries
# set wildcards at param boundaries
if ($f !~ m/[%_]/) {
$f =~ s/^\%*/%/;
$f =~ s/\%*$/%/;
@@ -82,6 +82,8 @@ get '/ajax/content/device/ports' => require_login sub {
});
}
delete $port_state{free};
# showing free ports requires showing down ports
++$port_state{down};
}
if (scalar keys %port_state < 3) {

View File

@@ -47,7 +47,7 @@ get '/ajax/content/report/nodevendor/data' => require_login sub {
my $match = $vendor eq 'blank' ? undef : $vendor;
$rs = $rs->search( { 'oui.abbrev' => $match },
{ '+columns' => [qw/ device.dns device.name oui.abbrev /],
{ '+columns' => [qw/ device.dns device.name oui.abbrev oui.company /],
join => [qw/ oui device /],
collapse => 1,
});
@@ -86,7 +86,7 @@ get '/ajax/content/report/nodevendor' => require_login sub {
my $match = $vendor eq 'blank' ? undef : $vendor;
$rs = $rs->search( { 'oui.abbrev' => $match },
{ '+columns' => [qw/ device.dns device.name oui.abbrev /],
{ '+columns' => [qw/ device.dns device.name oui.abbrev oui.company /],
join => [qw/ oui device /],
collapse => 1,
});
@@ -102,9 +102,9 @@ get '/ajax/content/report/nodevendor' => require_login sub {
$rs = $rs->search(
{ },
{ join => 'oui',
select => [ 'oui.abbrev', { count => {distinct => 'me.mac'}} ],
as => [qw/ vendor count /],
group_by => [qw/ oui.abbrev /]
select => [ 'oui.abbrev', 'oui.company', { count => {distinct => 'me.mac'}} ],
as => [qw/ abbrev vendor count /],
group_by => [qw/ oui.abbrev oui.company /]
}
)->order_by( { -desc => 'count' } );

View File

@@ -20,7 +20,7 @@ get '/ajax/content/report/portutilization' => require_login sub {
my $age_num = param('age_num') || 3;
my $age_unit = param('age_unit') || 'months';
my @results = schema('netdisco')->resultset('Virtual::PortUtilization')
->search(undef, { bind => [ "$age_num $age_unit" ] })->hri->all;
->search(undef, { bind => [ "$age_num $age_unit", "$age_num $age_unit", "$age_num $age_unit" ] })->hri->all;
if (request->is_ajax) {
my $json = to_json (\@results);

View File

@@ -6,6 +6,7 @@ use Dancer::Plugin::DBIC;
use Dancer::Plugin::Auth::Extensible;
use NetAddr::IP::Lite ':lower';
use Regexp::Common 'net';
use NetAddr::MAC ();
use App::Netdisco::Web::Plugin;
@@ -23,10 +24,14 @@ ajax '/ajax/content/search/node' => require_login sub {
my ( $start, $end ) = param('daterange') =~ m/(\d+-\d+-\d+)/gmx;
my $mac = NetAddr::MAC->new(mac => $node);
undef $mac if ($mac and $mac->as_ieee and ($mac->as_ieee eq '00:00:00:00'));
my @active = (param('archived') ? () : (-bool => 'active'));
undef $mac if
($mac and $mac->as_ieee
and (($mac->as_ieee eq '00:00:00:00:00:00')
or ($mac->as_ieee !~ m/$RE{net}{MAC}/)));
my @active = (param('archived') ? () : (-bool => 'active'));
my (@times, @wifitimes, @porttimes);
if ( $start and $end ) {
$start = $start . ' 00:00:00';
$end = $end . ' 23:59:59';

View File

@@ -6,6 +6,7 @@ use Dancer::Plugin::DBIC;
use Dancer::Plugin::Auth::Extensible;
use App::Netdisco::Util::Web 'sql_match';
use Regexp::Common 'net';
use NetAddr::MAC ();
hook 'before_template' => sub {
@@ -39,7 +40,11 @@ get '/search' => require_login sub {
my $nd = $s->resultset('Device')->search_fuzzy($q);
my ($likeval, $likeclause) = sql_match($q);
my $mac = NetAddr::MAC->new($q);
undef $mac if ($mac and $mac->as_ieee and ($mac->as_ieee eq '00:00:00:00'));
undef $mac if
($mac and $mac->as_ieee
and (($mac->as_ieee eq '00:00:00:00:00:00')
or ($mac->as_ieee !~ m/$RE{net}{MAC}/)));
if ($nd and $nd->count) {
if ($nd->count == 1) {

View File

@@ -16,9 +16,6 @@ register_worker({ phase => 'check' }, sub {
return Status->error("arpnip skipped: $device not yet discovered")
unless $device->in_storage;
return Status->info("arpnip skipped: $device has no layer 3 capability")
unless $device->has_layer(3);
return Status->info("arpnip skipped: $device is not arpnipable")
unless is_arpnipable_now($device);

View File

@@ -55,6 +55,7 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
push @{ vars->{'v6arps'} },
@{get_arps_snmp($device, $snmp->ipv6_n2p_mac, $snmp->ipv6_n2p_addr) };
$device->update({layers => \[q{overlay(layers placing '1' from 6 for 1)}]});
return Status->done("Gathered arp caches from $device");
});
@@ -82,24 +83,25 @@ sub get_arps_snmp {
}
register_worker({ phase => 'main', driver => 'cli' }, sub {
my ($job, $workerconf) = @_;
my ($job, $workerconf) = @_;
my $device = $job->device;
my $cli = App::Netdisco::Transport::SSH->session_for($device)
or return Status->defer("arpnip failed: could not SSH connect to $device");
my $device = $job->device;
my $cli = App::Netdisco::Transport::SSH->session_for($device)
or return Status->defer("arpnip failed: could not SSH connect to $device");
# should be both v4 and v6
my @arps = @{ get_arps_cli($device, [$cli->arpnip]) };
# should be both v4 and v6
my @arps = @{ get_arps_cli($device, [$cli->arpnip]) };
# cache v4 arp table
push @{ vars->{'v4arps'} },
grep { NetAddr::IP::Lite->new($_->{ip})->bits == 32 } @arps;
# cache v4 arp table
push @{ vars->{'v4arps'} },
grep { NetAddr::IP::Lite->new($_->{ip})->bits == 32 } @arps;
# cache v6 neighbor cache
push @{ vars->{'v6arps'} },
grep { NetAddr::IP::Lite->new($_->{ip})->bits == 128 } @arps;
# cache v6 neighbor cache
push @{ vars->{'v6arps'} },
grep { NetAddr::IP::Lite->new($_->{ip})->bits == 128 } @arps;
return Status->done("Gathered arp caches from $device");
$device->update({layers => \[q{overlay(layers placing '1' from 6 for 1)}]});
return Status->done("Gathered arp caches from $device");
});
sub get_arps_cli {

View File

@@ -16,9 +16,6 @@ register_worker({ phase => 'check' }, sub {
return Status->error("discover failed: no device param (need -d ?)")
if $device->ip eq '0.0.0.0';
return Status->info("discover skipped: $device is pseudo-device")
if $device->is_pseudo;
# runner has already called get_device to promote $job->device
return $job->cancel("fresh discover cancelled: $device already known")
if $device->in_storage

View File

@@ -95,7 +95,7 @@ port relationships.
The Device database object can be a fresh L<DBIx::Class::Row> object which is
not yet stored to the database.
A list of discovererd neighbors will be returned as [C<$ip>, C<$type>] tuples.
A list of discovered neighbors will be returned as [C<$ip>, C<$type>] tuples.
=cut

View File

@@ -16,12 +16,6 @@ register_worker({ phase => 'check' }, sub {
return Status->error("macsuck skipped: $device not yet discovered")
unless $device->in_storage;
return Status->info("macsuck skipped: $device is pseudo-device")
if $device->is_pseudo;
return Status->info("macsuck skipped: $device has no layer 2 capability")
unless $device->has_layer(2);
return Status->info("macsuck skipped: $device is not macsuckable")
unless is_macsuckable_now($device);

View File

@@ -78,7 +78,7 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
debug sprintf ' [%s] macsuck - %s updated forwarding table entries',
$device->ip, $total_nodes;
# a use for $now ... need to archive dissapeared nodes
# a use for $now ... need to archive disappeared nodes
my $archived = 0;
if (setting('node_freshness')) {
@@ -105,7 +105,7 @@ All four fields in the tuple are required. If you don't know the VLAN ID,
Netdisco supports using ID "0".
Optionally, a fifth argument can be the literal string passed to the time_last
field of the database record. If not provided, it defauls to C<now()>.
field of the database record. If not provided, it defaults to C<now()>.
=cut

View File

@@ -189,7 +189,7 @@ email config and creating the repository with C<rancid-cvs>.
=head2 C<rancid_conf>
The location where the rancid configuration (F<rancid.types.base> and
F<rancid.types.conf>) is installed. It will be used to check the existance
F<rancid.types.conf>) is installed. It will be used to check the existence
of device types before exporting the devices to the rancid configuration. If no match
is found the device will not be added to rancid.

View File

@@ -20,8 +20,9 @@ database:
# RECOMMENDED SETTINGS
# --------------------
# SNMP community string(s)
# ````````````````````````
# Device authentication settings
# define snmp communities and ssh credentials here
# ````````````````````````````````````````````````
device_auth:
- tag: 'default_v2_readonly'
community: 'public'
@@ -50,7 +51,7 @@ device_auth:
# SNMP, which just clogs up the job queue.
# ```````````````````````````````````````````````````````````````
#discover_waps: true
#disover_phones: false
#discover_phones: false
# this is the schedule for automatically keeping netdisco up-to-date;
# these are good defaults, so only uncomment if needing to change.

View File

@@ -245,6 +245,13 @@ td > form.nd_inline-form {
text-decoration: none;
}
/* badge for pseudo devices layer three toggle */
.nd_layer-three-link {
text-decoration: none !important;
display: inline-block;
margin-left: -4px;
}
/* for the job control admin page play/pause links */
#nd_countdown-refresh:hover, #nd_countdown-control:hover {
text-decoration: none;
@@ -359,7 +366,7 @@ td > form.nd_inline-form {
.container-fluid > .nd_sidebar {
position: absolute;
right: 20px;
width: 200px;
width: 205px;
left: auto;
}

View File

View File

@@ -0,0 +1,11 @@
BEGIN;
ALTER TABLE users ADD COLUMN "radius" boolean DEFAULT false;
UPDATE device SET layers = NULL WHERE vendor = 'netdisco';
UPDATE device SET layers = '00000000' WHERE layers IS NULL;
ALTER TABLE device ALTER layers SET DEFAULT '00000000';
COMMIT;

View File

@@ -4,6 +4,7 @@
<th class="nd_center-cell">Device Name</th>
<th class="nd_center-cell">Device IP</th>
<th class="nd_center-cell">Number of Ports</th>
<th class="nd_center-cell">Services</th>
<th class="nd_center-cell">Action</th>
</tr>
</thead>
@@ -11,7 +12,12 @@
<tr>
<td class="nd_center-cell"><input data-form="add" name="dns" type="text"></td>
<td class="nd_center-cell"><input data-form="add" name="ip" type="text"></td>
<td class="nd_center-cell"><input data-form="add" name="ports" type="number"></td>
<td class="nd_center-cell"><input data-form="add" name="ports" type="number" value="1"></td>
<td class="nd_center-cell">
<span class="badge">&nbsp;</span><span class="badge">&nbsp;</span>
<a class="nd_layer-three-link" href="#" rel="tooltip" data-placement="bottom" data-offset="3" data-title="Enable Arpnip"><span class="badge">3</span></a><span class="badge">&nbsp;</span><span class="badge">&nbsp;</span><span class="badge">&nbsp;</span><span class="badge">&nbsp;</span>
<input data-form="add" name="layers" type="hidden" value="00000000">
</td>
<td class="nd_center-cell">
<button class="btn btn-small nd_adminbutton" name="add" type="submit"><i class="icon-plus-sign"></i> Add</button>
</td>
@@ -26,6 +32,11 @@
<td class="nd_center-cell">
<input data-form="update" name="ports" type="number" value="[% row.port_count | html_entity %]">
</td>
<td class="nd_center-cell">
<span class="badge">&nbsp;</span><span class="badge">&nbsp;</span>
<a class="nd_layer-three-link" href="#" rel="tooltip" data-placement="bottom" data-offset="3" data-title="Enable Arpnip"><span class="badge[% ' badge-success' IF row.layers.substr(5,1) %]">3</span></a><span class="badge">&nbsp;</span><span class="badge">&nbsp;</span><span class="badge">&nbsp;</span><span class="badge">&nbsp;</span>
<input data-form="update" name="layers" type="hidden" value="[% row.layers | html_entity %]">
</td>
<td class="nd_center-cell">
<input data-form="update" name="dns" type="hidden" value="[% row.dns | html_entity %]">
<input data-form="update" name="ip" type="hidden" value="[% row.ip | html_entity %]">
@@ -68,6 +79,17 @@ $(document).ready(function() {
} ],
[% INCLUDE 'ajax/datatabledefaults.tt' -%]
} );
$('.nd_layer-three-link').click(function() {
var badge = $(this).children('span').first();
var layers = $(this).parent().children('input').first();
$(badge).toggleClass('badge-success');
if ($(badge).hasClass('badge-success')) {
$(layers).attr('value', '00000100');
}
else {
$(layers).attr('value', '00000000');
}
});
} );
</script>

View File

@@ -5,6 +5,7 @@
<th class="nd_center-cell">Username</th>
<th class="nd_center-cell">Password</th>
<th class="nd_center-cell">LDAP Auth</th>
<th class="nd_center-cell">RADIUS Auth</th>
<th class="nd_center-cell">Port Control</th>
<th class="nd_center-cell">Administrator</th>
<th class="nd_center-cell">Created</th>
@@ -16,15 +17,16 @@
<tbody>
<tr>
<td class="nd_center-cell"><input data-form="add" name="fullname" type="text"></td>
<td class="nd_center-cell"><input data-form="add" name="username" type="text"></td>
<td class="nd_center-cell"><input data-form="add" name="password" type="password"></td>
<td class="nd_center-cell"><input class="span2" data-form="add" name="username" type="text"></td>
<td class="nd_center-cell"><input class="span2" data-form="add" name="password" type="password"></td>
<td class="nd_center-cell"><input data-form="add" type="checkbox" name="ldap"></td>
<td class="nd_center-cell"><input data-form="add" type="checkbox" name="radius"></td>
<td class="nd_center-cell"><input data-form="add" type="checkbox" name="port_control"></td>
<td class="nd_center-cell"><input data-form="add" type="checkbox" name="admin"></td>
<td class="nd_center-cell"></td>
<td class="nd_center-cell"></td>
<td class="nd_center-cell"><input data-form="add" name="note" type="text"></td>
<td class="nd_center-cell">
<td class="nd_center-cell"><input class="span2" data-form="add" name="note" type="text"></td>
<td nowrap class="nd_center-cell">
<button class="btn btn-small nd_adminbutton" name="add" type="submit"><i class="icon-plus-sign"></i> Add</button>
</td>
</tr>
@@ -37,14 +39,17 @@
<input data-form="update" name="fullname" type="text" value="[% row.fullname | html_entity %]">
</td>
<td class="nd_center-cell">
<input data-form="update" name="username" type="text" value="[% row.username | html_entity %]">
<input class="span2" data-form="update" name="username" type="text" value="[% row.username | html_entity %]">
</td>
<td class="nd_center-cell">
<input data-form="update" name="password" type="password" value="********">
<input class="span2" data-form="update" name="password" type="password" value="********">
</td>
<td class="nd_center-cell">
<input data-form="update" name="ldap" type="checkbox" [% ' checked="checked"' IF row.ldap %]>
</td>
<td class="nd_center-cell">
<input data-form="update" name="radius" type="checkbox" [% ' checked="checked"' IF row.radius %]>
</td>
<td class="nd_center-cell">
<input data-form="update" name="port_control" type="checkbox" [% ' checked="checked"' IF row.port_control %]>
</td>
@@ -54,10 +59,10 @@
<td class="nd_center-cell">[% row.created | html_entity %]</td>
<td class="nd_center-cell">[% row.last_seen | html_entity %]</td>
<td class="nd_center-cell">
<input data-form="update" name="note" type="text" value="[% row.note | html_entity %]">
<input class="span2" data-form="update" name="note" type="text" value="[% row.note | html_entity %]">
</td>
<td class="nd_center-cell">
<td nowrap class="nd_center-cell">
<button class="btn nd_adminbutton" name="update" type="submit"><i class="icon-save text-warning"></i></button>
<button class="btn" data-toggle="modal"

View File

@@ -1,6 +1,6 @@
[% USE CSV -%]
[% CSV.dump([ 'Full Name' 'Username'
'LDAP Auth' 'Port Control' 'Administrator' 'Created'
'LDAP Auth' 'RADIUS Auth' 'Port Control' 'Administrator' 'Created'
'Last Login' 'Note']) %]
[% FOREACH row IN results %]
@@ -8,6 +8,7 @@
[% mylist.push(row.fullname) %]
[% mylist.push(row.username) %]
[% mylist.push(row.ldap) %]
[% mylist.push(row.radius) %]
[% mylist.push(row.port_control) %]
[% mylist.push(row.admin) %]
[% mylist.push(row.created) %]

View File

@@ -37,9 +37,13 @@
[% ELSIF row.error_disable_cause %]
<i class="icon-exclamation-sign text-error icon-large"></i>
[% ELSIF row.has_column_loaded('is_free') AND row.is_free %]
<i class="icon-arrow-down text-success icon-large"></i>
<i class="icon-circle-arrow-down text-success icon-large"></i>
[% ELSIF row.up_admin == 'up' AND (row.up != 'up' AND row.up != 'dormant') %]
<i class="icon-arrow-down text-error icon-large"></i>
[% IF params.port_state == 'free' %]
<i class="icon-circle-arrow-down text-success icon-large"></i>
[% ELSE %]
<i class="icon-arrow-down text-error icon-large"></i>
[% END %]
[% ELSE %]
<i class="icon-angle-up text-success icon-large"></i>
[% END %]
@@ -244,7 +248,7 @@
<i class="icon-off nd_power-on"></i>
[% END %]
<span>
[% IF row.power.power > 0 %]
[% IF row.power.power AND row.power.power > 0 %]
[% row.power.power | html_entity %]&nbsp;mW
[% ELSE %]
([% row.power.status | html_entity %])

View File

@@ -43,7 +43,7 @@ $(document).ready(function() {
}, {
"data": 'oui.abbrev',
"render": function(data, type, row, meta) {
return '<a href="[% uri_for('/report/nodevendor') %]?vendor=' + encodeURIComponent(data || 'blank') + '">' + he.encode(data ||'(Unknown Vendor)') + '</a>';
return '<a href="[% uri_for('/report/nodevendor') %]?vendor=' + encodeURIComponent(row.oui.abbrev || 'blank') + '">' + he.encode(row.oui.company ||'(Unknown Vendor)') + '</a>';
}
}, {
"data": 'port',
@@ -71,7 +71,7 @@ $(document).ready(function() {
{
"data": 'vendor',
"render": function(data, type, row, meta) {
return '<a href="[% uri_for('/report/nodevendor') %]?vendor=' + encodeURIComponent(data || 'blank') + '">' + he.encode(data ||'(Unknown Vendor)') + '</a>';
return '<a href="[% uri_for('/report/nodevendor') %]?vendor=' + encodeURIComponent(row.abbrev || 'blank') + '">' + he.encode(row.vendor ||'(Unknown Vendor)') + '</a>';
}
}, {
"data": 'count',

View File

@@ -1,11 +1,11 @@
[% USE CSV -%]
[% IF opt %]
[% CSV.dump(['MAC' 'Vendor' 'Company' 'Device' 'Port']) %]
[% CSV.dump(['MAC' 'Company' 'Device' 'Port']) %]
[% FOREACH row IN results %]
[% mylist = [] %]
[% device = row.device.dns || row.device.name || row.switch %]
[% FOREACH col IN [ row.mac.upper row.oui.abbrev row.oui.company device row.port ] %]
[% FOREACH col IN [ row.mac.upper row.oui.company device row.port ] %]
[% mylist.push(col) %]
[% END %]
[% CSV.dump(mylist) %]

View File

@@ -71,8 +71,9 @@
,minLength: 0
});
// activate modals
// activate modals and tooltips
$('.nd_modal').modal({show: false});
$("[rel=tooltip]").tooltip({live: true});
}
// on load, establish global delegations for now and future

View File

@@ -181,7 +181,7 @@
[% session.logged_in_fullname || session.logged_in_user | html_entity %] <b class="caret"></b>
</a>
<ul class="dropdown-menu">
[% IF NOT user_has_role('ldap') %]
[% IF NOT ( user_has_role('ldap') OR user_has_role('radius') ) %]
<li><a href="[% uri_for('/password') %]">Change Password</a></li>
[% END %]
[% IF NOT settings.no_auth %]

View File

@@ -34,10 +34,12 @@
<ul class="icons-ul"><!-- nd_inputs-list unstyled"> -->
<li><i class="icon-li icon-angle-up text-success"></i>&nbsp; Link Up</li>
<li><i class="icon-li icon-arrow-down text-error"></i>&nbsp; Link Down</li>
<li><i class="icon-li icon-arrow-down text-success"></i>&nbsp; Port Free</li>
<li><i class="icon-li icon-circle-arrow-down text-success"></i>&nbsp; Port Free</li>
<li><i class="icon-li icon-remove"></i>&nbsp; Admin Disabled</li>
<li><i class="icon-li icon-exclamation-sign text-error"></i>&nbsp; Error Disabled</li>
<li><i class="icon-li icon-ban-circle text-info"></i>&nbsp; Blocking</li>
<li><i class="icon-li icon-off"></i>&nbsp; PoE Disabled</li>
<li><i class="icon-li icon-off nd_power-on"></i>&nbsp; PoE Enabled</li>
<li><i class="icon-li icon-link text-warning"></i>&nbsp; Manual Topology</li>
<li><i class="icon-li icon-link"></i>&nbsp; Neighbor Device</li>
<li><i class="icon-li icon-unlink text-error"></i>&nbsp; Neighbor Inacessible</li>

8
xt/01-local-pod.t Normal file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
use Test::Pod;
all_pod_files_ok();