Merge branch 'master' of ssh://git.code.sf.net/p/netdisco/netdisco-ng
This commit is contained in:
@@ -1,13 +1,35 @@
|
|||||||
2.017001 -
|
2.018001 -
|
||||||
|
|
||||||
|
[BUG FIXES]
|
||||||
|
|
||||||
|
* Update Print media CSS to handle new UI components
|
||||||
|
|
||||||
|
2.018000 - 2013-10-08
|
||||||
|
|
||||||
|
[NEW FEATURES]
|
||||||
|
|
||||||
|
* Add VLAN Inventory Report
|
||||||
|
* Add Wireless SSID Inventory Report
|
||||||
|
* Add Device Inventory by Location Report
|
||||||
|
* Node DNS names resolved in their own job - see nodenames_no and nodenames_only
|
||||||
|
|
||||||
[ENHANCEMENTS]
|
[ENHANCEMENTS]
|
||||||
|
|
||||||
* Respect ignore_interfaces and i_ignore when detecting wrapped device uptime
|
* Respect ignore_interfaces and i_ignore when detecting wrapped device uptime
|
||||||
* Try NodeIp OUI company name search if no node results found
|
* Try NodeIp OUI company name search if no node results found
|
||||||
|
* Format About page numbers
|
||||||
|
|
||||||
[BUG FIXES]
|
[BUG FIXES]
|
||||||
|
|
||||||
* Update NodeWireless entries which match both MAC and SSID found, only
|
* Update NodeWireless entries which match both MAC and SSID found, only
|
||||||
|
* Fix SSL-proxy behaviour by using only path+query in links (W. Gould)
|
||||||
|
* Avoid macsuck generated SQL bug when cleaning NULL VLAN (W. Gould)
|
||||||
|
* During macsuck get VLAN from Q-BRIDGE if available (jeneric)
|
||||||
|
* OK to include device ports when doing arpnip (jeneric)
|
||||||
|
* Correct bulkwalk_off logic
|
||||||
|
* Silence warnings when ports don't support i_lastchange
|
||||||
|
* Correct *_only and *_no setting logic
|
||||||
|
* Correct the instructions for runing dev instance of web and daemon
|
||||||
|
|
||||||
2.017000 - 2013-09-23
|
2.017000 - 2013-09-23
|
||||||
|
|
||||||
|
|||||||
@@ -146,9 +146,12 @@ lib/App/Netdisco/Web/Plugin/Device/Ports.pm
|
|||||||
lib/App/Netdisco/Web/Plugin/Inventory.pm
|
lib/App/Netdisco/Web/Plugin/Inventory.pm
|
||||||
lib/App/Netdisco/Web/Plugin/Report/ApChannelDist.pm
|
lib/App/Netdisco/Web/Plugin/Report/ApChannelDist.pm
|
||||||
lib/App/Netdisco/Web/Plugin/Report/ApRadioChannelPower.pm
|
lib/App/Netdisco/Web/Plugin/Report/ApRadioChannelPower.pm
|
||||||
|
lib/App/Netdisco/Web/Plugin/Report/DeviceByLocation.pm
|
||||||
lib/App/Netdisco/Web/Plugin/Report/DuplexMismatch.pm
|
lib/App/Netdisco/Web/Plugin/Report/DuplexMismatch.pm
|
||||||
lib/App/Netdisco/Web/Plugin/Report/HalfDuplex.pm
|
lib/App/Netdisco/Web/Plugin/Report/HalfDuplex.pm
|
||||||
lib/App/Netdisco/Web/Plugin/Report/PortUtilization.pm
|
lib/App/Netdisco/Web/Plugin/Report/PortUtilization.pm
|
||||||
|
lib/App/Netdisco/Web/Plugin/Report/SsidInventory.pm
|
||||||
|
lib/App/Netdisco/Web/Plugin/Report/VlanInventory.pm
|
||||||
lib/App/Netdisco/Web/Plugin/Search/Device.pm
|
lib/App/Netdisco/Web/Plugin/Search/Device.pm
|
||||||
lib/App/Netdisco/Web/Plugin/Search/Node.pm
|
lib/App/Netdisco/Web/Plugin/Search/Node.pm
|
||||||
lib/App/Netdisco/Web/Plugin/Search/Port.pm
|
lib/App/Netdisco/Web/Plugin/Search/Port.pm
|
||||||
@@ -235,12 +238,18 @@ share/views/ajax/report/apchanneldist.tt
|
|||||||
share/views/ajax/report/apchanneldist_csv.tt
|
share/views/ajax/report/apchanneldist_csv.tt
|
||||||
share/views/ajax/report/apradiochannelpower.tt
|
share/views/ajax/report/apradiochannelpower.tt
|
||||||
share/views/ajax/report/apradiochannelpower_csv.tt
|
share/views/ajax/report/apradiochannelpower_csv.tt
|
||||||
|
share/views/ajax/report/devicebylocation.tt
|
||||||
|
share/views/ajax/report/devicebylocation_csv.tt
|
||||||
share/views/ajax/report/duplexmismatch.tt
|
share/views/ajax/report/duplexmismatch.tt
|
||||||
share/views/ajax/report/duplexmismatch_csv.tt
|
share/views/ajax/report/duplexmismatch_csv.tt
|
||||||
share/views/ajax/report/halfduplex.tt
|
share/views/ajax/report/halfduplex.tt
|
||||||
share/views/ajax/report/halfduplex_csv.tt
|
share/views/ajax/report/halfduplex_csv.tt
|
||||||
share/views/ajax/report/portutilization.tt
|
share/views/ajax/report/portutilization.tt
|
||||||
share/views/ajax/report/portutilization_csv.tt
|
share/views/ajax/report/portutilization_csv.tt
|
||||||
|
share/views/ajax/report/ssidinventory.tt
|
||||||
|
share/views/ajax/report/ssidinventory_csv.tt
|
||||||
|
share/views/ajax/report/vlaninventory.tt
|
||||||
|
share/views/ajax/report/vlaninventory_csv.tt
|
||||||
share/views/ajax/search/device.tt
|
share/views/ajax/search/device.tt
|
||||||
share/views/ajax/search/device_csv.tt
|
share/views/ajax/search/device_csv.tt
|
||||||
share/views/ajax/search/node_by_ip.tt
|
share/views/ajax/search/node_by_ip.tt
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ requires:
|
|||||||
DBD::Pg: 0
|
DBD::Pg: 0
|
||||||
DBD::SQLite: 1.37
|
DBD::SQLite: 1.37
|
||||||
DBIx::Class: 0.0821
|
DBIx::Class: 0.0821
|
||||||
DBIx::Class::Helpers: 2.016006
|
DBIx::Class::Helpers: 2.018004
|
||||||
Daemon::Control: 0.001
|
Daemon::Control: 0.001
|
||||||
Dancer: 1.3112
|
Dancer: 1.3112
|
||||||
Dancer::Plugin::Auth::Extensible: 0.2
|
Dancer::Plugin::Auth::Extensible: 0.2
|
||||||
Dancer::Plugin::DBIC: 0.1802
|
Dancer::Plugin::DBIC: 0.1803
|
||||||
File::ShareDir: 1.03
|
File::ShareDir: 1.03
|
||||||
HTML::Parser: 3.7
|
HTML::Parser: 3.7
|
||||||
HTTP::Tiny: 0.029
|
HTTP::Tiny: 0.029
|
||||||
@@ -52,6 +52,7 @@ requires:
|
|||||||
Starman: 0.4008
|
Starman: 0.4008
|
||||||
Template: 2.24
|
Template: 2.24
|
||||||
Template::Plugin::CSV: 0.04
|
Template::Plugin::CSV: 0.04
|
||||||
|
Template::Plugin::Number::Format: 1.02
|
||||||
URL::Encode: 0.01
|
URL::Encode: 0.01
|
||||||
YAML: 0.84
|
YAML: 0.84
|
||||||
YAML::XS: 0.41
|
YAML::XS: 0.41
|
||||||
@@ -64,4 +65,4 @@ resources:
|
|||||||
homepage: http://netdisco.org/
|
homepage: http://netdisco.org/
|
||||||
license: http://opensource.org/licenses/bsd-license.php
|
license: http://opensource.org/licenses/bsd-license.php
|
||||||
repository: git://git.code.sf.net/p/netdisco/netdisco-ng
|
repository: git://git.code.sf.net/p/netdisco/netdisco-ng
|
||||||
version: 2.017000
|
version: 2.018000
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ requires 'App::local::lib::helper' => 0.07;
|
|||||||
requires 'DBD::Pg' => 0;
|
requires 'DBD::Pg' => 0;
|
||||||
requires 'DBD::SQLite' => 1.37;
|
requires 'DBD::SQLite' => 1.37;
|
||||||
requires 'DBIx::Class' => 0.08210;
|
requires 'DBIx::Class' => 0.08210;
|
||||||
requires 'DBIx::Class::Helpers' => 2.016006;
|
requires 'DBIx::Class::Helpers' => 2.018004;
|
||||||
requires 'Daemon::Control' => 0.001000;
|
requires 'Daemon::Control' => 0.001000;
|
||||||
requires 'Dancer' => 1.3112;
|
requires 'Dancer' => 1.3112;
|
||||||
requires 'Dancer::Plugin::DBIC' => 0.1802;
|
requires 'Dancer::Plugin::DBIC' => 0.1803;
|
||||||
requires 'Dancer::Plugin::Auth::Extensible' => 0.20;
|
requires 'Dancer::Plugin::Auth::Extensible' => 0.20;
|
||||||
requires 'File::ShareDir' => 1.03;
|
requires 'File::ShareDir' => 1.03;
|
||||||
requires 'HTML::Parser' => 3.70;
|
requires 'HTML::Parser' => 3.70;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use 5.010_000;
|
|||||||
use File::ShareDir 'dist_dir';
|
use File::ShareDir 'dist_dir';
|
||||||
use Path::Class;
|
use Path::Class;
|
||||||
|
|
||||||
our $VERSION = '2.017000';
|
our $VERSION = '2.018000';
|
||||||
|
|
||||||
BEGIN {
|
BEGIN {
|
||||||
if (not ($ENV{DANCER_APPDIR} || '')
|
if (not ($ENV{DANCER_APPDIR} || '')
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package App::Netdisco::Core::Arpnip;
|
|||||||
use Dancer qw/:syntax :script/;
|
use Dancer qw/:syntax :script/;
|
||||||
use Dancer::Plugin::DBIC 'schema';
|
use Dancer::Plugin::DBIC 'schema';
|
||||||
|
|
||||||
use App::Netdisco::Util::PortMAC 'get_port_macs';
|
|
||||||
use App::Netdisco::Util::SanityCheck 'check_mac';
|
use App::Netdisco::Util::SanityCheck 'check_mac';
|
||||||
use App::Netdisco::Util::DNS ':all';
|
use App::Netdisco::Util::DNS ':all';
|
||||||
use NetAddr::IP::Lite ':lower';
|
use NetAddr::IP::Lite ':lower';
|
||||||
@@ -11,7 +10,7 @@ use Time::HiRes 'gettimeofday';
|
|||||||
|
|
||||||
use base 'Exporter';
|
use base 'Exporter';
|
||||||
our @EXPORT = ();
|
our @EXPORT = ();
|
||||||
our @EXPORT_OK = qw/ do_arpnip store_arp /;
|
our @EXPORT_OK = qw/ do_arpnip store_arp resolve_node_names /;
|
||||||
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||||
|
|
||||||
=head1 NAME
|
=head1 NAME
|
||||||
@@ -44,12 +43,10 @@ sub do_arpnip {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $port_macs = get_port_macs($device);
|
|
||||||
|
|
||||||
# get v4 arp table
|
# get v4 arp table
|
||||||
my @v4 = _get_arps($device, $port_macs, $snmp->at_paddr, $snmp->at_netaddr);
|
my @v4 = _get_arps($device, $snmp->at_paddr, $snmp->at_netaddr);
|
||||||
# get v6 neighbor cache
|
# get v6 neighbor cache
|
||||||
my @v6 = _get_arps($device, $port_macs, $snmp->ipv6_n2p_mac, $snmp->ipv6_n2p_addr);
|
my @v6 = _get_arps($device, $snmp->ipv6_n2p_mac, $snmp->ipv6_n2p_addr);
|
||||||
|
|
||||||
# get directly connected networks
|
# get directly connected networks
|
||||||
my @subnets = _gather_subnets($device, $snmp);
|
my @subnets = _gather_subnets($device, $snmp);
|
||||||
@@ -78,23 +75,23 @@ sub do_arpnip {
|
|||||||
|
|
||||||
# get an arp table (v4 or v6)
|
# get an arp table (v4 or v6)
|
||||||
sub _get_arps {
|
sub _get_arps {
|
||||||
my ($device, $port_macs, $paddr, $netaddr) = @_;
|
my ($device, $paddr, $netaddr) = @_;
|
||||||
my @arps = ();
|
my @arps = ();
|
||||||
|
|
||||||
while (my ($arp, $node) = each %$paddr) {
|
while (my ($arp, $node) = each %$paddr) {
|
||||||
my $ip = $netaddr->{$arp};
|
my $ip = $netaddr->{$arp};
|
||||||
next unless defined $ip;
|
next unless defined $ip;
|
||||||
next unless check_mac($device, $node, $port_macs);
|
next unless check_mac($device, $node);
|
||||||
push @arps, [$node, $ip, hostname_from_ip($ip)];
|
push @arps, [$node, $ip];
|
||||||
}
|
}
|
||||||
|
|
||||||
return @arps;
|
return @arps;
|
||||||
}
|
}
|
||||||
|
|
||||||
=head2 store_arp( $mac, $ip, $name, $now? )
|
=head2 store_arp( $mac, $ip, $now? )
|
||||||
|
|
||||||
Stores a new entry to the C<node_ip> table with the given MAC, IP (v4 or v6)
|
Stores a new entry to the C<node_ip> table with the given MAC, and IP (v4 or
|
||||||
and DNS host name.
|
v6).
|
||||||
|
|
||||||
Will mark old entries for this IP as no longer C<active>.
|
Will mark old entries for this IP as no longer C<active>.
|
||||||
|
|
||||||
@@ -104,7 +101,7 @@ C<time_last> timestamp, otherwise the current timestamp (C<now()>) is used.
|
|||||||
=cut
|
=cut
|
||||||
|
|
||||||
sub store_arp {
|
sub store_arp {
|
||||||
my ($mac, $ip, $name, $now) = @_;
|
my ($mac, $ip, $now) = @_;
|
||||||
$now ||= 'now()';
|
$now ||= 'now()';
|
||||||
|
|
||||||
schema('netdisco')->txn_do(sub {
|
schema('netdisco')->txn_do(sub {
|
||||||
@@ -122,7 +119,6 @@ sub store_arp {
|
|||||||
->search({'me.mac' => $mac, 'me.ip' => $ip})
|
->search({'me.mac' => $mac, 'me.ip' => $ip})
|
||||||
->update_or_create(
|
->update_or_create(
|
||||||
{
|
{
|
||||||
dns => $name,
|
|
||||||
active => \'true',
|
active => \'true',
|
||||||
time_last => \$now,
|
time_last => \$now,
|
||||||
},
|
},
|
||||||
@@ -175,4 +171,39 @@ sub _store_subnet {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
=head2 resolve_node_names( $device )
|
||||||
|
|
||||||
|
Given a Device database object, resolve Node IP (ARP) entries belonging to
|
||||||
|
this device into DNS names, and store them in the C<node_ip> database table.
|
||||||
|
|
||||||
|
This action is usually queued following C<do_arpip> so that it may run
|
||||||
|
asynchronously, and/or on another daemon worker node.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub resolve_node_names {
|
||||||
|
my ($device) = @_;
|
||||||
|
|
||||||
|
schema('netdisco')->txn_do(sub {
|
||||||
|
my $nodeips = schema('netdisco')
|
||||||
|
->resultset('NodeIp')->search(
|
||||||
|
{
|
||||||
|
-and => [
|
||||||
|
-bool => 'me.active',
|
||||||
|
-bool => 'nodes.active',
|
||||||
|
],
|
||||||
|
'nodes.switch' => $device->ip,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
join => 'nodes',
|
||||||
|
for => 'update',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
while (my $nodeip = $nodeips->next) {
|
||||||
|
$nodeip->update({dns => hostname_from_ip($nodeip->ip)});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|||||||
@@ -94,6 +94,10 @@ sub do_macsuck {
|
|||||||
$device->ip, $port, $vlan, scalar keys %{ $fwtable->{$vlan}->{$port} };
|
$device->ip, $port, $vlan, scalar keys %{ $fwtable->{$vlan}->{$port} };
|
||||||
|
|
||||||
foreach my $mac (keys %{ $fwtable->{$vlan}->{$port} }) {
|
foreach my $mac (keys %{ $fwtable->{$vlan}->{$port} }) {
|
||||||
|
# get VLAN from Q-BRIDGE if available
|
||||||
|
$vlan = $fwtable->{$vlan}->{$port}->{$mac}
|
||||||
|
if $vlan == 0;
|
||||||
|
|
||||||
# remove vlan 0 entry for this MAC addr
|
# remove vlan 0 entry for this MAC addr
|
||||||
delete $fwtable->{0}->{$_}->{$mac}
|
delete $fwtable->{0}->{$_}->{$mac}
|
||||||
for keys %{ $fwtable->{0} };
|
for keys %{ $fwtable->{0} };
|
||||||
@@ -130,53 +134,48 @@ sub store_node {
|
|||||||
my $nodes = schema('netdisco')->resultset('Node');
|
my $nodes = schema('netdisco')->resultset('Node');
|
||||||
|
|
||||||
# TODO: probably needs changing if we're to support VTP domains
|
# TODO: probably needs changing if we're to support VTP domains
|
||||||
my $old = $nodes->search(
|
my $old = $nodes->search({
|
||||||
{
|
mac => $mac,
|
||||||
mac => $mac,
|
vlan => $vlan,
|
||||||
vlan => $vlan,
|
-bool => 'active',
|
||||||
-bool => 'active',
|
-not => {
|
||||||
-not => {
|
switch => $ip,
|
||||||
switch => $ip,
|
port => $port,
|
||||||
port => $port,
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
# lock rows,
|
# lock rows,
|
||||||
# and get the count so we know whether to set time_recent
|
# and get the count so we know whether to set time_recent
|
||||||
my $old_count = scalar $old->search(undef,
|
my $old_count = scalar $old->search(undef,
|
||||||
{
|
{
|
||||||
columns => [qw/switch vlan port mac/],
|
columns => [qw/switch vlan port mac/],
|
||||||
order_by => [qw/switch vlan port mac/],
|
|
||||||
for => 'update',
|
for => 'update',
|
||||||
})->all;
|
})->all;
|
||||||
|
|
||||||
$old->update({ active => \'false' });
|
$old->update({ active => \'false' });
|
||||||
|
|
||||||
my $new = $nodes->search(
|
my $new = $nodes->search({
|
||||||
{
|
'me.switch' => $ip,
|
||||||
'me.switch' => $ip,
|
'me.port' => $port,
|
||||||
'me.port' => $port,
|
'me.mac' => $mac,
|
||||||
'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 => [0, undef]})->delete();
|
|
||||||
|
|
||||||
# new data
|
# new data
|
||||||
$new->update_or_create({
|
$new->update_or_create(
|
||||||
vlan => $vlan,
|
{
|
||||||
active => \'true',
|
vlan => $vlan,
|
||||||
oui => substr($mac,0,8),
|
active => \'true',
|
||||||
time_last => \$now,
|
oui => substr($mac,0,8),
|
||||||
($old_count ? (time_recent => \$now) : ()),
|
time_last => \$now,
|
||||||
});
|
($old_count ? (time_recent => \$now) : ()),
|
||||||
|
},
|
||||||
|
{ for => 'update' }
|
||||||
|
);
|
||||||
|
|
||||||
|
# upgrade old schema
|
||||||
|
# an entry for this MAC existed in old schema with vlan => null
|
||||||
|
# which now has either vlan number or 0
|
||||||
|
$new->search({vlan => undef})->delete({only_nodes => 1});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,7 +386,7 @@ sub _walk_fwtable {
|
|||||||
next unless setting('macsuck_bleed');
|
next unless setting('macsuck_bleed');
|
||||||
}
|
}
|
||||||
|
|
||||||
++$cache->{$port}->{$mac};
|
$cache->{$port}->{$mac} = ($fw_vlan->{$idx} || '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $cache;
|
return $cache;
|
||||||
|
|||||||
@@ -100,6 +100,10 @@ sub delete {
|
|||||||
# avoid letting DBIC delete nodes
|
# avoid letting DBIC delete nodes
|
||||||
return 0E0;
|
return 0E0;
|
||||||
}
|
}
|
||||||
|
elsif (exists $opts->{only_nodes} and $opts->{only_nodes}) {
|
||||||
|
# now let DBIC do its thing
|
||||||
|
return $self->next::method();
|
||||||
|
}
|
||||||
elsif (exists $opts->{keep_nodes} and $opts->{keep_nodes}) {
|
elsif (exists $opts->{keep_nodes} and $opts->{keep_nodes}) {
|
||||||
# avoid letting DBIC delete nodes
|
# avoid letting DBIC delete nodes
|
||||||
return 0E0;
|
return 0E0;
|
||||||
|
|||||||
@@ -22,7 +22,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/discoverall discover arpwalk arpnip macwalk macsuck/],
|
Poller => [qw/discoverall discover arpwalk arpnip nodenames macwalk macsuck/],
|
||||||
Interactive => [qw/location contact portcontrol portname vlan power/],
|
Interactive => [qw/location contact portcontrol portname vlan power/],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ my $fqdn = hostfqdn || 'localhost';
|
|||||||
|
|
||||||
my $role_map = {
|
my $role_map = {
|
||||||
(map {$_ => 'Poller'}
|
(map {$_ => 'Poller'}
|
||||||
qw/discoverall discover arpwalk arpnip macwalk macsuck/),
|
qw/discoverall discover arpwalk arpnip nodenames macwalk macsuck/),
|
||||||
(map {$_ => 'Interactive'}
|
(map {$_ => 'Interactive'}
|
||||||
qw/location contact portcontrol portname vlan power/)
|
qw/location contact portcontrol portname vlan power/)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
package App::Netdisco::Daemon::Worker::Poller::Arpnip;
|
package App::Netdisco::Daemon::Worker::Poller::Arpnip;
|
||||||
|
|
||||||
|
use Dancer::Plugin::DBIC 'schema';
|
||||||
|
|
||||||
use App::Netdisco::Core::Arpnip 'do_arpnip';
|
use App::Netdisco::Core::Arpnip 'do_arpnip';
|
||||||
use App::Netdisco::Util::Device 'is_arpnipable';
|
use App::Netdisco::Util::Device qw/get_device is_arpnipable can_nodenames/;
|
||||||
|
|
||||||
|
use App::Netdisco::Core::Arpnip 'resolve_node_names';
|
||||||
|
use App::Netdisco::Daemon::Util ':all';
|
||||||
|
|
||||||
|
use NetAddr::IP::Lite ':lower';
|
||||||
|
|
||||||
use Role::Tiny;
|
use Role::Tiny;
|
||||||
|
use Class::Method::Modifiers;
|
||||||
use namespace::clean;
|
use namespace::clean;
|
||||||
|
|
||||||
with 'App::Netdisco::Daemon::Worker::Poller::Common';
|
with 'App::Netdisco::Daemon::Worker::Poller::Common';
|
||||||
@@ -15,4 +23,43 @@ sub arpnip_layer { 3 }
|
|||||||
sub arpwalk { (shift)->_walk_body('arpnip', @_) }
|
sub arpwalk { (shift)->_walk_body('arpnip', @_) }
|
||||||
sub arpnip { (shift)->_single_body('arpnip', @_) }
|
sub arpnip { (shift)->_single_body('arpnip', @_) }
|
||||||
|
|
||||||
|
after 'arpnip' => sub {
|
||||||
|
my ($self, $job) = @_;
|
||||||
|
|
||||||
|
my $host = NetAddr::IP::Lite->new($job->device);
|
||||||
|
my $device = get_device($host->addr);
|
||||||
|
my $jobqueue = schema('netdisco')->resultset('Admin');
|
||||||
|
|
||||||
|
schema('netdisco')->txn_do(sub {
|
||||||
|
$jobqueue->create({
|
||||||
|
device => $device->ip,
|
||||||
|
action => 'nodenames',
|
||||||
|
status => 'queued',
|
||||||
|
username => $job->username,
|
||||||
|
userip => $job->userip,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
# run a nodenames job for one device
|
||||||
|
sub nodenames {
|
||||||
|
my ($self, $job) = @_;
|
||||||
|
|
||||||
|
my $host = NetAddr::IP::Lite->new($job->device);
|
||||||
|
my $device = get_device($host->addr);
|
||||||
|
my $jobqueue = schema('netdisco')->resultset('Admin');
|
||||||
|
|
||||||
|
if ($device->ip eq '0.0.0.0') {
|
||||||
|
return job_error("nodenames failed: no device param (need -d ?)");
|
||||||
|
}
|
||||||
|
|
||||||
|
unless (can_nodenames($device->ip)) {
|
||||||
|
return job_defer("nodenames deferred: cannot run for $host");
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_node_names($device);
|
||||||
|
|
||||||
|
return job_done("Ended nodenames for ". $host->addr);
|
||||||
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|||||||
@@ -444,6 +444,22 @@ Value: Number. Default: 0.
|
|||||||
Sets the minimum amount of time in seconds which must elapse between any two
|
Sets the minimum amount of time in seconds which must elapse between any two
|
||||||
arpnip jobs for a device.
|
arpnip jobs for a device.
|
||||||
|
|
||||||
|
=head3 C<nodenames_no>
|
||||||
|
|
||||||
|
Value: List of Network Identifiers or Device Properties. Default: Empty List.
|
||||||
|
|
||||||
|
Devices in the list will not have their Node IPs resolved to names using the
|
||||||
|
DNS. You can include hostnames, IP addresses and subnets (IPv4 or IPv6) in the
|
||||||
|
list.
|
||||||
|
|
||||||
|
=head3 C<nodenames_only>
|
||||||
|
|
||||||
|
Value: List of Network Identifiers or Device Properties. Default: Empty List.
|
||||||
|
|
||||||
|
Only thos devices in the list will have their Node IPs resolved to names using
|
||||||
|
the DNS. You can include hostnames, IP addresses and subnets (IPv4 or IPv6) in
|
||||||
|
the list.
|
||||||
|
|
||||||
=head3 C<store_wireless_clients>
|
=head3 C<store_wireless_clients>
|
||||||
|
|
||||||
Value: Boolean. Default: C<true>.
|
Value: Boolean. Default: C<true>.
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ the L<documentation|App::Netdisco>. Then:
|
|||||||
git clone git://git.code.sf.net/p/netdisco/netdisco-ng netdisco-ng
|
git clone git://git.code.sf.net/p/netdisco/netdisco-ng netdisco-ng
|
||||||
cd netdisco-ng/Netdisco
|
cd netdisco-ng/Netdisco
|
||||||
|
|
||||||
~/bin/localenv DBIC_TRACE_PROFILE=console DBIC_TRACE=1 plackup -R share,lib -p 5001 bin/netdisco-web-fg
|
DBIC_TRACE_PROFILE=console DBIC_TRACE=1 ~/bin/localenv plackup -R share,lib -p 5001 bin/netdisco-web-fg
|
||||||
|
|
||||||
The above creates you a git clone (change the URL if you're a Netdisco
|
The above creates you a git clone (change the URL if you're a Netdisco
|
||||||
Developer) and runs the web server:
|
Developer) and runs the web server:
|
||||||
@@ -47,8 +47,17 @@ Restarts the web server when you save a file in the C<share> or C<lib> directory
|
|||||||
|
|
||||||
=back
|
=back
|
||||||
|
|
||||||
You should be able to work out something similar for
|
You might also want to set C<check_userlog> to C<true> in your config to quieten
|
||||||
C<bin/netdisco-daemon-fg>, too. Happy hacking!
|
some of the web client callbacks.
|
||||||
|
|
||||||
|
For the daemon, it's very similar:
|
||||||
|
|
||||||
|
DBIC_TRACE_PROFILE=console DBIC_TRACE=1 ~/bin/localenv bin/netdisco-daemon-fg
|
||||||
|
|
||||||
|
Don't be alarmed by the high rate of database queries in the daemon - most of
|
||||||
|
them are communicating only with a local in-memory SQLite database.
|
||||||
|
|
||||||
|
Happy hacking!
|
||||||
|
|
||||||
=head1 Introduction
|
=head1 Introduction
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,21 @@ but they are backwards compatible.
|
|||||||
|
|
||||||
=back
|
=back
|
||||||
|
|
||||||
|
=head1 2.018000
|
||||||
|
|
||||||
|
=head2 General Notices
|
||||||
|
|
||||||
|
The previous mentioned bug in Macsuck is now fixed.
|
||||||
|
|
||||||
|
During Arpnip, Node IPs are no longer resolved to DNS hostnames in real-time.
|
||||||
|
Another job is queued to perform this action for the device. You can therefore
|
||||||
|
control using the new C<nodenames_no> and C<nodenames_only> config parameters
|
||||||
|
which daemons run this job.
|
||||||
|
|
||||||
|
The idea here is to support sites where the SNMP polling node has no useful
|
||||||
|
DNS, but another system can update the DNS entries for nodes (yet do no
|
||||||
|
polling).
|
||||||
|
|
||||||
=head1 2.017000
|
=head1 2.017000
|
||||||
|
|
||||||
=head2 General Notices
|
=head2 General Notices
|
||||||
|
|||||||
@@ -307,18 +307,18 @@ App::Netdisco:
|
|||||||
|
|
||||||
=item C<search_node>
|
=item C<search_node>
|
||||||
|
|
||||||
A base url which links to the Node tab of the Search page, together with the
|
A path and query string which links to the Node tab of the Search page,
|
||||||
correct default search options set.
|
together with the correct default search options set.
|
||||||
|
|
||||||
=item C<search_device>
|
=item C<search_device>
|
||||||
|
|
||||||
A base url which links to the Device tab of the Search page, together with the
|
A path and query string which links to the Device tab of the Search page,
|
||||||
correct default search options set.
|
together with the correct default search options set.
|
||||||
|
|
||||||
=item C<device_ports>
|
=item C<device_ports>
|
||||||
|
|
||||||
A base url which links to the Ports tab of the Device page, together with
|
A path and query sting which links to the Ports tab of the Device page,
|
||||||
the correct default column view options set.
|
together with the correct default column view options set.
|
||||||
|
|
||||||
=item C<uri_base>
|
=item C<uri_base>
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ our @EXPORT = ();
|
|||||||
our @EXPORT_OK = qw/
|
our @EXPORT_OK = qw/
|
||||||
get_device
|
get_device
|
||||||
check_no
|
check_no
|
||||||
|
check_only
|
||||||
is_discoverable
|
is_discoverable
|
||||||
is_arpnipable
|
is_arpnipable
|
||||||
|
can_nodenames
|
||||||
is_macsuckable
|
is_macsuckable
|
||||||
/;
|
/;
|
||||||
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||||
@@ -57,42 +59,11 @@ sub get_device {
|
|||||||
->find_or_new({ip => $ip});
|
->find_or_new({ip => $ip});
|
||||||
}
|
}
|
||||||
|
|
||||||
=head2 check_no( $ip, $setting_name )
|
sub _check_acl {
|
||||||
|
my ($ip, $config) = @_;
|
||||||
Given the IP address of a device, returns true if the configuration setting
|
|
||||||
C<$setting_name> matches that device, else returns false.
|
|
||||||
|
|
||||||
There are several options for what C<$setting_name> can contain:
|
|
||||||
|
|
||||||
=over 4
|
|
||||||
|
|
||||||
=item *
|
|
||||||
|
|
||||||
Hostname, IP address, IP prefix
|
|
||||||
|
|
||||||
=item *
|
|
||||||
|
|
||||||
C<"model:regex"> - matched against the device model
|
|
||||||
|
|
||||||
=item *
|
|
||||||
|
|
||||||
C<"vendor:regex"> - matched against the device vendor
|
|
||||||
|
|
||||||
=back
|
|
||||||
|
|
||||||
To simply match all devices, use IP Prefix "C<0.0.0.0/0>". All regular
|
|
||||||
expressions are anchored (that is, they must match the whole string).
|
|
||||||
|
|
||||||
=cut
|
|
||||||
|
|
||||||
sub check_no {
|
|
||||||
my ($ip, $setting_name) = @_;
|
|
||||||
my $device = get_device($ip) or return 0;
|
my $device = get_device($ip) or return 0;
|
||||||
my $addr = NetAddr::IP::Lite->new($device->ip);
|
my $addr = NetAddr::IP::Lite->new($device->ip);
|
||||||
|
|
||||||
my $config = setting($setting_name) || [];
|
|
||||||
return 0 unless scalar @$config;
|
|
||||||
|
|
||||||
foreach my $item (@$config) {
|
foreach my $item (@$config) {
|
||||||
if ($item =~ m/^(.*)\s*:\s*(.*)$/) {
|
if ($item =~ m/^(.*)\s*:\s*(.*)$/) {
|
||||||
my $prop = $1;
|
my $prop = $1;
|
||||||
@@ -116,6 +87,86 @@ sub check_no {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
=head2 check_no( $ip, $setting_name )
|
||||||
|
|
||||||
|
Given the IP address of a device, returns true if the configuration setting
|
||||||
|
C<$setting_name> matches that device, else returns false.
|
||||||
|
|
||||||
|
print "rejected!" if check_no($ip, 'discover_no');
|
||||||
|
|
||||||
|
There are several options for what C<$setting_name> can contain:
|
||||||
|
|
||||||
|
=over 4
|
||||||
|
|
||||||
|
=item *
|
||||||
|
|
||||||
|
Hostname, IP address, IP prefix
|
||||||
|
|
||||||
|
=item *
|
||||||
|
|
||||||
|
C<"model:regex"> - matched against the device model
|
||||||
|
|
||||||
|
=item *
|
||||||
|
|
||||||
|
C<"vendor:regex"> - matched against the device vendor
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
To simply match all devices, use "C<any>" or IP Prefix "C<0.0.0.0/0>". All
|
||||||
|
regular expressions are anchored (that is, they must match the whole string).
|
||||||
|
To match no devices we recommend an entry of "C<localhost>" in the setting.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub check_no {
|
||||||
|
my ($ip, $setting_name) = @_;
|
||||||
|
|
||||||
|
my $config = setting($setting_name) || [];
|
||||||
|
return 0 unless scalar @$config;
|
||||||
|
|
||||||
|
return _check_acl($ip, $config);
|
||||||
|
}
|
||||||
|
|
||||||
|
=head2 check_only( $ip, $setting_name )
|
||||||
|
|
||||||
|
Given the IP address of a device, returns false if the configuration setting
|
||||||
|
C<$setting_name> matches that device, else returns true.
|
||||||
|
|
||||||
|
print "rejected!" unless check_only($ip, 'discover_only');
|
||||||
|
|
||||||
|
There are several options for what C<$setting_name> can contain:
|
||||||
|
|
||||||
|
=over 4
|
||||||
|
|
||||||
|
=item *
|
||||||
|
|
||||||
|
Hostname, IP address, IP prefix
|
||||||
|
|
||||||
|
=item *
|
||||||
|
|
||||||
|
C<"model:regex"> - matched against the device model
|
||||||
|
|
||||||
|
=item *
|
||||||
|
|
||||||
|
C<"vendor:regex"> - matched against the device vendor
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
To simply match all devices, use "C<any>" or IP Prefix "C<0.0.0.0/0>". All
|
||||||
|
regular expressions are anchored (that is, they must match the whole string).
|
||||||
|
To match no devices we recommend an entry of "C<localhost>" in the setting.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub check_only {
|
||||||
|
my ($ip, $setting_name) = @_;
|
||||||
|
|
||||||
|
my $config = setting($setting_name) || [];
|
||||||
|
return 1 unless scalar @$config;
|
||||||
|
|
||||||
|
return _check_acl($ip, $config);
|
||||||
|
}
|
||||||
|
|
||||||
=head2 is_discoverable( $ip, $device_type? )
|
=head2 is_discoverable( $ip, $device_type? )
|
||||||
|
|
||||||
Given an IP address, returns C<true> if Netdisco on this host is permitted by
|
Given an IP address, returns C<true> if Netdisco on this host is permitted by
|
||||||
@@ -147,7 +198,7 @@ sub is_discoverable {
|
|||||||
if check_no($device, 'discover_no');
|
if check_no($device, 'discover_no');
|
||||||
|
|
||||||
return _bail_msg("is_discoverable: device failed to match discover_only")
|
return _bail_msg("is_discoverable: device failed to match discover_only")
|
||||||
if check_no($device, 'discover_only');
|
unless check_only($device, 'discover_only');
|
||||||
|
|
||||||
# cannot check last_discover for as yet undiscovered devices :-)
|
# cannot check last_discover for as yet undiscovered devices :-)
|
||||||
return 1 if not $device->in_storage;
|
return 1 if not $device->in_storage;
|
||||||
@@ -181,7 +232,7 @@ sub is_arpnipable {
|
|||||||
if check_no($device, 'arpnip_no');
|
if check_no($device, 'arpnip_no');
|
||||||
|
|
||||||
return _bail_msg("is_arpnipable: device failed to match arpnip_only")
|
return _bail_msg("is_arpnipable: device failed to match arpnip_only")
|
||||||
if check_no($device, 'arpnip_only');
|
unless check_only($device, 'arpnip_only');
|
||||||
|
|
||||||
return _bail_msg("is_arpnipable: cannot arpnip an undiscovered device")
|
return _bail_msg("is_arpnipable: cannot arpnip an undiscovered device")
|
||||||
if not $device->in_storage;
|
if not $device->in_storage;
|
||||||
@@ -195,6 +246,32 @@ sub is_arpnipable {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
=head2 can_nodenames( $ip )
|
||||||
|
|
||||||
|
Given an IP address, returns C<true> if Netdisco on this host is permitted by
|
||||||
|
the local configuration to resolve Node IPs to DNS names for the device.
|
||||||
|
|
||||||
|
The configuration items C<nodenames_no> and C<nodenames_only> are checked
|
||||||
|
against the given IP.
|
||||||
|
|
||||||
|
Returns false if the host is not permitted to do this job for the target
|
||||||
|
device.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub can_nodenames {
|
||||||
|
my $ip = shift;
|
||||||
|
my $device = get_device($ip) or return 0;
|
||||||
|
|
||||||
|
return _bail_msg("can_nodenames device matched nodenames_no")
|
||||||
|
if check_no($device, 'nodenames_no');
|
||||||
|
|
||||||
|
return _bail_msg("can_nodenames: device failed to match nodenames_only")
|
||||||
|
unless check_only($device, 'nodenames_only');
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
=head2 is_macsuckable( $ip )
|
=head2 is_macsuckable( $ip )
|
||||||
|
|
||||||
Given an IP address, returns C<true> if Netdisco on this host is permitted by
|
Given an IP address, returns C<true> if Netdisco on this host is permitted by
|
||||||
@@ -215,7 +292,7 @@ sub is_macsuckable {
|
|||||||
if check_no($device, 'macsuck_no');
|
if check_no($device, 'macsuck_no');
|
||||||
|
|
||||||
return _bail_msg("is_macsuckable: device failed to match macsuck_only")
|
return _bail_msg("is_macsuckable: device failed to match macsuck_only")
|
||||||
if check_no($device, 'macsuck_only');
|
unless check_only($device, 'macsuck_only');
|
||||||
|
|
||||||
return _bail_msg("is_macsuckable: cannot macsuck an undiscovered device")
|
return _bail_msg("is_macsuckable: cannot macsuck an undiscovered device")
|
||||||
if not $device->in_storage;
|
if not $device->in_storage;
|
||||||
|
|||||||
@@ -44,22 +44,25 @@ MAC address is well-formed (according to common formats)
|
|||||||
|
|
||||||
MAC address is not all-zero, broadcast, CLIP, VRRP or HSRP
|
MAC address is not all-zero, broadcast, CLIP, VRRP or HSRP
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
Optionally pass a cached set of Device port MAC addresses as the third
|
||||||
|
argument, in which case an additional check is added:
|
||||||
|
|
||||||
|
=over 4
|
||||||
|
|
||||||
=item *
|
=item *
|
||||||
|
|
||||||
MAC address does not belong to an interface on any known Device
|
MAC address does not belong to an interface on any known Device
|
||||||
|
|
||||||
=back
|
=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
|
=cut
|
||||||
|
|
||||||
sub check_mac {
|
sub check_mac {
|
||||||
my ($device, $node, $port_macs) = @_;
|
my ($device, $node, $port_macs) = @_;
|
||||||
$port_macs ||= get_port_macs($device);
|
|
||||||
my $mac = Net::MAC->new(mac => $node, 'die' => 0, verbose => 0);
|
my $mac = Net::MAC->new(mac => $node, 'die' => 0, verbose => 0);
|
||||||
|
$port_macs ||= {};
|
||||||
|
|
||||||
# incomplete MAC addresses (BayRS frame relay DLCI, etc)
|
# incomplete MAC addresses (BayRS frame relay DLCI, etc)
|
||||||
if ($mac->get_error) {
|
if ($mac->get_error) {
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ if (setting('extra_web_plugins') and ref [] eq ref setting('extra_web_plugins'))
|
|||||||
# workaround for https://github.com/PerlDancer/Dancer/issues/935
|
# workaround for https://github.com/PerlDancer/Dancer/issues/935
|
||||||
hook after_error_render => sub { setting('layout' => 'main') };
|
hook after_error_render => sub { setting('layout' => 'main') };
|
||||||
|
|
||||||
|
# this hook should be loaded _after_ all plugins
|
||||||
hook 'before_template' => sub {
|
hook 'before_template' => sub {
|
||||||
my $tokens = shift;
|
my $tokens = shift;
|
||||||
|
|
||||||
@@ -61,6 +62,10 @@ hook 'before_template' => sub {
|
|||||||
# access to logged in user's roles
|
# access to logged in user's roles
|
||||||
$tokens->{user_has_role} = sub { user_has_role(@_) };
|
$tokens->{user_has_role} = sub { user_has_role(@_) };
|
||||||
|
|
||||||
|
# fix Plugin Template Variables to be only path+query
|
||||||
|
$tokens->{$_} = $tokens->{$_}->path_query
|
||||||
|
for qw/search_node search_device device_ports/;
|
||||||
|
|
||||||
# allow very long lists of ports
|
# allow very long lists of ports
|
||||||
$Template::Directive::WHILE_MAX = 10_000;
|
$Template::Directive::WHILE_MAX = 10_000;
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ foreach my $jobtype (keys %jobs_all, keys %jobs) {
|
|||||||
if exists $jobs{$jobtype} and not param('device');
|
if exists $jobs{$jobtype} and not param('device');
|
||||||
|
|
||||||
add_job($jobtype, param('device'), param('extra'));
|
add_job($jobtype, param('device'), param('extra'));
|
||||||
redirect uri_for('/admin/jobqueue')->as_string;
|
redirect uri_for('/admin/jobqueue')->path;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ get '/device' => require_login sub {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!defined $dev) {
|
if (!defined $dev) {
|
||||||
return redirect uri_for('/', {nosuchdevice => 1})->as_string();
|
return redirect uri_for('/', {nosuchdevice => 1})->path_query;
|
||||||
}
|
}
|
||||||
|
|
||||||
params->{'tab'} ||= 'details';
|
params->{'tab'} ||= 'details';
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ get '/search' => require_login sub {
|
|||||||
|
|
||||||
if (not param('tab')) {
|
if (not param('tab')) {
|
||||||
if (not $q) {
|
if (not $q) {
|
||||||
return redirect uri_for('/')->as_string;
|
return redirect uri_for('/')->path;
|
||||||
}
|
}
|
||||||
|
|
||||||
# pick most likely tab for initial results
|
# pick most likely tab for initial results
|
||||||
@@ -83,7 +83,7 @@ get '/search' => require_login sub {
|
|||||||
tab => 'details',
|
tab => 'details',
|
||||||
q => ($nd->first->dns || $nd->first->ip),
|
q => ($nd->first->dns || $nd->first->ip),
|
||||||
f => '',
|
f => '',
|
||||||
})->as_string;
|
})->path_query;
|
||||||
}
|
}
|
||||||
|
|
||||||
# multiple devices
|
# multiple devices
|
||||||
|
|||||||
@@ -86,6 +86,8 @@ macsuck_min_age: 0
|
|||||||
arpnip_no: []
|
arpnip_no: []
|
||||||
arpnip_only: []
|
arpnip_only: []
|
||||||
arpnip_min_age: 0
|
arpnip_min_age: 0
|
||||||
|
nodenames_no: []
|
||||||
|
nodenames_only: []
|
||||||
store_wireless_clients: true
|
store_wireless_clients: true
|
||||||
store_modules: true
|
store_modules: true
|
||||||
ignore_interfaces:
|
ignore_interfaces:
|
||||||
|
|||||||
@@ -14,3 +14,12 @@ body {
|
|||||||
margin-left: 0px !important;
|
margin-left: 0px !important;
|
||||||
margin-right: 0px !important;
|
margin-right: 0px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.floatThead-table {
|
||||||
|
position: inherit;
|
||||||
|
top: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:after {
|
||||||
|
content: "" !important;
|
||||||
|
}
|
||||||
|
|||||||
32
TODO
32
TODO
@@ -1,31 +1 @@
|
|||||||
FRONTEND
|
See: https://sourceforge.net/p/netdisco/netdisco2/
|
||||||
========
|
|
||||||
|
|
||||||
* moar reports
|
|
||||||
|
|
||||||
DAEMON
|
|
||||||
======
|
|
||||||
|
|
||||||
BACKEND
|
|
||||||
=======
|
|
||||||
|
|
||||||
* PING from workers?
|
|
||||||
|
|
||||||
CORE
|
|
||||||
====
|
|
||||||
|
|
||||||
* VRF support (multi-tenancy?)
|
|
||||||
* import legacy config file?
|
|
||||||
|
|
||||||
DOCS
|
|
||||||
====
|
|
||||||
|
|
||||||
* Scheduler
|
|
||||||
* Discover/Refresh jobs
|
|
||||||
* new X:: plugin namespace
|
|
||||||
* observiumsparklines plugin
|
|
||||||
* port column plugins
|
|
||||||
* add css and js for plugin
|
|
||||||
* site_plugins
|
|
||||||
* missing user management ("User Rights")
|
|
||||||
* Auth::Extensible
|
|
||||||
|
|||||||
Reference in New Issue
Block a user