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, --> READ THIS FIRST!
<!-- see SNMP::Info: https://github.com/netdisco/snmp-info/issues/new --> ----------------
<!-- stop! If you have new MIBs to submit, --> STOP! If your ticket is about a device not being detected correctly,
<!-- see netdisco-mibs: https://github.com/netdisco/netdisco-mibs/issues/new --> 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 ## Expected Behavior
<!--- If you're describing a bug, tell us what should happen --> <!--- If you're describing a bug, tell us what should happen -->
@@ -37,6 +58,10 @@
* Netdisco version used: * Netdisco version used:
* SNMP::Info 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 ## Device information
<!--- if the issue relates to specific devices their info would be usefull --> <!--- if the issue relates to specific devices their info would be usefull -->
<!--- do note that the following command might contain sensitive info, you can --> <!--- do note that the following command might contain sensitive info, you can -->

4
.gitignore vendored
View File

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

View File

@@ -2,12 +2,14 @@ language: perl
perl: perl:
- "5.30" - "5.30"
- "5.10" - "5.10"
os: linux
dist: trusty
addons: addons:
apt: apt:
packages: packages:
- libsnmp-dev - libsnmp-dev
- phantomjs - phantomjs
- graphviz - graphviz
hosts: hosts:
- localhost - localhost
branches: branches:
@@ -15,7 +17,7 @@ branches:
- /^2\.\d{6}$/ - /^2\.\d{6}$/
- 'master' - 'master'
install: 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: | script: |
perl Build.PL && \ perl Build.PL && \
./Build && \ ./Build && \

View File

