Merge of og-work branch, many new features.
Squashed commit of the following: commita43c98962aAuthor: Oliver Gorwits <oliver@cpan.org> Date: Mon Jun 3 20:37:39 2013 +0100 Missing mibdirs causes all MIBs to be loaded (with a warning) commit09829a25b8Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Jun 3 20:07:31 2013 +0100 local plugins site_plugins dir commitb0e804e558Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Jun 3 19:59:04 2013 +0100 use send_error and redirect from Dancer commit3d1185261aAuthor: Oliver Gorwits <oliver@cpan.org> Date: Mon Jun 3 19:13:40 2013 +0100 support path config option commit31ca119f84Merge:9a798554d2b3a5Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Jun 3 00:06:17 2013 +0100 Merge remote-tracking branch 'origin/og-work' into og-work g-work" This reverts commit9a79855361, reversing changes made to6fd6118354. Conflicts: Netdisco/share/views/plugin/device_port_column/c_observiumsparklines.tt commit9a79855361Merge:6fd6118c8c3b82Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Jun 3 00:03:32 2013 +0100 Merge remote-tracking branch 'origin/master' into og-work commit6fd6118354Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Jun 2 15:47:45 2013 +0100 extra note about behind proxy commit798086ca29Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Jun 2 15:30:26 2013 +0100 complete the observium plugin commit66b3ced179Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Jun 2 12:48:06 2013 +0100 Plugins can have CSS and Javascript loaded within <head> commit4d2b3a5307Author: Oliver Gorwits <oliver@cpan.org> Date: Thu May 30 08:50:16 2013 +0100 get device dns to port template commited1bfa1ae7Author: Oliver Gorwits <oliver@cpan.org> Date: Thu May 30 08:17:02 2013 +0100 observium sparklines plugin; support X:: namespace commit76b7636c74Author: Oliver Gorwits <oliver@cpan.org> Date: Thu May 30 06:30:06 2013 +0100 rename private settings keys commitfdac8f6c33Author: Oliver Gorwits <oliver@cpan.org> Date: Thu May 30 05:59:53 2013 +0100 add macwalk and arpnip buttons to device details commit3d688c7d83Author: Oliver Gorwits <oliver@cpan.org> Date: Thu May 30 05:57:20 2013 +0100 Revert "reduce refresh to 5sec" This reverts commit8ea9ec7dd9. commitdc62382112Author: Oliver Gorwits <oliver@cpan.org> Date: Thu May 30 05:50:34 2013 +0100 support for arpwalk and macwalk and all jobs via web commit8bc7d83c98Author: Oliver Gorwits <oliver@cpan.org> Date: Thu May 30 05:35:41 2013 +0100 simplify discover options to only discoverall and discover commit8ea9ec7dd9Author: Oliver Gorwits <oliver@cpan.org> Date: Wed May 29 20:23:08 2013 +0100 reduce refresh to 5sec commit8c54e6c58bAuthor: Oliver Gorwits <oliver@cpan.org> Date: Wed May 29 20:11:06 2013 +0100 show undiscovered neighbor properly commite0ee25628fAuthor: Oliver Gorwits <oliver@cpan.org> Date: Wed May 29 19:54:09 2013 +0100 avoid unecessary log for queueing commitd5565423f2Author: Oliver Gorwits <oliver@cpan.org> Date: Wed May 29 19:51:37 2013 +0100 avoid warning on undefined remote type commit5d9b58a6b2Author: Oliver Gorwits <oliver@cpan.org> Date: Wed May 29 19:48:22 2013 +0100 avoid explosion when not admin commit377bb942e0Author: Oliver Gorwits <oliver@cpan.org> Date: Wed May 29 19:46:52 2013 +0100 avoid undefined warning commit08806dcfa2Author: Oliver Gorwits <oliver@cpan.org> Date: Wed May 29 19:46:42 2013 +0100 get_db_version will be 0 at first deploy commit9511c17056Author: Oliver Gorwits <oliver@cpan.org> Date: Wed May 29 19:15:55 2013 +0100 fix name of Template module commiteb0288de35Author: Oliver Gorwits <oliver@cpan.org> Date: Tue May 28 07:17:07 2013 +0100 initial config settings documentation commit7f2ea7f8dcAuthor: Oliver Gorwits <oliver@cpan.org> Date: Mon May 27 15:18:15 2013 +0100 remove check_mac to own module, use in macsuck too commitb995cf6398Author: Oliver Gorwits <oliver@cpan.org> Date: Mon May 27 15:01:29 2013 +0100 show probable but undiscovered neighbor is ports display commitdd8d461188Author: Oliver Gorwits <oliver@cpan.org> Date: Mon May 27 14:52:41 2013 +0100 new schema version for is_uplink and is_uplink_admin commit3f6a7b5aa2Author: Oliver Gorwits <oliver@cpan.org> Date: Mon May 27 14:47:59 2013 +0100 make sure device_port is updated when manual_topo is set commit33bf9a6599Author: Oliver Gorwits <oliver@cpan.org> Date: Sun May 26 19:51:49 2013 +0100 export store_arp and store_node commit0ed356d560Author: Oliver Gorwits <oliver@cpan.org> Date: Sat May 25 17:12:31 2013 +0100 use row lock not table lock commitf830bc3a3bAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat May 25 16:38:33 2013 +0100 move macsuck/arpnip/discover to ::Core namespace commitbe40788987Author: Oliver Gorwits <oliver@cpan.org> Date: Fri May 24 21:10:34 2013 +0100 add maybe_uplink to device_port; more macsuck implementation commit88371026d5Author: Oliver Gorwits <oliver@cpan.org> Date: Fri May 24 14:34:58 2013 +0100 start on macsuck; tweak update locking commit6f7c87ac07Author: Oliver Gorwits <oliver@cpan.org> Date: Fri May 24 13:10:58 2013 +0100 ORDER BY ... FOR UPDATE will allow us to avoid table lock commit7c438e01fcAuthor: Oliver Gorwits <oliver@cpan.org> Date: Fri May 24 12:12:46 2013 +0100 yet more efficient arpnip commitc74c56dc02Author: Oliver Gorwits <oliver@cpan.org> Date: Fri May 24 11:34:23 2013 +0100 guard against race with *_or_* DBIC methods commitd50c54972eAuthor: Oliver Gorwits <oliver@cpan.org> Date: Mon May 20 23:42:41 2013 +0100 more efficient arpnip commit73c8979130Author: Oliver Gorwits <oliver@cpan.org> Date: Sun May 19 22:52:15 2013 +0100 fix confusing name commitbf78e82411Author: Oliver Gorwits <oliver@cpan.org> Date: Sun May 19 22:37:22 2013 +0100 fix mistake in DBIx::Class schema commit6a5af95836Author: Oliver Gorwits <oliver@cpan.org> Date: Sun May 19 22:06:27 2013 +0100 arpnip implementation commit594abd3f82Author: Oliver Gorwits <oliver@cpan.org> Date: Thu May 16 00:00:50 2013 +0100 PostgreSQL explicit locking support. Squashed commit of the following: commit76e1539102Author: Oliver Gorwits <oliver@cpan.org> Date: Wed May 15 23:54:25 2013 +0100 finished explicit locking module commit369387258bAuthor: Oliver Gorwits <oliver@cpan.org> Date: Tue May 14 23:50:42 2013 +0100 initial implementation of locking from schema object commit55c6d4fe63Author: Oliver Gorwits <oliver@cpan.org> Date: Tue May 14 21:05:01 2013 +0100 add discover button to device details page commit11fd8bf964Author: Oliver Gorwits <oliver@cpan.org> Date: Tue May 14 20:43:43 2013 +0100 fix typo and clear port box on autocomplete dropdown commita00f9b5c2eAuthor: Oliver Gorwits <oliver@cpan.org> Date: Tue May 14 20:38:54 2013 +0100 move admin tasks and remove JobControl package commit74bc0023dfAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat May 11 18:25:04 2013 +0100 complete job queue delete and kill running timers properly when reloading page commitdd6947f38dAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat May 11 16:51:28 2013 +0100 fix improper use of bootstrap table class commitcd5b83f71eAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat May 11 15:55:45 2013 +0100 fix update view icon in sidebar commite9349f325dAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat May 11 11:57:19 2013 +0100 css audit commit201470275dAuthor: Oliver Gorwits <oliver@cpan.org> Date: Thu May 9 23:48:05 2013 +0100 add job queue to standard plugins list commita18a3c72a3Author: Oliver Gorwits <oliver@cpan.org> Date: Thu May 9 23:37:43 2013 +0100 fix table headings and improve Action display in Job Queue commit70f5da8bb6Author: Oliver Gorwits <oliver@cpan.org> Date: Thu May 9 23:30:32 2013 +0100 implement "no devices" prompt for admin users to do first discover commit2e8ac83173Author: Oliver Gorwits <oliver@cpan.org> Date: Thu May 9 21:53:39 2013 +0100 more js refactoring for report and search commit479ac0e55dAuthor: Oliver Gorwits <oliver@cpan.org> Date: Thu May 9 21:50:29 2013 +0100 refactor js for device tabs commit6a17fe5d6cAuthor: Oliver Gorwits <oliver@cpan.org> Date: Thu May 9 21:05:42 2013 +0100 fix crazy races with javasacript by using global delegations commite94e3cef3bAuthor: Oliver Gorwits <oliver@cpan.org> Date: Wed May 8 23:06:41 2013 +0100 remove Try::Tiny from web runtime commitc746e68b9bAuthor: Oliver Gorwits <oliver@cpan.org> Date: Tue May 7 21:54:11 2013 +0100 make topo autocomplete more responsive commit24c511786fAuthor: Oliver Gorwits <oliver@cpan.org> Date: Tue May 7 21:52:17 2013 +0100 display name and IP for device typeahead commit52ab7d1266Author: Oliver Gorwits <oliver@cpan.org> Date: Tue May 7 21:47:05 2013 +0100 add drop-down control for the topo form fields commit5744b6845fAuthor: Oliver Gorwits <oliver@cpan.org> Date: Tue May 7 21:25:30 2013 +0100 complete the topology editor (add/delete) commitb510fbe8c5Author: Oliver Gorwits <oliver@cpan.org> Date: Tue May 7 00:59:11 2013 +0100 add new admin tasks to default plugins list commit11d55e0129Author: Oliver Gorwits <oliver@cpan.org> Date: Tue May 7 00:56:19 2013 +0100 Manual Device Topology Needed to add the 'autocomplete' jQuery UI component because it can do minLength=0 properly. Used the smoothness UI theme. Added typeahead AJAX calls to support the topology searching. Added new plugin and template for the topology editing page. commitbf7a419d08Author: Oliver Gorwits <oliver@cpan.org> Date: Mon May 6 22:16:24 2013 +0100 add a little colour to lone tab titles commit9690a31f19Author: Oliver Gorwits <oliver@cpan.org> Date: Mon May 6 22:01:13 2013 +0100 complete Manage Pseudo Devices commit024f4d9a83Author: Oliver Gorwits <oliver@cpan.org> Date: Mon May 6 00:49:47 2013 +0100 use bootstrap font colour instead of css commitf75f1e5cbfAuthor: Oliver Gorwits <oliver@cpan.org> Date: Mon May 6 00:45:18 2013 +0100 add frontend update/del forms, and display port count commitf0899e16b3Author: Oliver Gorwits <oliver@cpan.org> Date: Sun May 5 23:53:20 2013 +0100 add frontend pseudo device add form commit3271c01931Author: Oliver Gorwits <oliver@cpan.org> Date: Sun May 5 21:45:17 2013 +0100 complete the code for admin tasks page loading commit38f70624f3Author: Oliver Gorwits <oliver@cpan.org> Date: Sun May 5 17:04:30 2013 +0100 set up file paths consistently in all scripts commitc761ca839bAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sun May 5 17:00:30 2013 +0100 Helper script to import the Netdisco 1.x Topology file to the database commitf468b48049Author: Oliver Gorwits <oliver@cpan.org> Date: Sun May 5 16:20:39 2013 +0100 Handle whitespace ahead of OUI data commit5c8a5754f6Author: Oliver Gorwits <oliver@cpan.org> Date: Sun May 5 16:16:20 2013 +0100 also set neighbor info when discovering device interfaces commitacb988b6afAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sun May 5 15:34:20 2013 +0100 try to avoid duplicate execution of scheduled jobs commitc6bcaf66c5Author: Oliver Gorwits <oliver@cpan.org> Date: Sun May 5 14:16:25 2013 +0100 do not clobber manual topo when discovering neighbors commitd9a6a1882aAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sun May 5 13:02:45 2013 +0100 User icon color indicates port_control/admin ability commit2cdcb9db7eAuthor: Oliver Gorwits <oliver@cpan.org> Date: Mon Apr 29 23:34:27 2013 +0100 add support for admin tasks as plugins commit075a770c9aAuthor: Oliver Gorwits <oliver@cpan.org> Date: Mon Apr 29 22:23:20 2013 +0100 skip pseudo devices (vendor netdisco) commit045c022d42Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Apr 29 21:58:33 2013 +0100 incorporate manual topo info from the topology db table commit09285d42b4Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Apr 27 18:39:12 2013 +0100 add unique constraints to topology table commit2780b72e49Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Apr 27 15:38:05 2013 +0100 muted help text in sidebar commit733d4f83fbAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Apr 27 14:39:54 2013 +0100 sorry, testing hook changes commit71e366e352Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Apr 27 14:34:36 2013 +0100 sorry, testing hook changes commit7f9eaa99f5Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Apr 27 14:33:44 2013 +0100 sorry, testing hook changes commit5215fd632dAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Apr 27 14:30:07 2013 +0100 sorry, testing hook changes commitbe817d60c2Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Apr 27 14:21:45 2013 +0100 sorry, testing hook changes commit1fd3695358Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Apr 27 14:18:57 2013 +0100 sorry, testing hook changes commitac448c4a91Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Apr 27 14:13:03 2013 +0100 sorry, testing hook changes commitc563b8d9afAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Apr 27 14:08:54 2013 +0100 sorry, testing hook changes commit3abcfb01d5Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Apr 27 14:06:25 2013 +0100 sorry, testing hook changes commit877a81facfAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Apr 27 14:05:25 2013 +0100 sorry, testing hook changes
@@ -2,10 +2,19 @@
|
|||||||
|
|
||||||
[NEW FEATURES]
|
[NEW FEATURES]
|
||||||
|
|
||||||
* Finally we have a discover/refresh daemon job :)
|
* Finally we have a discover/refresh/arpnip/macsuck daemon jobs :)
|
||||||
* Also... a Scheduler which removes need for crontab installation
|
* Also... a Scheduler which removes need for crontab installation
|
||||||
* The netdisco-do script can run a one-off discover for a device
|
* The netdisco-do script can queue any one-off job
|
||||||
* Can select MAC Address display format on Node and Device Port search
|
* Select MAC Address display format on Node and Device Port search
|
||||||
|
* Helper script to import the Netdisco 1.x Topology file to the database
|
||||||
|
* Support for pseudo devices (useful for dummy device links)
|
||||||
|
* Manual Topology editing via the web
|
||||||
|
* Job Queue view and delete page
|
||||||
|
* Empty device table prompts initial discover on homepage
|
||||||
|
* Support for App::NetdiscoX::Web::Plugin namespace
|
||||||
|
* Plugins can add columns to Device Ports display
|
||||||
|
* Observium Sparklines port column plugin
|
||||||
|
* Plugins can have CSS and Javascript loaded within <head>
|
||||||
|
|
||||||
[ENHANCEMENTS]
|
[ENHANCEMENTS]
|
||||||
|
|
||||||
@@ -14,12 +23,20 @@
|
|||||||
* Port filter in device port display is now highlighted green
|
* Port filter in device port display is now highlighted green
|
||||||
* Navbar search is fuzzier
|
* Navbar search is fuzzier
|
||||||
* Phone node icon is a little phone handset
|
* Phone node icon is a little phone handset
|
||||||
|
* User icon color indicates port_control/admin ability
|
||||||
|
* Buttons for discover/macsuck/arpnip on device details page
|
||||||
|
* Support 'path' config option as alternative to --path /mountpoint
|
||||||
|
* Local plugins can be placed in ${NETDISCO_HOME}/site_plugins/...
|
||||||
|
* Missing mibdirs causes all MIBs to be loaded (with a warning)
|
||||||
|
|
||||||
[BUG FIXES]
|
[BUG FIXES]
|
||||||
|
|
||||||
* Rename plugins developer doc to .pod
|
* Rename plugins developer doc to .pod
|
||||||
* Update to latest Bootstrap and JQuery, and temp. fix #7326 in Bootstrap
|
* Update to latest Bootstrap and JQuery, and temp. fix #7326 in Bootstrap
|
||||||
* Partial Name in Port search now working
|
* Partial Name in Port search now working
|
||||||
|
* Add unique constraints to topology table
|
||||||
|
* Handle whitespace ahead of OUI data
|
||||||
|
* Wasn't using Bootstrap table class properly
|
||||||
|
|
||||||
2.007000_001 - 2013-03-17
|
2.007000_001 - 2013-03-17
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ requires 'HTML::Parser' => 3.70;
|
|||||||
requires 'HTTP::Tiny' => 0.029;
|
requires 'HTTP::Tiny' => 0.029;
|
||||||
requires 'JSON' => 0;
|
requires 'JSON' => 0;
|
||||||
requires 'List::MoreUtils' => 0.33;
|
requires 'List::MoreUtils' => 0.33;
|
||||||
|
requires 'MIME::Base64' => 3.13;
|
||||||
requires 'Moo' => 1.001000;
|
requires 'Moo' => 1.001000;
|
||||||
requires 'MCE' => 1.408;
|
requires 'MCE' => 1.408;
|
||||||
requires 'Net::DNS' => 0.72;
|
requires 'Net::DNS' => 0.72;
|
||||||
@@ -32,7 +33,7 @@ requires 'Socket6' => 0.23;
|
|||||||
requires 'Starman' => 0.3008;
|
requires 'Starman' => 0.3008;
|
||||||
requires 'SNMP::Info' => 3.01;
|
requires 'SNMP::Info' => 3.01;
|
||||||
requires 'SQL::Translator' => 0.11016;
|
requires 'SQL::Translator' => 0.11016;
|
||||||
requires 'Template::Toolkit' => 2.24;
|
requires 'Template' => 2.24;
|
||||||
requires 'YAML' => 0.84;
|
requires 'YAML' => 0.84;
|
||||||
requires 'namespace::clean' => 0.24;
|
requires 'namespace::clean' => 0.24;
|
||||||
requires 'version' => 0.9902;
|
requires 'version' => 0.9902;
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
#!/usr/bin/env perl
|
#!/usr/bin/env perl
|
||||||
|
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
FindBin::again();
|
||||||
use App::Netdisco;
|
use Path::Class 'dir';
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
# stuff useful locations into @INC
|
||||||
|
unshift @INC,
|
||||||
|
dir($FindBin::RealBin)->parent->subdir('lib')->stringify,
|
||||||
|
dir($FindBin::RealBin, 'lib')->stringify;
|
||||||
|
}
|
||||||
|
|
||||||
|
use App::Netdisco;
|
||||||
use Dancer ':script';
|
use Dancer ':script';
|
||||||
use Dancer::Plugin::DBIC 'schema';
|
use Dancer::Plugin::DBIC 'schema';
|
||||||
|
|
||||||
|
|||||||
99
Netdisco/bin/nd-import-topology
Executable file
@@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env perl
|
||||||
|
|
||||||
|
use FindBin;
|
||||||
|
FindBin::again();
|
||||||
|
use Path::Class 'dir';
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
# stuff useful locations into @INC
|
||||||
|
unshift @INC,
|
||||||
|
dir($FindBin::RealBin)->parent->subdir('lib')->stringify,
|
||||||
|
dir($FindBin::RealBin, 'lib')->stringify;
|
||||||
|
}
|
||||||
|
|
||||||
|
use App::Netdisco;
|
||||||
|
use Dancer ':script';
|
||||||
|
use Dancer::Plugin::DBIC 'schema';
|
||||||
|
|
||||||
|
use App::Netdisco::Util::Device 'get_device';
|
||||||
|
use NetAddr::IP::Lite ':lower';
|
||||||
|
use Try::Tiny;
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
nd-import-topology - Import a Nedisco 1.x Manual Topology File
|
||||||
|
|
||||||
|
=head2 USAGE
|
||||||
|
|
||||||
|
./nd-import-topology /path/to/netdisco-topology.txt
|
||||||
|
|
||||||
|
=head2 DESCRIPTION
|
||||||
|
|
||||||
|
This helper script will read and import the content of a Netdisco 1.x format
|
||||||
|
Manual Topology file into the Netdisco 2.x database's C<topology> table.
|
||||||
|
|
||||||
|
It's safe to run the script multiple times on the same file - any new data
|
||||||
|
will be imported.
|
||||||
|
|
||||||
|
The file syntax must be like so:
|
||||||
|
|
||||||
|
left-device
|
||||||
|
link:left-port,right-device,right-port
|
||||||
|
|
||||||
|
The devices can be either host names or IPs. Data will be imported even if the
|
||||||
|
devices are currently unknown to Netdisco.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
my $file = $ARGV[0];
|
||||||
|
die "missing topology file name on command line\n" unless $file;
|
||||||
|
|
||||||
|
chomp $file;
|
||||||
|
my $dev = undef; # current device
|
||||||
|
print "Loading topology information from $file\n";
|
||||||
|
|
||||||
|
open (DEVS,'<', $file)
|
||||||
|
or die "topo_load_file($file): $!\n";
|
||||||
|
|
||||||
|
while (my $line = <DEVS>) {
|
||||||
|
chomp $line;
|
||||||
|
$line =~ s/(?<!\\)#.*//;
|
||||||
|
$line =~ s/\\#/#/g;
|
||||||
|
$line =~ s/^\s+//g;
|
||||||
|
$line =~ s/\s+$//g;
|
||||||
|
next if $line =~ m/^\s*$/;
|
||||||
|
|
||||||
|
if ($line =~ m/^link:(.*)/){
|
||||||
|
my ($from_port, $to, $to_port) = split(m/,/, $1);
|
||||||
|
|
||||||
|
unless (defined $dev) {
|
||||||
|
print " Skipping $line. No device yet defined!\n";
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
# save Link info
|
||||||
|
try {
|
||||||
|
schema('netdisco')->txn_do(sub {
|
||||||
|
schema('netdisco')->resultset('Topology')->create({
|
||||||
|
dev1 => $dev,
|
||||||
|
port1 => $from_port,
|
||||||
|
dev2 => get_device($to)->ip,
|
||||||
|
port2 => $to_port,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
elsif ($line =~ /^alias:(.*)/) {
|
||||||
|
# ignore aliases
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
my $ip = NetAddr::IP::Lite->new($line)
|
||||||
|
or next;
|
||||||
|
next if $ip->addr eq '0.0.0.0';
|
||||||
|
|
||||||
|
$dev = get_device($ip->addr)->ip;
|
||||||
|
print " Set device: $dev\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close (DEVS);
|
||||||
@@ -1,9 +1,17 @@
|
|||||||
#!/usr/bin/env perl
|
#!/usr/bin/env perl
|
||||||
|
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
FindBin::again();
|
||||||
use App::Netdisco;
|
use Path::Class 'dir';
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
# stuff useful locations into @INC
|
||||||
|
unshift @INC,
|
||||||
|
dir($FindBin::RealBin)->parent->subdir('lib')->stringify,
|
||||||
|
dir($FindBin::RealBin, 'lib')->stringify;
|
||||||
|
}
|
||||||
|
|
||||||
|
use App::Netdisco;
|
||||||
use Dancer ':script';
|
use Dancer ':script';
|
||||||
use Dancer::Plugin::DBIC 'schema';
|
use Dancer::Plugin::DBIC 'schema';
|
||||||
|
|
||||||
@@ -69,7 +77,8 @@ try {
|
|||||||
};
|
};
|
||||||
|
|
||||||
# upgrade from whatever dbix_class_schema_versions says, to $VERSION
|
# upgrade from whatever dbix_class_schema_versions says, to $VERSION
|
||||||
my $db_version = $schema->get_db_version;
|
# except that get_db_version will be 0 at first deploy
|
||||||
|
my $db_version = ($schema->get_db_version || 1);
|
||||||
my $target_version = $schema->schema_version;
|
my $target_version = $schema->schema_version;
|
||||||
|
|
||||||
# one step at a time, in case user has applied local changes already
|
# one step at a time, in case user has applied local changes already
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ sub deploy_oui {
|
|||||||
|
|
||||||
if ($resp->{success}) {
|
if ($resp->{success}) {
|
||||||
foreach my $line (split /\n/, $resp->{content}) {
|
foreach my $line (split /\n/, $resp->{content}) {
|
||||||
if ($line =~ m/^(.{2}-.{2}-.{2})\s+\(hex\)\s+(.*)\s*$/i) {
|
if ($line =~ m/^\s*(.{2}-.{2}-.{2})\s+\(hex\)\s+(.*)\s*$/i) {
|
||||||
my ($oui, $company) = ($1, $2);
|
my ($oui, $company) = ($1, $2);
|
||||||
$oui =~ s/-/:/g;
|
$oui =~ s/-/:/g;
|
||||||
$data{lc($oui)} = $company;
|
$data{lc($oui)} = $company;
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ if (!length $action) {
|
|||||||
package MyWorker;
|
package MyWorker;
|
||||||
use Moo;
|
use Moo;
|
||||||
with 'App::Netdisco::Daemon::Worker::Poller::Device';
|
with 'App::Netdisco::Daemon::Worker::Poller::Device';
|
||||||
|
with 'App::Netdisco::Daemon::Worker::Poller::Arpnip';
|
||||||
|
with 'App::Netdisco::Daemon::Worker::Poller::Macsuck';
|
||||||
}
|
}
|
||||||
my $worker = MyWorker->new();
|
my $worker = MyWorker->new();
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,16 @@ set plack_middlewares => [
|
|||||||
];
|
];
|
||||||
|
|
||||||
use App::Netdisco::Web;
|
use App::Netdisco::Web;
|
||||||
dance;
|
use Plack::Builder;
|
||||||
|
|
||||||
|
my $app = sub {
|
||||||
|
my $env = shift;
|
||||||
|
my $request = Dancer::Request->new(env => $env);
|
||||||
|
Dancer->dance($request);
|
||||||
|
};
|
||||||
|
|
||||||
|
my $path = (setting('path') || '/');
|
||||||
|
builder { mount $path => $app };
|
||||||
|
|
||||||
=head1 NAME
|
=head1 NAME
|
||||||
|
|
||||||
|
|||||||
176
Netdisco/lib/App/Netdisco/Core/Arpnip.pm
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
package App::Netdisco::Core::Arpnip;
|
||||||
|
|
||||||
|
use Dancer qw/:syntax :script/;
|
||||||
|
use Dancer::Plugin::DBIC 'schema';
|
||||||
|
|
||||||
|
use App::Netdisco::Util::PortMAC 'get_port_macs';
|
||||||
|
use App::Netdisco::Util::SanityCheck 'check_mac';
|
||||||
|
use App::Netdisco::Util::DNS ':all';
|
||||||
|
use NetAddr::IP::Lite ':lower';
|
||||||
|
use Time::HiRes 'gettimeofday';
|
||||||
|
|
||||||
|
use base 'Exporter';
|
||||||
|
our @EXPORT = ();
|
||||||
|
our @EXPORT_OK = qw/ do_arpnip store_arp /;
|
||||||
|
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
App::Netdisco::Core::Arpnip
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
Helper subroutines to support parts of the Netdisco application.
|
||||||
|
|
||||||
|
There are no default exports, however the C<:all> tag will export all
|
||||||
|
subroutines.
|
||||||
|
|
||||||
|
=head1 EXPORT_OK
|
||||||
|
|
||||||
|
=head2 do_arpnip( $device, $snmp )
|
||||||
|
|
||||||
|
Given a Device database object, and a working SNMP connection, connect to a
|
||||||
|
device and discover its ARP cache for IPv4 and Neighbor cache for IPv6.
|
||||||
|
|
||||||
|
Will also discover subnets in use on the device and update the Subnets table.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub do_arpnip {
|
||||||
|
my ($device, $snmp) = @_;
|
||||||
|
|
||||||
|
unless ($device->in_storage) {
|
||||||
|
debug sprintf ' [%s] arpnip - skipping device not yet discovered', $device->ip;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $port_macs = get_port_macs($device);
|
||||||
|
|
||||||
|
# get v4 arp table
|
||||||
|
my @v4 = _get_arps($device, $port_macs, $snmp->at_paddr, $snmp->at_netaddr);
|
||||||
|
# get v6 neighbor cache
|
||||||
|
my @v6 = _get_arps($device, $port_macs, $snmp->ipv6_n2p_mac, $snmp->ipv6_n2p_addr);
|
||||||
|
|
||||||
|
# get directly connected networks
|
||||||
|
my @subnets = _gather_subnets($device, $snmp);
|
||||||
|
# TODO: IPv6 subnets
|
||||||
|
|
||||||
|
# would be possible just to use now() on updated records, but by using this
|
||||||
|
# same value for them all, we _can_ if we want add a job at the end to
|
||||||
|
# select and do something with the updated set (no reason to yet, though)
|
||||||
|
my $now = 'to_timestamp('. (join '.', gettimeofday) .')';
|
||||||
|
|
||||||
|
# update node_ip with ARP and Neighbor Cache entries
|
||||||
|
store_arp(@$_, $now) for @v4;
|
||||||
|
debug sprintf ' [%s] arpnip - processed %s ARP Cache entries',
|
||||||
|
$device->ip, scalar @v4;
|
||||||
|
|
||||||
|
store_arp(@$_, $now) for @v6;
|
||||||
|
debug sprintf ' [%s] arpnip - processed %s IPv6 Neighbor Cache entries',
|
||||||
|
$device->ip, scalar @v6;
|
||||||
|
|
||||||
|
_store_subnet($_, $now) for @subnets;
|
||||||
|
debug sprintf ' [%s] arpnip - processed %s Subnet entries',
|
||||||
|
$device->ip, scalar @subnets;
|
||||||
|
}
|
||||||
|
|
||||||
|
# get an arp table (v4 or v6)
|
||||||
|
sub _get_arps {
|
||||||
|
my ($device, $port_macs, $paddr, $netaddr) = @_;
|
||||||
|
my @arps = ();
|
||||||
|
|
||||||
|
while (my ($arp, $node) = each %$paddr) {
|
||||||
|
my $ip = $netaddr->{$arp};
|
||||||
|
next unless defined $ip;
|
||||||
|
next unless check_mac($device, $node, $port_macs);
|
||||||
|
push @arps, [$node, $ip, hostname_from_ip($ip)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return @arps;
|
||||||
|
}
|
||||||
|
|
||||||
|
=head2 store_arp( $mac, $ip, $name, $now? )
|
||||||
|
|
||||||
|
Stores a new entry to the C<node_ip> table with the given MAC, IP (v4 or v6)
|
||||||
|
and DNS host name.
|
||||||
|
|
||||||
|
Will mark old entries for this IP as no longer C<active>.
|
||||||
|
|
||||||
|
Optionally a literal string can be passed in the fourth argument for the
|
||||||
|
C<time_last> timestamp, otherwise the current timestamp (C<now()>) is used.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub store_arp {
|
||||||
|
my ($mac, $ip, $name, $now) = @_;
|
||||||
|
$now ||= 'now()';
|
||||||
|
|
||||||
|
schema('netdisco')->txn_do(sub {
|
||||||
|
my $current = schema('netdisco')->resultset('NodeIp')
|
||||||
|
->search({ip => $ip, -bool => 'active'})
|
||||||
|
->search(undef, {
|
||||||
|
columns => [qw/mac ip/],
|
||||||
|
order_by => [qw/mac ip/],
|
||||||
|
for => 'update'
|
||||||
|
});
|
||||||
|
$current->first; # lock rows
|
||||||
|
$current->update({active => \'false'});
|
||||||
|
|
||||||
|
schema('netdisco')->resultset('NodeIp')
|
||||||
|
->search({'me.mac' => $mac, 'me.ip' => $ip})
|
||||||
|
->update_or_create(
|
||||||
|
{
|
||||||
|
dns => $name,
|
||||||
|
active => \'true',
|
||||||
|
time_last => \$now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
order_by => [qw/mac ip/],
|
||||||
|
for => 'update',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
# gathers device subnets
|
||||||
|
sub _gather_subnets {
|
||||||
|
my ($device, $snmp) = @_;
|
||||||
|
my @subnets = ();
|
||||||
|
|
||||||
|
my $ip_netmask = $snmp->ip_netmask;
|
||||||
|
my $localnet = NetAddr::IP::Lite->new('127.0.0.0/8');
|
||||||
|
|
||||||
|
foreach my $entry (keys %$ip_netmask) {
|
||||||
|
my $ip = NetAddr::IP::Lite->new($entry);
|
||||||
|
my $addr = $ip->addr;
|
||||||
|
|
||||||
|
next if $addr eq '0.0.0.0';
|
||||||
|
next if $ip->within($localnet);
|
||||||
|
next if setting('ignore_private_nets') and $ip->is_rfc1918;
|
||||||
|
|
||||||
|
my $netmask = $ip_netmask->{$addr};
|
||||||
|
next if $netmask eq '255.255.255.255' or $netmask eq '0.0.0.0';
|
||||||
|
|
||||||
|
my $cidr = NetAddr::IP::Lite->new($addr, $netmask)->network->cidr;
|
||||||
|
|
||||||
|
debug sprintf ' [%s] arpnip - found subnet %s', $device->ip, $cidr;
|
||||||
|
push @subnets, $cidr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return @subnets;
|
||||||
|
}
|
||||||
|
|
||||||
|
# update subnets with new networks
|
||||||
|
sub _store_subnet {
|
||||||
|
my ($subnet, $now) = @_;
|
||||||
|
|
||||||
|
schema('netdisco')->txn_do(sub {
|
||||||
|
schema('netdisco')->resultset('Subnet')->update_or_create(
|
||||||
|
{
|
||||||
|
net => $subnet,
|
||||||
|
last_discover => \$now,
|
||||||
|
},
|
||||||
|
{ for => 'update' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package App::Netdisco::Util::DiscoverAndStore;
|
package App::Netdisco::Core::Discover;
|
||||||
|
|
||||||
use Dancer qw/:syntax :script/;
|
use Dancer qw/:syntax :script/;
|
||||||
use Dancer::Plugin::DBIC 'schema';
|
use Dancer::Plugin::DBIC 'schema';
|
||||||
@@ -13,13 +13,13 @@ our @EXPORT = ();
|
|||||||
our @EXPORT_OK = qw/
|
our @EXPORT_OK = qw/
|
||||||
store_device store_interfaces store_wireless
|
store_device store_interfaces store_wireless
|
||||||
store_vlans store_power store_modules
|
store_vlans store_power store_modules
|
||||||
find_neighbors
|
store_neighbors discover_new_neighbors
|
||||||
/;
|
/;
|
||||||
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||||
|
|
||||||
=head1 NAME
|
=head1 NAME
|
||||||
|
|
||||||
App::Netdisco::Util::DiscoverAndStore
|
App::Netdisco::Core::Discover
|
||||||
|
|
||||||
=head1 DESCRIPTION
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
@@ -52,6 +52,7 @@ sub store_device {
|
|||||||
|
|
||||||
my $hostname = hostname_from_ip($device->ip);
|
my $hostname = hostname_from_ip($device->ip);
|
||||||
$device->dns($hostname) if length $hostname;
|
$device->dns($hostname) if length $hostname;
|
||||||
|
my $localnet = NetAddr::IP::Lite->new('127.0.0.0/8');
|
||||||
|
|
||||||
# build device aliases suitable for DBIC
|
# build device aliases suitable for DBIC
|
||||||
my @aliases;
|
my @aliases;
|
||||||
@@ -60,7 +61,7 @@ sub store_device {
|
|||||||
my $addr = $ip->addr;
|
my $addr = $ip->addr;
|
||||||
|
|
||||||
next if $addr eq '0.0.0.0';
|
next if $addr eq '0.0.0.0';
|
||||||
next if $ip->within(NetAddr::IP::Lite->new('127.0.0.0/8'));
|
next if $ip->within($localnet);
|
||||||
next if setting('ignore_private_nets') and $ip->is_rfc1918;
|
next if setting('ignore_private_nets') and $ip->is_rfc1918;
|
||||||
|
|
||||||
my $iid = $ip_index->{$addr};
|
my $iid = $ip_index->{$addr};
|
||||||
@@ -105,7 +106,7 @@ sub store_device {
|
|||||||
my $gone = $device->device_ips->delete;
|
my $gone = $device->device_ips->delete;
|
||||||
debug sprintf ' [%s] device - removed %s aliases',
|
debug sprintf ' [%s] device - removed %s aliases',
|
||||||
$device->ip, $gone;
|
$device->ip, $gone;
|
||||||
$device->update_or_insert;
|
$device->update_or_insert(undef, {for => 'update'});
|
||||||
$device->device_ips->populate(\@aliases);
|
$device->device_ips->populate(\@aliases);
|
||||||
debug sprintf ' [%s] device - added %d new aliases',
|
debug sprintf ' [%s] device - added %d new aliases',
|
||||||
$device->ip, scalar @aliases;
|
$device->ip, scalar @aliases;
|
||||||
@@ -118,7 +119,7 @@ sub _set_canonical_ip {
|
|||||||
my $oldip = $device->ip;
|
my $oldip = $device->ip;
|
||||||
my $newip = $snmp->root_ip;
|
my $newip = $snmp->root_ip;
|
||||||
|
|
||||||
if (length $newip) {
|
if (defined $newip) {
|
||||||
if ($oldip ne $newip) {
|
if ($oldip ne $newip) {
|
||||||
debug sprintf ' [%s] device - changing root IP to alt IP %s',
|
debug sprintf ' [%s] device - changing root IP to alt IP %s',
|
||||||
$oldip, $newip;
|
$oldip, $newip;
|
||||||
@@ -252,7 +253,7 @@ sub store_interfaces {
|
|||||||
my $gone = $device->ports->delete;
|
my $gone = $device->ports->delete;
|
||||||
debug sprintf ' [%s] interfaces - removed %s interfaces',
|
debug sprintf ' [%s] interfaces - removed %s interfaces',
|
||||||
$device->ip, $gone;
|
$device->ip, $gone;
|
||||||
$device->update_or_insert;
|
$device->update_or_insert(undef, {for => 'update'});
|
||||||
$device->ports->populate(\@interfaces);
|
$device->ports->populate(\@interfaces);
|
||||||
debug sprintf ' [%s] interfaces - added %d new interfaces',
|
debug sprintf ' [%s] interfaces - added %d new interfaces',
|
||||||
$device->ip, scalar @interfaces;
|
$device->ip, scalar @interfaces;
|
||||||
@@ -562,26 +563,34 @@ sub store_modules {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
=head2 find_neighbors( $device, $snmp )
|
=head2 store_neighbors( $device, $snmp )
|
||||||
|
|
||||||
|
returns: C<@to_discover>
|
||||||
|
|
||||||
Given a Device database object, and a working SNMP connection, discover and
|
Given a Device database object, and a working SNMP connection, discover and
|
||||||
store the device's port neighbors information.
|
store the device's port neighbors information.
|
||||||
|
|
||||||
If any neighbor is unknown to Netdisco, a discover job for it will immediately
|
Entries in the Topology database table will override any discovered device
|
||||||
be queued (modulo configuration file C<discover_no_type> setting).
|
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.
|
||||||
|
|
||||||
=cut
|
=cut
|
||||||
|
|
||||||
sub find_neighbors {
|
sub store_neighbors {
|
||||||
my ($device, $snmp) = @_;
|
my ($device, $snmp) = @_;
|
||||||
|
my @to_discover = ();
|
||||||
|
|
||||||
|
# first allow any manually configred topology to be set
|
||||||
|
_set_manual_topology($device, $snmp);
|
||||||
|
|
||||||
my $c_ip = $snmp->c_ip;
|
my $c_ip = $snmp->c_ip;
|
||||||
unless ($snmp->hasCDP or scalar keys %$c_ip) {
|
unless ($snmp->hasCDP or scalar keys %$c_ip) {
|
||||||
debug sprintf ' [%s] neigh - CDP/LLDP not enabled!', $device->ip;
|
debug sprintf ' [%s] neigh - CDP/LLDP not enabled!', $device->ip;
|
||||||
return;
|
return @to_discover;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $interfaces = $snmp->interfaces;
|
my $interfaces = $snmp->interfaces;
|
||||||
@@ -642,17 +651,28 @@ sub find_neighbors {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# IP Phone detection type fixup
|
||||||
|
if (defined $remote_type and $remote_type =~ m/(mitel.5\d{3})/i) {
|
||||||
|
$remote_type = 'IP Phone - '. $remote_type
|
||||||
|
if $remote_type !~ /ip phone/i;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$remote_type = '';
|
||||||
|
}
|
||||||
|
|
||||||
# hack for devices seeing multiple neighbors on the port
|
# hack for devices seeing multiple neighbors on the port
|
||||||
if (ref [] eq ref $remote_ip) {
|
if (ref [] eq ref $remote_ip) {
|
||||||
debug sprintf
|
debug sprintf
|
||||||
' [%s] neigh - port %s has multiple neighbors, setting remote as self',
|
' [%s] neigh - port %s has multiple neighbors, setting remote as self',
|
||||||
$device->ip, $port;
|
$device->ip, $port;
|
||||||
|
|
||||||
foreach my $n (@$remote_ip) {
|
if (wantarray) {
|
||||||
debug sprintf
|
foreach my $n (@$remote_ip) {
|
||||||
' [%s] neigh - adding neighbor %s, type [%s], on %s to discovery queue',
|
debug sprintf
|
||||||
$device->ip, $n, $remote_type, $port;
|
' [%s] neigh - adding neighbor %s, type [%s], on %s to discovery queue',
|
||||||
_enqueue_discover($n, $remote_type);
|
$device->ip, $n, $remote_type, $port;
|
||||||
|
push @to_discover, [$n, $remote_type];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# set self as remote IP to suppress any further work
|
# set self as remote IP to suppress any further work
|
||||||
@@ -660,6 +680,14 @@ sub find_neighbors {
|
|||||||
$remote_port = $port;
|
$remote_port = $port;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
# what we came here to do.... discover the neighbor
|
||||||
|
if (wantarray) {
|
||||||
|
debug sprintf
|
||||||
|
' [%s] neigh - adding neighbor %s, type [%s], on %s to discovery queue',
|
||||||
|
$device->ip, $remote_ip, $remote_type, $port;
|
||||||
|
push @to_discover, [$remote_ip, $remote_type];
|
||||||
|
}
|
||||||
|
|
||||||
$remote_port = $c_port->{$entry};
|
$remote_port = $c_port->{$entry};
|
||||||
|
|
||||||
if (defined $remote_port) {
|
if (defined $remote_port) {
|
||||||
@@ -672,12 +700,7 @@ sub find_neighbors {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# XXX too custom? IP Phone detection
|
# if all the data looks sane, update the port row with neighbor info
|
||||||
if (defined $remote_type and $remote_type =~ m/(mitel.5\d{3})/i) {
|
|
||||||
$remote_type = 'IP Phone - '. $remote_type
|
|
||||||
if $remote_type !~ /ip phone/i;
|
|
||||||
}
|
|
||||||
|
|
||||||
my $portrow = schema('netdisco')->resultset('DevicePort')
|
my $portrow = schema('netdisco')->resultset('DevicePort')
|
||||||
->single({ip => $device->ip, port => $port});
|
->single({ip => $device->ip, port => $port});
|
||||||
|
|
||||||
@@ -687,44 +710,120 @@ sub find_neighbors {
|
|||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($portrow->manual_topo) {
|
||||||
|
info sprintf ' [%s] neigh - %s has manually defined topology',
|
||||||
|
$device->ip, $port;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
$portrow->update({
|
$portrow->update({
|
||||||
remote_ip => $remote_ip,
|
remote_ip => $remote_ip,
|
||||||
remote_port => $remote_port,
|
remote_port => $remote_port,
|
||||||
remote_type => $remote_type,
|
remote_type => $remote_type,
|
||||||
remote_id => $remote_id,
|
remote_id => $remote_id,
|
||||||
|
is_uplink => \"true",
|
||||||
|
manual_topo => \"false",
|
||||||
});
|
});
|
||||||
|
|
||||||
debug sprintf
|
|
||||||
' [%s] neigh - adding neighbor %s, type [%s], on %s to discovery queue',
|
|
||||||
$device->ip, $remote_ip, $remote_type, $port;
|
|
||||||
_enqueue_discover($remote_ip, $remote_type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return @to_discover;
|
||||||
}
|
}
|
||||||
|
|
||||||
# only enqueue if device is not already discovered, and
|
# take data from the topology table and update remote_ip and remote_port
|
||||||
# discover_no_type config permits the discovery
|
# in the devices table. only use root_ips and skip any bad topo entries.
|
||||||
sub _enqueue_discover {
|
sub _set_manual_topology {
|
||||||
my ($ip, $remote_type) = @_;
|
my ($device, $snmp) = @_;
|
||||||
|
|
||||||
my $device = get_device($ip);
|
schema('netdisco')->txn_do(sub {
|
||||||
return if $device->in_storage;
|
# clear manual topology flags
|
||||||
|
schema('netdisco')->resultset('DevicePort')->update({manual_topo => \'false'});
|
||||||
|
|
||||||
my $remote_type_match = setting('discover_no_type');
|
my $topo_links = schema('netdisco')->resultset('Topology');
|
||||||
if ($remote_type and $remote_type_match
|
debug sprintf ' [%s] neigh - setting manual topology links', $device->ip;
|
||||||
and $remote_type =~ m/$remote_type_match/) {
|
|
||||||
debug sprintf ' queue - %s, type [%s] excluded by discover_no_type',
|
while (my $link = $topo_links->next) {
|
||||||
$ip, $remote_type;
|
# could fail for broken topo, but we ignore to try the rest
|
||||||
return;
|
try {
|
||||||
}
|
schema('netdisco')->txn_do(sub {
|
||||||
|
# only work on root_ips
|
||||||
|
my $left = get_device($link->dev1);
|
||||||
|
my $right = get_device($link->dev2);
|
||||||
|
|
||||||
|
# skip bad entries
|
||||||
|
return unless ($left->in_storage and $right->in_storage);
|
||||||
|
|
||||||
|
$left->ports
|
||||||
|
->single({port => $link->port1}, {for => 'update'})
|
||||||
|
->update({
|
||||||
|
remote_ip => $right->ip,
|
||||||
|
remote_port => $link->port2,
|
||||||
|
remote_type => undef,
|
||||||
|
remote_id => undef,
|
||||||
|
is_uplink => \"true",
|
||||||
|
manual_topo => \"true",
|
||||||
|
});
|
||||||
|
|
||||||
|
$right->ports
|
||||||
|
->single({port => $link->port2}, {for => 'update'})
|
||||||
|
->update({
|
||||||
|
remote_ip => $left->ip,
|
||||||
|
remote_port => $link->port1,
|
||||||
|
remote_type => undef,
|
||||||
|
remote_id => undef,
|
||||||
|
is_uplink => \"true",
|
||||||
|
manual_topo => \"true",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
=head2 discover_new_neighbors( $device, $snmp )
|
||||||
|
|
||||||
|
Given a Device database object, and a working SNMP connection, discover and
|
||||||
|
store the device's port neighbors information.
|
||||||
|
|
||||||
|
Entries in the Topology database table will override any discovered device
|
||||||
|
port relationships.
|
||||||
|
|
||||||
|
The Device database object can be a fresh L<DBIx::Class::Row> object which is
|
||||||
|
not yet stored to the database.
|
||||||
|
|
||||||
|
Any discovered neighbor unknown to Netdisco will have a C<discover> job
|
||||||
|
immediately queued (subject to the filtering by the C<discover_no_type>
|
||||||
|
setting).
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub discover_new_neighbors {
|
||||||
|
my @to_discover = store_neighbors(@_);
|
||||||
|
|
||||||
|
# only enqueue if device is not already discovered, and
|
||||||
|
# discover_no_type config permits the discovery
|
||||||
|
foreach my $neighbor (@to_discover) {
|
||||||
|
my ($ip, $remote_type) = @$neighbor;
|
||||||
|
|
||||||
|
my $device = get_device($ip);
|
||||||
|
next if $device->in_storage;
|
||||||
|
|
||||||
|
my $remote_type_match = setting('discover_no_type');
|
||||||
|
if ($remote_type and $remote_type_match
|
||||||
|
and $remote_type =~ m/$remote_type_match/) {
|
||||||
|
debug sprintf ' queue - %s, type [%s] excluded by discover_no_type',
|
||||||
|
$ip, $remote_type;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
# could fail if queued job already exists
|
# could fail if queued job already exists
|
||||||
schema('netdisco')->resultset('Admin')->create({
|
try {
|
||||||
device => $ip,
|
schema('netdisco')->resultset('Admin')->create({
|
||||||
action => 'discover',
|
device => $ip,
|
||||||
status => 'queued',
|
action => 'discover',
|
||||||
});
|
status => 'queued',
|
||||||
};
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
457
Netdisco/lib/App/Netdisco/Core/Macsuck.pm
Normal file
@@ -0,0 +1,457 @@
|
|||||||
|
package App::Netdisco::Core::Macsuck;
|
||||||
|
|
||||||
|
use Dancer qw/:syntax :script/;
|
||||||
|
use Dancer::Plugin::DBIC 'schema';
|
||||||
|
|
||||||
|
use App::Netdisco::Util::PortMAC 'get_port_macs';
|
||||||
|
use App::Netdisco::Util::SanityCheck 'check_mac';
|
||||||
|
use App::Netdisco::Util::SNMP 'snmp_comm_reindex';
|
||||||
|
use Time::HiRes 'gettimeofday';
|
||||||
|
|
||||||
|
use base 'Exporter';
|
||||||
|
our @EXPORT = ();
|
||||||
|
our @EXPORT_OK = qw/
|
||||||
|
do_macsuck
|
||||||
|
store_node
|
||||||
|
store_wireless_client_info
|
||||||
|
/;
|
||||||
|
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
App::Netdisco::Core::Macsuck
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
Helper subroutines to support parts of the Netdisco application.
|
||||||
|
|
||||||
|
There are no default exports, however the C<:all> tag will export all
|
||||||
|
subroutines.
|
||||||
|
|
||||||
|
=head1 EXPORT_OK
|
||||||
|
|
||||||
|
=head2 do_macsuck( $device, $snmp )
|
||||||
|
|
||||||
|
Given a Device database object, and a working SNMP connection, connect to a
|
||||||
|
device and discover the MAC addresses listed against each physical port
|
||||||
|
without a neighbor.
|
||||||
|
|
||||||
|
If the device has VLANs, C<do_macsuck> will walk each VALN to get the MAC
|
||||||
|
addresses from there.
|
||||||
|
|
||||||
|
It will also gather wireless client information if C<store_wireless_client>
|
||||||
|
configuration setting is enabled.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub do_macsuck {
|
||||||
|
my ($device, $snmp) = @_;
|
||||||
|
|
||||||
|
unless ($device->in_storage) {
|
||||||
|
debug sprintf
|
||||||
|
' [%s] macsuck - skipping device not yet discovered',
|
||||||
|
$device->ip;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# would be possible just to use now() on updated records, but by using this
|
||||||
|
# same value for them all, we _can_ if we want add a job at the end to
|
||||||
|
# select and do something with the updated set (no reason to yet, though)
|
||||||
|
my $now = 'to_timestamp('. (join '.', gettimeofday) .')';
|
||||||
|
my $total_nodes = 0;
|
||||||
|
|
||||||
|
# do this before we start messing with the snmp community string
|
||||||
|
store_wireless_client_info($device, $snmp, $now);
|
||||||
|
|
||||||
|
# cache the device ports to save hitting the database for many single rows
|
||||||
|
my $device_ports = {map {($_->port => $_)} $device->ports->all};
|
||||||
|
my $port_macs = get_port_macs($device);
|
||||||
|
|
||||||
|
# get forwarding table data via basic snmp connection
|
||||||
|
my $fwtable = { 0 => _walk_fwtable($device, $snmp, $port_macs, $device_ports) };
|
||||||
|
|
||||||
|
# ...then per-vlan if supported
|
||||||
|
my @vlan_list = _get_vlan_list($device, $snmp);
|
||||||
|
foreach my $vlan (@vlan_list) {
|
||||||
|
snmp_comm_reindex($snmp, $vlan);
|
||||||
|
$fwtable->{$vlan} = _walk_fwtable($device, $snmp, $port_macs, $device_ports);
|
||||||
|
}
|
||||||
|
|
||||||
|
# now it's time to call store_node for every node discovered
|
||||||
|
# on every port on every vlan on this device.
|
||||||
|
|
||||||
|
# reverse sort allows vlan 0 entries to be included only as fallback
|
||||||
|
foreach my $vlan (reverse sort keys %$fwtable) {
|
||||||
|
foreach my $port (keys %{ $fwtable->{$vlan} }) {
|
||||||
|
if ($device_ports->{$port}->is_uplink) {
|
||||||
|
debug sprintf
|
||||||
|
' [%s] macsuck - port %s is uplink, topo broken - skipping.',
|
||||||
|
$device->ip, $port;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug sprintf ' [%s] macsuck - port %s vlan %s : %s nodes',
|
||||||
|
$device->ip, $port, $vlan, scalar keys %{ $fwtable->{$vlan}->{$port} };
|
||||||
|
|
||||||
|
foreach my $mac (keys %{ $fwtable->{$vlan}->{$port} }) {
|
||||||
|
# remove vlan 0 entry for this MAC addr
|
||||||
|
delete $fwtable->{0}->{$_}->{$mac}
|
||||||
|
for keys %{ $fwtable->{0} };
|
||||||
|
|
||||||
|
++$total_nodes;
|
||||||
|
store_node($device->ip, $vlan, $port, $mac, $now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug sprintf ' [%s] macsuck - %s forwarding table entries',
|
||||||
|
$device->ip, $total_nodes;
|
||||||
|
$device->update({last_macsuck => \$now});
|
||||||
|
}
|
||||||
|
|
||||||
|
=head2 store_node( $ip, $vlan, $port, $mac, $now? )
|
||||||
|
|
||||||
|
Writes a fresh entry to the Netdisco C<node> database table. Will mark old
|
||||||
|
entries for this data as no longer C<active>.
|
||||||
|
|
||||||
|
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()>.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub store_node {
|
||||||
|
my ($ip, $vlan, $port, $mac, $now) = @_;
|
||||||
|
$now ||= 'now()';
|
||||||
|
|
||||||
|
schema('netdisco')->txn_do(sub {
|
||||||
|
my $nodes = schema('netdisco')->resultset('Node');
|
||||||
|
|
||||||
|
# TODO: probably needs changing if we're to support VTP domains
|
||||||
|
my $old = $nodes->search(
|
||||||
|
{
|
||||||
|
mac => $mac,
|
||||||
|
vlan => $vlan,
|
||||||
|
-bool => 'active',
|
||||||
|
-not => {
|
||||||
|
switch => $ip,
|
||||||
|
port => $port,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
# lock rows,
|
||||||
|
# and get the count so we know whether to set time_recent
|
||||||
|
my $old_count = scalar $old->search(undef,
|
||||||
|
{
|
||||||
|
columns => [qw/switch vlan port mac/],
|
||||||
|
order_by => [qw/switch vlan port mac/],
|
||||||
|
for => 'update',
|
||||||
|
})->all;
|
||||||
|
|
||||||
|
$old->update({ active => \'false' });
|
||||||
|
|
||||||
|
my $new = $nodes->search(
|
||||||
|
{
|
||||||
|
'me.switch' => $ip,
|
||||||
|
'me.port' => $port,
|
||||||
|
'me.mac' => $mac,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
order_by => [qw/switch vlan port mac/],
|
||||||
|
for => 'update',
|
||||||
|
});
|
||||||
|
|
||||||
|
# lock rows
|
||||||
|
$new->search({vlan => [$vlan, 0, undef]})->first;
|
||||||
|
|
||||||
|
# upgrade old schema
|
||||||
|
$new->search({vlan => [$vlan, 0, undef]})
|
||||||
|
->update({vlan => $vlan});
|
||||||
|
|
||||||
|
$new->update_or_create({
|
||||||
|
vlan => $vlan,
|
||||||
|
active => \'true',
|
||||||
|
oui => substr($mac,0,8),
|
||||||
|
time_last => \$now,
|
||||||
|
($old_count ? (time_recent => \$now) : ()),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
# return a list of vlan numbers which are OK to macsuck on this device
|
||||||
|
sub _get_vlan_list {
|
||||||
|
my ($device, $snmp) = @_;
|
||||||
|
|
||||||
|
return () unless $snmp->cisco_comm_indexing;
|
||||||
|
|
||||||
|
my (%vlans, %vlan_names);
|
||||||
|
my $i_vlan = $snmp->i_vlan || {};
|
||||||
|
|
||||||
|
# get list of vlans in use
|
||||||
|
while (my ($idx, $vlan) = each %$i_vlan) {
|
||||||
|
# hack: if vlan id comes as 1.142 instead of 142
|
||||||
|
$vlan =~ s/^\d+\.//;
|
||||||
|
|
||||||
|
++$vlans{$vlan};
|
||||||
|
}
|
||||||
|
|
||||||
|
unless (scalar keys %vlans) {
|
||||||
|
debug sprintf ' [%s] macsuck - no VLANs found.', $device->ip;
|
||||||
|
return ();
|
||||||
|
}
|
||||||
|
|
||||||
|
my $v_name = $snmp->v_name || {};
|
||||||
|
|
||||||
|
# get vlan names (required for config which filters by name)
|
||||||
|
while (my ($idx, $name) = each %$v_name) {
|
||||||
|
# hack: if vlan id comes as 1.142 instead of 142
|
||||||
|
(my $vlan = $idx) =~ s/^\d+\.//;
|
||||||
|
|
||||||
|
# just in case i_vlan is different to v_name set
|
||||||
|
++$vlans{$vlan};
|
||||||
|
|
||||||
|
$vlan_names{$vlan} = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug sprintf ' [%s] macsuck - VLANs: %s', $device->ip,
|
||||||
|
(join ',', sort keys %vlans);
|
||||||
|
|
||||||
|
my @ok_vlans = ();
|
||||||
|
foreach my $vlan (sort keys %vlans) {
|
||||||
|
my $name = $vlan_names{$vlan} || '(unnamed)';
|
||||||
|
|
||||||
|
# FIXME: macsuck_no_vlan
|
||||||
|
# FIXME: macsuck_no_devicevlan
|
||||||
|
|
||||||
|
if (setting('macsuck_no_unnamed') and $name eq '(unnamed)') {
|
||||||
|
debug sprintf
|
||||||
|
' [%s] macsuck VLAN %s - skipped by macsuck_no_unnamed config',
|
||||||
|
$device->ip, $vlan;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($vlan == 0 or $vlan > 4094) {
|
||||||
|
debug sprintf ' [%s] macsuck - invalid VLAN number %s',
|
||||||
|
$device->ip, $vlan;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
# check in use by a port on this device
|
||||||
|
if (scalar keys %$i_vlan and not exists $vlans{$vlan}
|
||||||
|
and not setting('macsuck_all_vlans')) {
|
||||||
|
|
||||||
|
debug sprintf
|
||||||
|
' [%s] macsuck VLAN %s/%s - not in use by any port - skipping.',
|
||||||
|
$device->ip, $vlan, $name;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
push @ok_vlans, $vlan;
|
||||||
|
}
|
||||||
|
|
||||||
|
return @ok_vlans;
|
||||||
|
}
|
||||||
|
|
||||||
|
# walks the forwarding table (BRIDGE-MIB) for the device and returns a
|
||||||
|
# table of node entries.
|
||||||
|
sub _walk_fwtable {
|
||||||
|
my ($device, $snmp, $port_macs, $device_ports) = @_;
|
||||||
|
my $cache = {};
|
||||||
|
|
||||||
|
my $fw_mac = $snmp->fw_mac;
|
||||||
|
my $fw_port = $snmp->fw_port;
|
||||||
|
my $fw_vlan = $snmp->qb_fw_vlan;
|
||||||
|
my $bp_index = $snmp->bp_index;
|
||||||
|
my $interfaces = $snmp->interfaces;
|
||||||
|
|
||||||
|
# to map forwarding table port to device port we have
|
||||||
|
# fw_port -> bp_index -> interfaces
|
||||||
|
|
||||||
|
while (my ($idx, $mac) = each %$fw_mac) {
|
||||||
|
my $bp_id = $fw_port->{$idx};
|
||||||
|
next unless check_mac($device, $mac, $port_macs);
|
||||||
|
|
||||||
|
unless (defined $bp_id) {
|
||||||
|
debug sprintf
|
||||||
|
' [%s] macsuck %s - %s has no fw_port mapping - skipping.',
|
||||||
|
$device->ip, $mac, $idx;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $iid = $bp_index->{$bp_id};
|
||||||
|
|
||||||
|
unless (defined $iid) {
|
||||||
|
debug sprintf
|
||||||
|
' [%s] macsuck %s - port %s has no bp_index mapping - skipping.',
|
||||||
|
$device->ip, $mac, $bp_id;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $port = $interfaces->{$iid};
|
||||||
|
|
||||||
|
unless (defined $port) {
|
||||||
|
debug sprintf
|
||||||
|
' [%s] macsuck %s - iid %s has no port mapping - skipping.',
|
||||||
|
$device->ip, $mac, $iid;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO: add proper port channel support!
|
||||||
|
if ($port =~ m/port.channel/i) {
|
||||||
|
debug sprintf
|
||||||
|
' [%s] macsuck %s - port %s is LAG member - skipping.',
|
||||||
|
$device->ip, $mac, $port;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
# this uses the cached $ports resultset to limit hits on the db
|
||||||
|
my $device_port = $device_ports->{$port};
|
||||||
|
|
||||||
|
unless (defined $device_port) {
|
||||||
|
debug sprintf
|
||||||
|
' [%s] macsuck %s - port %s is not in database - skipping.',
|
||||||
|
$device->ip, $mac, $port;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
# check to see if the port is connected to another device
|
||||||
|
# and if we have that device in the database.
|
||||||
|
|
||||||
|
# we have several ways to detect "uplink" port status:
|
||||||
|
# * a neighbor was discovered using CDP/LLDP
|
||||||
|
# * a mac addr is seen which belongs to any device port/interface
|
||||||
|
# * (TODO) admin sets is_uplink_admin on the device_port
|
||||||
|
|
||||||
|
if ($device_port->is_uplink) {
|
||||||
|
if (my $neighbor = $device_port->neighbor) {
|
||||||
|
debug sprintf
|
||||||
|
' [%s] macsuck %s - port %s has neighbor %s - skipping.',
|
||||||
|
$device->ip, $mac, $port, $neighbor->ip;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
elsif (my $remote = $device_port->remote_ip) {
|
||||||
|
debug sprintf
|
||||||
|
' [%s] macsuck %s - port %s has undiscovered neighbor %s',
|
||||||
|
$device->ip, $mac, $port, $remote;
|
||||||
|
# continue!!
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
debug sprintf
|
||||||
|
' [%s] macsuck %s - port %s is detected uplink - skipping.',
|
||||||
|
$device->ip, $mac, $port;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exists $port_macs->{$mac}) {
|
||||||
|
my $switch_ip = $port_macs->{$mac};
|
||||||
|
if ($device->ip eq $switch_ip) {
|
||||||
|
debug sprintf
|
||||||
|
' [%s] macsuck %s - port %s connects to self - skipping.',
|
||||||
|
$device->ip, $mac, $port;
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug sprintf ' [%s] macsuck %s - port %s is probably an uplink',
|
||||||
|
$device->ip, $mac, $port;
|
||||||
|
$device_port->update({is_uplink => \'true'});
|
||||||
|
|
||||||
|
# when there's no CDP/LLDP, we only want to gather macs at the
|
||||||
|
# topology edge, hence skip ports with known device macs.
|
||||||
|
next unless setting('macsuck_bleed');
|
||||||
|
}
|
||||||
|
|
||||||
|
++$cache->{$port}->{$mac};
|
||||||
|
}
|
||||||
|
|
||||||
|
return $cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
=head2 store_wireless_client_info( $device, $snmp, $now? )
|
||||||
|
|
||||||
|
Given a Device database object, and a working SNMP connection, connect to a
|
||||||
|
device and discover 802.11 related information for all connected wireless
|
||||||
|
clients.
|
||||||
|
|
||||||
|
If the device doesn't support the 802.11 MIBs, then this will silently return.
|
||||||
|
|
||||||
|
If the device does support the 802.11 MIBs but Netdisco's configuration
|
||||||
|
does not permit polling (C<store_wireless_client> must be true) then a debug
|
||||||
|
message is logged and the subroutine returns.
|
||||||
|
|
||||||
|
Otherwise, client information is gathered and stored to the database.
|
||||||
|
|
||||||
|
Optionally, a third argument can be the literal string passed to the time_last
|
||||||
|
field of the database record. If not provided, it defauls to C<now()>.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub store_wireless_client_info {
|
||||||
|
my ($device, $snmp, $now) = @_;
|
||||||
|
$now ||= 'now()';
|
||||||
|
|
||||||
|
my $cd11_txrate = $snmp->cd11_txrate;
|
||||||
|
return unless $cd11_txrate and scalar keys %$cd11_txrate;
|
||||||
|
|
||||||
|
if (setting('store_wireless_client')) {
|
||||||
|
debug sprintf ' [%s] macsuck - gathering wireless client info',
|
||||||
|
$device->ip;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
debug sprintf ' [%s] macsuck - dot11 info available but skipped due to config',
|
||||||
|
$device->ip;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $cd11_rateset = $snmp->cd11_rateset();
|
||||||
|
my $cd11_uptime = $snmp->cd11_uptime();
|
||||||
|
my $cd11_sigstrength = $snmp->cd11_sigstrength();
|
||||||
|
my $cd11_sigqual = $snmp->cd11_sigqual();
|
||||||
|
my $cd11_mac = $snmp->cd11_mac();
|
||||||
|
my $cd11_port = $snmp->cd11_port();
|
||||||
|
my $cd11_rxpkt = $snmp->cd11_rxpkt();
|
||||||
|
my $cd11_txpkt = $snmp->cd11_txpkt();
|
||||||
|
my $cd11_rxbyte = $snmp->cd11_rxbyte();
|
||||||
|
my $cd11_txbyte = $snmp->cd11_txbyte();
|
||||||
|
my $cd11_ssid = $snmp->cd11_ssid();
|
||||||
|
|
||||||
|
while (my ($idx, $txrates) = each %$cd11_txrate) {
|
||||||
|
my $rates = $cd11_rateset->{$idx};
|
||||||
|
my $mac = $cd11_mac->{$idx};
|
||||||
|
next unless defined $mac; # avoid null entries
|
||||||
|
# there can be more rows in txrate than other tables
|
||||||
|
|
||||||
|
my $txrate = defined $txrates->[$#$txrates]
|
||||||
|
? int($txrates->[$#$txrates])
|
||||||
|
: undef;
|
||||||
|
|
||||||
|
my $maxrate = defined $rates->[$#$rates]
|
||||||
|
? int($rates->[$#$rates])
|
||||||
|
: undef;
|
||||||
|
|
||||||
|
schema('netdisco')->txn_do(sub {
|
||||||
|
schema('netdisco')->resultset('NodeWireless')
|
||||||
|
->search({ 'me.mac' => $mac })
|
||||||
|
->update_or_create({
|
||||||
|
txrate => $txrate,
|
||||||
|
maxrate => $maxrate,
|
||||||
|
uptime => $cd11_uptime->{$idx},
|
||||||
|
rxpkt => $cd11_rxpkt->{$idx},
|
||||||
|
txpkt => $cd11_txpkt->{$idx},
|
||||||
|
rxbyte => $cd11_rxbyte->{$idx},
|
||||||
|
txbyte => $cd11_txbyte->{$idx},
|
||||||
|
sigqual => $cd11_sigqual->{$idx},
|
||||||
|
sigstrength => $cd11_sigstrength->{$idx},
|
||||||
|
ssid => ($cd11_ssid->{$idx} || 'unknown'),
|
||||||
|
time_last => \$now,
|
||||||
|
}, {
|
||||||
|
order_by => [qw/mac ssid/],
|
||||||
|
for => 'update',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
@@ -8,7 +8,7 @@ use base 'DBIx::Class::Schema';
|
|||||||
|
|
||||||
__PACKAGE__->load_namespaces;
|
__PACKAGE__->load_namespaces;
|
||||||
|
|
||||||
our $VERSION = 17; # schema version used for upgrades, keep as integer
|
our $VERSION = 20; # schema version used for upgrades, keep as integer
|
||||||
|
|
||||||
use Path::Class;
|
use Path::Class;
|
||||||
use File::Basename;
|
use File::Basename;
|
||||||
@@ -17,7 +17,11 @@ my (undef, $libpath, undef) = fileparse( $INC{ 'App/Netdisco/DB.pm' } );
|
|||||||
our $schema_versions_dir = Path::Class::Dir->new($libpath)
|
our $schema_versions_dir = Path::Class::Dir->new($libpath)
|
||||||
->subdir("DB", "schema_versions")->stringify;
|
->subdir("DB", "schema_versions")->stringify;
|
||||||
|
|
||||||
__PACKAGE__->load_components(qw/Schema::Versioned/);
|
__PACKAGE__->load_components(qw/
|
||||||
|
Schema::Versioned
|
||||||
|
+App::Netdisco::DB::ExplicitLocking
|
||||||
|
/);
|
||||||
|
|
||||||
__PACKAGE__->upgrade_directory($schema_versions_dir);
|
__PACKAGE__->upgrade_directory($schema_versions_dir);
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|||||||
165
Netdisco/lib/App/Netdisco/DB/ExplicitLocking.pm
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
package App::Netdisco::DB::ExplicitLocking;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings FATAL => 'all';
|
||||||
|
|
||||||
|
our %lock_modes;
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
%lock_modes = (
|
||||||
|
ACCESS_SHARE => 'ACCESS SHARE',
|
||||||
|
ROW_SHARE => 'ROW SHARE',
|
||||||
|
ROW_EXCLUSIVE => 'ROW EXCLUSIVE',
|
||||||
|
SHARE_UPDATE_EXCLUSIVE => 'SHARE UPDATE EXCLUSIVE',
|
||||||
|
SHARE => 'SHARE',
|
||||||
|
SHARE_ROW_EXCLUSIVE => 'SHARE ROW EXCLUSIVE',
|
||||||
|
EXCLUSIVE => 'EXCLUSIVE',
|
||||||
|
ACCESS_EXCLUSIVE => 'ACCESS EXCLUSIVE',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
use constant \%lock_modes;
|
||||||
|
|
||||||
|
use base 'Exporter';
|
||||||
|
our @EXPORT = ();
|
||||||
|
our @EXPORT_OK = (keys %lock_modes);
|
||||||
|
our %EXPORT_TAGS = (modes => \@EXPORT_OK);
|
||||||
|
|
||||||
|
sub txn_do_locked {
|
||||||
|
my ($self, $table, $mode, $sub) = @_;
|
||||||
|
my $sql_fmt = q{LOCK TABLE %s IN %%s MODE};
|
||||||
|
my $schema = $self;
|
||||||
|
|
||||||
|
if ($self->can('result_source')) {
|
||||||
|
# ResultSet component
|
||||||
|
$sub = $mode;
|
||||||
|
$mode = $table;
|
||||||
|
$table = $self->result_source->from;
|
||||||
|
$schema = $self->result_source->schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
$schema->throw_exception('missing Table name to txn_do_locked()')
|
||||||
|
unless length $table;
|
||||||
|
|
||||||
|
$table = [$table] if ref '' eq ref $table;
|
||||||
|
my $table_fmt = join ', ', ('%s' x scalar @$table);
|
||||||
|
my $sql = sprintf $sql_fmt, $table_fmt;
|
||||||
|
|
||||||
|
if (ref '' eq ref $mode and length $mode) {
|
||||||
|
scalar grep {$_ eq $mode} values %lock_modes
|
||||||
|
or $schema->throw_exception('bad LOCK_MODE to txn_do_locked()');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$sub = $mode;
|
||||||
|
$mode = 'ACCESS EXCLUSIVE';
|
||||||
|
}
|
||||||
|
|
||||||
|
$schema->txn_do(sub {
|
||||||
|
my @params = map {$schema->storage->dbh->quote_identifier($_)} @$table;
|
||||||
|
$schema->storage->dbh->do(sprintf $sql, @params, $mode);
|
||||||
|
$sub->();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
App::Netdisco::DB::ExplicitLocking - Support for PostgreSQL Lock Modes
|
||||||
|
|
||||||
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
|
In your L<DBIx::Class> schema:
|
||||||
|
|
||||||
|
package My::Schema;
|
||||||
|
__PACKAGE__->load_components('+App::Netdisco::DB::ExplicitLocking');
|
||||||
|
|
||||||
|
Then, in your application code:
|
||||||
|
|
||||||
|
use App::Netdisco::DB::ExplicitLocking ':modes';
|
||||||
|
$schema->txn_do_locked($table, MODE_NAME, sub { ... });
|
||||||
|
|
||||||
|
This also works for the ResultSet:
|
||||||
|
|
||||||
|
package My::Schema::ResultSet::TableName;
|
||||||
|
__PACKAGE__->load_components('+App::Netdisco::DB::ExplicitLocking');
|
||||||
|
|
||||||
|
Then, in your application code:
|
||||||
|
|
||||||
|
use App::Netdisco::DB::ExplicitLocking ':modes';
|
||||||
|
$schema->resultset('TableName')->txn_do_locked(MODE_NAME, sub { ... });
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
This L<DBIx::Class> component provides an easy way to execute PostgreSQL table
|
||||||
|
locks before a transaction block.
|
||||||
|
|
||||||
|
You can load the component in either the Schema class or ResultSet class (or
|
||||||
|
both) and then use an interface very similar to C<DBIx::Class>'s C<txn_do()>.
|
||||||
|
|
||||||
|
The package also exports constants for each of the table lock modes supported
|
||||||
|
by PostgreSQL, which must be used if specifying the mode (default mode is
|
||||||
|
C<ACCESS EXCLUSIVE>).
|
||||||
|
|
||||||
|
=head1 EXPORTS
|
||||||
|
|
||||||
|
With the C<:modes> tag (as in SYNOPSIS above) the following constants are
|
||||||
|
exported and must be used if specifying the lock mode:
|
||||||
|
|
||||||
|
=over 4
|
||||||
|
|
||||||
|
=item * C<ACCESS_SHARE>
|
||||||
|
|
||||||
|
=item * C<ROW_SHARE>
|
||||||
|
|
||||||
|
=item * C<ROW_EXCLUSIVE>
|
||||||
|
|
||||||
|
=item * C<SHARE_UPDATE_EXCLUSIVE>
|
||||||
|
|
||||||
|
=item * C<SHARE>
|
||||||
|
|
||||||
|
=item * C<SHARE_ROW_EXCLUSIVE>
|
||||||
|
|
||||||
|
=item * C<EXCLUSIVE>
|
||||||
|
|
||||||
|
=item * C<ACCESS_EXCLUSIVE>
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
=head1 METHODS
|
||||||
|
|
||||||
|
=head2 C<< $schema->txn_do_locked($table|\@tables, MODE_NAME?, $subref) >>
|
||||||
|
|
||||||
|
This is the method signature used when the component is loaded into your
|
||||||
|
Schema class. The reason you might want to use this over the ResultSet version
|
||||||
|
(below) is to specify multiple tables to be locked before the transaction.
|
||||||
|
|
||||||
|
The first argument is one or more tables, and is required. Note that these are
|
||||||
|
the real table names in PostgreSQL, and not C<DBIx::Class> ResultSet aliases
|
||||||
|
or anything like that.
|
||||||
|
|
||||||
|
The mode name is optional, and defaults to C<ACCESS EXCLUSIVE>. You must use
|
||||||
|
one of the exported constants in this parameter.
|
||||||
|
|
||||||
|
Finally pass a subroutine reference, just as you would to the normal
|
||||||
|
C<DBIx::Class> C<txn_do()> method. Note that additional arguments are not
|
||||||
|
supported.
|
||||||
|
|
||||||
|
=head2 C<< $resultset->txn_do_locked(MODE_NAME?, $subref) >>
|
||||||
|
|
||||||
|
This is the method signature used when the component is loaded into your
|
||||||
|
ResultSet class. If you don't yet have a ResultSet class (which is the default
|
||||||
|
- normally only Result classes are created) then you can create a stub which
|
||||||
|
simply loads this component (and inherits from C<DBIx::Class::ResultSet>).
|
||||||
|
|
||||||
|
This is the simplest way to use this module if you only want to lock one table
|
||||||
|
before your transaction block.
|
||||||
|
|
||||||
|
The first argument is the optional mode name, which defaults to C<ACCESS
|
||||||
|
EXCLUSIVE>. You must use one of the exported constants in this parameter.
|
||||||
|
|
||||||
|
The second argument is a subroutine reference, just as you would pass to the
|
||||||
|
normal C<DBIx::Class> C<txn_do()> method. Note that additional arguments are
|
||||||
|
not supported.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
1;
|
||||||
@@ -55,4 +55,46 @@ __PACKAGE__->add_columns(
|
|||||||
__PACKAGE__->set_primary_key("job");
|
__PACKAGE__->set_primary_key("job");
|
||||||
|
|
||||||
# You can replace this text with custom code or comments, and it will be preserved on regeneration
|
# You can replace this text with custom code or comments, and it will be preserved on regeneration
|
||||||
|
|
||||||
|
=head1 ADDITIONAL COLUMNS
|
||||||
|
|
||||||
|
=head2 entererd_stamp
|
||||||
|
|
||||||
|
Formatted version of the C<entered> field, accurate to the minute.
|
||||||
|
|
||||||
|
The format is somewhat like ISO 8601 or RFC3339 but without the middle C<T>
|
||||||
|
between the date stamp and time stamp. That is:
|
||||||
|
|
||||||
|
2012-02-06 12:49
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub entered_stamp { return (shift)->get_column('entered_stamp') }
|
||||||
|
|
||||||
|
=head2 started_stamp
|
||||||
|
|
||||||
|
Formatted version of the C<started> field, accurate to the minute.
|
||||||
|
|
||||||
|
The format is somewhat like ISO 8601 or RFC3339 but without the middle C<T>
|
||||||
|
between the date stamp and time stamp. That is:
|
||||||
|
|
||||||
|
2012-02-06 12:49
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub started_stamp { return (shift)->get_column('started_stamp') }
|
||||||
|
|
||||||
|
=head2 finished_stamp
|
||||||
|
|
||||||
|
Formatted version of the C<finished> field, accurate to the minute.
|
||||||
|
|
||||||
|
The format is somewhat like ISO 8601 or RFC3339 but without the middle C<T>
|
||||||
|
between the date stamp and time stamp. That is:
|
||||||
|
|
||||||
|
2012-02-06 12:49
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub finished_stamp { return (shift)->get_column('finished_stamp') }
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|||||||
@@ -180,6 +180,16 @@ __PACKAGE__->has_many(
|
|||||||
|
|
||||||
=head1 ADDITIONAL COLUMNS
|
=head1 ADDITIONAL COLUMNS
|
||||||
|
|
||||||
|
=head2 port_count
|
||||||
|
|
||||||
|
Returns the number of ports on this device. Enable this
|
||||||
|
column by applying the C<with_port_count()> modifier to C<search()>.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub port_count { return (shift)->get_column('port_count') }
|
||||||
|
|
||||||
|
|
||||||
=head2 uptime_age
|
=head2 uptime_age
|
||||||
|
|
||||||
Formatted version of the C<uptime> field.
|
Formatted version of the C<uptime> field.
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ package App::Netdisco::DB::Result::DevicePort;
|
|||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
|
||||||
|
use MIME::Base64 'encode_base64url';
|
||||||
|
|
||||||
use base 'DBIx::Class::Core';
|
use base 'DBIx::Class::Core';
|
||||||
__PACKAGE__->table("device_port");
|
__PACKAGE__->table("device_port");
|
||||||
__PACKAGE__->add_columns(
|
__PACKAGE__->add_columns(
|
||||||
@@ -51,6 +53,10 @@ __PACKAGE__->add_columns(
|
|||||||
{ data_type => "text", is_nullable => 1 },
|
{ data_type => "text", is_nullable => 1 },
|
||||||
"remote_id",
|
"remote_id",
|
||||||
{ data_type => "text", is_nullable => 1 },
|
{ data_type => "text", is_nullable => 1 },
|
||||||
|
"manual_topo",
|
||||||
|
{ data_type => "bool", is_nullable => 0, default_value => \"false" },
|
||||||
|
"is_uplink",
|
||||||
|
{ data_type => "bool", is_nullable => 1 },
|
||||||
"vlan",
|
"vlan",
|
||||||
{ data_type => "text", is_nullable => 1 },
|
{ data_type => "text", is_nullable => 1 },
|
||||||
"pvid",
|
"pvid",
|
||||||
@@ -262,4 +268,13 @@ See the C<with_is_free> and C<only_free_ports> modifiers to C<search()>.
|
|||||||
|
|
||||||
sub is_free { return (shift)->get_column('is_free') }
|
sub is_free { return (shift)->get_column('is_free') }
|
||||||
|
|
||||||
|
=head2 base64url_port
|
||||||
|
|
||||||
|
Returns a Base64 encoded version of the C<port> column value suitable for use
|
||||||
|
in a URL.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub base64url_port { return encode_base64url((shift)->port) }
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ __PACKAGE__->add_columns(
|
|||||||
original => { default_value => \"now()" },
|
original => { default_value => \"now()" },
|
||||||
},
|
},
|
||||||
"vlan",
|
"vlan",
|
||||||
{ data_type => "text", is_nullable => 1, default_value => '0' },
|
{ data_type => "text", is_nullable => 0, default_value => '0' },
|
||||||
);
|
);
|
||||||
__PACKAGE__->set_primary_key("mac", "switch", "port", "vlan");
|
__PACKAGE__->set_primary_key("mac", "switch", "port", "vlan");
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ __PACKAGE__->add_columns(
|
|||||||
original => { default_value => \"now()" },
|
original => { default_value => \"now()" },
|
||||||
},
|
},
|
||||||
"ssid",
|
"ssid",
|
||||||
{ data_type => "text", is_nullable => 1, default_value => '' },
|
{ data_type => "text", is_nullable => 0, default_value => '' },
|
||||||
);
|
);
|
||||||
__PACKAGE__->set_primary_key("mac", "ssid");
|
__PACKAGE__->set_primary_key("mac", "ssid");
|
||||||
|
|
||||||
|
|||||||
@@ -19,4 +19,7 @@ __PACKAGE__->add_columns(
|
|||||||
{ data_type => "text", is_nullable => 0 },
|
{ data_type => "text", is_nullable => 0 },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
__PACKAGE__->add_unique_constraint(['dev1','port1']);
|
||||||
|
__PACKAGE__->add_unique_constraint(['dev2','port2']);
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|||||||
41
Netdisco/lib/App/Netdisco/DB/ResultSet/Admin.pm
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package App::Netdisco::DB::ResultSet::Admin;
|
||||||
|
use base 'DBIx::Class::ResultSet';
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings FATAL => 'all';
|
||||||
|
|
||||||
|
=head1 ADDITIONAL METHODS
|
||||||
|
|
||||||
|
=head2 with_times
|
||||||
|
|
||||||
|
This is a modifier for any C<search()> (including the helpers below) which
|
||||||
|
will add the following additional synthesized columns to the result set:
|
||||||
|
|
||||||
|
=over 4
|
||||||
|
|
||||||
|
=item entered_stamp
|
||||||
|
|
||||||
|
=item started_stamp
|
||||||
|
|
||||||
|
=item finished_stamp
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub with_times {
|
||||||
|
my ($rs, $cond, $attrs) = @_;
|
||||||
|
|
||||||
|
return $rs
|
||||||
|
->search_rs($cond, $attrs)
|
||||||
|
->search({},
|
||||||
|
{
|
||||||
|
'+columns' => {
|
||||||
|
entered_stamp => \"to_char(entered, 'YYYY-MM-DD HH24:MI')",
|
||||||
|
started_stamp => \"to_char(started, 'YYYY-MM-DD HH24:MI')",
|
||||||
|
finished_stamp => \"to_char(finished, 'YYYY-MM-DD HH24:MI')",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
@@ -491,4 +491,34 @@ sub get_distinct_col {
|
|||||||
)->get_column($col)->all;
|
)->get_column($col)->all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
=head2 with_port_count
|
||||||
|
|
||||||
|
This is a modifier for any C<search()> which
|
||||||
|
will add the following additional synthesized column to the result set:
|
||||||
|
|
||||||
|
=over 4
|
||||||
|
|
||||||
|
=item port_count
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub with_port_count {
|
||||||
|
my ($rs, $cond, $attrs) = @_;
|
||||||
|
|
||||||
|
return $rs
|
||||||
|
->search_rs($cond, $attrs)
|
||||||
|
->search({},
|
||||||
|
{
|
||||||
|
'+columns' => { port_count =>
|
||||||
|
$rs->result_source->schema->resultset('DevicePort')
|
||||||
|
->search(
|
||||||
|
{ 'dp.ip' => { -ident => 'me.ip' } },
|
||||||
|
{ alias => 'dp' }
|
||||||
|
)->count_rs->as_query
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ use base 'DBIx::Class::ResultSet';
|
|||||||
use strict;
|
use strict;
|
||||||
use warnings FATAL => 'all';
|
use warnings FATAL => 'all';
|
||||||
|
|
||||||
|
__PACKAGE__->load_components(qw/
|
||||||
|
+App::Netdisco::DB::ExplicitLocking
|
||||||
|
/);
|
||||||
|
|
||||||
=head1 search_by_mac( \%cond, \%attrs? )
|
=head1 search_by_mac( \%cond, \%attrs? )
|
||||||
|
|
||||||
my $set = $rs->search_by_mac({mac => '00:11:22:33:44:55', active => 1});
|
my $set = $rs->search_by_mac({mac => '00:11:22:33:44:55', active => 1});
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ use base 'DBIx::Class::ResultSet';
|
|||||||
use strict;
|
use strict;
|
||||||
use warnings FATAL => 'all';
|
use warnings FATAL => 'all';
|
||||||
|
|
||||||
|
__PACKAGE__->load_components(qw/
|
||||||
|
+App::Netdisco::DB::ExplicitLocking
|
||||||
|
/);
|
||||||
|
|
||||||
my $search_attr = {
|
my $search_attr = {
|
||||||
order_by => {'-desc' => 'time_last'},
|
order_by => {'-desc' => 'time_last'},
|
||||||
'+columns' => [
|
'+columns' => [
|
||||||
|
|||||||
11
Netdisco/lib/App/Netdisco/DB/ResultSet/NodeWireless.pm
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package App::Netdisco::DB::ResultSet::NodeWireless;
|
||||||
|
use base 'DBIx::Class::ResultSet';
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings FATAL => 'all';
|
||||||
|
|
||||||
|
__PACKAGE__->load_components(qw/
|
||||||
|
+App::Netdisco::DB::ExplicitLocking
|
||||||
|
/);
|
||||||
|
|
||||||
|
1;
|
||||||
11
Netdisco/lib/App/Netdisco/DB/ResultSet/Subnet.pm
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package App::Netdisco::DB::ResultSet::Subnet;
|
||||||
|
use base 'DBIx::Class::ResultSet';
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings FATAL => 'all';
|
||||||
|
|
||||||
|
__PACKAGE__->load_components(qw/
|
||||||
|
+App::Netdisco::DB::ExplicitLocking
|
||||||
|
/);
|
||||||
|
|
||||||
|
1;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
-- Convert schema '/home/devver/netdisco-ng/Netdisco/bin/../lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-17-PostgreSQL.sql' to '/home/devver/netdisco-ng/Netdisco/bin/../lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-18-PostgreSQL.sql':;
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
ALTER TABLE topology ADD CONSTRAINT topology_dev1_port1 UNIQUE (dev1, port1);
|
||||||
|
|
||||||
|
ALTER TABLE topology ADD CONSTRAINT topology_dev2_port2 UNIQUE (dev2, port2);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
ALTER TABLE device_port ADD COLUMN "manual_topo" bool DEFAULT false NOT NULL;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
ALTER TABLE device_port ADD COLUMN "is_uplink" bool;
|
||||||
|
ALTER TABLE device_port ADD COLUMN "is_uplink_admin" bool;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -33,7 +33,7 @@ sub capacity_for {
|
|||||||
debug "checking local capacity for action $action";
|
debug "checking local capacity for action $action";
|
||||||
|
|
||||||
my $action_map = {
|
my $action_map = {
|
||||||
Poller => [qw/refresh discover discovernew discover_neighbors/],
|
Poller => [qw/discoverall discover arpwalk arpnip macwalk macsuck/],
|
||||||
Interactive => [qw/location contact portcontrol portname vlan power/],
|
Interactive => [qw/location contact portcontrol portname vlan power/],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ sub close_job {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
schema('netdisco')->resultset('Admin')
|
schema('netdisco')->resultset('Admin')
|
||||||
->find($job->job)
|
->find($job->job, {for => 'update'})
|
||||||
->update({
|
->update({
|
||||||
status => $status,
|
status => $status,
|
||||||
log => $log,
|
log => $log,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ my $fqdn = hostfqdn || 'localhost';
|
|||||||
|
|
||||||
my $role_map = {
|
my $role_map = {
|
||||||
(map {$_ => 'Poller'}
|
(map {$_ => 'Poller'}
|
||||||
qw/refresh discover discovernew discover_neighbors/),
|
qw/discoverall discover arpwalk arpnip macwalk macsuck/),
|
||||||
(map {$_ => 'Interactive'}
|
(map {$_ => 'Interactive'}
|
||||||
qw/location contact portcontrol portname vlan power/)
|
qw/location contact portcontrol portname vlan power/)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ use namespace::clean;
|
|||||||
|
|
||||||
# add dispatch methods for poller tasks
|
# add dispatch methods for poller tasks
|
||||||
with 'App::Netdisco::Daemon::Worker::Poller::Device';
|
with 'App::Netdisco::Daemon::Worker::Poller::Device';
|
||||||
|
with 'App::Netdisco::Daemon::Worker::Poller::Arpnip';
|
||||||
|
with 'App::Netdisco::Daemon::Worker::Poller::Macsuck';
|
||||||
|
|
||||||
sub worker_body {
|
sub worker_body {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
@@ -61,7 +63,7 @@ sub close_job {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
schema('netdisco')->resultset('Admin')
|
schema('netdisco')->resultset('Admin')
|
||||||
->find($job->job)
|
->find($job->job, {for => 'update'})
|
||||||
->update({
|
->update({
|
||||||
status => $status,
|
status => $status,
|
||||||
log => $log,
|
log => $log,
|
||||||
|
|||||||
71
Netdisco/lib/App/Netdisco/Daemon/Worker/Poller/Arpnip.pm
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package App::Netdisco::Daemon::Worker::Poller::Arpnip;
|
||||||
|
|
||||||
|
use Dancer qw/:moose :syntax :script/;
|
||||||
|
use Dancer::Plugin::DBIC 'schema';
|
||||||
|
|
||||||
|
use App::Netdisco::Util::SNMP 'snmp_connect';
|
||||||
|
use App::Netdisco::Util::Device 'get_device';
|
||||||
|
use App::Netdisco::Core::Arpnip 'do_arpnip';
|
||||||
|
use App::Netdisco::Daemon::Util ':all';
|
||||||
|
|
||||||
|
use NetAddr::IP::Lite ':lower';
|
||||||
|
|
||||||
|
use Role::Tiny;
|
||||||
|
use namespace::clean;
|
||||||
|
|
||||||
|
# queue an arpnip job for all devices known to Netdisco
|
||||||
|
sub arpwalk {
|
||||||
|
my ($self, $job) = @_;
|
||||||
|
|
||||||
|
my $devices = schema('netdisco')->resultset('Device')->get_column('ip');
|
||||||
|
my $jobqueue = schema('netdisco')->resultset('Admin');
|
||||||
|
|
||||||
|
schema('netdisco')->txn_do(sub {
|
||||||
|
# clean up user submitted jobs older than 1min,
|
||||||
|
# assuming skew between schedulers' clocks is not greater than 1min
|
||||||
|
$jobqueue->search({
|
||||||
|
action => 'arpnip',
|
||||||
|
status => 'queued',
|
||||||
|
entered => { '<' => \"(now() - interval '1 minute')" },
|
||||||
|
})->delete;
|
||||||
|
|
||||||
|
# is scuppered by any user job submitted in last 1min (bad), or
|
||||||
|
# any similar job from another scheduler (good)
|
||||||
|
$jobqueue->populate([
|
||||||
|
map {{
|
||||||
|
device => $_,
|
||||||
|
action => 'arpnip',
|
||||||
|
status => 'queued',
|
||||||
|
}} ($devices->all)
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return job_done("Queued arpnip job for all devices");
|
||||||
|
}
|
||||||
|
|
||||||
|
sub arpnip {
|
||||||
|
my ($self, $job) = @_;
|
||||||
|
|
||||||
|
my $host = NetAddr::IP::Lite->new($job->device);
|
||||||
|
my $device = get_device($host->addr);
|
||||||
|
|
||||||
|
if ($device->in_storage
|
||||||
|
and $device->vendor and $device->vendor eq 'netdisco') {
|
||||||
|
return job_done("Skipped arpnip for pseudo-device $host");
|
||||||
|
}
|
||||||
|
|
||||||
|
my $snmp = snmp_connect($device);
|
||||||
|
if (!defined $snmp) {
|
||||||
|
return job_error("arpnip failed: could not SNMP connect to $host");
|
||||||
|
}
|
||||||
|
|
||||||
|
unless ($snmp->has_layer(3)) {
|
||||||
|
return job_done("Skipped arpnip for device $host without OSI layer 3 capability");
|
||||||
|
}
|
||||||
|
|
||||||
|
do_arpnip($device, $snmp);
|
||||||
|
|
||||||
|
return job_done("Ended arpnip for ". $host->addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
@@ -5,7 +5,7 @@ use Dancer::Plugin::DBIC 'schema';
|
|||||||
|
|
||||||
use App::Netdisco::Util::SNMP 'snmp_connect';
|
use App::Netdisco::Util::SNMP 'snmp_connect';
|
||||||
use App::Netdisco::Util::Device 'get_device';
|
use App::Netdisco::Util::Device 'get_device';
|
||||||
use App::Netdisco::Util::DiscoverAndStore ':all';
|
use App::Netdisco::Core::Discover ':all';
|
||||||
use App::Netdisco::Daemon::Util ':all';
|
use App::Netdisco::Daemon::Util ':all';
|
||||||
|
|
||||||
use NetAddr::IP::Lite ':lower';
|
use NetAddr::IP::Lite ':lower';
|
||||||
@@ -14,29 +14,48 @@ use Role::Tiny;
|
|||||||
use namespace::clean;
|
use namespace::clean;
|
||||||
|
|
||||||
# queue a discover job for all devices known to Netdisco
|
# queue a discover job for all devices known to Netdisco
|
||||||
sub refresh {
|
sub discoverall {
|
||||||
my ($self, $job) = @_;
|
my ($self, $job) = @_;
|
||||||
|
|
||||||
my $devices = schema('netdisco')->resultset('Device')->get_column('ip');
|
my $devices = schema('netdisco')->resultset('Device')->get_column('ip');
|
||||||
|
my $jobqueue = schema('netdisco')->resultset('Admin');
|
||||||
|
|
||||||
schema('netdisco')->resultset('Admin')->populate([
|
schema('netdisco')->txn_do(sub {
|
||||||
map {{
|
# clean up user submitted jobs older than 1min,
|
||||||
device => $_,
|
# assuming skew between schedulers' clocks is not greater than 1min
|
||||||
|
$jobqueue->search({
|
||||||
action => 'discover',
|
action => 'discover',
|
||||||
status => 'queued',
|
status => 'queued',
|
||||||
}} ($devices->all)
|
entered => { '<' => \"(now() - interval '1 minute')" },
|
||||||
]);
|
})->delete;
|
||||||
|
|
||||||
|
# is scuppered by any user job submitted in last 1min (bad), or
|
||||||
|
# any similar job from another scheduler (good)
|
||||||
|
$jobqueue->populate([
|
||||||
|
map {{
|
||||||
|
device => $_,
|
||||||
|
action => 'discover',
|
||||||
|
status => 'queued',
|
||||||
|
}} ($devices->all)
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
return job_done("Queued discover job for all devices");
|
return job_done("Queued discover job for all devices");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# queue a discover job for one device, and its *new* neighbors
|
||||||
sub discover {
|
sub discover {
|
||||||
my ($self, $job) = @_;
|
my ($self, $job) = @_;
|
||||||
|
|
||||||
my $host = NetAddr::IP::Lite->new($job->device);
|
my $host = NetAddr::IP::Lite->new($job->device);
|
||||||
my $device = get_device($host->addr);
|
my $device = get_device($host->addr);
|
||||||
my $snmp = snmp_connect($device);
|
|
||||||
|
|
||||||
|
if ($device->in_storage
|
||||||
|
and $device->vendor and $device->vendor eq 'netdisco') {
|
||||||
|
return job_done("Skipped discover for pseudo-device $host");
|
||||||
|
}
|
||||||
|
|
||||||
|
my $snmp = snmp_connect($device);
|
||||||
if (!defined $snmp) {
|
if (!defined $snmp) {
|
||||||
return job_error("discover failed: could not SNMP connect to $host");
|
return job_error("discover failed: could not SNMP connect to $host");
|
||||||
}
|
}
|
||||||
@@ -47,42 +66,9 @@ sub discover {
|
|||||||
store_vlans($device, $snmp);
|
store_vlans($device, $snmp);
|
||||||
store_power($device, $snmp);
|
store_power($device, $snmp);
|
||||||
store_modules($device, $snmp);
|
store_modules($device, $snmp);
|
||||||
|
discover_new_neighbors($device, $snmp);
|
||||||
|
|
||||||
return job_done("Ended discover for $host");
|
return job_done("Ended discover for ". $host->addr);
|
||||||
}
|
|
||||||
|
|
||||||
# run find_neighbors on all known devices, and run discover on any
|
|
||||||
# newly found devices.
|
|
||||||
sub discovernew {
|
|
||||||
my ($self, $job) = @_;
|
|
||||||
|
|
||||||
my $devices = schema('netdisco')->resultset('Device')->get_column('ip');
|
|
||||||
|
|
||||||
schema('netdisco')->resultset('Admin')->populate([
|
|
||||||
map {{
|
|
||||||
device => $_,
|
|
||||||
action => 'discover_neighbors',
|
|
||||||
status => 'queued',
|
|
||||||
}} ($devices->all)
|
|
||||||
]);
|
|
||||||
|
|
||||||
return job_done("Queued discover_neighbors job for all devices");
|
|
||||||
}
|
|
||||||
|
|
||||||
sub discover_neighbors {
|
|
||||||
my ($self, $job) = @_;
|
|
||||||
|
|
||||||
my $host = NetAddr::IP::Lite->new($job->device);
|
|
||||||
my $device = get_device($host->addr);
|
|
||||||
my $snmp = snmp_connect($device);
|
|
||||||
|
|
||||||
if (!defined $snmp) {
|
|
||||||
return job_error("discover_neighbors failed: could not SNMP connect to $host");
|
|
||||||
}
|
|
||||||
|
|
||||||
find_neighbors($device, $snmp);
|
|
||||||
|
|
||||||
return job_done("Ended find_neighbors for $host");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|||||||
71
Netdisco/lib/App/Netdisco/Daemon/Worker/Poller/Macsuck.pm
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package App::Netdisco::Daemon::Worker::Poller::Macsuck;
|
||||||
|
|
||||||
|
use Dancer qw/:moose :syntax :script/;
|
||||||
|
use Dancer::Plugin::DBIC 'schema';
|
||||||
|
|
||||||
|
use App::Netdisco::Util::SNMP 'snmp_connect';
|
||||||
|
use App::Netdisco::Util::Device 'get_device';
|
||||||
|
use App::Netdisco::Core::Macsuck ':all';
|
||||||
|
use App::Netdisco::Daemon::Util ':all';
|
||||||
|
|
||||||
|
use NetAddr::IP::Lite ':lower';
|
||||||
|
|
||||||
|
use Role::Tiny;
|
||||||
|
use namespace::clean;
|
||||||
|
|
||||||
|
# queue a macsuck job for all devices known to Netdisco
|
||||||
|
sub macwalk {
|
||||||
|
my ($self, $job) = @_;
|
||||||
|
|
||||||
|
my $devices = schema('netdisco')->resultset('Device')->get_column('ip');
|
||||||
|
my $jobqueue = schema('netdisco')->resultset('Admin');
|
||||||
|
|
||||||
|
schema('netdisco')->txn_do(sub {
|
||||||
|
# clean up user submitted jobs older than 1min,
|
||||||
|
# assuming skew between schedulers' clocks is not greater than 1min
|
||||||
|
$jobqueue->search({
|
||||||
|
action => 'macsuck',
|
||||||
|
status => 'queued',
|
||||||
|
entered => { '<' => \"(now() - interval '1 minute')" },
|
||||||
|
})->delete;
|
||||||
|
|
||||||
|
# is scuppered by any user job submitted in last 1min (bad), or
|
||||||
|
# any similar job from another scheduler (good)
|
||||||
|
$jobqueue->populate([
|
||||||
|
map {{
|
||||||
|
device => $_,
|
||||||
|
action => 'macsuck',
|
||||||
|
status => 'queued',
|
||||||
|
}} ($devices->all)
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return job_done("Queued macsuck job for all devices");
|
||||||
|
}
|
||||||
|
|
||||||
|
sub macsuck {
|
||||||
|
my ($self, $job) = @_;
|
||||||
|
|
||||||
|
my $host = NetAddr::IP::Lite->new($job->device);
|
||||||
|
my $device = get_device($host->addr);
|
||||||
|
|
||||||
|
if ($device->in_storage
|
||||||
|
and $device->vendor and $device->vendor eq 'netdisco') {
|
||||||
|
return job_done("Skipped macsuck for pseudo-device $host");
|
||||||
|
}
|
||||||
|
|
||||||
|
my $snmp = snmp_connect($device);
|
||||||
|
if (!defined $snmp) {
|
||||||
|
return job_error("macsuck failed: could not SNMP connect to $host");
|
||||||
|
}
|
||||||
|
|
||||||
|
unless ($snmp->has_layer(2)) {
|
||||||
|
return job_done("Skipped macsuck for device $host without OSI layer 2 capability");
|
||||||
|
}
|
||||||
|
|
||||||
|
do_macsuck($device, $snmp);
|
||||||
|
|
||||||
|
return job_done("Ended macsuck for ". $host->addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
@@ -11,12 +11,11 @@ use namespace::clean;
|
|||||||
|
|
||||||
my $jobactions = {
|
my $jobactions = {
|
||||||
map {$_ => undef} qw/
|
map {$_ => undef} qw/
|
||||||
refresh
|
discoverall
|
||||||
discovernew
|
arpwalk
|
||||||
|
macwalk
|
||||||
/
|
/
|
||||||
# saveconfigs
|
# saveconfigs
|
||||||
# macwalk
|
|
||||||
# arpwalk
|
|
||||||
# nbtwalk
|
# nbtwalk
|
||||||
# backup
|
# backup
|
||||||
};
|
};
|
||||||
|
|||||||
142
Netdisco/lib/App/Netdisco/Manual/Configuration.pod
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
App::Netdisco::Manual::Configuration - How to Configure Netdisco
|
||||||
|
|
||||||
|
=head1 INTRODUCTION
|
||||||
|
|
||||||
|
The configuration files for Netdisco come with all options set to sensible
|
||||||
|
default values, and just a few that you must initially set yourself.
|
||||||
|
|
||||||
|
However as you use the system over time, there are many situations where you
|
||||||
|
might want to tune the behaviour of Netdisco, and for that we have a lot of
|
||||||
|
configuration settings available.
|
||||||
|
|
||||||
|
=head2 GUIDANCE
|
||||||
|
|
||||||
|
There are two configuration files: C<config.yml> (which lives inside Netdisco)
|
||||||
|
and C<deployment.yml> (which usually lives in C<${HOME}/environments>).
|
||||||
|
|
||||||
|
The C<config.yml> file includes defaults for every setting, and should be left
|
||||||
|
alone. Any time you want to set an option, use only the C<deployment.yml>
|
||||||
|
file. The two are merged when Netdisco starts, with your settings in
|
||||||
|
C<deployment.yml> overriding the defaults from C<config.yml>.
|
||||||
|
|
||||||
|
The configuration file format for Netdisco is YAML. This is easy for humans to
|
||||||
|
edit, but you should take care over whitespace and avoid TAB characters. YAML
|
||||||
|
supports several data types:
|
||||||
|
|
||||||
|
=over 4
|
||||||
|
|
||||||
|
=item *
|
||||||
|
|
||||||
|
Boolean - True/False value, using C<1> and C<0> or C<true> and C<false>
|
||||||
|
respectively
|
||||||
|
|
||||||
|
=item *
|
||||||
|
|
||||||
|
List - Set of things using C<[a, b, c]> on one line or C<-> on separate lines
|
||||||
|
|
||||||
|
=item *
|
||||||
|
|
||||||
|
Dictionary - Key/Value pairs (like Perl Hash) using C<{key1: val1, key2,
|
||||||
|
val2}> on one line or C<key: value> on separate lines
|
||||||
|
|
||||||
|
=item *
|
||||||
|
|
||||||
|
String - Quoted, just like in Perl (and essential if the item contains the
|
||||||
|
colon character)
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
=head1 SUPPORTED SETTINGS
|
||||||
|
|
||||||
|
=head2 Essential Settings
|
||||||
|
|
||||||
|
If you followed the installation instructions, then you should have set the
|
||||||
|
database connection parameters to match those of your local system. That is,
|
||||||
|
the C<dsn> (DB name, host, port), C<user> and C<pass>.
|
||||||
|
|
||||||
|
=head2 General Settings
|
||||||
|
|
||||||
|
=head3 C<log: debug|warning|error>
|
||||||
|
|
||||||
|
Default: C<warning>
|
||||||
|
|
||||||
|
The log level used by Netdisco. It's useful to see warning messages from the
|
||||||
|
backend poller, as this can highlight broken topology.
|
||||||
|
|
||||||
|
=head3 C<logger: console|file>
|
||||||
|
|
||||||
|
Default: C<file>
|
||||||
|
|
||||||
|
Destination for log messages. Console means standard ouput. When set to
|
||||||
|
C<file>, the default destination is the C<${HOME}/logs> directory.
|
||||||
|
|
||||||
|
=head3 C<logger_format: String>
|
||||||
|
|
||||||
|
Default: C<< '[%P] %L @%D> %m' >>
|
||||||
|
|
||||||
|
Structure of the log messages. See L<Dancer::Logger::Abstract/"logger_format">
|
||||||
|
for details.
|
||||||
|
|
||||||
|
=head2 Web Frontend
|
||||||
|
|
||||||
|
=head3 C<domain_suffix: String>
|
||||||
|
|
||||||
|
Default: None
|
||||||
|
|
||||||
|
Set this to your local site's domain name. This is usually removed from node
|
||||||
|
names in the web interface to make things more readable.
|
||||||
|
|
||||||
|
=head3 C<no_auth: Boolean>
|
||||||
|
|
||||||
|
Default: C<false>
|
||||||
|
|
||||||
|
Enable this to disable login authentication in the web frontend. The username
|
||||||
|
will be set to C<guest> so if you want to allow extended permissions (C<admin>
|
||||||
|
or C<port_control>, create a dummy user with the appropriate flag, in the
|
||||||
|
database:
|
||||||
|
|
||||||
|
netdisco=> insert into users (username, port_control) values ('guest', true);
|
||||||
|
|
||||||
|
=head3 C<port: String>
|
||||||
|
|
||||||
|
Default: C<5000>
|
||||||
|
|
||||||
|
Port which the web server listens on. Netdisco comes with a good pre-forking
|
||||||
|
web server, so you can change this to C<80> if you want to use it directly.
|
||||||
|
However the default is designed to work well with servers such as Apache in
|
||||||
|
reverse-proxy mode.
|
||||||
|
|
||||||
|
=head3 C<web_plugins: List of String>
|
||||||
|
|
||||||
|
Default: List of L<App::Netdisco::Web::Plugin> names
|
||||||
|
|
||||||
|
Netdisco's plugin system allows the user more control over the user interface.
|
||||||
|
Plugins can be distributed independently from Netdisco and are a better
|
||||||
|
alternative to source code patches. This setting is the list of Plugins which
|
||||||
|
are used in the default Netdisco distribution.
|
||||||
|
|
||||||
|
You can override this to set your own list. If you only want to add to the
|
||||||
|
default list then use C<extra_web_plugins>, which allows the Netdisco
|
||||||
|
developers to update C<web_plugins> in a future release.
|
||||||
|
|
||||||
|
=head3 C<extra_web_plugins: List of String>
|
||||||
|
|
||||||
|
Default: None
|
||||||
|
|
||||||
|
List of additional L<App::Netdisco::Web::Plugin> names to load. See also the
|
||||||
|
C<web_plugins> setting.
|
||||||
|
|
||||||
|
=head2 Netdisco Core
|
||||||
|
|
||||||
|
=head2 Backend Daemon
|
||||||
|
|
||||||
|
=head2 Dancer Internal
|
||||||
|
|
||||||
|
=head1 UNSUPPORTED SETTINGS
|
||||||
|
|
||||||
|
These settings are from Netdisco 1.x but are yet to be supported in Netdisco
|
||||||
|
2. If you really need the feature, please let the developers know.
|
||||||
|
|
||||||
|
=cut
|
||||||
@@ -22,6 +22,11 @@ parameter to the web startup script:
|
|||||||
|
|
||||||
~/bin/netdisco-web --path /netdisco2
|
~/bin/netdisco-web --path /netdisco2
|
||||||
|
|
||||||
|
Alternatively, can set the C<path> configuration option in your
|
||||||
|
C<deployment.yml> file:
|
||||||
|
|
||||||
|
path: '/netdisco2'
|
||||||
|
|
||||||
=head1 Behind a Proxy
|
=head1 Behind a Proxy
|
||||||
|
|
||||||
By default the web application daemon starts listening on port 5000 and goes
|
By default the web application daemon starts listening on port 5000 and goes
|
||||||
@@ -39,8 +44,13 @@ configuration would be:
|
|||||||
Allow from all
|
Allow from all
|
||||||
</Proxy>
|
</Proxy>
|
||||||
|
|
||||||
|
You also need to set the following configuration in your C<deployment.yml>
|
||||||
|
file:
|
||||||
|
|
||||||
|
behind_proxy: 1
|
||||||
|
|
||||||
To combine this with Non-root Hosting as above, simply change the paths
|
To combine this with Non-root Hosting as above, simply change the paths
|
||||||
referenced in the configuration like so (and use C<--path> option):
|
referenced in the configuration like so (and use Non-root Hosting as above):
|
||||||
|
|
||||||
ProxyPass /netdisco2 http://localhost:5000/
|
ProxyPass /netdisco2 http://localhost:5000/
|
||||||
ProxyPassReverse /netdisco2 http://localhost:5000/
|
ProxyPassReverse /netdisco2 http://localhost:5000/
|
||||||
|
|||||||
@@ -175,6 +175,24 @@ any query parameters which might customize the report search.
|
|||||||
See the L<App::Netdisco::Web::Plugin::Report::DuplexMismatch> module for a
|
See the L<App::Netdisco::Web::Plugin::Report::DuplexMismatch> module for a
|
||||||
simple example of how to implement the handler.
|
simple example of how to implement the handler.
|
||||||
|
|
||||||
|
=head1 Admin Tasks
|
||||||
|
|
||||||
|
These components appear in the black navigation bar under an Admin menu, but only
|
||||||
|
if the logged in user has Administrator rights in Netdisco.
|
||||||
|
|
||||||
|
To register an item for display in the Admin menu, use the following code:
|
||||||
|
|
||||||
|
register_admin_task({
|
||||||
|
tag => 'newfeature',
|
||||||
|
label => 'My New Feature',
|
||||||
|
});
|
||||||
|
|
||||||
|
This causes an item to appear in the Admin menu with a visible text of "My New
|
||||||
|
Feature" which when clicked sends the user to the C</admin/mynewfeature> page.
|
||||||
|
Note that this won't work for any target link - the path must be an
|
||||||
|
App::Netdisco Dancer route handler. Please bug the App::Netdisco devs if you
|
||||||
|
want arbitrary links supported.
|
||||||
|
|
||||||
=head1 Templates
|
=head1 Templates
|
||||||
|
|
||||||
All of Netdisco's web page templates are stashed away in its distribution,
|
All of Netdisco's web page templates are stashed away in its distribution,
|
||||||
|
|||||||
56
Netdisco/lib/App/Netdisco/Util/PortMAC.pm
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package App::Netdisco::Util::PortMAC;
|
||||||
|
|
||||||
|
use Dancer qw/:syntax :script/;
|
||||||
|
use Dancer::Plugin::DBIC 'schema';
|
||||||
|
|
||||||
|
use base 'Exporter';
|
||||||
|
our @EXPORT = ();
|
||||||
|
our @EXPORT_OK = qw/ get_port_macs /;
|
||||||
|
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
App::Netdisco::Util::PortMAC
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
Helper subroutine to support parts of the Netdisco application.
|
||||||
|
|
||||||
|
There are no default exports, however the C<:all> tag will export all
|
||||||
|
subroutines.
|
||||||
|
|
||||||
|
=head1 EXPORT_OK
|
||||||
|
|
||||||
|
=head2 get_port_macs( $device )
|
||||||
|
|
||||||
|
Returns a Hash reference of C<< { MAC => IP } >> for all interface MAC
|
||||||
|
addresses on a device.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub get_port_macs {
|
||||||
|
my $device = shift;
|
||||||
|
my $port_macs = {};
|
||||||
|
|
||||||
|
unless ($device->in_storage) {
|
||||||
|
debug sprintf ' [%s] get_port_macs - skipping device not yet discovered',
|
||||||
|
$device->ip;
|
||||||
|
return $port_macs;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $dp_macs = schema('netdisco')->resultset('DevicePort')
|
||||||
|
->search({ mac => { '!=' => undef} });
|
||||||
|
while (my $r = $dp_macs->next) {
|
||||||
|
$port_macs->{ $r->mac } = $r->ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $d_macs = schema('netdisco')->resultset('Device')
|
||||||
|
->search({ mac => { '!=' => undef} });
|
||||||
|
while (my $r = $d_macs->next) {
|
||||||
|
$port_macs->{ $r->mac } = $r->ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $port_macs;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
@@ -10,7 +10,7 @@ use Path::Class 'dir';
|
|||||||
use base 'Exporter';
|
use base 'Exporter';
|
||||||
our @EXPORT = ();
|
our @EXPORT = ();
|
||||||
our @EXPORT_OK = qw/
|
our @EXPORT_OK = qw/
|
||||||
snmp_connect snmp_connect_rw
|
snmp_connect snmp_connect_rw snmp_comm_reindex
|
||||||
/;
|
/;
|
||||||
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||||
|
|
||||||
@@ -88,8 +88,8 @@ sub _snmp_connect_generic {
|
|||||||
my $comm_type = pop;
|
my $comm_type = pop;
|
||||||
my @communities = @{ setting($comm_type) || []};
|
my @communities = @{ setting($comm_type) || []};
|
||||||
unshift @communities, $device->snmp_comm
|
unshift @communities, $device->snmp_comm
|
||||||
if length $device->snmp_comm
|
if defined $device->snmp_comm
|
||||||
and length $comm_type and $comm_type eq 'community';
|
and defined $comm_type and $comm_type eq 'community';
|
||||||
|
|
||||||
my $info = undef;
|
my $info = undef;
|
||||||
VERSION: foreach my $ver (@versions) {
|
VERSION: foreach my $ver (@versions) {
|
||||||
@@ -123,7 +123,7 @@ sub _try_connect {
|
|||||||
$info = $class->new(%$snmp_args, Version => $ver, Community => $comm);
|
$info = $class->new(%$snmp_args, Version => $ver, Community => $comm);
|
||||||
undef $info unless (
|
undef $info unless (
|
||||||
(not defined $info->error)
|
(not defined $info->error)
|
||||||
and length $info->uptime
|
and defined $info->uptime
|
||||||
and ($info->layers or $info->description)
|
and ($info->layers or $info->description)
|
||||||
and $info->class
|
and $info->class
|
||||||
);
|
);
|
||||||
@@ -149,7 +149,35 @@ sub _try_connect {
|
|||||||
sub _build_mibdirs {
|
sub _build_mibdirs {
|
||||||
my $home = (setting('mibhome') || $ENV{NETDISCO_HOME} || $ENV{HOME});
|
my $home = (setting('mibhome') || $ENV{NETDISCO_HOME} || $ENV{HOME});
|
||||||
return map { dir($home, $_) }
|
return map { dir($home, $_) }
|
||||||
@{ setting('mibdirs') || [] };
|
@{ setting('mibdirs') || _get_mibdirs_content($home) };
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _get_mibdirs_content {
|
||||||
|
my $home = shift;
|
||||||
|
warning 'Netdisco SNMP work will be really slow - loading ALL MIBs. Please set mibdirs.';
|
||||||
|
my @list = map {s|$home/||; $_} grep {-d} glob("$home/*");
|
||||||
|
return \@list;
|
||||||
|
}
|
||||||
|
|
||||||
|
=head2 snmp_comm_reindex( $snmp, $vlan )
|
||||||
|
|
||||||
|
Takes an established L<SNMP::Info> instance and makes a fresh connection using
|
||||||
|
community indexing, with the given C<$vlan> ID. Works for all SNMP versions.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub snmp_comm_reindex {
|
||||||
|
my ($snmp, $vlan) = @_;
|
||||||
|
|
||||||
|
my $ver = $snmp->snmp_ver;
|
||||||
|
my $comm = $snmp->snmp_comm;
|
||||||
|
|
||||||
|
if ($ver == 3) {
|
||||||
|
$snmp->update(Context => "vlan-$vlan");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$snmp->update(Community => $comm . '@' . $vlan);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|||||||
115
Netdisco/lib/App/Netdisco/Util/SanityCheck.pm
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package App::Netdisco::Util::SanityCheck;
|
||||||
|
|
||||||
|
use Dancer qw/:syntax :script/;
|
||||||
|
use Dancer::Plugin::DBIC 'schema';
|
||||||
|
|
||||||
|
use App::Netdisco::Util::PortMAC ':all';
|
||||||
|
use Net::MAC;
|
||||||
|
|
||||||
|
use base 'Exporter';
|
||||||
|
our @EXPORT = ();
|
||||||
|
our @EXPORT_OK = qw/ check_mac /;
|
||||||
|
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
App::Netdisco::Util::SanityCheck
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
Helper subroutines to support parts of the Netdisco application.
|
||||||
|
|
||||||
|
There are no default exports, however the C<:all> tag will export all
|
||||||
|
subroutines.
|
||||||
|
|
||||||
|
=head1 EXPORT_OK
|
||||||
|
|
||||||
|
=head2 check_mac( $device, $node, $port_macs? )
|
||||||
|
|
||||||
|
Given a Device database object and a MAC address, perform various sanity
|
||||||
|
checks which need to be done before writing an ARP/Neighbor entry to the
|
||||||
|
database storage.
|
||||||
|
|
||||||
|
Returns false, and might log a debug level message, if the checks fail.
|
||||||
|
|
||||||
|
Returns a true value if these checks pass:
|
||||||
|
|
||||||
|
=over 4
|
||||||
|
|
||||||
|
=item *
|
||||||
|
|
||||||
|
MAC address is well-formed (according to common formats)
|
||||||
|
|
||||||
|
=item *
|
||||||
|
|
||||||
|
MAC address is not all-zero, broadcast, CLIP, VRRP or HSRP
|
||||||
|
|
||||||
|
=item *
|
||||||
|
|
||||||
|
MAC address does not belong to an interface on any known Device
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
Optionally pass a cached set of Device port MAC addresses as the third
|
||||||
|
argument, or else C<check_mac> will retrieve this for itself from the
|
||||||
|
database.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub check_mac {
|
||||||
|
my ($device, $node, $port_macs) = @_;
|
||||||
|
$port_macs ||= get_port_macs($device);
|
||||||
|
my $mac = Net::MAC->new(mac => $node, 'die' => 0, verbose => 0);
|
||||||
|
|
||||||
|
# incomplete MAC addresses (BayRS frame relay DLCI, etc)
|
||||||
|
if ($mac->get_error) {
|
||||||
|
debug sprintf ' [%s] check_mac - mac [%s] malformed - skipping',
|
||||||
|
$device->ip, $node;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
# lower case, hex, colon delimited, 8-bit groups
|
||||||
|
$node = lc $mac->as_IEEE;
|
||||||
|
}
|
||||||
|
|
||||||
|
# broadcast MAC addresses
|
||||||
|
return 0 if $node eq 'ff:ff:ff:ff:ff:ff';
|
||||||
|
|
||||||
|
# all-zero MAC addresses
|
||||||
|
return 0 if $node eq '00:00:00:00:00:00';
|
||||||
|
|
||||||
|
# CLIP
|
||||||
|
return 0 if $node eq '00:00:00:00:00:01';
|
||||||
|
|
||||||
|
# multicast
|
||||||
|
if ($node =~ m/^[0-9a-f](?:1|3|5|7|9|b|d|f):/) {
|
||||||
|
debug sprintf ' [%s] check_mac - multicast mac [%s] - skipping',
|
||||||
|
$device->ip, $node;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
# VRRP
|
||||||
|
if (index($node, '00:00:5e:00:01:') == 0) {
|
||||||
|
debug sprintf ' [%s] check_mac - VRRP mac [%s] - skipping',
|
||||||
|
$device->ip, $node;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
# HSRP
|
||||||
|
if (index($node, '00:00:0c:07:ac:') == 0) {
|
||||||
|
debug sprintf ' [%s] check_mac - HSRP mac [%s] - skipping',
|
||||||
|
$device->ip, $node;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
# device's own MACs
|
||||||
|
if (exists $port_macs->{$node}) {
|
||||||
|
debug sprintf ' [%s] check_mac - mac [%s] is device port - skipping',
|
||||||
|
$device->ip, $node;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
@@ -8,11 +8,14 @@ use Dancer::Plugin::DBIC;
|
|||||||
use Socket6 (); # to ensure dependency is met
|
use Socket6 (); # to ensure dependency is met
|
||||||
use HTML::Entities (); # to ensure dependency is met
|
use HTML::Entities (); # to ensure dependency is met
|
||||||
use URI::QueryParam (); # part of URI, to add helper methods
|
use URI::QueryParam (); # part of URI, to add helper methods
|
||||||
|
use Path::Class 'dir';
|
||||||
|
|
||||||
use App::Netdisco::Web::AuthN;
|
use App::Netdisco::Web::AuthN;
|
||||||
|
use App::Netdisco::Web::Static;
|
||||||
use App::Netdisco::Web::Search;
|
use App::Netdisco::Web::Search;
|
||||||
use App::Netdisco::Web::Device;
|
use App::Netdisco::Web::Device;
|
||||||
use App::Netdisco::Web::Report;
|
use App::Netdisco::Web::Report;
|
||||||
|
use App::Netdisco::Web::AdminTask;
|
||||||
use App::Netdisco::Web::TypeAhead;
|
use App::Netdisco::Web::TypeAhead;
|
||||||
use App::Netdisco::Web::PortControl;
|
use App::Netdisco::Web::PortControl;
|
||||||
|
|
||||||
@@ -20,8 +23,9 @@ sub _load_web_plugins {
|
|||||||
my $plugin_list = shift;
|
my $plugin_list = shift;
|
||||||
|
|
||||||
foreach my $plugin (@$plugin_list) {
|
foreach my $plugin (@$plugin_list) {
|
||||||
|
$plugin =~ s/^X::/+App::NetdiscoX::Web::Plugin::/;
|
||||||
$plugin = 'App::Netdisco::Web::Plugin::'. $plugin
|
$plugin = 'App::Netdisco::Web::Plugin::'. $plugin
|
||||||
unless $plugin =~ m/^\+/;
|
if $plugin !~ m/^\+/;
|
||||||
$plugin =~ s/^\+//;
|
$plugin =~ s/^\+//;
|
||||||
|
|
||||||
debug "loading Netdisco plugin $plugin";
|
debug "loading Netdisco plugin $plugin";
|
||||||
@@ -35,6 +39,7 @@ if (setting('web_plugins') and ref [] eq ref setting('web_plugins')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (setting('extra_web_plugins') and ref [] eq ref setting('extra_web_plugins')) {
|
if (setting('extra_web_plugins') and ref [] eq ref setting('extra_web_plugins')) {
|
||||||
|
unshift @INC, dir(($ENV{NETDISCO_HOME} || $ENV{HOME}), 'site_plugins')->stringify;
|
||||||
_load_web_plugins( setting('extra_web_plugins') );
|
_load_web_plugins( setting('extra_web_plugins') );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,9 +55,18 @@ hook 'before_template' => sub {
|
|||||||
|
|
||||||
# allow very long lists of ports
|
# allow very long lists of ports
|
||||||
$Template::Directive::WHILE_MAX = 10_000;
|
$Template::Directive::WHILE_MAX = 10_000;
|
||||||
|
|
||||||
|
# allow hash keys with leading underscores
|
||||||
|
$Template::Stash::PRIVATE = undef;
|
||||||
};
|
};
|
||||||
|
|
||||||
get '/' => sub {
|
get '/' => sub {
|
||||||
|
if (var('user') and var('user')->admin) {
|
||||||
|
if (schema('netdisco')->resultset('Device')->count == 0) {
|
||||||
|
var('nodevices' => true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template 'index';
|
template 'index';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
80
Netdisco/lib/App/Netdisco/Web/AdminTask.pm
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package App::Netdisco::Web::AdminTask;
|
||||||
|
|
||||||
|
use Dancer ':syntax';
|
||||||
|
use Dancer::Plugin::Ajax;
|
||||||
|
use Dancer::Plugin::DBIC;
|
||||||
|
|
||||||
|
use Try::Tiny;
|
||||||
|
|
||||||
|
sub add_job {
|
||||||
|
my ($jobtype, $device) = @_;
|
||||||
|
|
||||||
|
if ($device) {
|
||||||
|
$device = NetAddr::IP::Lite->new($device);
|
||||||
|
return send_error('Bad device', 400)
|
||||||
|
if ! $device or $device->addr eq '0.0.0.0';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
# job might already be in the queue, so this could die
|
||||||
|
schema('netdisco')->resultset('Admin')->create({
|
||||||
|
($device ? (device => $device->addr) : ()),
|
||||||
|
action => $jobtype,
|
||||||
|
status => 'queued',
|
||||||
|
username => session('user'),
|
||||||
|
userip => request->remote_address,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
# we have a separate list for jobs needing a device to avoid queueing
|
||||||
|
# such a job when there's no device param (it could still be duff, tho).
|
||||||
|
my %jobs = map { $_ => 1} qw/
|
||||||
|
discover
|
||||||
|
macsuck
|
||||||
|
arpnip
|
||||||
|
/;
|
||||||
|
my %jobs_all = map {$_ => 1} qw/
|
||||||
|
discoverall
|
||||||
|
macwalk
|
||||||
|
arpwalk
|
||||||
|
/;
|
||||||
|
|
||||||
|
foreach my $jobtype (keys %jobs_all, keys %jobs) {
|
||||||
|
ajax "/ajax/control/admin/$jobtype" => sub {
|
||||||
|
send_error('Forbidden', 403)
|
||||||
|
unless var('user')->admin;
|
||||||
|
send_error('Missing device', 400)
|
||||||
|
if exists $jobs{$jobtype} and not param('device');
|
||||||
|
|
||||||
|
add_job($jobtype, param('device'));
|
||||||
|
};
|
||||||
|
|
||||||
|
post "/admin/$jobtype" => sub {
|
||||||
|
send_error('Forbidden', 403)
|
||||||
|
unless var('user')->admin;
|
||||||
|
send_error('Missing device', 400)
|
||||||
|
if exists $jobs{$jobtype} and not param('device');
|
||||||
|
|
||||||
|
add_job($jobtype, param('device'));
|
||||||
|
redirect uri_for('/admin/jobqueue')->path_query;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get '/admin/*' => sub {
|
||||||
|
my ($tag) = splat;
|
||||||
|
|
||||||
|
if (! eval { var('user')->admin }) {
|
||||||
|
return redirect uri_for('/')->path_query;
|
||||||
|
}
|
||||||
|
|
||||||
|
# trick the ajax into working as if this were a tabbed page
|
||||||
|
params->{tab} = $tag;
|
||||||
|
|
||||||
|
var(nav => 'admin');
|
||||||
|
template 'admintask', {
|
||||||
|
task => setting('_admin_tasks')->{ $tag },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
true;
|
||||||
@@ -18,30 +18,31 @@ hook 'before' => sub {
|
|||||||
if (session('user') && session->id) {
|
if (session('user') && session->id) {
|
||||||
var(user => schema('netdisco')->resultset('User')
|
var(user => schema('netdisco')->resultset('User')
|
||||||
->find(session('user')));
|
->find(session('user')));
|
||||||
|
|
||||||
|
# really just for dev work, to quieten the logs
|
||||||
|
var('user')->port_control(0) if setting('no_port_control');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
post '/login' => sub {
|
post '/login' => sub {
|
||||||
status(302);
|
|
||||||
|
|
||||||
if (param('username') and param('password')) {
|
if (param('username') and param('password')) {
|
||||||
my $user = schema('netdisco')->resultset('User')->find(param('username'));
|
my $user = schema('netdisco')->resultset('User')->find(param('username'));
|
||||||
|
|
||||||
if ($user) {
|
if ($user) {
|
||||||
my $sum = Digest::MD5::md5_hex(param('password'));
|
my $sum = Digest::MD5::md5_hex(param('password'));
|
||||||
if (($sum and $user->password) and ($sum eq $user->password)) {
|
if (($sum and $user->password) and ($sum eq $user->password)) {
|
||||||
session(user => $user->username);
|
session(user => $user->username);
|
||||||
header(Location => uri_for('/inventory')->path_query());
|
return redirect uri_for('/inventory')->path_query;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
header(Location => uri_for('/', {failed => 1})->path_query());
|
|
||||||
|
redirect uri_for('/', {failed => 1})->path_query;
|
||||||
};
|
};
|
||||||
|
|
||||||
get '/logout' => sub {
|
get '/logout' => sub {
|
||||||
session->destroy;
|
session->destroy;
|
||||||
status(302);
|
redirect uri_for('/', {logout => 1})->path_query;
|
||||||
header(Location => uri_for('/', {logout => 1})->path_query());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
true;
|
true;
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ use Dancer::Plugin::Ajax;
|
|||||||
use Dancer::Plugin::DBIC;
|
use Dancer::Plugin::DBIC;
|
||||||
|
|
||||||
hook 'before' => sub {
|
hook 'before' => sub {
|
||||||
# list of port detail columns
|
my @default_port_columns_left = (
|
||||||
var('port_columns' => [
|
{ name => 'c_admin', label => 'Port Controls', default => '' },
|
||||||
{ name => 'c_admin', label => 'Admin Controls', default => '' },
|
|
||||||
{ name => 'c_port', label => 'Port', default => 'on' },
|
{ name => 'c_port', label => 'Port', default => 'on' },
|
||||||
|
);
|
||||||
|
|
||||||
|
my @default_port_columns_right = (
|
||||||
{ name => 'c_descr', label => 'Description', default => '' },
|
{ name => 'c_descr', label => 'Description', default => '' },
|
||||||
{ name => 'c_type', label => 'Type', default => '' },
|
{ name => 'c_type', label => 'Type', default => '' },
|
||||||
{ name => 'c_duplex', label => 'Duplex', default => '' },
|
{ name => 'c_duplex', label => 'Duplex', default => '' },
|
||||||
@@ -24,7 +26,21 @@ hook 'before' => sub {
|
|||||||
{ name => 'c_neighbors', label => 'Connected Devices', default => 'on' },
|
{ name => 'c_neighbors', label => 'Connected Devices', default => 'on' },
|
||||||
{ name => 'c_stp', label => 'Spanning Tree', default => '' },
|
{ name => 'c_stp', label => 'Spanning Tree', default => '' },
|
||||||
{ name => 'c_up', label => 'Status', default => '' },
|
{ name => 'c_up', label => 'Status', default => '' },
|
||||||
]);
|
);
|
||||||
|
|
||||||
|
# build list of port detail columns
|
||||||
|
my @port_columns = ();
|
||||||
|
|
||||||
|
push @port_columns,
|
||||||
|
grep {$_->{position} eq 'left'} @{ setting('_extra_device_port_cols') };
|
||||||
|
push @port_columns, @default_port_columns_left;
|
||||||
|
push @port_columns,
|
||||||
|
grep {$_->{position} eq 'mid'} @{ setting('_extra_device_port_cols') };
|
||||||
|
push @port_columns, @default_port_columns_right;
|
||||||
|
push @port_columns,
|
||||||
|
grep {$_->{position} eq 'right'} @{ setting('_extra_device_port_cols') };
|
||||||
|
|
||||||
|
var('port_columns' => \@port_columns);
|
||||||
|
|
||||||
# view settings for port connected devices
|
# view settings for port connected devices
|
||||||
var('connected_properties' => [
|
var('connected_properties' => [
|
||||||
@@ -100,9 +116,7 @@ get '/device' => sub {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!defined $dev) {
|
if (!defined $dev) {
|
||||||
status(302);
|
return redirect uri_for('/', {nosuchdevice => 1})->path_query;
|
||||||
header(Location => uri_for('/', {nosuchdevice => 1})->path_query());
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
params->{'tab'} ||= 'details';
|
params->{'tab'} ||= 'details';
|
||||||
|
|||||||
@@ -4,12 +4,16 @@ use Dancer ':syntax';
|
|||||||
use Dancer::Plugin;
|
use Dancer::Plugin;
|
||||||
|
|
||||||
set(
|
set(
|
||||||
'navbar_items' => [],
|
'_additional_css' => [],
|
||||||
'search_tabs' => [],
|
'_additional_javascript' => [],
|
||||||
'device_tabs' => [],
|
'_extra_device_port_cols' => [],
|
||||||
'reports_menu' => {},
|
'_navbar_items' => [],
|
||||||
'reports' => {},
|
'_search_tabs' => [],
|
||||||
'report_order' => [qw/Device Port Node VLAN Network Wireless/],
|
'_device_tabs' => [],
|
||||||
|
'_admin_tasks' => {},
|
||||||
|
'_reports_menu' => {},
|
||||||
|
'_reports' => {},
|
||||||
|
'_report_order' => [qw/Device Port Node VLAN Network Wireless/],
|
||||||
);
|
);
|
||||||
|
|
||||||
# this is what Dancer::Template::TemplateToolkit does by default
|
# this is what Dancer::Template::TemplateToolkit does by default
|
||||||
@@ -19,8 +23,7 @@ register 'register_template_path' => sub {
|
|||||||
my ($self, $path) = plugin_args(@_);
|
my ($self, $path) = plugin_args(@_);
|
||||||
|
|
||||||
if (!length $path) {
|
if (!length $path) {
|
||||||
error "bad template path to register_template_paths";
|
return error "bad template path to register_template_paths";
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unshift
|
unshift
|
||||||
@@ -28,6 +31,49 @@ register 'register_template_path' => sub {
|
|||||||
$path;
|
$path;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sub _register_include {
|
||||||
|
my ($type, $plugin) = @_;
|
||||||
|
|
||||||
|
if (!length $type) {
|
||||||
|
return error "bad type to _register_include";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!length $plugin) {
|
||||||
|
return error "bad plugin name to register_$type";
|
||||||
|
}
|
||||||
|
|
||||||
|
push @{ setting("_additional_$type") }, $plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
register 'register_css' => sub {
|
||||||
|
my ($self, $plugin) = plugin_args(@_);
|
||||||
|
_register_include('css', $plugin);
|
||||||
|
};
|
||||||
|
|
||||||
|
register 'register_javascript' => sub {
|
||||||
|
my ($self, $plugin) = plugin_args(@_);
|
||||||
|
_register_include('javascript', $plugin);
|
||||||
|
};
|
||||||
|
|
||||||
|
register 'register_device_port_column' => sub {
|
||||||
|
my ($self, $config) = plugin_args(@_);
|
||||||
|
$config->{default} ||= '';
|
||||||
|
$config->{position} ||= 'right';
|
||||||
|
|
||||||
|
if (!length $config->{name} or !length $config->{label}) {
|
||||||
|
return error "bad config to register_device_port_column";
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach my $item (@{ setting('_extra_device_port_cols') }) {
|
||||||
|
if ($item->{name} eq $config->{name}) {
|
||||||
|
$item = $config;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
push @{ setting('_extra_device_port_cols') }, $config;
|
||||||
|
};
|
||||||
|
|
||||||
register 'register_navbar_item' => sub {
|
register 'register_navbar_item' => sub {
|
||||||
my ($self, $config) = plugin_args(@_);
|
my ($self, $config) = plugin_args(@_);
|
||||||
|
|
||||||
@@ -35,29 +81,39 @@ register 'register_navbar_item' => sub {
|
|||||||
or !length $config->{path}
|
or !length $config->{path}
|
||||||
or !length $config->{label}) {
|
or !length $config->{label}) {
|
||||||
|
|
||||||
error "bad config to register_navbar_item";
|
return error "bad config to register_navbar_item";
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach my $item (@{ setting('navbar_items') }) {
|
foreach my $item (@{ setting('_navbar_items') }) {
|
||||||
if ($item->{tag} eq $config->{tag}) {
|
if ($item->{tag} eq $config->{tag}) {
|
||||||
$item = $config;
|
$item = $config;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
push @{ setting('navbar_items') }, $config;
|
push @{ setting('_navbar_items') }, $config;
|
||||||
};
|
};
|
||||||
|
|
||||||
sub _register_tab {
|
register 'register_admin_task' => sub {
|
||||||
my ($nav, $config) = @_;
|
my ($self, $config) = plugin_args(@_);
|
||||||
my $stash = setting("${nav}_tabs");
|
|
||||||
|
|
||||||
if (!length $config->{tag}
|
if (!length $config->{tag}
|
||||||
or !length $config->{label}) {
|
or !length $config->{label}) {
|
||||||
|
|
||||||
error "bad config to register_${nav}_item";
|
return error "bad config to register_admin_task";
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
setting('_admin_tasks')->{ $config->{tag} } = $config;
|
||||||
|
};
|
||||||
|
|
||||||
|
sub _register_tab {
|
||||||
|
my ($nav, $config) = @_;
|
||||||
|
my $stash = setting("_${nav}_tabs");
|
||||||
|
|
||||||
|
if (!length $config->{tag}
|
||||||
|
or !length $config->{label}) {
|
||||||
|
|
||||||
|
return error "bad config to register_${nav}_item";
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach my $item (@{ $stash }) {
|
foreach my $item (@{ $stash }) {
|
||||||
@@ -82,26 +138,25 @@ register 'register_device_tab' => sub {
|
|||||||
|
|
||||||
register 'register_report' => sub {
|
register 'register_report' => sub {
|
||||||
my ($self, $config) = plugin_args(@_);
|
my ($self, $config) = plugin_args(@_);
|
||||||
my @categories = @{ setting('report_order') };
|
my @categories = @{ setting('_report_order') };
|
||||||
|
|
||||||
if (!length $config->{category}
|
if (!length $config->{category}
|
||||||
or !length $config->{tag}
|
or !length $config->{tag}
|
||||||
or !length $config->{label}
|
or !length $config->{label}
|
||||||
or 0 == scalar grep {$config->{category} eq $_} @categories) {
|
or 0 == scalar grep {$config->{category} eq $_} @categories) {
|
||||||
|
|
||||||
error "bad config to register_report";
|
return error "bad config to register_report";
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach my $item (@{setting('reports_menu')->{ $config->{category} }}) {
|
foreach my $item (@{setting('_reports_menu')->{ $config->{category} }}) {
|
||||||
if ($item eq $config->{tag}) {
|
if ($item eq $config->{tag}) {
|
||||||
setting('reports')->{$config->{tag}} = $config;
|
setting('_reports')->{$config->{tag}} = $config;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
push @{setting('reports_menu')->{ $config->{category} }}, $config->{tag};
|
push @{setting('_reports_menu')->{ $config->{category} }}, $config->{tag};
|
||||||
setting('reports')->{$config->{tag}} = $config;
|
setting('_reports')->{$config->{tag}} = $config;
|
||||||
};
|
};
|
||||||
|
|
||||||
register_plugin;
|
register_plugin;
|
||||||
|
|||||||
37
Netdisco/lib/App/Netdisco/Web/Plugin/AdminTask/JobQueue.pm
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package App::Netdisco::Web::Plugin::AdminTask::JobQueue;
|
||||||
|
|
||||||
|
use Dancer ':syntax';
|
||||||
|
use Dancer::Plugin::Ajax;
|
||||||
|
use Dancer::Plugin::DBIC;
|
||||||
|
|
||||||
|
use App::Netdisco::Web::Plugin;
|
||||||
|
|
||||||
|
register_admin_task({
|
||||||
|
tag => 'jobqueue',
|
||||||
|
label => 'Job Queue',
|
||||||
|
});
|
||||||
|
|
||||||
|
ajax '/ajax/control/admin/jobqueue/del' => sub {
|
||||||
|
send_error('Forbidden', 403) unless var('user')->admin;
|
||||||
|
send_error('Missing job', 400) unless length param('job');
|
||||||
|
|
||||||
|
schema('netdisco')->txn_do(sub {
|
||||||
|
my $device = schema('netdisco')->resultset('Admin')
|
||||||
|
->search({job => param('job')})->delete;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ajax '/ajax/content/admin/jobqueue' => sub {
|
||||||
|
send_error('Forbidden', 403) unless var('user')->admin;
|
||||||
|
|
||||||
|
my $set = schema('netdisco')->resultset('Admin')
|
||||||
|
->with_times
|
||||||
|
->search({}, {order_by => { -desc => [qw/entered device action/] }});
|
||||||
|
|
||||||
|
content_type('text/html');
|
||||||
|
template 'ajax/admintask/jobqueue.tt', {
|
||||||
|
results => $set,
|
||||||
|
}, { layout => undef };
|
||||||
|
};
|
||||||
|
|
||||||
|
true;
|
||||||
103
Netdisco/lib/App/Netdisco/Web/Plugin/AdminTask/PseudoDevice.pm
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package App::Netdisco::Web::Plugin::AdminTask::PseudoDevice;
|
||||||
|
|
||||||
|
use Dancer ':syntax';
|
||||||
|
use Dancer::Plugin::Ajax;
|
||||||
|
use Dancer::Plugin::DBIC;
|
||||||
|
|
||||||
|
use App::Netdisco::Web::Plugin;
|
||||||
|
use NetAddr::IP::Lite ':lower';
|
||||||
|
|
||||||
|
register_admin_task({
|
||||||
|
tag => 'pseudodevice',
|
||||||
|
label => 'Pseudo Devices',
|
||||||
|
});
|
||||||
|
|
||||||
|
sub _sanity_ok {
|
||||||
|
return 0 unless var('user') and var('user')->admin;
|
||||||
|
|
||||||
|
return 0 unless length param('dns')
|
||||||
|
and param('dns') =~ m/^[[:print:]]+$/
|
||||||
|
and param('dns') !~ m/[[:space:]]/;
|
||||||
|
|
||||||
|
my $ip = NetAddr::IP::Lite->new(param('ip'));
|
||||||
|
return 0 unless ($ip and$ip->addr ne '0.0.0.0');
|
||||||
|
|
||||||
|
return 0 unless length param('ports')
|
||||||
|
and param('ports') =~ m/^[[:digit:]]+$/;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ajax '/ajax/control/admin/pseudodevice/add' => sub {
|
||||||
|
send_error('Bad Request', 400) unless _sanity_ok();
|
||||||
|
|
||||||
|
schema('netdisco')->txn_do(sub {
|
||||||
|
my $device = schema('netdisco')->resultset('Device')
|
||||||
|
->create({
|
||||||
|
ip => param('ip'),
|
||||||
|
dns => param('dns'),
|
||||||
|
vendor => 'netdisco',
|
||||||
|
last_discover => \'now()',
|
||||||
|
});
|
||||||
|
return unless $device;
|
||||||
|
|
||||||
|
$device->ports->populate([
|
||||||
|
['port'],
|
||||||
|
map {["Port$_"]} @{[1 .. param('ports')]},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ajax '/ajax/control/admin/pseudodevice/del' => sub {
|
||||||
|
send_error('Bad Request', 400) unless _sanity_ok();
|
||||||
|
|
||||||
|
schema('netdisco')->txn_do(sub {
|
||||||
|
my $device = schema('netdisco')->resultset('Device')
|
||||||
|
->find({ip => param('ip')});
|
||||||
|
|
||||||
|
$device->ports->delete;
|
||||||
|
$device->delete;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ajax '/ajax/control/admin/pseudodevice/update' => sub {
|
||||||
|
send_error('Bad Request', 400) unless _sanity_ok();
|
||||||
|
|
||||||
|
schema('netdisco')->txn_do(sub {
|
||||||
|
my $device = schema('netdisco')->resultset('Device')
|
||||||
|
->with_port_count->find({ip => param('ip')});
|
||||||
|
return unless $device;
|
||||||
|
my $count = $device->port_count;
|
||||||
|
|
||||||
|
if (param('ports') > $count) {
|
||||||
|
my $start = $count + 1;
|
||||||
|
$device->ports->populate([
|
||||||
|
['port'],
|
||||||
|
map {["Port$_"]} @{[$start .. param('ports')]},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
elsif (param('ports') < $count) {
|
||||||
|
my $start = param('ports') + 1;
|
||||||
|
$device->ports
|
||||||
|
->single({port => "Port$_"})->delete
|
||||||
|
for ($start .. $count);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ajax '/ajax/content/admin/pseudodevice' => sub {
|
||||||
|
send_error('Forbidden', 403) unless var('user')->admin;
|
||||||
|
|
||||||
|
my $set = schema('netdisco')->resultset('Device')
|
||||||
|
->search(
|
||||||
|
{vendor => 'netdisco'},
|
||||||
|
{order_by => { -desc => 'last_discover' }},
|
||||||
|
)->with_port_count;
|
||||||
|
|
||||||
|
content_type('text/html');
|
||||||
|
template 'ajax/admintask/pseudodevice.tt', {
|
||||||
|
results => $set,
|
||||||
|
}, { layout => undef };
|
||||||
|
};
|
||||||
|
|
||||||
|
true;
|
||||||
103
Netdisco/lib/App/Netdisco/Web/Plugin/AdminTask/Topology.pm
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package App::Netdisco::Web::Plugin::AdminTask::Topology;
|
||||||
|
|
||||||
|
use Dancer ':syntax';
|
||||||
|
use Dancer::Plugin::Ajax;
|
||||||
|
use Dancer::Plugin::DBIC;
|
||||||
|
|
||||||
|
use App::Netdisco::Web::Plugin;
|
||||||
|
use NetAddr::IP::Lite ':lower';
|
||||||
|
|
||||||
|
register_admin_task({
|
||||||
|
tag => 'topology',
|
||||||
|
label => 'Manual Device Topology',
|
||||||
|
});
|
||||||
|
|
||||||
|
sub _sanity_ok {
|
||||||
|
return 0 unless var('user') and var('user')->admin;
|
||||||
|
|
||||||
|
my $dev1 = NetAddr::IP::Lite->new(param('dev1'));
|
||||||
|
return 0 unless ($dev1 and $dev1->addr ne '0.0.0.0');
|
||||||
|
|
||||||
|
my $dev2 = NetAddr::IP::Lite->new(param('dev2'));
|
||||||
|
return 0 unless ($dev2 and $dev2->addr ne '0.0.0.0');
|
||||||
|
|
||||||
|
return 0 unless length param('port1');
|
||||||
|
return 0 unless length param('port2');
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ajax '/ajax/control/admin/topology/add' => sub {
|
||||||
|
send_error('Bad Request', 400) unless _sanity_ok();
|
||||||
|
|
||||||
|
my $device = schema('netdisco')->resultset('Topology')
|
||||||
|
->create({
|
||||||
|
dev1 => param('dev1'),
|
||||||
|
port1 => param('port1'),
|
||||||
|
dev2 => param('dev2'),
|
||||||
|
port2 => param('port2'),
|
||||||
|
});
|
||||||
|
|
||||||
|
# re-set remote device details in affected ports
|
||||||
|
# could fail for bad device or port names
|
||||||
|
try {
|
||||||
|
schema('netdisco')->txn_do(sub {
|
||||||
|
# only work on root_ips
|
||||||
|
my $left = get_device(param('dev1'));
|
||||||
|
my $right = get_device(param('dev2'));
|
||||||
|
|
||||||
|
# skip bad entries
|
||||||
|
return unless ($left->in_storage and $right->in_storage);
|
||||||
|
|
||||||
|
$left->ports
|
||||||
|
->single({port => param('port1')}, {for => 'update'})
|
||||||
|
->update({
|
||||||
|
remote_ip => param('dev2'),
|
||||||
|
remote_port => param('port2'),
|
||||||
|
remote_type => undef,
|
||||||
|
remote_id => undef,
|
||||||
|
is_uplink => \"true",
|
||||||
|
manual_topo => \"true",
|
||||||
|
});
|
||||||
|
|
||||||
|
$right->ports
|
||||||
|
->single({port => param('port2')}, {for => 'update'})
|
||||||
|
->update({
|
||||||
|
remote_ip => param('dev1'),
|
||||||
|
remote_port => param('port1'),
|
||||||
|
remote_type => undef,
|
||||||
|
remote_id => undef,
|
||||||
|
is_uplink => \"true",
|
||||||
|
manual_topo => \"true",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
ajax '/ajax/control/admin/topology/del' => sub {
|
||||||
|
send_error('Bad Request', 400) unless _sanity_ok();
|
||||||
|
|
||||||
|
schema('netdisco')->txn_do(sub {
|
||||||
|
my $device = schema('netdisco')->resultset('Topology')
|
||||||
|
->search({
|
||||||
|
dev1 => param('dev1'),
|
||||||
|
port1 => param('port1'),
|
||||||
|
dev2 => param('dev2'),
|
||||||
|
port2 => param('port2'),
|
||||||
|
})->delete;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ajax '/ajax/content/admin/topology' => sub {
|
||||||
|
send_error('Forbidden', 403) unless var('user')->admin;
|
||||||
|
|
||||||
|
my $set = schema('netdisco')->resultset('Topology')
|
||||||
|
->search({},{order_by => [qw/dev1 dev2 port1/]});
|
||||||
|
|
||||||
|
content_type('text/html');
|
||||||
|
template 'ajax/admintask/topology.tt', {
|
||||||
|
results => $set,
|
||||||
|
}, { layout => undef };
|
||||||
|
};
|
||||||
|
|
||||||
|
true;
|
||||||
@@ -13,7 +13,7 @@ ajax '/ajax/content/device/addresses' => sub {
|
|||||||
my $q = param('q');
|
my $q = param('q');
|
||||||
|
|
||||||
my $device = schema('netdisco')->resultset('Device')
|
my $device = schema('netdisco')->resultset('Device')
|
||||||
->search_for_device($q) or return;
|
->search_for_device($q) or send_error('Bad device', 400);
|
||||||
|
|
||||||
my $set = $device->device_ips->search({}, {order_by => 'alias'});
|
my $set = $device->device_ips->search({}, {order_by => 'alias'});
|
||||||
return unless $set->count;
|
return unless $set->count;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ register_device_tab({ tag => 'details', label => 'Details' });
|
|||||||
ajax '/ajax/content/device/details' => sub {
|
ajax '/ajax/content/device/details' => sub {
|
||||||
my $q = param('q');
|
my $q = param('q');
|
||||||
my $device = schema('netdisco')->resultset('Device')
|
my $device = schema('netdisco')->resultset('Device')
|
||||||
->with_times()->search_for_device($q) or return;
|
->with_times()->search_for_device($q) or send_error('Bad device', 400);
|
||||||
|
|
||||||
content_type('text/html');
|
content_type('text/html');
|
||||||
template 'ajax/device/details.tt', {
|
template 'ajax/device/details.tt', {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ get '/ajax/data/device/netmap' => sub {
|
|||||||
my $q = param('q');
|
my $q = param('q');
|
||||||
|
|
||||||
my $device = schema('netdisco')->resultset('Device')
|
my $device = schema('netdisco')->resultset('Device')
|
||||||
->search_for_device($q) or return;
|
->search_for_device($q) or send_error('Bad device', 400);
|
||||||
my $start = $device->ip;
|
my $start = $device->ip;
|
||||||
|
|
||||||
my @devices = schema('netdisco')->resultset('Device')->search({}, {
|
my @devices = schema('netdisco')->resultset('Device')->search({}, {
|
||||||
@@ -72,7 +72,7 @@ get '/ajax/data/device/netmap' => sub {
|
|||||||
_add_children($tree{children}, var('links')->{$start});
|
_add_children($tree{children}, var('links')->{$start});
|
||||||
|
|
||||||
content_type('application/json');
|
content_type('application/json');
|
||||||
return to_json(\%tree);
|
to_json(\%tree);
|
||||||
};
|
};
|
||||||
|
|
||||||
ajax '/ajax/data/device/alldevicelinks' => sub {
|
ajax '/ajax/data/device/alldevicelinks' => sub {
|
||||||
@@ -93,7 +93,7 @@ ajax '/ajax/data/device/alldevicelinks' => sub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
content_type('application/json');
|
content_type('application/json');
|
||||||
return to_json(\%tree);
|
to_json(\%tree);
|
||||||
};
|
};
|
||||||
|
|
||||||
true;
|
true;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ ajax '/ajax/content/device/ports' => sub {
|
|||||||
my $q = param('q');
|
my $q = param('q');
|
||||||
|
|
||||||
my $device = schema('netdisco')->resultset('Device')
|
my $device = schema('netdisco')->resultset('Device')
|
||||||
->search_for_device($q) or return;
|
->search_for_device($q) or send_error('Bad device', 400);
|
||||||
my $set = $device->ports;
|
my $set = $device->ports;
|
||||||
|
|
||||||
# refine by ports if requested
|
# refine by ports if requested
|
||||||
@@ -77,7 +77,7 @@ ajax '/ajax/content/device/ports' => sub {
|
|||||||
template 'ajax/device/ports.tt', {
|
template 'ajax/device/ports.tt', {
|
||||||
results => $results,
|
results => $results,
|
||||||
nodes => $nodes_name,
|
nodes => $nodes_name,
|
||||||
device => $device->ip,
|
device => $device,
|
||||||
}, { layout => undef };
|
}, { layout => undef };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ ajax '/ajax/content/search/device' => sub {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
my $q = param('q');
|
my $q = param('q');
|
||||||
return unless $q;
|
send_error('Missing query', 400) unless $q;
|
||||||
|
|
||||||
$set = schema('netdisco')->resultset('Device')->search_fuzzy($q);
|
$set = schema('netdisco')->resultset('Device')->search_fuzzy($q);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ register_search_tab({ tag => 'node', label => 'Node' });
|
|||||||
# nodes matching the param as an IP or DNS hostname or MAC
|
# nodes matching the param as an IP or DNS hostname or MAC
|
||||||
ajax '/ajax/content/search/node' => sub {
|
ajax '/ajax/content/search/node' => sub {
|
||||||
my $node = param('q');
|
my $node = param('q');
|
||||||
return unless $node;
|
send_error('Missing node', 400) unless $node;
|
||||||
content_type('text/html');
|
content_type('text/html');
|
||||||
|
|
||||||
my $mac = Net::MAC->new(mac => $node, 'die' => 0, verbose => 0);
|
my $mac = Net::MAC->new(mac => $node, 'die' => 0, verbose => 0);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ register_search_tab({ tag => 'port', label => 'Port' });
|
|||||||
# device ports with a description (er, name) matching
|
# device ports with a description (er, name) matching
|
||||||
ajax '/ajax/content/search/port' => sub {
|
ajax '/ajax/content/search/port' => sub {
|
||||||
my $q = param('q');
|
my $q = param('q');
|
||||||
return unless $q;
|
send_error('Missing query', 400) unless $q;
|
||||||
my $set;
|
my $set;
|
||||||
|
|
||||||
if ($q =~ m/^\d+$/) {
|
if ($q =~ m/^\d+$/) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ register_search_tab({ tag => 'vlan', label => 'VLAN' });
|
|||||||
# devices carrying vlan xxx
|
# devices carrying vlan xxx
|
||||||
ajax '/ajax/content/search/vlan' => sub {
|
ajax '/ajax/content/search/vlan' => sub {
|
||||||
my $q = param('q');
|
my $q = param('q');
|
||||||
return unless $q;
|
send_error('Missing query', 400) unless $q;
|
||||||
my $set;
|
my $set;
|
||||||
|
|
||||||
if ($q =~ m/^\d+$/) {
|
if ($q =~ m/^\d+$/) {
|
||||||
|
|||||||
@@ -4,42 +4,43 @@ use Dancer ':syntax';
|
|||||||
use Dancer::Plugin::Ajax;
|
use Dancer::Plugin::Ajax;
|
||||||
use Dancer::Plugin::DBIC;
|
use Dancer::Plugin::DBIC;
|
||||||
|
|
||||||
use Try::Tiny;
|
|
||||||
|
|
||||||
ajax '/ajax/portcontrol' => sub {
|
ajax '/ajax/portcontrol' => sub {
|
||||||
try {
|
send_error('Forbidden', 403)
|
||||||
my $log = sprintf 'd:[%s] p:[%s] f:[%s]. a:[%s] v[%s]',
|
unless var('user')->port_control;
|
||||||
param('device'), (param('port') || ''), param('field'),
|
send_error('No device/port/field', 400)
|
||||||
(param('action') || ''), (param('value') || '');
|
unless param('device') and param('port') and param('field');
|
||||||
|
|
||||||
my %action_map = (
|
my $log = sprintf 'd:[%s] p:[%s] f:[%s]. a:[%s] v[%s]',
|
||||||
'location' => 'location',
|
param('device'), (param('port') || ''), param('field'),
|
||||||
'contact' => 'contact',
|
(param('action') || ''), (param('value') || '');
|
||||||
'c_port' => 'portcontrol',
|
|
||||||
'c_name' => 'portname',
|
|
||||||
'c_vlan' => 'vlan',
|
|
||||||
'c_power' => 'power',
|
|
||||||
);
|
|
||||||
|
|
||||||
my $action = $action_map{ param('field') };
|
my %action_map = (
|
||||||
my $subaction = ($action =~ m/^(?:power|portcontrol)/
|
'location' => 'location',
|
||||||
? (param('action') ."-other")
|
'contact' => 'contact',
|
||||||
: param('value'));
|
'c_port' => 'portcontrol',
|
||||||
|
'c_name' => 'portname',
|
||||||
|
'c_vlan' => 'vlan',
|
||||||
|
'c_power' => 'power',
|
||||||
|
);
|
||||||
|
|
||||||
schema('netdisco')->resultset('Admin')->create({
|
send_error('No action/value', 400)
|
||||||
device => param('device'),
|
unless (param('action') or param('value'));
|
||||||
port => param('port'),
|
|
||||||
action => $action,
|
my $action = $action_map{ param('field') };
|
||||||
subaction => $subaction,
|
my $subaction = ($action =~ m/^(?:power|portcontrol)/
|
||||||
status => 'queued',
|
? (param('action') ."-other")
|
||||||
username => session('user'),
|
: param('value'));
|
||||||
userip => request->remote_address,
|
|
||||||
log => $log,
|
schema('netdisco')->resultset('Admin')->create({
|
||||||
});
|
device => param('device'),
|
||||||
}
|
port => param('port'),
|
||||||
catch {
|
action => $action,
|
||||||
send_error('Failed to parse params or add DB record');
|
subaction => $subaction,
|
||||||
};
|
status => 'queued',
|
||||||
|
username => session('user'),
|
||||||
|
userip => request->remote_address,
|
||||||
|
log => $log,
|
||||||
|
});
|
||||||
|
|
||||||
content_type('application/json');
|
content_type('application/json');
|
||||||
to_json({});
|
to_json({});
|
||||||
@@ -47,7 +48,7 @@ ajax '/ajax/portcontrol' => sub {
|
|||||||
|
|
||||||
ajax '/ajax/userlog' => sub {
|
ajax '/ajax/userlog' => sub {
|
||||||
my $user = session('user');
|
my $user = session('user');
|
||||||
send_error('No username') unless $user;
|
send_error('No username', 400) unless $user;
|
||||||
|
|
||||||
my $rs = schema('netdisco')->resultset('Admin')->search({
|
my $rs = schema('netdisco')->resultset('Admin')->search({
|
||||||
username => $user,
|
username => $user,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ get '/report/*' => sub {
|
|||||||
|
|
||||||
var(nav => 'reports');
|
var(nav => 'reports');
|
||||||
template 'report', {
|
template 'report', {
|
||||||
report => setting('reports')->{ $tag },
|
report => setting('_reports')->{ $tag },
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -65,9 +65,7 @@ get '/search' => sub {
|
|||||||
|
|
||||||
if (not param('tab')) {
|
if (not param('tab')) {
|
||||||
if (not $q) {
|
if (not $q) {
|
||||||
status(302);
|
return redirect uri_for('/')->path_query;
|
||||||
header(Location => uri_for('/')->path_query());
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# pick most likely tab for initial results
|
# pick most likely tab for initial results
|
||||||
@@ -80,13 +78,11 @@ get '/search' => sub {
|
|||||||
if ($nd and $nd->count) {
|
if ($nd and $nd->count) {
|
||||||
if ($nd->count == 1) {
|
if ($nd->count == 1) {
|
||||||
# redirect to device details for the one device
|
# redirect to device details for the one device
|
||||||
status(302);
|
return redirect uri_for('/device', {
|
||||||
header(Location => uri_for('/device', {
|
|
||||||
tab => 'details',
|
tab => 'details',
|
||||||
q => ($nd->first->dns || $nd->first->ip),
|
q => ($nd->first->dns || $nd->first->ip),
|
||||||
f => '',
|
f => '',
|
||||||
})->path_query());
|
})->path_query;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# multiple devices
|
# multiple devices
|
||||||
|
|||||||
30
Netdisco/lib/App/Netdisco/Web/Static.pm
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package App::Netdisco::Web::Static;
|
||||||
|
|
||||||
|
use Dancer ':syntax';
|
||||||
|
use Path::Class;
|
||||||
|
|
||||||
|
get '/plugin/*/*.js' => sub {
|
||||||
|
my ($plugin) = splat;
|
||||||
|
|
||||||
|
my $content = template
|
||||||
|
"plugin/$plugin/$plugin.js", {},
|
||||||
|
{ layout => undef };
|
||||||
|
|
||||||
|
send_file \$content,
|
||||||
|
content_type => 'application/javascript',
|
||||||
|
filename => "$plugin.js";
|
||||||
|
};
|
||||||
|
|
||||||
|
get '/plugin/*/*.css' => sub {
|
||||||
|
my ($plugin) = splat;
|
||||||
|
|
||||||
|
my $content = template
|
||||||
|
"plugin/$plugin/$plugin.css", {},
|
||||||
|
{ layout => undef };
|
||||||
|
|
||||||
|
send_file \$content,
|
||||||
|
content_type => 'text/css',
|
||||||
|
filename => "$plugin.css";
|
||||||
|
};
|
||||||
|
|
||||||
|
true;
|
||||||
@@ -4,13 +4,51 @@ use Dancer ':syntax';
|
|||||||
use Dancer::Plugin::Ajax;
|
use Dancer::Plugin::Ajax;
|
||||||
use Dancer::Plugin::DBIC;
|
use Dancer::Plugin::DBIC;
|
||||||
|
|
||||||
# support typeahead with simple AJAX query for device names
|
use App::Netdisco::Util::Web (); # for sort_port
|
||||||
ajax '/ajax/data/device/typeahead' => sub {
|
|
||||||
my $q = param('query');
|
ajax '/ajax/data/devicename/typeahead' => sub {
|
||||||
|
my $q = param('query') || param('term');
|
||||||
my $set = schema('netdisco')->resultset('Device')->search_fuzzy($q);
|
my $set = schema('netdisco')->resultset('Device')->search_fuzzy($q);
|
||||||
|
|
||||||
content_type 'application/json';
|
content_type 'application/json';
|
||||||
return to_json [map {$_->dns || $_->name || $_->ip} $set->all];
|
to_json [map {$_->dns || $_->name || $_->ip} $set->all];
|
||||||
|
};
|
||||||
|
|
||||||
|
ajax '/ajax/data/deviceip/typeahead' => sub {
|
||||||
|
my $q = param('query') || param('term');
|
||||||
|
my $set = schema('netdisco')->resultset('Device')->search_fuzzy($q);
|
||||||
|
|
||||||
|
my @data = ();
|
||||||
|
while (my $d = $set->next) {
|
||||||
|
my $label = $d->ip;
|
||||||
|
if ($d->dns or $d->name) {
|
||||||
|
$label = sprintf '%s (%s)',
|
||||||
|
($d->dns || $d->name), $d->ip;
|
||||||
|
}
|
||||||
|
push @data, {label => $label, value => $d->ip};
|
||||||
|
}
|
||||||
|
|
||||||
|
content_type 'application/json';
|
||||||
|
to_json \@data;
|
||||||
|
};
|
||||||
|
|
||||||
|
ajax '/ajax/data/port/typeahead' => sub {
|
||||||
|
my $dev = param('dev1') || param('dev2');
|
||||||
|
my $port = param('port1') || param('port2');
|
||||||
|
send_error('Missing device', 400) unless length $dev;
|
||||||
|
|
||||||
|
my $device = schema('netdisco')->resultset('Device')
|
||||||
|
->find({ip => $dev});
|
||||||
|
send_error('Bad device', 400) unless $device;
|
||||||
|
|
||||||
|
my $set = $device->ports({},{order_by => 'port'});
|
||||||
|
$set = $set->search({port => { -ilike => "\%$port\%" }})
|
||||||
|
if length $port;
|
||||||
|
|
||||||
|
my $results = [ sort { &App::Netdisco::Util::Web::sort_port($a->port, $b->port) } $set->all ];
|
||||||
|
|
||||||
|
content_type 'application/json';
|
||||||
|
to_json [map {$_->port} @$results];
|
||||||
};
|
};
|
||||||
|
|
||||||
true;
|
true;
|
||||||
|
|||||||
21
Netdisco/lib/App/NetdiscoX/Web/Plugin/Observium.pm
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package App::NetdiscoX::Web::Plugin::Observium;
|
||||||
|
|
||||||
|
use Dancer ':syntax';
|
||||||
|
use Dancer::Plugin::DBIC;
|
||||||
|
|
||||||
|
use App::Netdisco::Web::Plugin;
|
||||||
|
|
||||||
|
use File::ShareDir 'dist_dir';
|
||||||
|
use Path::Class;
|
||||||
|
|
||||||
|
register_device_port_column({
|
||||||
|
name => 'observium',
|
||||||
|
position => 'mid',
|
||||||
|
label => 'Traffic',
|
||||||
|
default => 'on',
|
||||||
|
});
|
||||||
|
|
||||||
|
register_css('observium');
|
||||||
|
register_javascript('observium');
|
||||||
|
|
||||||
|
true;
|
||||||
@@ -38,6 +38,9 @@ engines:
|
|||||||
web_plugins:
|
web_plugins:
|
||||||
- Inventory
|
- Inventory
|
||||||
- Report::DuplexMismatch
|
- Report::DuplexMismatch
|
||||||
|
- AdminTask::PseudoDevice
|
||||||
|
- AdminTask::Topology
|
||||||
|
- AdminTask::JobQueue
|
||||||
- Search::Device
|
- Search::Device
|
||||||
- Search::Node
|
- Search::Node
|
||||||
- Search::VLAN
|
- Search::VLAN
|
||||||
|
|||||||
2
Netdisco/share/public/css/jquery.qtip.min.css
vendored
Normal file
@@ -2,11 +2,11 @@ body {
|
|||||||
padding-top: 0px !important;
|
padding-top: 0px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#search_results > li:not(.active) {
|
#nd_search-results > li:not(.active) {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar, .sidebar {
|
.navbar, .nd_sidebar {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
/* for the fixed navbar make sure content stops short of page top*/
|
/* style common to all pages in the site */
|
||||||
|
|
||||||
|
/* for the fixed navbar make sure content stops short of page top*/
|
||||||
body {
|
body {
|
||||||
padding-top: 50px;
|
padding-top: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* magnifying glass icon for search box */
|
/* magnifying glass icon for search box */
|
||||||
.navbar_icon {
|
.nd_navbar-icon {
|
||||||
vertical-align: sub;
|
vertical-align: sub;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* for the "logged in as..." text */
|
/* for the "logged in as..." text */
|
||||||
.nd_navbartext {
|
.nd_navbar-text {
|
||||||
color: #666;
|
color: #666;
|
||||||
padding-top: 11px;
|
padding-top: 11px;
|
||||||
}
|
}
|
||||||
@@ -27,45 +28,36 @@ body {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
/* jquery ui autocomplete scrollable */
|
||||||
/* various styles to adjust the hero box used for homepage + login */
|
.ui-autocomplete {
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.nd_herorow {
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
|
/* styles to adjust the hero box used for homepage + login */
|
||||||
|
|
||||||
|
/* space between hero box and navbar */
|
||||||
|
.nd_hero-row {
|
||||||
margin-top: 50px;
|
margin-top: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* alter proportions of hero unit to make it "tighter" on content */
|
||||||
.hero-unit {
|
.hero-unit {
|
||||||
padding: 30px 60px 40px 90px;
|
padding: 30px 60px 40px 90px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nd_loginform {
|
/* push user/pass/login form down+away from the Netdisco banner text */
|
||||||
|
.nd_login-form {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
/* styles for Reports */
|
/* styles for device inventory */
|
||||||
|
|
||||||
/* from Bootstrap doc style sheet */
|
.nd_inventory-table-head {
|
||||||
.nd_show-grid [class*="span"] {
|
|
||||||
background-color: cornsilk;
|
|
||||||
text-align: center;
|
|
||||||
-webkit-border-radius: 3px;
|
|
||||||
-moz-border-radius: 3px;
|
|
||||||
border-radius: 3px;
|
|
||||||
min-height: 30px;
|
|
||||||
line-height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
|
||||||
/* styles for Inventory */
|
|
||||||
|
|
||||||
#nd_dev_age_form {
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nd_inv_tbl_head {
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: lightSlateGray;
|
color: lightSlateGray;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
@@ -73,12 +65,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
/* results table links */
|
/* styles for links in results tables */
|
||||||
|
|
||||||
.nd_stealthlink {
|
|
||||||
text-decoration: none !important;
|
|
||||||
color: #404040;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* make the whole cell become a hyperlink in results table */
|
/* make the whole cell become a hyperlink in results table */
|
||||||
.nd_linkcell {
|
.nd_linkcell {
|
||||||
@@ -87,110 +74,65 @@ body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* special placing for edit icon in details tab */
|
/* still a link, but styled like normal text */
|
||||||
.nd_device_details_edit {
|
.nd_stealth-link {
|
||||||
float: right !important;
|
text-decoration: none !important;
|
||||||
font-size: 14px;
|
color: #404040;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* port admin up/down control */
|
/* nudge cell content to the right when port_control controls are enabled */
|
||||||
.nd_edit_icon, .nd_hand_icon {
|
.nd_editable-cell > .nd_this-port-only {
|
||||||
cursor: pointer;
|
|
||||||
float: left;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nd_power_icon {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-off {
|
|
||||||
vertical-align: middle;
|
|
||||||
color: darkRed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nd_power_on {
|
|
||||||
color: darkGreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* placement of port link when port admin hint is enabled */
|
|
||||||
.nd_editable_cell > .nd_this_port_only {
|
|
||||||
margin-left: 18px;
|
margin-left: 18px;
|
||||||
}
|
}
|
||||||
.nd_editable_cell > .nd_editable_cell_content {
|
.nd_editable-cell > .nd_editable-cell-content {
|
||||||
margin-left: 18px;
|
margin-left: 18px;
|
||||||
}
|
}
|
||||||
|
.table .nd_nudge-for-icon {
|
||||||
/* style of editable content in table */
|
padding-left: 25px;
|
||||||
[contenteditable]:focus {
|
|
||||||
background: #FFFFD3 !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
/* many styles for the collapsing lists */
|
/* styles to position table cell content */
|
||||||
|
|
||||||
/* mouse-over should be pointer to show JS collapser is clickable */
|
.table td {
|
||||||
.nd_collapser {
|
|
||||||
cursor: pointer;
|
|
||||||
color: #0088CC;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* collapser label should not have any decoration even though it's clickable */
|
|
||||||
.clearfix > a {
|
|
||||||
text-decoration: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* collapser label should not have any decoration even though it's clickable */
|
|
||||||
.nd_collapse_vlans {
|
|
||||||
text-decoration: none !important;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #0088CC;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* class to control default state of collapsible lists on page load */
|
|
||||||
.nd_collapse_pre_hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* for the tagged vlans total when hiding the full list */
|
|
||||||
.vlan_total {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* little up/down chevron to the right of some collapser link */
|
|
||||||
.arrow-up-down {
|
|
||||||
float: right;
|
|
||||||
margin-top: 1px;
|
|
||||||
margin-right: 1px;
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* draw little up arrow to the left of a label for collapsed list */
|
|
||||||
.cell-arrow-up-down {
|
|
||||||
float: left;
|
|
||||||
margin-right: 6px;
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
|
||||||
/* for table and to position cell content */
|
|
||||||
|
|
||||||
td {
|
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.center_cell {
|
.table .nd_center-cell {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
/* fix layout of form fields inside the (topology) table */
|
||||||
/* tabs */
|
td div.input-append {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
#search_results {
|
/* admin buttons in the device details view */
|
||||||
|
td > form.nd_inline-form {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fix layout of form fields inside the (pseudo devices) table */
|
||||||
|
.nd_center-cell input {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* with two forms inside one cell, make the submit buttons side-by-side */
|
||||||
|
.nd_inline-form {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
|
/* styles for "tabs" and surrounding content */
|
||||||
|
|
||||||
|
|
||||||
|
/* add a small bottom margin (gutter) below all pages */
|
||||||
|
#nd_search-results {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#nd_device_name {
|
/* for any label which we want to appear alongside tabs, floated to the right */
|
||||||
|
#nd_device-name {
|
||||||
float: right;
|
float: right;
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
margin-top: 9px;
|
margin-top: 9px;
|
||||||
@@ -198,159 +140,131 @@ td {
|
|||||||
color: #6D5720;
|
color: #6D5720;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
/* when there's only one tab (report, task etc) change the text color */
|
||||||
/* style customization for many items which appear in the sidebar */
|
.nd_single-tab {
|
||||||
|
color: rgb(187,112,0) !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* fixups for prepended checkbox in sidebar */
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
.nd_searchcheckbox {
|
/* style for port_control controls */
|
||||||
width: 123px;
|
|
||||||
padding-left: 8px;
|
/* edit icon in details tab is in the label (not content) cell so nudge to RHS*/
|
||||||
|
.nd_device-details-edit {
|
||||||
|
float: right !important;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* port admin up/down control */
|
||||||
|
.nd_edit-icon, .nd_hand-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
float: left;
|
||||||
|
display: none;
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* port power control */
|
||||||
|
.nd_power-icon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* for some reason bootstrap 2.1.0 displays add-on as block - no check supprt? */
|
/* the port power icon, whether it's on or off */
|
||||||
.nd_checkboxlabel {
|
.icon-off {
|
||||||
display: inline;
|
vertical-align: middle;
|
||||||
|
color: darkRed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* fixups for placing the Archived "A" inside the prepended checkbox */
|
/* change color of icon from default of red (which is OK for power-off) */
|
||||||
.nd_legendlabel {
|
.nd_power-on {
|
||||||
|
color: darkGreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* style of editable content in any table - yellow background */
|
||||||
|
[contenteditable]:focus {
|
||||||
|
background: #FFFFD3 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
|
/* styles for collapsing lists - sidebar or main table cell content */
|
||||||
|
|
||||||
|
/* sidebar collapser is clickable and deep grey */
|
||||||
|
.nd_collapser {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #0088CC;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vlans collapser also clickable and deep grey but with no link styling */
|
||||||
|
.nd_collapse-vlans {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #0088CC;
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set default state of collapsible lists as collapsed (hidden) */
|
||||||
|
.nd_collapse-pre-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* for the tagged vlans total when hiding the full list */
|
||||||
|
.nd_vlan-total {
|
||||||
float: right;
|
float: right;
|
||||||
line-height: 1.2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nd_side_input {
|
/* little up/down chevron to the right of some collapsed list */
|
||||||
margin-left: -3px;
|
.nd_arrow-up-down-right {
|
||||||
width: 152px;
|
float: right;
|
||||||
|
margin-top: 1px;
|
||||||
|
margin-right: 1px;
|
||||||
|
color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nd_side_select {
|
/* little up arrow to the left of a label for collapsed list */
|
||||||
margin-left: -3px;
|
.nd_arrow-up-down-left {
|
||||||
width: 165px;
|
float: left;
|
||||||
|
margin-right: 6px;
|
||||||
|
color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar .input-prepend {
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
margin-left: -2px;
|
/* styles for sidebar placement and sizing */
|
||||||
|
|
||||||
|
/* make the sidebar fixed on the screen */
|
||||||
|
.container-fluid > .nd_sidebar {
|
||||||
|
position: absolute;
|
||||||
|
right: 20px;
|
||||||
|
width: 200px;
|
||||||
|
left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* nudge content in the sidebar closer to the left */
|
||||||
|
.nd_sidebar-form {
|
||||||
|
padding-left: 0px;
|
||||||
|
margin-top: -9px;
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* nudge the port name/vlan filter over a little */
|
/* reduce padding at the bottom of the sidebar content */
|
||||||
#nd_port_query {
|
.container-fluid > .nd_sidebar > .well {
|
||||||
margin-left: 5px !important;
|
padding-bottom: 15px;
|
||||||
width: 152px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* somewhere between span1 and span2 is desirable */
|
/* pull tab content away from the sidebar */
|
||||||
#nd_days_select {
|
.container-fluid > .content {
|
||||||
margin-top: 4px;
|
margin-right: 215px;
|
||||||
width: 56px;
|
margin-left: 0px;
|
||||||
}
|
|
||||||
|
|
||||||
/* set the day/mon/year drop-down width */
|
|
||||||
#nd_age_select {
|
|
||||||
margin-top: 4px;
|
|
||||||
width: 95px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* set the MAC format drop-down width */
|
|
||||||
#nd_mac_format {
|
|
||||||
margin-top: 4px;
|
|
||||||
width: 154px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* set the MAC format drop-down width */
|
|
||||||
#nd_node_mac_format {
|
|
||||||
margin-left: -2px;
|
|
||||||
margin-top: 4px;
|
|
||||||
width: 165px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* sidebar submit button width and spacing from Node Props */
|
|
||||||
.sidebar button {
|
|
||||||
margin-top: 9px;
|
|
||||||
margin-left: -3px;
|
|
||||||
width: 165px;
|
|
||||||
}
|
|
||||||
.sidebar #ports_submit {
|
|
||||||
margin-top: 9px;
|
|
||||||
width: 165px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* little icon inside of search input fields */
|
|
||||||
.field_clear_icon, .field_copy_icon {
|
|
||||||
position: absolute;
|
|
||||||
margin-left: 140px;
|
|
||||||
margin-top: 5px;
|
|
||||||
z-index: 1;
|
|
||||||
padding: 0px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field_copy_icon {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field_clear_icon {
|
|
||||||
background-color: #A9DBA9;
|
|
||||||
color: #3A87AD;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* for the ports form, but the positioning is slightly different */
|
|
||||||
#ports_form .field_clear_icon {
|
|
||||||
margin-left: 149px;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* change highlighting for form fields which are being used in a search */
|
|
||||||
form .clearfix.success select {
|
|
||||||
background-color: #A9DBA9;
|
|
||||||
}
|
|
||||||
form .clearfix.success input {
|
|
||||||
background-color: #A9DBA9;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* when we use font-awesome icons, override the size */
|
|
||||||
#nd_legend i {
|
|
||||||
width: 9px;
|
|
||||||
}
|
|
||||||
.table-bordered i {
|
|
||||||
width: 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* bring sidebar items closer together */
|
|
||||||
.inputs-list label {
|
|
||||||
margin-bottom: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputs-list i {
|
|
||||||
margin-right: 5px;
|
|
||||||
margin-left: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* nudge content closer to the header labels in the sidebar */
|
|
||||||
.inputs-list li:first-child {
|
|
||||||
padding-top: 3px !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
/* sidebar collapser */
|
/* styles for sidebar position controls (collapse, pin) */
|
||||||
|
|
||||||
.nd_sidebar_title {
|
.nd_sidebar-pinned {
|
||||||
margin-left: 10px;
|
|
||||||
margin-top: 6px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar_pinned {
|
|
||||||
position: fixed !important;
|
position: fixed !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar_pin_clicked {
|
.nd_sidebar-pin-clicked {
|
||||||
color: rgba(255,0,0,0.8) !important;
|
color: rgba(255,0,0,0.8) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* for placing the sidebar pin icons */
|
.nd_sidebar-pin {
|
||||||
.sidebar_pin {
|
|
||||||
float: left;
|
float: left;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
margin-left: -16px;
|
margin-left: -16px;
|
||||||
@@ -359,8 +273,7 @@ form .clearfix.success input {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* for placing the sidebar toggle icons */
|
#nd_sidebar-toggle-img-in {
|
||||||
#sidebar_toggle_img_in {
|
|
||||||
float: left;
|
float: left;
|
||||||
margin-top: -9px;
|
margin-top: -9px;
|
||||||
margin-left: -16px;
|
margin-left: -16px;
|
||||||
@@ -369,8 +282,7 @@ form .clearfix.success input {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* for placing the sidebar toggle icons */
|
#nd_sidebar-toggle-img-out {
|
||||||
#sidebar_toggle_img_out {
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 60px;
|
top: 60px;
|
||||||
right: 7px;
|
right: 7px;
|
||||||
@@ -381,7 +293,7 @@ form .clearfix.success input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* question mark image with popover for netmap instructions */
|
/* question mark image with popover for netmap instructions */
|
||||||
#netmap_help_img {
|
#nd_netmap-help {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 160px;
|
top: 160px;
|
||||||
right: 7px;
|
right: 7px;
|
||||||
@@ -393,34 +305,147 @@ form .clearfix.success input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
/* sidebar placement and sizing */
|
/* style customization for many items which appear in the sidebar */
|
||||||
|
|
||||||
/* make the sidebar fixed on the screen */
|
.nd_sidebar-title {
|
||||||
.container-fluid > .sidebar {
|
margin-left: 10px;
|
||||||
position: absolute;
|
margin-top: 6px;
|
||||||
right: 20px;
|
margin-bottom: 12px;
|
||||||
width: 200px;
|
|
||||||
left: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* smaller padding below form button in sidebar well */
|
/* fixup for prepended checkbox in sidebar */
|
||||||
.container-fluid > .sidebar > .well {
|
.nd_searchcheckbox {
|
||||||
padding-bottom: 15px;
|
width: 123px;
|
||||||
|
padding-left: 8px;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* make the content start more to the left now the sidebar is narrower */
|
/* fixup for prepended checkbox in sidebar */
|
||||||
.container-fluid > .content {
|
.nd_sidebar .input-prepend {
|
||||||
margin-right: 215px;
|
margin-left: -2px;
|
||||||
margin-left: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* nudge content in the sidebar closer to the left */
|
|
||||||
.nd_sidesearchform {
|
|
||||||
padding-left: 0px;
|
|
||||||
margin-top: -9px;
|
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* for some reason bootstrap 2.1.0 displays add-on as block - no check supprt? */
|
||||||
|
.nd_checkboxlabel {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fixup for placing the Archived "A" inside the prepended checkbox */
|
||||||
|
.nd_legendlabel {
|
||||||
|
float: right;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* placement of form field in sidebar */
|
||||||
|
.nd_side-input {
|
||||||
|
margin-left: -3px;
|
||||||
|
width: 152px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* placement of form field in sidebar */
|
||||||
|
.nd_side-select {
|
||||||
|
margin-left: -3px;
|
||||||
|
width: 165px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* nudge the port name/vlan filter over a little (as compared to nd_side-select) */
|
||||||
|
#nd_port-query {
|
||||||
|
margin-left: 5px !important;
|
||||||
|
width: 152px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set the day/mon/year drop-down width */
|
||||||
|
#nd_days-select {
|
||||||
|
margin-top: 4px;
|
||||||
|
width: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set the day/mon/year drop-down width */
|
||||||
|
#nd_age-select {
|
||||||
|
margin-top: 4px;
|
||||||
|
width: 95px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set the MAC format drop-down width */
|
||||||
|
#nd_mac-format {
|
||||||
|
margin-top: 4px;
|
||||||
|
width: 154px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set the MAC format drop-down width */
|
||||||
|
#nd_node-mac-format {
|
||||||
|
margin-left: -2px;
|
||||||
|
margin-top: 4px;
|
||||||
|
width: 165px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sidebar submit button width and spacing */
|
||||||
|
.nd_sidebar button {
|
||||||
|
margin-top: 9px;
|
||||||
|
margin-left: -3px;
|
||||||
|
width: 165px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* little icon inside of search input fields */
|
||||||
|
.nd_field-clear-icon, .nd_field-copy-icon {
|
||||||
|
position: absolute;
|
||||||
|
margin-left: 140px;
|
||||||
|
margin-top: 5px;
|
||||||
|
z-index: 1;
|
||||||
|
padding: 0px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* little icon inside of search input fields */
|
||||||
|
.nd_field-copy-icon {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* little icon inside of search input fields */
|
||||||
|
.nd_field-clear-icon {
|
||||||
|
background-color: #A9DBA9;
|
||||||
|
color: #3A87AD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* same for the ports form, but the positioning is slightly different */
|
||||||
|
#ports_form .nd_field-clear-icon {
|
||||||
|
margin-left: 149px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* change bg color for form fields which are being used in a search */
|
||||||
|
form .clearfix.success select {
|
||||||
|
background-color: #A9DBA9;
|
||||||
|
}
|
||||||
|
form .clearfix.success input {
|
||||||
|
background-color: #A9DBA9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* when we use font-awesome icons, override the size */
|
||||||
|
#nd_legend i {
|
||||||
|
width: 9px;
|
||||||
|
}
|
||||||
|
.table i {
|
||||||
|
width: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* bring sidebar items closer together */
|
||||||
|
.nd_inputs-list label {
|
||||||
|
margin-bottom: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* compact icons for the sidebar legend */
|
||||||
|
.nd_inputs-list i {
|
||||||
|
margin-right: 5px;
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* nudge content closer to the header labels in the sidebar */
|
||||||
|
.nd_inputs-list li:first-child {
|
||||||
|
padding-top: 3px !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
/* D3 SVG */
|
/* D3 SVG */
|
||||||
|
|
||||||
|
|||||||
BIN
Netdisco/share/public/css/smoothness/images/animated-overlay.gif
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 212 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 335 B |
|
After Width: | Height: | Size: 207 B |
|
After Width: | Height: | Size: 262 B |
|
After Width: | Height: | Size: 262 B |
|
After Width: | Height: | Size: 332 B |
|
After Width: | Height: | Size: 280 B |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
5
Netdisco/share/public/css/smoothness/jquery-ui.custom.min.css
vendored
Normal file
6
Netdisco/share/public/javascripts/jquery-ui.custom.min.js
vendored
Normal file
7
Netdisco/share/public/javascripts/jquery.qtip.min.js
vendored
Normal file
@@ -9,8 +9,8 @@ function do_search (event, tab) {
|
|||||||
|
|
||||||
// page title
|
// page title
|
||||||
var pgtitle = 'Netdisco';
|
var pgtitle = 'Netdisco';
|
||||||
if ($('#nd_device_name').text().length) {
|
if ($('#nd_device-name').text().length) {
|
||||||
var pgtitle = $('#nd_device_name').text() +' - '+ $('#'+ tab + '_link').text();
|
var pgtitle = $('#nd_device-name').text() +' - '+ $('#'+ tab + '_link').text();
|
||||||
}
|
}
|
||||||
|
|
||||||
// each sidebar search form has a hidden copy of the main navbar search
|
// each sidebar search form has a hidden copy of the main navbar search
|
||||||
@@ -26,16 +26,16 @@ function do_search (event, tab) {
|
|||||||
// hide or show sidebars depending on previous state,
|
// hide or show sidebars depending on previous state,
|
||||||
// and whether the sidebar contains any content (detected by TT)
|
// and whether the sidebar contains any content (detected by TT)
|
||||||
if (has_sidebar[tab] == 0) {
|
if (has_sidebar[tab] == 0) {
|
||||||
$('.sidebar, #sidebar_toggle_img_out').hide();
|
$('.nd_sidebar, #nd_sidebar-toggle-img-out').hide();
|
||||||
$('.content').css('margin-right', '10px');
|
$('.content').css('margin-right', '10px');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (sidebar_hidden) {
|
if (sidebar_hidden) {
|
||||||
$('#sidebar_toggle_img_out').show();
|
$('#nd_sidebar-toggle-img-out').show();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$('.content').css('margin-right', '215px');
|
$('.content').css('margin-right', '215px');
|
||||||
$('.sidebar').show();
|
$('.nd_sidebar').show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,8 +44,8 @@ function do_search (event, tab) {
|
|||||||
|
|
||||||
// update browser search history with the new query.
|
// update browser search history with the new query.
|
||||||
// however if it's the same tab, this is a *replace* of the query url.
|
// however if it's the same tab, this is a *replace* of the query url.
|
||||||
// and just skip this bit if it's the reports display.
|
// and just skip this bit if it's the report or admin display.
|
||||||
if (path != 'report' && window.History && window.History.enabled) {
|
if (path != 'report' && path != 'admin' && window.History && window.History.enabled) {
|
||||||
is_from_history_plugin = 1;
|
is_from_history_plugin = 1;
|
||||||
window.History.replaceState(
|
window.History.replaceState(
|
||||||
{name: tab, fields: $(form).serializeArray()},
|
{name: tab, fields: $(form).serializeArray()},
|
||||||
@@ -104,8 +104,8 @@ function update_content(from, to) {
|
|||||||
|
|
||||||
// page title
|
// page title
|
||||||
var pgtitle = 'Netdisco';
|
var pgtitle = 'Netdisco';
|
||||||
if ($('#nd_device_name').text().length) {
|
if ($('#nd_device-name').text().length) {
|
||||||
var pgtitle = $('#nd_device_name').text() +' - '+ $('#'+ to + '_link').text();
|
var pgtitle = $('#nd_device-name').text() +' - '+ $('#'+ to + '_link').text();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.History && window.History.enabled && is_from_state_event == 0) {
|
if (window.History && window.History.enabled && is_from_state_event == 0) {
|
||||||
@@ -144,7 +144,7 @@ function device_form_state(e) {
|
|||||||
$('#nq').css('text-decoration', 'line-through');
|
$('#nq').css('text-decoration', 'line-through');
|
||||||
|
|
||||||
if (e.attr('type') == 'text') {
|
if (e.attr('type') == 'text') {
|
||||||
$('.field_copy_icon').hide();
|
$('.nd_field-copy-icon').hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,20 +160,20 @@ function device_form_state(e) {
|
|||||||
function(n,i) {return($(n).val() != "")}).length;
|
function(n,i) {return($(n).val() != "")}).length;
|
||||||
if (num_empty === 3) {
|
if (num_empty === 3) {
|
||||||
$('#nq').css('text-decoration', 'none');
|
$('#nq').css('text-decoration', 'none');
|
||||||
$('.field_copy_icon').show();
|
$('.nd_field-copy-icon').show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
// sidebar form fields should change colour and have bin/copy icon
|
// sidebar form fields should change colour and have bin/copy icon
|
||||||
$('.field_copy_icon').hide();
|
$('.nd_field-copy-icon').hide();
|
||||||
$('.field_clear_icon').hide();
|
$('.nd_field-clear-icon').hide();
|
||||||
|
|
||||||
// activate typeahead on the main search box, for device names only
|
// activate typeahead on the main search box, for device names only
|
||||||
$('#nq').typeahead({
|
$('#nq').typeahead({
|
||||||
source: function (query, process) {
|
source: function (query, process) {
|
||||||
return $.get('/ajax/data/device/typeahead', { query: query }, function (data) {
|
return $.get('/ajax/data/devicename/typeahead', { query: query }, function (data) {
|
||||||
return process(data);
|
return process(data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -197,30 +197,30 @@ $(document).ready(function() {
|
|||||||
$('.add-on :checkbox').each(syncCheckBox).click(syncCheckBox);
|
$('.add-on :checkbox').each(syncCheckBox).click(syncCheckBox);
|
||||||
|
|
||||||
// sidebar toggle - pinning
|
// sidebar toggle - pinning
|
||||||
$('.sidebar_pin').click(function() {
|
$('.nd_sidebar-pin').click(function() {
|
||||||
$('.sidebar').toggleClass('sidebar_pinned');
|
$('.nd_sidebar').toggleClass('nd_sidebar-pinned');
|
||||||
$('.sidebar_pin').toggleClass('sidebar_pin_clicked');
|
$('.nd_sidebar-pin').toggleClass('nd_sidebar-pin-clicked');
|
||||||
// update tooltip note for current state
|
// update tooltip note for current state
|
||||||
if ($('.sidebar_pin').hasClass('sidebar_pin_clicked')) {
|
if ($('.nd_sidebar-pin').hasClass('nd_sidebar-pin-clicked')) {
|
||||||
$('.sidebar_pin').first().data('tooltip').options.title = 'Unpin Sidebar';
|
$('.nd_sidebar-pin').first().data('tooltip').options.title = 'Unpin Sidebar';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$('.sidebar_pin').first().data('tooltip').options.title = 'Pin Sidebar';
|
$('.nd_sidebar-pin').first().data('tooltip').options.title = 'Pin Sidebar';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// sidebar toggle - trigger in/out on image click()
|
// sidebar toggle - trigger in/out on image click()
|
||||||
$('#sidebar_toggle_img_in').click(function() {
|
$('#nd_sidebar-toggle-img-in').click(function() {
|
||||||
$('.sidebar').toggle(250);
|
$('.nd_sidebar').toggle(250);
|
||||||
$('#sidebar_toggle_img_out').toggle();
|
$('#nd_sidebar-toggle-img-out').toggle();
|
||||||
$('.content').css('margin-right', '10px');
|
$('.content').css('margin-right', '10px');
|
||||||
sidebar_hidden = 1;
|
sidebar_hidden = 1;
|
||||||
});
|
});
|
||||||
$('#sidebar_toggle_img_out').click(function() {
|
$('#nd_sidebar-toggle-img-out').click(function() {
|
||||||
$('#sidebar_toggle_img_out').toggle();
|
$('#nd_sidebar-toggle-img-out').toggle();
|
||||||
$('.content').css('margin-right', '215px');
|
$('.content').css('margin-right', '215px');
|
||||||
$('.sidebar').toggle(250);
|
$('.nd_sidebar').toggle(250);
|
||||||
if (! $('.sidebar').hasClass('sidebar_pinned')) {
|
if (! $('.nd_sidebar').hasClass('nd_sidebar-pinned')) {
|
||||||
$(window).scrollTop(0);
|
$(window).scrollTop(0);
|
||||||
}
|
}
|
||||||
sidebar_hidden = 0;
|
sidebar_hidden = 0;
|
||||||
@@ -228,7 +228,7 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
// could not get twitter bootstrap tabs to behave, so implemented this
|
// could not get twitter bootstrap tabs to behave, so implemented this
|
||||||
// but warning! will probably not work for dropdowns in tabs
|
// but warning! will probably not work for dropdowns in tabs
|
||||||
$('#search_results li').delegate('a', 'click', function(event) {
|
$('#nd_search-results li').delegate('a', 'click', function(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
var from_li = $('.nav-tabs').find('> .active').first();
|
var from_li = $('.nav-tabs').find('> .active').first();
|
||||||
var to_li = $(this).parent('li')
|
var to_li = $(this).parent('li')
|
||||||
|
|||||||
@@ -32,12 +32,12 @@ function port_control (e) {
|
|||||||
}
|
}
|
||||||
else if ($.trim(td.attr('data-action')) == 'false') {
|
else if ($.trim(td.attr('data-action')) == 'false') {
|
||||||
$(e).next('span').text('');
|
$(e).next('span').text('');
|
||||||
$(e).toggleClass('nd_power_on');
|
$(e).toggleClass('nd_power-on');
|
||||||
$(e).data('tooltip').options.title = 'Click to Enable';
|
$(e).data('tooltip').options.title = 'Click to Enable';
|
||||||
td.attr('data-action', 'true');
|
td.attr('data-action', 'true');
|
||||||
}
|
}
|
||||||
else if ($.trim(td.attr('data-action')) == 'true') {
|
else if ($.trim(td.attr('data-action')) == 'true') {
|
||||||
$(e).toggleClass('nd_power_on');
|
$(e).toggleClass('nd_power-on');
|
||||||
$(e).data('tooltip').options.title = 'Click to Disable';
|
$(e).data('tooltip').options.title = 'Click to Disable';
|
||||||
td.attr('data-action', 'false');
|
td.attr('data-action', 'false');
|
||||||
}
|
}
|
||||||
|
|||||||
42
Netdisco/share/views/admintask.tt
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<i class="nd_sidebar-toggle icon-wrench icon-large" id="nd_sidebar-toggle-img-out"
|
||||||
|
rel="tooltip" data-placement="left" data-offset="5" data-title="Show Sidebar"></i>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="nd_sidebar nd_sidebar-pinned">
|
||||||
|
<div class="well">
|
||||||
|
<i class="nd_sidebar-toggle icon-signout" id="nd_sidebar-toggle-img-in"
|
||||||
|
rel="tooltip" data-placement="left" data-offset="5" data-title="Hide Sidebar"></i>
|
||||||
|
<i class="nd_sidebar-pin icon-pushpin nd_sidebar-pin-clicked"
|
||||||
|
rel="tooltip" data-placement="left" data-offset="5" data-title="Unpin Sidebar"></i>
|
||||||
|
|
||||||
|
<div class="tab-content">
|
||||||
|
<div id="[% task.tag %]_search" class="tab-pane active">
|
||||||
|
<form id="[% task.tag %]_form" class="nd_sidebar-form form-stacked"
|
||||||
|
method="get" action="[% uri_for('/admin') %]">
|
||||||
|
[% TRY %]
|
||||||
|
[% INCLUDE "sidebar/admintask/${task.tag}.tt" %]
|
||||||
|
<script type="text/javascript">has_sidebar["[% task.tag %]"] = 1;</script>
|
||||||
|
[% CATCH %]
|
||||||
|
<script type="text/javascript">has_sidebar["[% task.tag %]"] = 0;</script>
|
||||||
|
[% END %]
|
||||||
|
</form>
|
||||||
|
</div> <!-- /tab-pane -->
|
||||||
|
</div> <!-- /tab-content -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<ul id="nd_search-results" class="nav nav-tabs">
|
||||||
|
<li class="active"><a id="[% task.tag %]_link" class="nd_single-tab"
|
||||||
|
href="#[% task.tag %]_pane">[% task.label %]</a></li>
|
||||||
|
[% IF task.tag == 'jobqueue' %]
|
||||||
|
<span id="nd_device-name"></span>
|
||||||
|
[% END %]
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="tab-pane active" id="[% task.tag %]_pane"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
[%+ INCLUDE 'js/admintask.js' -%]
|
||||||
|
</script>
|
||||||
51
Netdisco/share/views/ajax/admintask/jobqueue.tt
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<table class="table table-bordered table-condensed table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="nd_center-cell">Entered</th>
|
||||||
|
<th class="nd_center-cell">Action</th>
|
||||||
|
<th class="nd_center-cell">Status</th>
|
||||||
|
<th class="nd_center-cell">Device</th>
|
||||||
|
<th class="nd_center-cell">Port</th>
|
||||||
|
<th class="nd_center-cell">Param</th>
|
||||||
|
<th class="nd_center-cell">User</th>
|
||||||
|
<th class="nd_center-cell">Started</th>
|
||||||
|
<th class="nd_center-cell">Finished</th>
|
||||||
|
<th class="nd_center-cell">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</tbody>
|
||||||
|
[% WHILE (row = results.next) %]
|
||||||
|
<tr
|
||||||
|
[% ' class="success"' IF row.status == 'done' %]
|
||||||
|
[% ' class="error"' IF row.status == 'error' %]
|
||||||
|
[% ' class="info"' IF row.status.search('^queued-') %]
|
||||||
|
>
|
||||||
|
<td class="nd_center-cell">[% row.entered_stamp | html_entity %]</td>
|
||||||
|
<td class="nd_center-cell">
|
||||||
|
[% FOREACH word IN row.action.split('_') %]
|
||||||
|
[% word.ucfirst | html_entity %]
|
||||||
|
[% END %]
|
||||||
|
</td>
|
||||||
|
[% IF row.status.search('^queued-') %]
|
||||||
|
<td class="nd_center-cell">Running on "[% row.status.remove('^queued-') | html_entity %]"</td>
|
||||||
|
[% ELSE %]
|
||||||
|
<td class="nd_center-cell">[% row.status.ucfirst | html_entity %]</td>
|
||||||
|
[% END %]
|
||||||
|
<td class="nd_center-cell"><a class="nd_linkcell"
|
||||||
|
href="[% uri_for('/device') %]?q=[% row.device | uri %]">[% row.device | html_entity %]</a></td>
|
||||||
|
<td class="nd_center-cell">[% row.port | html_entity %]</td>
|
||||||
|
<td class="nd_center-cell">[% row.subaction | html_entity %]</td>
|
||||||
|
<td class="nd_center-cell">[% row.username | html_entity %]</td>
|
||||||
|
<td class="nd_center-cell">[% row.started_stamp | html_entity %]</td>
|
||||||
|
<td class="nd_center-cell">[% row.finished_stamp | html_entity %]</td>
|
||||||
|
<td class="nd_center-cell">
|
||||||
|
<form name="del" class="nd_inline-form">
|
||||||
|
<input name="job" type="hidden" value="[% row.job | html_entity %]">
|
||||||
|
<button class="btn" name="del" type="submit"><i class="icon-trash text-error"></i></button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
[% END %]
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
44
Netdisco/share/views/ajax/admintask/pseudodevice.tt
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<table class="table table-bordered table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<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">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</tbody>
|
||||||
|
<tr>
|
||||||
|
<form name="add">
|
||||||
|
<td class="nd_center-cell"><input name="dns" type="text"></td>
|
||||||
|
<td class="nd_center-cell"><input name="ip" type="text"></td>
|
||||||
|
<td class="nd_center-cell"><input name="ports" type="number"></td>
|
||||||
|
<td class="nd_center-cell">
|
||||||
|
<button class="btn btn-small" name="add" type="submit"><i class="icon-plus-sign"></i> Add</button>
|
||||||
|
</td>
|
||||||
|
</form>
|
||||||
|
</tr>
|
||||||
|
[% WHILE (row = results.next) %]
|
||||||
|
<tr>
|
||||||
|
<form name="update">
|
||||||
|
<td class="nd_center-cell"><a class="nd_linkcell"
|
||||||
|
href="[% uri_for('/device') %]?q=[% row.dns | uri %]">[% row.dns | html_entity %]</a></td>
|
||||||
|
<td class="nd_center-cell">[% row.ip | html_entity %]</td>
|
||||||
|
<td class="nd_center-cell"><input name="ports" type="number" value="[% row.port_count | html_entity %]"></td>
|
||||||
|
<td class="nd_center-cell">
|
||||||
|
<input name="dns" type="hidden" value="[% row.dns | html_entity %]">
|
||||||
|
<input name="ip" type="hidden" value="[% row.ip | html_entity %]">
|
||||||
|
<button class="btn" name="update" type="submit"><i class="icon-save text-warning"></i></button>
|
||||||
|
</form>
|
||||||
|
<form name="del" class="nd_inline-form">
|
||||||
|
<input name="dns" type="hidden" value="[% row.dns | html_entity %]">
|
||||||
|
<input name="ip" type="hidden" value="[% row.ip | html_entity %]">
|
||||||
|
<input name="ports" type="hidden" value="[% row.port_count | html_entity %]">
|
||||||
|
<button class="btn" name="del" type="submit"><i class="icon-trash text-error"></i></button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
[% END %]
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
64
Netdisco/share/views/ajax/admintask/topology.tt
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<table class="table table-bordered table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="nd_center-cell">Left Device</th>
|
||||||
|
<th class="nd_center-cell">Left Port</th>
|
||||||
|
<th class="nd_center-cell">Right Device</th>
|
||||||
|
<th class="nd_center-cell">Right Port</th>
|
||||||
|
<th class="nd_center-cell">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</tbody>
|
||||||
|
<tr>
|
||||||
|
<form name="add">
|
||||||
|
<td class="nd_center-cell">
|
||||||
|
<div class="input-append">
|
||||||
|
<input class="nd_topo_dev nd_topo_dev1" name="dev1" type="text">
|
||||||
|
<span class="add-on nd_topo_dev_caret"><i class="icon-caret-down icon-large"></i></span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="nd_center-cell">
|
||||||
|
<div class="input-append">
|
||||||
|
<input class="nd_topo_port nd_topo_dev1" name="port1" type="text">
|
||||||
|
<span class="add-on nd_topo_port_caret"><i class="icon-caret-down icon-large"></i></span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="nd_center-cell">
|
||||||
|
<div class="input-append">
|
||||||
|
<input class="nd_topo_dev nd_topo_dev2" name="dev2" type="text">
|
||||||
|
<span class="add-on nd_topo_dev_caret"><i class="icon-caret-down icon-large"></i></span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="nd_center-cell">
|
||||||
|
<div class="input-append">
|
||||||
|
<input class="nd_topo_port nd_topo_dev2" name="port2" type="text">
|
||||||
|
<span class="add-on nd_topo_port_caret"><i class="icon-caret-down icon-large"></i></span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="nd_center-cell">
|
||||||
|
<button class="btn btn-small" name="add" type="submit"><i class="icon-plus-sign"></i> Add</button>
|
||||||
|
</td>
|
||||||
|
</form>
|
||||||
|
</tr>
|
||||||
|
[% WHILE (row = results.next) %]
|
||||||
|
<tr>
|
||||||
|
<form name="del">
|
||||||
|
<td class="nd_center-cell"><a class="nd_linkcell"
|
||||||
|
href="[% uri_for('/device') %]?q=[% row.dev1 | uri %]">[% row.dev1 | html_entity %]</a></td>
|
||||||
|
<td class="nd_center-cell">[% row.port1 | html_entity %]</td>
|
||||||
|
<td class="nd_center-cell"><a class="nd_linkcell"
|
||||||
|
href="[% uri_for('/device') %]?q=[% row.dev2 | uri %]">[% row.dev2 | html_entity %]</a></td>
|
||||||
|
<td class="nd_center-cell">[% row.port2 | html_entity %]</td>
|
||||||
|
<td class="nd_center-cell">
|
||||||
|
<input name="dev1" type="hidden" value="[% row.dev1 | html_entity %]">
|
||||||
|
<input name="port1" type="hidden" value="[% row.port1 | html_entity %]">
|
||||||
|
<input name="dev2" type="hidden" value="[% row.dev2 | html_entity %]">
|
||||||
|
<input name="port2" type="hidden" value="[% row.port2 | html_entity %]">
|
||||||
|
<button class="btn" name="del" type="submit"><i class="icon-trash text-error"></i></button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
[% END %]
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<table class="table-bordered table-condensed table-striped">
|
<table class="table table-bordered table-condensed table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Address</th>
|
<th>Address</th>
|
||||||
<th>DNS</th>
|
<th>DNS</th>
|
||||||
<th class="center_cell">Interface</th>
|
<th class="nd_center-cell">Interface</th>
|
||||||
<th>Description</th>
|
<th>Description</th>
|
||||||
<th>Prefix</th>
|
<th>Prefix</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>[% row.alias | html_entity %]</a>
|
<td>[% row.alias | html_entity %]</a>
|
||||||
<td>[% row.dns | html_entity %]</a>
|
<td>[% row.dns | html_entity %]</a>
|
||||||
<td class="center_cell"><a class="nd_linkcell"
|
<td class="nd_center-cell"><a class="nd_linkcell"
|
||||||
href="[% device_ports %]&q=[% params.q | uri %]&f=[% row.port | uri %]">[% row.port | html_entity %]</a></td>
|
href="[% device_ports %]&q=[% params.q | uri %]&f=[% row.port | uri %]">[% row.port | html_entity %]</a></td>
|
||||||
<td>[% row.device_port.name | html_entity %]</td>
|
<td>[% row.device_port.name | html_entity %]</td>
|
||||||
<td><a class="nd_linkcell"
|
<td><a class="nd_linkcell"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<table class="table-condensed table-striped">
|
<table class="table table-condensed table-striped">
|
||||||
</tbody>
|
</tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>System Name</td>
|
<td>System Name</td>
|
||||||
@@ -7,11 +7,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Location
|
<td>Location
|
||||||
[% IF vars.user.port_control %]
|
[% IF vars.user.port_control %]
|
||||||
<i class="icon-edit nd_edit_icon nd_device_details_edit"></i>
|
<i class="icon-edit nd_edit-icon nd_device-details-edit"></i>
|
||||||
[% END %]
|
[% END %]
|
||||||
</td>
|
</td>
|
||||||
[% IF vars.user.port_control %]
|
[% IF vars.user.port_control %]
|
||||||
<td class="nd_editable_cell" contenteditable="true"
|
<td class="nd_editable-cell" contenteditable="true"
|
||||||
data-field="location" data-for-device="[% d.ip %]">
|
data-field="location" data-for-device="[% d.ip %]">
|
||||||
[% d.location | html_entity %]
|
[% d.location | html_entity %]
|
||||||
</td>
|
</td>
|
||||||
@@ -25,11 +25,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Contact
|
<td>Contact
|
||||||
[% IF vars.user.port_control %]
|
[% IF vars.user.port_control %]
|
||||||
<i class="icon-edit nd_edit_icon nd_device_details_edit"></i>
|
<i class="icon-edit nd_edit-icon nd_device-details-edit"></i>
|
||||||
[% END %]
|
[% END %]
|
||||||
</td>
|
</td>
|
||||||
[% IF vars.user.port_control %]
|
[% IF vars.user.port_control %]
|
||||||
<td class="nd_editable_cell" contenteditable="true"
|
<td class="nd_editable-cell" contenteditable="true"
|
||||||
data-field="contact" data-for-device="[% d.ip | html_entity %]">
|
data-field="contact" data-for-device="[% d.ip | html_entity %]">
|
||||||
[% d.contact | html_entity %]
|
[% d.contact | html_entity %]
|
||||||
</td>
|
</td>
|
||||||
@@ -93,5 +93,24 @@
|
|||||||
<td>VTP Domain</td>
|
<td>VTP Domain</td>
|
||||||
<td>[% d.vtp_domain | html_entity %]</td>
|
<td>[% d.vtp_domain | html_entity %]</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
[% IF vars.user.admin %]
|
||||||
|
<tr>
|
||||||
|
<td>Admin Tasks</td>
|
||||||
|
<td>
|
||||||
|
<form method="post" class="nd_inline-form" action="[% uri_for('/admin/discover') %]">
|
||||||
|
<input type="hidden" value="[% d.ip %]" name="device" type="text"/>
|
||||||
|
<button type="submit" class="btn btn-info btn-small">Discover</button>
|
||||||
|
</form>
|
||||||
|
<form method="post" class="nd_inline-form" action="[% uri_for('/admin/arpnip') %]">
|
||||||
|
<input type="hidden" value="[% d.ip %]" name="device" type="text"/>
|
||||||
|
<button type="submit" class="btn btn-info btn-small">Arpnip</button>
|
||||||
|
</form>
|
||||||
|
<form method="post" class="nd_inline-form" action="[% uri_for('/admin/macsuck') %]">
|
||||||
|
<input type="hidden" value="[% d.ip %]" name="device" type="text"/>
|
||||||
|
<button type="submit" class="btn btn-info btn-small">Macsuck</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
[% END %]
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<table class="table-bordered table-condensed table-striped">
|
<table class="table table-bordered table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
@@ -6,7 +6,10 @@
|
|||||||
[% NEXT IF item.name == 'c_admin' %]
|
[% NEXT IF item.name == 'c_admin' %]
|
||||||
[% NEXT IF item.name == 'c_nodes' AND params.c_nodes AND params.c_neighbors %]
|
[% NEXT IF item.name == 'c_nodes' AND params.c_nodes AND params.c_neighbors %]
|
||||||
[% NEXT UNLESS params.${item.name} %]
|
[% NEXT UNLESS params.${item.name} %]
|
||||||
<th[% ' class="center_cell"' IF NOT loop.first %]>[% item.label | html_entity %]</th>
|
<th[% ' class="nd_nudge-for-icon"' IF
|
||||||
|
(vars.user.port_control AND params.c_admin AND (item.name == 'c_port' OR item.name == 'c_name')) %]>
|
||||||
|
[% item.label | html_entity %]
|
||||||
|
</th>
|
||||||
[% END %]
|
[% END %]
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -25,87 +28,109 @@
|
|||||||
[% END %]
|
[% END %]
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
[% FOREACH config IN settings._extra_device_port_cols %]
|
||||||
|
[% NEXT UNLESS config.position == 'left' AND params.${config.name} %]
|
||||||
|
<td>
|
||||||
|
[% TRY %]
|
||||||
|
[% INCLUDE "plugin/${config.name}/device_port_column.tt" %]
|
||||||
|
[% CATCH %]
|
||||||
|
<!-- dummy content required by Template Toolkit TRY -->
|
||||||
|
[% END %]
|
||||||
|
</td>
|
||||||
|
[% END %]
|
||||||
|
|
||||||
[% IF params.c_port %]
|
[% IF params.c_port %]
|
||||||
[% IF vars.user.port_control AND params.c_admin %]
|
[% IF vars.user.port_control AND params.c_admin %]
|
||||||
[% IF row.up_admin == 'up' %]
|
[% IF row.up_admin == 'up' %]
|
||||||
<td nowrap class="nd_editable_cell" data-action="down"
|
<td nowrap class="nd_editable-cell" data-action="down"
|
||||||
data-field="c_port" data-for-device="[% device | html_entity %]" data-for-port="[% row.port | html_entity %]">
|
data-field="c_port" data-for-device="[% device.ip | html_entity %]" data-for-port="[% row.port | html_entity %]">
|
||||||
<i class="icon-hand-down nd_hand_icon"
|
<i class="icon-hand-down nd_hand-icon"
|
||||||
rel="tooltip" data-placement="top" data-offset="3"
|
rel="tooltip" data-placement="top" data-offset="3"
|
||||||
data-animation="" data-title="Click to Disable"></i>
|
data-animation="" data-title="Click to Disable"></i>
|
||||||
[% ELSE %]
|
[% ELSE %]
|
||||||
<td nowrap class="nd_editable_cell" data-action="up"
|
<td nowrap class="nd_editable-cell" data-action="up"
|
||||||
data-field="c_port" data-for-device="[% device | html_entity %]" data-for-port="[% row.port | html_entity %]">
|
data-field="c_port" data-for-device="[% device.ip | html_entity %]" data-for-port="[% row.port | html_entity %]">
|
||||||
<i class="icon-hand-up nd_hand_icon"
|
<i class="icon-hand-up nd_hand-icon"
|
||||||
rel="tooltip" data-placement="top" data-offset="3"
|
rel="tooltip" data-placement="top" data-offset="3"
|
||||||
data-animation="" data-title="Click to Enable"></i>
|
data-animation="" data-title="Click to Enable"></i>
|
||||||
[% END %]
|
[% END %]
|
||||||
[% ELSE %]
|
[% ELSE %]
|
||||||
<td nowrap>
|
<td nowrap>
|
||||||
[% END %]
|
[% END %]
|
||||||
<a class="nd_linkcell nd_this_port_only" href="[% uri_for('/device',
|
<a class="nd_linkcell nd_this-port-only" href="[% uri_for('/device',
|
||||||
self_options) %]&q=[% params.q | uri %]&f=[% row.port | uri %]">
|
self_options) %]&q=[% params.q | uri %]&f=[% row.port | uri %]">
|
||||||
[% row.port | html_entity %]
|
[% row.port | html_entity %]
|
||||||
</a></td>
|
</a></td>
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
||||||
|
[% FOREACH config IN settings._extra_device_port_cols %]
|
||||||
|
[% NEXT UNLESS config.position == 'mid' AND params.${config.name} %]
|
||||||
|
<td>
|
||||||
|
[% TRY %]
|
||||||
|
[% INCLUDE "plugin/${config.name}/device_port_column.tt" %]
|
||||||
|
[% CATCH %]
|
||||||
|
<!-- dummy content required by Template Toolkit TRY -->
|
||||||
|
[% END %]
|
||||||
|
</td>
|
||||||
|
[% END %]
|
||||||
|
|
||||||
[% IF params.c_descr %]
|
[% IF params.c_descr %]
|
||||||
<td nowrap class="center_cell">[% row.descr | html_entity %]</td>
|
<td nowrap>[% row.descr | html_entity %]</td>
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
||||||
[% IF params.c_type %]
|
[% IF params.c_type %]
|
||||||
<td class="center_cell">[% row.type | html_entity %]</td>
|
<td>[% row.type | html_entity %]</td>
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
||||||
[% IF params.c_duplex %]
|
[% IF params.c_duplex %]
|
||||||
<td class="center_cell">
|
<td>
|
||||||
[% IF row.up == 'up' AND row.duplex %]
|
[% IF row.up == 'up' AND row.duplex %]
|
||||||
[% row.duplex_admin | html_entity %] / [% row.duplex | html_entity %]
|
[% row.duplex_admin.ucfirst | html_entity %] / [% row.duplex.ucfirst | html_entity %]
|
||||||
[% END %]
|
[% END %]
|
||||||
</td>
|
</td>
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
||||||
[% IF params.c_lastchange %]
|
[% IF params.c_lastchange %]
|
||||||
<td class="center_cell">[% row.lastchange_stamp | html_entity %]</td>
|
<td>[% row.lastchange_stamp | html_entity %]</td>
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
||||||
[% IF params.c_name %]
|
[% IF params.c_name %]
|
||||||
[% IF vars.user.port_control AND params.c_admin %]
|
[% IF vars.user.port_control AND params.c_admin %]
|
||||||
<td nowrap class="center_cell nd_editable_cell" contenteditable="true"
|
<td nowrap class="nd_editable-cell" contenteditable="true"
|
||||||
data-field="c_name" data-for-device="[% device | html_entity %]" data-for-port="[% row.port | html_entity %]">
|
data-field="c_name" data-for-device="[% device.ip | html_entity %]" data-for-port="[% row.port | html_entity %]">
|
||||||
<i class="icon-edit nd_edit_icon"></i>
|
<i class="icon-edit nd_edit-icon"></i>
|
||||||
[% ELSE %]
|
[% ELSE %]
|
||||||
<td nowrap class="center_cell">
|
<td nowrap>
|
||||||
[% END %]
|
[% END %]
|
||||||
<div class="nd_editable_cell_content">
|
<div class="nd_editable-cell-content">
|
||||||
[% row.name | html_entity %]
|
[% row.name | html_entity %]
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
||||||
[% IF params.c_speed %]
|
[% IF params.c_speed %]
|
||||||
<td class="center_cell">[% row.speed | html_entity %]</td>
|
<td>[% row.speed | html_entity %]</td>
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
||||||
[% IF params.c_mac %]
|
[% IF params.c_mac %]
|
||||||
<td class="center_cell">[% row.mac | html_entity %]</td>
|
<td>[% row.mac | html_entity %]</td>
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
||||||
[% IF params.c_mtu %]
|
[% IF params.c_mtu %]
|
||||||
<td class="center_cell">[% row.mtu | html_entity %]</td>
|
<td>[% row.mtu | html_entity %]</td>
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
||||||
[% IF params.c_vlan %]
|
[% IF params.c_vlan %]
|
||||||
[% IF vars.user.port_control AND params.c_admin %]
|
[% IF vars.user.port_control AND params.c_admin %]
|
||||||
<td class="center_cell nd_editable_cell" contenteditable="true"
|
<td class="nd_editable-cell" contenteditable="true"
|
||||||
data-field="c_vlan" data-for-device="[% device | html_entity %]" data-for-port="[% row.port | html_entity %]">
|
data-field="c_vlan" data-for-device="[% device.ip | html_entity %]" data-for-port="[% row.port | html_entity %]">
|
||||||
<i class="icon-edit nd_edit_icon"></i>
|
<i class="icon-edit nd_edit-icon"></i>
|
||||||
<div class="nd_editable_cell_content">
|
<div class="nd_editable-cell-content">
|
||||||
[% IF row.vlan %][% row.vlan | html_entity %][% END %]
|
[% IF row.vlan %][% row.vlan | html_entity %][% END %]
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
[% ELSE %]
|
[% ELSE %]
|
||||||
<td class="center_cell">
|
<td>
|
||||||
<a class="nd_linkcell"
|
<a class="nd_linkcell"
|
||||||
href="[% uri_for('/search') %]?tab=vlan&q=[% row.vlan | uri %]">
|
href="[% uri_for('/search') %]?tab=vlan&q=[% row.vlan | uri %]">
|
||||||
[% row.vlan | html_entity %]</a>
|
[% row.vlan | html_entity %]</a>
|
||||||
@@ -123,11 +148,10 @@
|
|||||||
[% SET output = output _ ', ' IF NOT loop.last %]
|
[% SET output = output _ ', ' IF NOT loop.last %]
|
||||||
[% END %]
|
[% END %]
|
||||||
[% IF row.tagged_vlans_count > 10 %] [%# TODO make this a settable variable %]
|
[% IF row.tagged_vlans_count > 10 %] [%# TODO make this a settable variable %]
|
||||||
[% SET output = '<div class="vlan_total">(' _ row.tagged_vlans_count
|
[% SET output = '<div class="nd_vlan-total">(' _ row.tagged_vlans_count
|
||||||
_ ')</div><span class="nd_linkcell nd_collapse_vlans">
|
_ ')</div><span class="nd_linkcell nd_collapse-vlans">
|
||||||
<i class="cell-arrow-up-down icon-chevron-up icon-large">
|
<div class="nd_arrow-up-down-left icon-chevron-up icon-large"></div>Show VLANs</span>
|
||||||
</i>Show VLANs</span>
|
<div class="nd_collapsing nd_collapse-pre-hidden">' _ output %]
|
||||||
<div class="nd_collapsing nd_collapse_pre_hidden">' _ output %]
|
|
||||||
[% SET output = output _ '</div>' %]
|
[% SET output = output _ '</div>' %]
|
||||||
[% END %]
|
[% END %]
|
||||||
[% output %]
|
[% output %]
|
||||||
@@ -140,15 +164,15 @@
|
|||||||
[% IF row.power.admin == 'true' %]
|
[% IF row.power.admin == 'true' %]
|
||||||
[% IF vars.user.port_control AND params.c_admin %]
|
[% IF vars.user.port_control AND params.c_admin %]
|
||||||
<td nowrap data-action="false"
|
<td nowrap data-action="false"
|
||||||
data-field="c_power" data-for-device="[% device | html_entity %]"
|
data-field="c_power" data-for-device="[% device.ip | html_entity %]"
|
||||||
data-for-port="[% row.port | html_entity %]">
|
data-for-port="[% row.port | html_entity %]">
|
||||||
|
|
||||||
<i class="icon-off nd_power_icon nd_power_on"
|
<i class="icon-off nd_power-icon nd_power-on"
|
||||||
rel="tooltip" data-placement="top" data-offset="3"
|
rel="tooltip" data-placement="top" data-offset="3"
|
||||||
data-animation="" data-title="Click to Disable"></i>
|
data-animation="" data-title="Click to Disable"></i>
|
||||||
[% ELSE %]
|
[% ELSE %]
|
||||||
<td nowrap>
|
<td nowrap>
|
||||||
<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 > 0 %]
|
||||||
@@ -160,10 +184,10 @@
|
|||||||
[% ELSE %]
|
[% ELSE %]
|
||||||
[% IF vars.user.port_control AND params.c_admin %]
|
[% IF vars.user.port_control AND params.c_admin %]
|
||||||
<td nowrap data-action="true"
|
<td nowrap data-action="true"
|
||||||
data-field="c_power" data-for-device="[% device | html_entity %]"
|
data-field="c_power" data-for-device="[% device.ip | html_entity %]"
|
||||||
data-for-port="[% row.port | html_entity %]">
|
data-for-port="[% row.port | html_entity %]">
|
||||||
|
|
||||||
<i class="icon-off nd_power_icon"
|
<i class="icon-off nd_power-icon"
|
||||||
rel="tooltip" data-placement="top" data-offset="3"
|
rel="tooltip" data-placement="top" data-offset="3"
|
||||||
data-animation="" data-title="Click to Enable"></i>
|
data-animation="" data-title="Click to Enable"></i>
|
||||||
[% ELSE %]
|
[% ELSE %]
|
||||||
@@ -179,22 +203,25 @@
|
|||||||
|
|
||||||
[% IF params.c_nodes OR params.c_neighbors %]
|
[% IF params.c_nodes OR params.c_neighbors %]
|
||||||
<td>
|
<td>
|
||||||
[% IF params.c_neighbors AND row.remote_ip %]
|
[% IF params.c_neighbors AND (row.remote_ip OR row.is_uplink) %]
|
||||||
[% IF row.neighbor %]
|
[% IF row.neighbor %]
|
||||||
<a href="[% uri_for('/device',
|
<a href="[% uri_for('/device',
|
||||||
self_options) %]&q=[% row.neighbor.dns || row.neighbor.ip | uri %]&f=[% row.remote_port | uri %]">
|
self_options) %]&q=[% row.neighbor.dns || row.neighbor.ip | uri %]&f=[% row.remote_port | uri %]">
|
||||||
[% row.neighbor.dns.remove(settings.domain_suffix) || row.neighbor.ip | html_entity %]
|
[% row.neighbor.dns.remove(settings.domain_suffix) || row.neighbor.ip | html_entity %]
|
||||||
([% row.remote_port | html_entity %])</a>
|
([% row.remote_port | html_entity %])</a>
|
||||||
[% ELSE %]
|
[% ELSIF row.remote_ip AND row.remote_port %]
|
||||||
<span class="label label-important">N</span>
|
<span class="label label-important">N</span>
|
||||||
<a href="[% search_node %]&q=[% row.remote_ip | uri %]">
|
<a href="[% search_node %]&q=[% row.remote_ip | uri %]">
|
||||||
[% row.remote_ip | html_entity %] (port: [% row.remote_port | html_entity %]
|
[% row.remote_ip | html_entity %] (port: [% row.remote_port | html_entity %]
|
||||||
id: [% (row.remote_type _ ' / ') IF row.remote_type %][% row.remote_id | html_entity %])</a>
|
[% ' id: '_ row.remote_type IF row.remote_type%]
|
||||||
|
[% ' type: '_ row.remote_id IF row.remote_id%])</a>
|
||||||
|
[% ELSE %]
|
||||||
|
<span class="label label-important">N</span> (probable neighbor)
|
||||||
[% END %]
|
[% END %]
|
||||||
[% END %]
|
[% END %]
|
||||||
[% IF params.c_nodes %]
|
[% IF params.c_nodes %]
|
||||||
[% FOREACH node IN row.$nodes %]
|
[% FOREACH node IN row.$nodes %]
|
||||||
[% '<br/>' IF row.remote_ip OR NOT loop.first %]
|
[% '<br/>' IF (row.remote_ip OR row.is_uplink) OR NOT loop.first %]
|
||||||
[% '<span class="label label-warning">A</span> ' IF NOT node.active %]
|
[% '<span class="label label-warning">A</span> ' IF NOT node.active %]
|
||||||
<a href="[% search_node %]&q=[% node.net_mac.$mac_format_call | uri %]">
|
<a href="[% search_node %]&q=[% node.net_mac.$mac_format_call | uri %]">
|
||||||
[% node.net_mac.$mac_format_call | html_entity %]</a>
|
[% node.net_mac.$mac_format_call | html_entity %]</a>
|
||||||
@@ -216,14 +243,25 @@
|
|||||||
[% END %]
|
[% END %]
|
||||||
|
|
||||||
[% IF params.c_stp %]
|
[% IF params.c_stp %]
|
||||||
<td class="center_cell">[% row.stp | html_entity %]</td>
|
<td>[% row.stp | html_entity %]</td>
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
||||||
[% IF params.c_up %]
|
[% IF params.c_up %]
|
||||||
<td class="center_cell">
|
<td>
|
||||||
[% row.up_admin | html_entity %] / [% row.up | html_entity %]
|
[% row.up_admin.ucfirst | html_entity %] / [% row.up.ucfirst | html_entity %]
|
||||||
</td>
|
</td>
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|
||||||
|
[% FOREACH config IN settings._extra_device_port_cols %]
|
||||||
|
[% NEXT UNLESS config.position == 'right' AND params.${config.name} %]
|
||||||
|
<td>
|
||||||
|
[% TRY %]
|
||||||
|
[% INCLUDE "plugin/${config.name}/device_port_column.tt" %]
|
||||||
|
[% CATCH %]
|
||||||
|
<!-- dummy content required by Template Toolkit TRY -->
|
||||||
|
[% END %]
|
||||||
|
</td>
|
||||||
|
[% END %]
|
||||||
</tr>
|
</tr>
|
||||||
[% END %]
|
[% END %]
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
<table class="table-bordered table-condensed table-striped">
|
<table class="table table-bordered table-condensed table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="center_cell">Left Device</th>
|
<th class="nd_center-cell">Left Device</th>
|
||||||
<th class="center_cell">Interface</th>
|
<th class="nd_center-cell">Interface</th>
|
||||||
<th class="center_cell">Duplex</th>
|
<th class="nd_center-cell">Duplex</th>
|
||||||
<th class="center_cell">Right Device</th>
|
<th class="nd_center-cell">Right Device</th>
|
||||||
<th class="center_cell">Interface</th>
|
<th class="nd_center-cell">Interface</th>
|
||||||
<th class="center_cell">Duplex</th>
|
<th class="nd_center-cell">Duplex</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
</tbody>
|
</tbody>
|
||||||
[% WHILE (row = results.next) %]
|
[% WHILE (row = results.next) %]
|
||||||
<tr>
|
<tr>
|
||||||
<td class="center_cell">[% row.left_dns || row.left_ip | html_entity %]</a>
|
<td class="nd_center-cell">[% row.left_dns || row.left_ip | html_entity %]</a>
|
||||||
<td class="center_cell"><a class="nd_linkcell"
|
<td class="nd_center-cell"><a class="nd_linkcell"
|
||||||
href="[% device_ports %]&q=[% row.left_dns || row.left_ip | uri %]&f=[% row.left_port | uri %]&c_duplex=on">
|
href="[% device_ports %]&q=[% row.left_dns || row.left_ip | uri %]&f=[% row.left_port | uri %]&c_duplex=on">
|
||||||
[% row.left_port | html_entity %]</a></td>
|
[% row.left_port | html_entity %]</a></td>
|
||||||
<td class="center_cell">[% row.left_duplex.ucfirst | html_entity %]</td>
|
<td class="nd_center-cell">[% row.left_duplex.ucfirst | html_entity %]</td>
|
||||||
|
|
||||||
<td class="center_cell">[% row.right_dns || row.right_ip | html_entity %]</a>
|
<td class="nd_center-cell">[% row.right_dns || row.right_ip | html_entity %]</a>
|
||||||
<td class="center_cell"><a class="nd_linkcell"
|
<td class="nd_center-cell"><a class="nd_linkcell"
|
||||||
href="[% device_ports %]&q=[% row.right_dns || row.right_ip | uri %]&f=[% row.right_port | uri %]&c_duplex=on">
|
href="[% device_ports %]&q=[% row.right_dns || row.right_ip | uri %]&f=[% row.right_port | uri %]&c_duplex=on">
|
||||||
[% row.right_port | html_entity %]</a></td>
|
[% row.right_port | html_entity %]</a></td>
|
||||||
<td class="center_cell">[% row.right_duplex.ucfirst | html_entity %]</td>
|
<td class="nd_center-cell">[% row.right_duplex.ucfirst | html_entity %]</td>
|
||||||
</tr>
|
</tr>
|
||||||
[% END %]
|
[% END %]
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<table class="table-bordered table-condensed table-striped">
|
<table class="table table-bordered table-condensed table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Device</th>
|
<th>Device</th>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<table class="table-bordered table-condensed table-striped">
|
<table class="table table-bordered table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>MAC</th>
|
<th>MAC</th>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<table class="table-bordered table-condensed table-striped">
|
<table class="table table-bordered table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>MAC</th>
|
<th>MAC</th>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<table class="table-bordered table-condensed table-striped">
|
<table class="table table-bordered table-condensed table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Description</th>
|
<th>Description</th>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<table class="table-bordered table-condensed table-striped">
|
<table class="table table-bordered table-condensed table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Vlan</th>
|
<th>Vlan</th>
|
||||||
@@ -12,17 +12,17 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
[% WHILE (row = results.next) %]
|
[% WHILE (row = results.next) %]
|
||||||
<tr>
|
<tr>
|
||||||
<td><a class="nd_linkcell nd_stealthlink"
|
<td><a class="nd_linkcell nd_stealth-link"
|
||||||
href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.vlan.vlan | uri %]">[% row.vlan.vlan | html_entity %]</a></td>
|
href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.vlan.vlan | uri %]">[% row.vlan.vlan | html_entity %]</a></td>
|
||||||
<td><a class="nd_linkcell"
|
<td><a class="nd_linkcell"
|
||||||
href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.vlan.vlan | uri %]">[% row.dns || row.ip | html_entity %]</a></td>
|
href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.vlan.vlan | uri %]">[% row.dns || row.ip | html_entity %]</a></td>
|
||||||
<td><a class="nd_linkcell nd_stealthlink"
|
<td><a class="nd_linkcell nd_stealth-link"
|
||||||
href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.vlan.vlan | uri %]">[% row.vlan.description | html_entity %]</a></td>
|
href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.vlan.vlan | uri %]">[% row.vlan.description | html_entity %]</a></td>
|
||||||
<td><a class="nd_linkcell nd_stealthlink"
|
<td><a class="nd_linkcell nd_stealth-link"
|
||||||
href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.vlan.vlan | uri %]">[% row.model | html_entity %]</a></td>
|
href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.vlan.vlan | uri %]">[% row.model | html_entity %]</a></td>
|
||||||
<td><a class="nd_linkcell nd_stealthlink"
|
<td><a class="nd_linkcell nd_stealth-link"
|
||||||
href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.vlan.vlan | uri %]">[% row.os | html_entity %]</a></td>
|
href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.vlan.vlan | uri %]">[% row.os | html_entity %]</a></td>
|
||||||
<td><a class="nd_linkcell nd_stealthlink"
|
<td><a class="nd_linkcell nd_stealth-link"
|
||||||
href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.vlan.vlan | uri %]">[% row.vendor | html_entity %]</a></td>
|
href="[% device_ports %]&q=[% row.dns || row.ip | uri %]&f=[% row.vlan.vlan | uri %]">[% row.vendor | html_entity %]</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
[% END %]
|
[% END %]
|
||||||
|
|||||||