@@ -26,6 +26,7 @@ Module::Build->new(
'App::cpanminus' => '1.6108', 'App::cpanminus' => '1.6108',
'App::local::lib::helper' => '0.07', 'App::local::lib::helper' => '0.07',
'Archive::Extract' => '0', 'Archive::Extract' => '0',
'Authen::Radius' => '0',
'CGI::Expand' => '2.05', 'CGI::Expand' => '2.05',
'Data::Printer' => '0', 'Data::Printer' => '0',
'DBD::Pg' => '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/DevicePortSpeed.pm
lib/App/Netdisco/DB/Result/Virtual/DuplexMismatch.pm lib/App/Netdisco/DB/Result/Virtual/DuplexMismatch.pm
lib/App/Netdisco/DB/Result/Virtual/GenericReport.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/NodeIp4.pm
lib/App/Netdisco/DB/Result/Virtual/NodeIp6.pm lib/App/Netdisco/DB/Result/Virtual/NodeIp6.pm
lib/App/Netdisco/DB/Result/Virtual/NodeMonitor.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/Runner.pm
lib/App/Netdisco/Worker/Status.pm lib/App/Netdisco/Worker/Status.pm
lib/Dancer/Template/NetdiscoTemplateToolkit.pm lib/Dancer/Template/NetdiscoTemplateToolkit.pm
LICENCE LICENSE.md
MANIFEST This list of files MANIFEST This list of files
META.json META.json
META.yml META.yml
@@ -318,6 +319,7 @@ share/public/javascripts/netdisco_portcontrol.js
share/public/javascripts/portsort.js share/public/javascripts/portsort.js
share/public/javascripts/toastr.js share/public/javascripts/toastr.js
share/public/javascripts/underscore.min.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-1-2-PostgreSQL.sql
share/schema_versions/App-Netdisco-DB-10-11-PostgreSQL.sql share/schema_versions/App-Netdisco-DB-10-11-PostgreSQL.sql
share/schema_versions/App-Netdisco-DB-11-12-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-55-56-PostgreSQL.sql
share/schema_versions/App-Netdisco-DB-56-57-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-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-6-7-PostgreSQL.sql
share/schema_versions/App-Netdisco-DB-7-8-PostgreSQL.sql share/schema_versions/App-Netdisco-DB-7-8-PostgreSQL.sql
share/schema_versions/App-Netdisco-DB-8-9-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/node.tt
share/views/sidebar/search/port.tt share/views/sidebar/search/port.tt
xt/00-compile.t xt/00-compile.t
xt/01-local-pod.t
xt/10-sort_port.t xt/10-sort_port.t
xt/11-portsort.t xt/11-portsort.t
xt/20-checkacl.t xt/20-checkacl.t

View File

@@ -4,7 +4,7 @@
"Oliver Gorwits <oliver@cpan.org>" "Oliver Gorwits <oliver@cpan.org>"
], ],
"dynamic_config" : 1, "dynamic_config" : 1,
"generated_by" : "Module::Build version 0.4224", "generated_by" : "Module::Build version 0.4229",
"license" : [ "license" : [
"bsd" "bsd"
], ],
@@ -39,6 +39,7 @@
"App::cpanminus" : "1.6108", "App::cpanminus" : "1.6108",
"App::local::lib::helper" : "0.07", "App::local::lib::helper" : "0.07",
"Archive::Extract" : "0", "Archive::Extract" : "0",
"Authen::Radius" : "0",
"CGI::Expand" : "2.05", "CGI::Expand" : "2.05",
"DBD::Pg" : "0", "DBD::Pg" : "0",
"DBIx::Class" : "0.082841", "DBIx::Class" : "0.082841",
@@ -143,7 +144,7 @@
}, },
"App::Netdisco::DB" : { "App::Netdisco::DB" : {
"file" : "lib/App/Netdisco/DB.pm", "file" : "lib/App/Netdisco/DB.pm",
"version" : "58" "version" : "59"
}, },
"App::Netdisco::DB::ExplicitLocking" : { "App::Netdisco::DB::ExplicitLocking" : {
"file" : "lib/App/Netdisco/DB/ExplicitLocking.pm" "file" : "lib/App/Netdisco/DB/ExplicitLocking.pm"
@@ -268,6 +269,9 @@
"App::Netdisco::DB::Result::Virtual::GenericReport" : { "App::Netdisco::DB::Result::Virtual::GenericReport" : {
"file" : "lib/App/Netdisco/DB/Result/Virtual/GenericReport.pm" "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" : { "App::Netdisco::DB::Result::Virtual::NodeIp4" : {
"file" : "lib/App/Netdisco/DB/Result/Virtual/NodeIp4.pm" "file" : "lib/App/Netdisco/DB/Result/Virtual/NodeIp4.pm"
}, },
@@ -804,7 +808,7 @@
}, },
"homepage" : "http://netdisco.org/", "homepage" : "http://netdisco.org/",
"license" : [ "license" : [
"http://opensource.org/licenses/BSD-3-Clause" "http://opensource.org/licenses/bsd-license.php"
], ],
"repository" : { "repository" : {
"url" : "https://github.com/netdisco/netdisco" "url" : "https://github.com/netdisco/netdisco"
@@ -813,5 +817,5 @@
"x_MailingList" : "https://lists.sourceforge.net/lists/listinfo/netdisco-users" "x_MailingList" : "https://lists.sourceforge.net/lists/listinfo/netdisco-users"
}, },
"version" : "2.042010", "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' DBIx::Class: '0.082810'
Module::Build: '0.42' Module::Build: '0.42'
dynamic_config: 1 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 license: bsd
meta-spec: meta-spec:
url: http://module-build.sourceforge.net/META-spec-v1.4.html url: http://module-build.sourceforge.net/META-spec-v1.4.html
@@ -38,7 +38,7 @@ provides:
file: lib/App/Netdisco/Configuration.pm file: lib/App/Netdisco/Configuration.pm
App::Netdisco::DB: App::Netdisco::DB:
file: lib/App/Netdisco/DB.pm file: lib/App/Netdisco/DB.pm
version: '58' version: '59'
App::Netdisco::DB::ExplicitLocking: App::Netdisco::DB::ExplicitLocking:
file: lib/App/Netdisco/DB/ExplicitLocking.pm file: lib/App/Netdisco/DB/ExplicitLocking.pm
App::Netdisco::DB::Result::Admin: App::Netdisco::DB::Result::Admin:
@@ -121,6 +121,8 @@ provides:
file: lib/App/Netdisco/DB/Result/Virtual/DuplexMismatch.pm file: lib/App/Netdisco/DB/Result/Virtual/DuplexMismatch.pm
App::Netdisco::DB::Result::Virtual::GenericReport: App::Netdisco::DB::Result::Virtual::GenericReport:
file: lib/App/Netdisco/DB/Result/Virtual/GenericReport.pm 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: App::Netdisco::DB::Result::Virtual::NodeIp4:
file: lib/App/Netdisco/DB/Result/Virtual/NodeIp4.pm file: lib/App/Netdisco/DB/Result/Virtual/NodeIp4.pm
App::Netdisco::DB::Result::Virtual::NodeIp6: App::Netdisco::DB::Result::Virtual::NodeIp6:
@@ -483,6 +485,7 @@ requires:
App::cpanminus: '1.6108' App::cpanminus: '1.6108'
App::local::lib::helper: '0.07' App::local::lib::helper: '0.07'
Archive::Extract: '0' Archive::Extract: '0'
Authen::Radius: '0'
CGI::Expand: '2.05' CGI::Expand: '2.05'
DBD::Pg: '0' DBD::Pg: '0'
DBIx::Class: '0.082841' DBIx::Class: '0.082841'
@@ -556,7 +559,7 @@ resources:
MailingList: https://lists.sourceforge.net/lists/listinfo/netdisco-users MailingList: https://lists.sourceforge.net/lists/listinfo/netdisco-users
bugtracker: https://github.com/netdisco/netdisco/issues bugtracker: https://github.com/netdisco/netdisco/issues
homepage: http://netdisco.org/ 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 repository: https://github.com/netdisco/netdisco
version: '2.042010' version: '2.042010'
x_serialization_backend: 'CPAN::Meta::YAML version 0.018' 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 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 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 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 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 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 Similarly the latest Netdisco MIB bundle is also downloaded and placed into
the user's home directory (or C<$ENV{NETDISCO_HOME}>). 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 =cut
print color 'bold cyan'; print color 'bold cyan';
say 'This is the Netdisco II deployment script.'; say 'This is the Netdisco 2 deployment script.';
say ''; say '';
say 'Before we continue, the following prerequisites must be in place:'; say 'Before we continue, the following prerequisites must be in place:';
say ' * Database added to PostgreSQL for Netdisco'; 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 ' * A full backup of any existing Netdisco database data';
say ' * Internet access (for OUIs and MIBs)'; say ' * Internet access (for OUIs and MIBs)';
say ''; 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 'You will be asked to confirm all changes to your system.';
say ''; say '';
print color 'reset'; print color 'reset';
@@ -194,13 +206,13 @@ sub deploy_db {
} }
sub get_userpass { sub get_userpass {
my $term = shift; my $upterm = shift;
my $name = $term->get_reply(prompt => 'Username: '); my $name = $upterm->get_reply(prompt => 'Username: ');
my $pass = $term->get_reply(prompt => 'Password: '); my $pass = $upterm->get_reply(prompt => 'Password: ');
unless ($name and $pass) { unless ($name and $pass) {
say 'username and password cannot be empty, please try again.'; say 'username and password cannot be empty, please try again.';
($name, $pass) = get_userpass($term); ($name, $pass) = get_userpass($upterm);
} }
return ($name, $pass); return ($name, $pass);
@@ -257,7 +269,7 @@ sub deploy_oui {
print color 'reset'; 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 # http://anonsvn.wireshark.org/wireshark/trunk/tools/make-manuf
sub shorten { sub shorten {
my $manuf = shift; my $manuf = shift;
@@ -271,22 +283,27 @@ sub shorten {
# & isn't needed when Standalone # & isn't needed when Standalone
$manuf =~ s/ \& / /g; $manuf =~ s/ \& / /g;
# remove junk whitespace
$manuf =~ s/\s+/ /g;
# Remove any "the", "inc", "plc" ... # Remove any "the", "inc", "plc" ...
$manuf $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 # Convert to consistent case
$manuf =~ s/(\w+)/\u\L$1/g; $manuf =~ s/(\w+)/\u\L$1/g;
# Remove all spaces
$manuf =~ s/\s+//g;
# Deviating from make-manuf for HP # Deviating from make-manuf for HP
$manuf =~ s/Hewlett[-]?Packard/Hp/; $manuf =~ s/Hewlett[-]?Packard/Hp/;
# Truncate all names to a reasonable length, say, 8 characters. # Truncate all names to first two words max 20 chars
# If the string contains UTF-8, this may be substantially more than 8 bytes. if (length($manuf) > 21) {
$manuf = substr( $manuf, 0, 8 ); my @twowords = grep {defined} (split ' ', $manuf)[0 .. 1];
$manuf = join ' ', @twowords;
}
# Remove all spaces
$manuf =~ s/\s+//g;
return encode( "utf8", $manuf ); 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 Netdisco has several Perl library dependencies which will be automatically
installed. However it's required that you first install the following 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: On Ubuntu/Debian:
@@ -100,7 +101,7 @@ will take about 250MB including MIB files.
root:~# useradd -m -p x -s /bin/bash netdisco root:~# useradd -m -p x -s /bin/bash netdisco
Netdisco uses the PostgreSQL database server. Install PostgreSQL (at least 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 C<postgres>). Create a new database and PostgreSQL user for the Netdisco
application: application:
@@ -115,14 +116,25 @@ application:
You may wish to L<amend the PostgreSQL You may wish to L<amend the PostgreSQL
configuration|https://github.com/netdisco/netdisco/wiki/Install-Tips#enable-md5-authentication-to-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 so that local connections are working. The default PostgreSQL configuration
also needs tuning for modern server hardware. We recommend that you use the can also use tuning for modern server hardware. We recommend that you use one of the following
C<pgtune> Python program to auto-tune your C<postgresql.conf> file: tools to tune your C<postgresql.conf> file:
=over 4 =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 =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 the full installation instructions, above. This process is for upgrading
version 2.x only. 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: Then the process below should be run for each installation:
# upgrade Netdisco # 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 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 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 =head2 Command-Line Device and Port Actions
@@ -332,7 +344,7 @@ built upon.
=head1 COPYRIGHT AND LICENSE =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 Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:

View File

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

View File

@@ -11,7 +11,7 @@ __PACKAGE__->load_namespaces(
); );
our # try to hide from kwalitee 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 Path::Class;
use File::ShareDir 'dist_dir'; use File::ShareDir 'dist_dir';

View File

@@ -242,6 +242,23 @@ __PACKAGE__->belongs_to( neighbor_alias => 'App::Netdisco::DB::Result::DeviceIp'
{ join_type => 'LEFT' }, { 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 =head2 vlans
As compared to C<port_vlans>, this relationship returns a set of Device VLAN 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 }, { data_type => "boolean", default_value => \"false", is_nullable => 1 },
"ldap", "ldap",
{ data_type => "boolean", default_value => \"false", is_nullable => 1 }, { data_type => "boolean", default_value => \"false", is_nullable => 1 },
"radius",
{ data_type => "boolean", default_value => \"false", is_nullable => 1 },
"admin", "admin",
{ data_type => "boolean", default_value => \"false", is_nullable => 1 }, { data_type => "boolean", default_value => \"false", is_nullable => 1 },
"fullname", "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'); __PACKAGE__->table_class('DBIx::Class::ResultSource::View');
# NOTE this query is in `git grep 'THREE PLACES'`
__PACKAGE__->table('port_utilization'); __PACKAGE__->table('port_utilization');
__PACKAGE__->result_source_instance->is_virtual(1); __PACKAGE__->result_source_instance->is_virtual(1);
__PACKAGE__->result_source_instance->view_definition(<<ENDSQL __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, ELSE 0 END) as ports_in_use,
sum(CASE WHEN (dp.type != 'propVirtual' AND dp.up_admin != 'up') THEN 1 sum(CASE WHEN (dp.type != 'propVirtual' AND dp.up_admin != 'up') THEN 1
ELSE 0 END) as ports_shutdown, ELSE 0 END) as ports_shutdown,
sum(CASE WHEN (dp.type != 'propVirtual' AND dp.up_admin = 'up' AND dp.up != 'up' sum(CASE
AND ( age(now(), to_timestamp(extract(epoch from d.last_discover) - (d.uptime - dp.lastchange)/100)) > ?::interval )) THEN 1 WHEN ( dp.type != 'propVirtual' AND dp.up_admin = 'up' AND dp.up != 'up'
ELSE 0 END) as ports_free AND (age(now(), to_timestamp(extract(epoch from d.last_discover) - (d.uptime/100))) < ?::interval)
FROM device d LEFT JOIN device_port dp 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 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 GROUP BY d.dns, d.ip
ORDER BY d.dns, d.ip ORDER BY d.dns, d.ip
ENDSQL ENDSQL

View File

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

View File

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

View File

@@ -12,7 +12,7 @@ __PACKAGE__->load_components(qw/
=head2 skipped( $backend?, $max_deferrals?, $retry_after? ) =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. 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 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 =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 =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 =item vendor
@@ -226,21 +226,20 @@ sub search_by_field {
} }
# For Search on Layers # For Search on Layers
my @layer_search = ( '_', '_', '_', '_', '_', '_', '_' );
# @layer_search is computer indexed, left->right
my $layers = $p->{layers}; my $layers = $p->{layers};
my @layer_select = ();
if ( defined $layers && ref $layers ) { if ( defined $layers && ref $layers ) {
foreach my $layer (@$layers) { foreach my $layer (@$layers) {
next unless defined $layer and length($layer); next unless defined $layer and length($layer);
next if ( $layer < 1 || $layer > 7 ); 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 ) { 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 return $rs
->search_rs({}, $attrs) ->search_rs({}, $attrs)
@@ -252,8 +251,6 @@ sub search_by_field {
{ '-ilike' => "\%$p->{location}\%" }) : ()), { '-ilike' => "\%$p->{location}\%" }) : ()),
($p->{description} ? ('me.description' => ($p->{description} ? ('me.description' =>
{ '-ilike' => "\%$p->{description}\%" }) : ()), { '-ilike' => "\%$p->{description}\%" }) : ()),
($p->{layers} ? ('me.layers' =>
{ '-ilike' => "\%$layer_string" }) : ()),
($p->{model} ? ('me.model' => ($p->{model} ? ('me.model' =>
{ '-in' => $p->{model} }) : ()), { '-in' => $p->{model} }) : ()),
@@ -264,6 +261,8 @@ sub search_by_field {
($p->{vendor} ? ('me.vendor' => ($p->{vendor} ? ('me.vendor' =>
{ '-in' => $p->{vendor} }) : ()), { '-in' => $p->{vendor} }) : ()),
($p->{layers} ? (-or => \@layer_select) : ()),
($p->{dns} ? ( ($p->{dns} ? (
-or => [ -or => [
'me.dns' => { '-ilike' => "\%$p->{dns}\%" }, '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 This is a modifier for any C<search()> (including the helpers below) which
will add the following additional synthesized columns to the result set: will add the following additional synthesized columns to the result set:
@@ -67,12 +67,13 @@ sub with_is_free {
->search({}, ->search({},
{ {
'+columns' => { is_free => '+columns' => { is_free =>
\["me.up != 'up' and " # NOTE this query is in `git grep 'THREE PLACES'`
."age(now(), to_timestamp(extract(epoch from device.last_discover) " \["me.up_admin = 'up' AND me.up != 'up' AND me.type != 'propVirtual' AND "
."- (device.uptime - me.lastchange)/100)) " ."((age(now(), to_timestamp(extract(epoch from device.last_discover) - (device.uptime/100))) < ?::interval "
."> ?::interval", ."AND (last_node.time_last IS NULL OR age(now(), last_node.time_last) > ?::interval)) "
[{} => $interval]] }, ."OR age(now(), to_timestamp(extract(epoch from device.last_discover) - (device.uptime - me.lastchange)/100)) > ?::interval)",
join => 'device', [{} => $interval],[ {} => $interval],[ {} => $interval]] },
join => [qw/device last_node/],
}); });
} }
@@ -96,14 +97,23 @@ sub only_free_ports {
->search_rs($cond, $attrs) ->search_rs($cond, $attrs)
->search( ->search(
{ {
'me.up' => { '!=' => 'up' }, # NOTE this query is in `git grep 'THREE PLACES'`
},{ 'me.up_admin' => 'up',
where => 'me.up' => { '!=' => 'up' },
\["age(now(), to_timestamp(extract(epoch from device.last_discover) " 'me.type' => { '!=' => 'propVirtual' },
."- (device.uptime - me.lastchange)/100)) " -or => [
."> ?::interval", -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]], [{} => $interval]],
join => 'device' }, ],
},{ join => [qw/device last_node/] },
); );
} }

View File

@@ -79,6 +79,19 @@ sub jq_warm_thrusters {
actionset => $actionset{$_}, actionset => $actionset{$_},
}, { key => 'primary' }) for keys %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 { sub jq_getsome {

View File

@@ -1,7 +1,5 @@
package App::Netdisco::SSHCollector::Platform::ACE; package App::Netdisco::SSHCollector::Platform::ACE;
# vim: set expandtab tabstop=8 softtabstop=4 shiftwidth=4:
=head1 NAME =head1 NAME
App::Netdisco::SSHCollector::Platform::ACE 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 with C<show context>, afterwards the commands C<changeto CONTEXTNAME> and
C<show arp> must be executed for every context. 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, line, and Net::OpenSSH uses individual connections for individual commands,
so we need to use Expect to execute the changeto and show commands in so we need to use Expect to execute the changeto and show commands in
the same context. the same context.
@@ -36,7 +34,7 @@ use Moo;
Retrieve ARP entries from device. C<$host> is the hostname or IP address 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. 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 =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 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: C<enable_password> under C<device_auth> tag in your configuration file:
device_auth: device_auth:
- tag: sshasa - tag: sshasa
driver: cli driver: cli
platform: ASA platform: ASA
only: '192.0.2.1' only: '192.0.2.1'
username: oliver username: oliver
password: letmein password: letmein
enable_password: myenablepass enable_password: myenablepass
=cut =cut
@@ -43,7 +43,7 @@ use Moo;
Retrieve ARP and neighbor entries from device. C<$host> is the hostname or IP 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. 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 =back

View File

@@ -34,7 +34,7 @@ use Moo;
Retrieve ARP entries from device. C<$host> is the hostname or IP address 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. 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 =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. system. Clish "C<show arp>" does not work correctly in versions prior to R77.30.
Config example: Config example:
device_auth: device_auth:
- tag: sshcpvsx - tag: sshcpvsx
driver: cli driver: cli
platform: CPVSX platform: CPVSX
only: '192.0.2.1' only: '192.0.2.1'
username: oliver username: oliver
password: letmein password: letmein
expert_password: letmein2 expert_password: letmein2
=cut =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> system. If you wish to specify an absolute path, then add an C<arp_command>
item to your configuration: item to your configuration:
device_auth: device_auth:
- tag: sshfreebsd - tag: sshfreebsd
driver: cli driver: cli
platform: FreeBSD platform: FreeBSD
only: '192.0.2.1' only: '192.0.2.1'
username: oliver username: oliver
password: letmein password: letmein
arp_command: '/usr/sbin/arp' arp_command: '/usr/sbin/arp'
=cut =cut
@@ -39,7 +39,7 @@ use Moo;
Retrieve ARP entries from device. C<$host> is the hostname or IP address 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. 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 =back

View File

@@ -7,21 +7,21 @@ App::Netdisco::SSHCollector::Platform::GAIAEmbedded
=head1 DESCRIPTION =head1 DESCRIPTION
Collect ARP entries from Checkpoint GAIA embedded Systems Collect ARP entries from Checkpoint GAIA embedded Systems
To get this Plugin to work you have to add an User like 'netdisco' with To get this Plugin to work you have to add a user like 'netdisco' with
'Network admin' right in the GAIA embedded OS 'Network admin' rights in the GAIA embedded OS.
This collector uses "C<arp>" as the command for the arp utility on your 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: item to your configuration:
device_auth: device_auth:
- tag: sshgaia - tag: sshgaia
driver: cli driver: cli
platform: GAIAEmbedded platform: GAIAEmbedded
only: '192.0.2.1' only: '192.0.2.1'
username: oliver username: oliver
password: letmein password: letmein
arp_command: 'arp' arp_command: 'arp'
=cut =cut
@@ -41,7 +41,7 @@ use Moo;
Retrieve ARP entries from device. C<$host> is the hostname or IP address 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. 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 =back

View File

@@ -27,7 +27,7 @@ use Moo;
Retrieve ARP entries from device. C<$host> is the hostname or IP address 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. 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 =back

View File

@@ -27,7 +27,7 @@ use Moo;
Retrieve ARP entries from device. C<$host> is the hostname or IP address 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. 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 =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> system. If you wish to specify an absolute path, then add an C<arp_command>
item to your configuration: item to your configuration:
device_auth: device_auth:
- tag: sshlinux - tag: sshlinux
driver: cli driver: cli
platform: Linux platform: Linux
only: '192.0.2.1' only: '192.0.2.1'
username: oliver username: oliver
password: letmein password: letmein
arp_command: '/usr/sbin/arp' arp_command: '/usr/sbin/arp'
=cut =cut
@@ -39,7 +39,7 @@ use Moo;
Retrieve ARP entries from device. C<$host> is the hostname or IP address 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. 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 =back

View File

@@ -58,8 +58,11 @@ Returns C<undef> if the connection fails.
sub reader_for { sub reader_for {
my ($class, $ip, $useclass) = @_; my ($class, $ip, $useclass) = @_;
my $device = get_device($ip) or return undef; 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; my $readers = $class->instance->readers or return undef;
return $readers->{$device->ip} if exists $readers->{$device->ip}; return $readers->{$device->ip} if exists $readers->{$device->ip};
debug sprintf 'snmp reader cache warm: [%s]', $device->ip; debug sprintf 'snmp reader cache warm: [%s]', $device->ip;
return ($readers->{$device->ip} return ($readers->{$device->ip}
= _snmp_connect_generic('read', $device, $useclass)); = _snmp_connect_generic('read', $device, $useclass));
@@ -104,8 +107,11 @@ Returns C<undef> if the connection fails.
sub writer_for { sub writer_for {
my ($class, $ip, $useclass) = @_; my ($class, $ip, $useclass) = @_;
my $device = get_device($ip) or return undef; 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; my $writers = $class->instance->writers or return undef;
return $writers->{$device->ip} if exists $writers->{$device->ip}; return $writers->{$device->ip} if exists $writers->{$device->ip};
debug sprintf 'snmp writer cache warm: [%s]', $device->ip; debug sprintf 'snmp writer cache warm: [%s]', $device->ip;
return ($writers->{$device->ip} return ($writers->{$device->ip}
= _snmp_connect_generic('write', $device, $useclass)); = _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 Also respects C<discover_phones> and C<discover_waps> if either are set to
false. 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. Returns false if the host is not permitted to discover the target device.
=cut =cut
@@ -168,6 +170,9 @@ sub is_discoverable {
$remote_type ||= ''; $remote_type ||= '';
$remote_cap ||= []; $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") return _bail_msg("is_discoverable: $device matches wap_platforms but discover_waps is not enabled")
if ((not setting('discover_waps')) and if ((not setting('discover_waps')) and
(match_to_setting($remote_type, 'wap_platforms') or (match_to_setting($remote_type, 'wap_platforms') or
@@ -192,9 +197,8 @@ sub is_discoverable {
=head2 is_discoverable_now( $ip, $device_type? ) =head2 is_discoverable_now( $ip, $device_type? )
Same as C<is_discoverable>, but also checks the last_discover field if the Same as C<is_discoverable>, but also compares the C<last_discover> field
device is in storage, and returns false if that host has been too recently of the C<device> to the C<discover_min_age> configuration.
discovered.
Returns false if the host is not permitted to discover the target device. 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 The configuration items C<arpnip_no> and C<arpnip_only> are checked
against the given IP. 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. Returns false if the host is not permitted to arpnip the target device.
=cut =cut
@@ -230,6 +236,9 @@ sub is_arpnipable {
my $ip = shift; my $ip = shift;
my $device = get_device($ip) or return 0; 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") return _bail_msg("is_arpnipable: $device matched arpnip_no")
if check_acl_no($device, 'arpnip_no'); if check_acl_no($device, 'arpnip_no');
@@ -241,9 +250,8 @@ sub is_arpnipable {
=head2 is_arpnipable_now( $ip ) =head2 is_arpnipable_now( $ip )
Same as C<is_arpnipable>, but also checks the last_arpnip field if the Same as C<is_arpnipable>, but also compares the C<last_arpnip> field
device is in storage, and returns false if that host has been too recently of the C<device> to the C<arpnip_min_age> configuration.
arpnipped.
Returns false if the host is not permitted to arpnip the target device. 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 The configuration items C<macsuck_no> and C<macsuck_only> are checked
against the given IP. 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. Returns false if the host is not permitted to macsuck the target device.
=cut =cut
@@ -279,6 +289,9 @@ sub is_macsuckable {
my $ip = shift; my $ip = shift;
my $device = get_device($ip) or return 0; 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") return _bail_msg("is_macsuckable: $device matched macsuck_no")
if check_acl_no($device, 'macsuck_no'); if check_acl_no($device, 'macsuck_no');
@@ -290,9 +303,8 @@ sub is_macsuckable {
=head2 is_macsuckable_now( $ip ) =head2 is_macsuckable_now( $ip )
Same as C<is_macsuckable>, but also checks the last_macsuck field if the Same as C<is_macsuckable>, but also compares the C<last_macsuck> field
device is in storage, and returns false if that host has been too recently of the C<device> to the C<macsuck_min_age> configuration.
macsucked.
Returns false if the host is not permitted to macsuck the target device. 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 { get '/admin/*' => require_role admin => sub {
my ($tag) = splat; my ($tag) = splat;
# trick the ajax into working as if this were a tabbed page if (exists setting('_admin_tasks')->{ $tag }) {
params->{tab} = $tag; # trick the ajax into working as if this were a tabbed page
params->{tab} = $tag;
var(nav => 'admin'); var(nav => 'admin');
template 'admintask', { template 'admintask', {
task => setting('_admin_tasks')->{ $tag }, task => setting('_admin_tasks')->{ $tag },
}; };
}
else {
var('notfound' => true);
status 'not_found';
template 'index';
}
}; };
true; true;

View File

@@ -13,6 +13,7 @@ use Dancer::Plugin::DBIC;
use Dancer::Plugin::Passphrase; use Dancer::Plugin::Passphrase;
use Digest::MD5; use Digest::MD5;
use Net::LDAP; use Net::LDAP;
use Authen::Radius;
use Try::Tiny; use Try::Tiny;
sub authenticate_user { sub authenticate_user {
@@ -103,9 +104,20 @@ sub match_password {
my $settings = $self->realm_settings; my $settings = $self->realm_settings;
my $username_column = $settings->{users_username_column} || 'username'; my $username_column = $settings->{users_username_column} || 'username';
return $user->ldap my $pwmatch_result = 0;
? $self->match_with_ldap($password, $user->$username_column) my $username = $user->$username_column;
: $self->match_with_local_pass($password, $user);
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 { sub match_with_local_pass {
@@ -215,4 +227,28 @@ sub _ldap_search {
return undef; 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; 1;

View File

@@ -36,7 +36,7 @@ ajax '/ajax/control/admin/pseudodevice/add' => require_role admin => sub {
ip => param('ip'), ip => param('ip'),
dns => param('dns'), dns => param('dns'),
vendor => 'netdisco', vendor => 'netdisco',
layers => '00000100', layers => param('layers'),
last_discover => \'now()', last_discover => \'now()',
}); });
return unless $device; return unless $device;
@@ -87,6 +87,9 @@ ajax '/ajax/control/admin/pseudodevice/update' => require_role admin => sub {
})->delete; })->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')), password => _make_password(param('password')),
fullname => param('fullname'), fullname => param('fullname'),
ldap => (param('ldap') ? \'true' : \'false'), ldap => (param('ldap') ? \'true' : \'false'),
radius => (param('radius') ? \'true' : \'false'),
port_control => (param('port_control') ? \'true' : \'false'), port_control => (param('port_control') ? \'true' : \'false'),
admin => (param('admin') ? \'true' : \'false'), admin => (param('admin') ? \'true' : \'false'),
note => param('note'), note => param('note'),
@@ -71,6 +72,7 @@ ajax '/ajax/control/admin/users/update' => require_role setting('defanged_admin'
: ()), : ()),
fullname => param('fullname'), fullname => param('fullname'),
ldap => (param('ldap') ? \'true' : \'false'), ldap => (param('ldap') ? \'true' : \'false'),
radius => (param('radius') ? \'true' : \'false'),
port_control => (param('port_control') ? \'true' : \'false'), port_control => (param('port_control') ? \'true' : \'false'),
admin => (param('admin') ? \'true' : \'false'), admin => (param('admin') ? \'true' : \'false'),
note => param('note'), note => param('note'),

View File

@@ -31,7 +31,7 @@ get '/ajax/content/device/ports' => require_login sub {
# change wildcard chars to SQL # change wildcard chars to SQL
$f =~ s/\*/%/g; $f =~ s/\*/%/g;
$f =~ s/\?/_/g; $f =~ s/\?/_/g;
# set wilcards at param boundaries # set wildcards at param boundaries
if ($f !~ m/[%_]/) { if ($f !~ m/[%_]/) {
$f =~ s/^\%*/%/; $f =~ s/^\%*/%/;
$f =~ s/\%*$/%/; $f =~ s/\%*$/%/;
@@ -82,6 +82,8 @@ get '/ajax/content/device/ports' => require_login sub {
}); });
} }
delete $port_state{free}; delete $port_state{free};
# showing free ports requires showing down ports
++$port_state{down};
} }
if (scalar keys %port_state < 3) { 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; my $match = $vendor eq 'blank' ? undef : $vendor;
$rs = $rs->search( { 'oui.abbrev' => $match }, $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 /], join => [qw/ oui device /],
collapse => 1, collapse => 1,
}); });
@@ -86,7 +86,7 @@ get '/ajax/content/report/nodevendor' => require_login sub {
my $match = $vendor eq 'blank' ? undef : $vendor; my $match = $vendor eq 'blank' ? undef : $vendor;
$rs = $rs->search( { 'oui.abbrev' => $match }, $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 /], join => [qw/ oui device /],
collapse => 1, collapse => 1,
}); });
@@ -102,9 +102,9 @@ get '/ajax/content/report/nodevendor' => require_login sub {
$rs = $rs->search( $rs = $rs->search(
{ }, { },
{ join => 'oui', { join => 'oui',
select => [ 'oui.abbrev', { count => {distinct => 'me.mac'}} ], select => [ 'oui.abbrev', 'oui.company', { count => {distinct => 'me.mac'}} ],
as => [qw/ vendor count /], as => [qw/ abbrev vendor count /],
group_by => [qw/ oui.abbrev /] group_by => [qw/ oui.abbrev oui.company /]
} }
)->order_by( { -desc => 'count' } ); )->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_num = param('age_num') || 3;
my $age_unit = param('age_unit') || 'months'; my $age_unit = param('age_unit') || 'months';
my @results = schema('netdisco')->resultset('Virtual::PortUtilization') 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) { if (request->is_ajax) {
my $json = to_json (\@results); my $json = to_json (\@results);

View File

@@ -6,6 +6,7 @@ use Dancer::Plugin::DBIC;
use Dancer::Plugin::Auth::Extensible; use Dancer::Plugin::Auth::Extensible;
use NetAddr::IP::Lite ':lower'; use NetAddr::IP::Lite ':lower';
use Regexp::Common 'net';
use NetAddr::MAC (); use NetAddr::MAC ();
use App::Netdisco::Web::Plugin; 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 ( $start, $end ) = param('daterange') =~ m/(\d+-\d+-\d+)/gmx;
my $mac = NetAddr::MAC->new(mac => $node); my $mac = NetAddr::MAC->new(mac => $node);
undef $mac if ($mac and $mac->as_ieee and ($mac->as_ieee eq '00:00:00:00')); undef $mac if
my @active = (param('archived') ? () : (-bool => 'active')); ($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); my (@times, @wifitimes, @porttimes);
if ( $start and $end ) { if ( $start and $end ) {
$start = $start . ' 00:00:00'; $start = $start . ' 00:00:00';
$end = $end . ' 23:59:59'; $end = $end . ' 23:59:59';

View File

@@ -6,6 +6,7 @@ use Dancer::Plugin::DBIC;
use Dancer::Plugin::Auth::Extensible; use Dancer::Plugin::Auth::Extensible;
use App::Netdisco::Util::Web 'sql_match'; use App::Netdisco::Util::Web 'sql_match';
use Regexp::Common 'net';
use NetAddr::MAC (); use NetAddr::MAC ();
hook 'before_template' => sub { hook 'before_template' => sub {
@@ -39,7 +40,11 @@ get '/search' => require_login sub {
my $nd = $s->resultset('Device')->search_fuzzy($q); my $nd = $s->resultset('Device')->search_fuzzy($q);
my ($likeval, $likeclause) = sql_match($q); my ($likeval, $likeclause) = sql_match($q);
my $mac = NetAddr::MAC->new($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 and $nd->count) {
if ($nd->count == 1) { if ($nd->count == 1) {

View File

@@ -16,9 +16,6 @@ register_worker({ phase => 'check' }, sub {
return Status->error("arpnip skipped: $device not yet discovered") return Status->error("arpnip skipped: $device not yet discovered")
unless $device->in_storage; 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") return Status->info("arpnip skipped: $device is not arpnipable")
unless is_arpnipable_now($device); unless is_arpnipable_now($device);

View File

@@ -55,6 +55,7 @@ register_worker({ phase => 'main', driver => 'snmp' }, sub {
push @{ vars->{'v6arps'} }, push @{ vars->{'v6arps'} },
@{get_arps_snmp($device, $snmp->ipv6_n2p_mac, $snmp->ipv6_n2p_addr) }; @{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"); return Status->done("Gathered arp caches from $device");
}); });
@@ -82,24 +83,25 @@ sub get_arps_snmp {
} }
register_worker({ phase => 'main', driver => 'cli' }, sub { register_worker({ phase => 'main', driver => 'cli' }, sub {
my ($job, $workerconf) = @_; my ($job, $workerconf) = @_;
my $device = $job->device; my $device = $job->device;
my $cli = App::Netdisco::Transport::SSH->session_for($device) my $cli = App::Netdisco::Transport::SSH->session_for($device)
or return Status->defer("arpnip failed: could not SSH connect to $device"); or return Status->defer("arpnip failed: could not SSH connect to $device");
# should be both v4 and v6 # should be both v4 and v6
my @arps = @{ get_arps_cli($device, [$cli->arpnip]) }; my @arps = @{ get_arps_cli($device, [$cli->arpnip]) };
# cache v4 arp table # cache v4 arp table
push @{ vars->{'v4arps'} }, push @{ vars->{'v4arps'} },
grep { NetAddr::IP::Lite->new($_->{ip})->bits == 32 } @arps; grep { NetAddr::IP::Lite->new($_->{ip})->bits == 32 } @arps;
# cache v6 neighbor cache # cache v6 neighbor cache
push @{ vars->{'v6arps'} }, push @{ vars->{'v6arps'} },
grep { NetAddr::IP::Lite->new($_->{ip})->bits == 128 } @arps; 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 { 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 ?)") return Status->error("discover failed: no device param (need -d ?)")
if $device->ip eq '0.0.0.0'; 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 # runner has already called get_device to promote $job->device
return $job->cancel("fresh discover cancelled: $device already known") return $job->cancel("fresh discover cancelled: $device already known")
if $device->in_storage 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 The Device database object can be a fresh L<DBIx::Class::Row> object which is
not yet stored to the database. 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 =cut

View File

@@ -16,12 +16,6 @@ register_worker({ phase => 'check' }, sub {
return Status->error("macsuck skipped: $device not yet discovered") return Status->error("macsuck skipped: $device not yet discovered")
unless $device->in_storage; 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") return Status->info("macsuck skipped: $device is not macsuckable")
unless is_macsuckable_now($device); 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', debug sprintf ' [%s] macsuck - %s updated forwarding table entries',
$device->ip, $total_nodes; $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; my $archived = 0;
if (setting('node_freshness')) { 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". Netdisco supports using ID "0".
Optionally, a fifth argument can be the literal string passed to the time_last 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 =cut

View File

@@ -189,7 +189,7 @@ email config and creating the repository with C<rancid-cvs>.
=head2 C<rancid_conf> =head2 C<rancid_conf>
The location where the rancid configuration (F<rancid.types.base> and 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 of device types before exporting the devices to the rancid configuration. If no match
is found the device will not be added to rancid. is found the device will not be added to rancid.

View File

@@ -20,8 +20,9 @@ database:
# RECOMMENDED SETTINGS # RECOMMENDED SETTINGS
# -------------------- # --------------------
# SNMP community string(s) # Device authentication settings
# ```````````````````````` # define snmp communities and ssh credentials here
# ````````````````````````````````````````````````
device_auth: device_auth:
- tag: 'default_v2_readonly' - tag: 'default_v2_readonly'
community: 'public' community: 'public'
@@ -50,7 +51,7 @@ device_auth:
# SNMP, which just clogs up the job queue. # SNMP, which just clogs up the job queue.
# ``````````````````````````````````````````````````````````````` # ```````````````````````````````````````````````````````````````
#discover_waps: true #discover_waps: true
#disover_phones: false #discover_phones: false
# this is the schedule for automatically keeping netdisco up-to-date; # this is the schedule for automatically keeping netdisco up-to-date;
# these are good defaults, so only uncomment if needing to change. # 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; 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 */ /* for the job control admin page play/pause links */
#nd_countdown-refresh:hover, #nd_countdown-control:hover { #nd_countdown-refresh:hover, #nd_countdown-control:hover {
text-decoration: none; text-decoration: none;
@@ -359,7 +366,7 @@ td > form.nd_inline-form {
.container-fluid > .nd_sidebar { .container-fluid > .nd_sidebar {
position: absolute; position: absolute;
right: 20px; right: 20px;
width: 200px; width: 205px;
left: auto; 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 Name</th>
<th class="nd_center-cell">Device IP</th> <th class="nd_center-cell">Device IP</th>
<th class="nd_center-cell">Number of Ports</th> <th class="nd_center-cell">Number of Ports</th>
<th class="nd_center-cell">Services</th>
<th class="nd_center-cell">Action</th> <th class="nd_center-cell">Action</th>
</tr> </tr>
</thead> </thead>
@@ -11,7 +12,12 @@
<tr> <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="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="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"> <td class="nd_center-cell">
<button class="btn btn-small nd_adminbutton" name="add" type="submit"><i class="icon-plus-sign"></i> Add</button> <button class="btn btn-small nd_adminbutton" name="add" type="submit"><i class="icon-plus-sign"></i> Add</button>
</td> </td>
@@ -26,6 +32,11 @@
<td class="nd_center-cell"> <td class="nd_center-cell">
<input data-form="update" name="ports" type="number" value="[% row.port_count | html_entity %]"> <input data-form="update" name="ports" type="number" value="[% row.port_count | html_entity %]">
</td> </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"> <td class="nd_center-cell">
<input data-form="update" name="dns" type="hidden" value="[% row.dns | html_entity %]"> <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 %]"> <input data-form="update" name="ip" type="hidden" value="[% row.ip | html_entity %]">
@@ -68,6 +79,17 @@ $(document).ready(function() {
} ], } ],
[% INCLUDE 'ajax/datatabledefaults.tt' -%] [% 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> </script>

View File

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

View File

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

View File

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

View File

@@ -43,7 +43,7 @@ $(document).ready(function() {
}, { }, {
"data": 'oui.abbrev', "data": 'oui.abbrev',
"render": function(data, type, row, meta) { "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', "data": 'port',
@@ -71,7 +71,7 @@ $(document).ready(function() {
{ {
"data": 'vendor', "data": 'vendor',
"render": function(data, type, row, meta) { "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', "data": 'count',

View File

@@ -1,11 +1,11 @@
[% USE CSV -%] [% USE CSV -%]
[% IF opt %] [% IF opt %]
[% CSV.dump(['MAC' 'Vendor' 'Company' 'Device' 'Port']) %] [% CSV.dump(['MAC' 'Company' 'Device' 'Port']) %]
[% FOREACH row IN results %] [% FOREACH row IN results %]
[% mylist = [] %] [% mylist = [] %]
[% device = row.device.dns || row.device.name || row.switch %] [% 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) %] [% mylist.push(col) %]
[% END %] [% END %]
[% CSV.dump(mylist) %] [% CSV.dump(mylist) %]

View File

@@ -71,8 +71,9 @@
,minLength: 0 ,minLength: 0
}); });
// activate modals // activate modals and tooltips
$('.nd_modal').modal({show: false}); $('.nd_modal').modal({show: false});
$("[rel=tooltip]").tooltip({live: true});
} }
// on load, establish global delegations for now and future // 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> [% session.logged_in_fullname || session.logged_in_user | html_entity %] <b class="caret"></b>
</a> </a>
<ul class="dropdown-menu"> <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> <li><a href="[% uri_for('/password') %]">Change Password</a></li>
[% END %] [% END %]
[% IF NOT settings.no_auth %] [% IF NOT settings.no_auth %]

View File

@@ -34,10 +34,12 @@
<ul class="icons-ul"><!-- nd_inputs-list unstyled"> --> <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-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-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-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-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-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 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-link"></i>&nbsp; Neighbor Device</li>
<li><i class="icon-li icon-unlink text-error"></i>&nbsp; Neighbor Inacessible</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();