Merge branch 'master' of ssh://git.code.sf.net/p/netdisco/netdisco-ng

This commit is contained in:
Eric A. Miller
2013-10-10 10:42:23 -04:00
24 changed files with 363 additions and 144 deletions

View File

@@ -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]
* Respect ignore_interfaces and i_ignore when detecting wrapped device uptime
* Try NodeIp OUI company name search if no node results found
* Format About page numbers
[BUG FIXES]
* 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

View File

@@ -146,9 +146,12 @@ lib/App/Netdisco/Web/Plugin/Device/Ports.pm
lib/App/Netdisco/Web/Plugin/Inventory.pm
lib/App/Netdisco/Web/Plugin/Report/ApChannelDist.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/HalfDuplex.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/Node.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/apradiochannelpower.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_csv.tt
share/views/ajax/report/halfduplex.tt
share/views/ajax/report/halfduplex_csv.tt
share/views/ajax/report/portutilization.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_csv.tt
share/views/ajax/search/node_by_ip.tt

View File

@@ -25,11 +25,11 @@ requires:
DBD::Pg: 0
DBD::SQLite: 1.37
DBIx::Class: 0.0821
DBIx::Class::Helpers: 2.016006
DBIx::Class::Helpers: 2.018004
Daemon::Control: 0.001
Dancer: 1.3112
Dancer::Plugin::Auth::Extensible: 0.2
Dancer::Plugin::DBIC: 0.1802
Dancer::Plugin::DBIC: 0.1803
File::ShareDir: 1.03
HTML::Parser: 3.7
HTTP::Tiny: 0.029
@@ -52,6 +52,7 @@ requires:
Starman: 0.4008
Template: 2.24
Template::Plugin::CSV: 0.04
Template::Plugin::Number::Format: 1.02
URL::Encode: 0.01
YAML: 0.84
YAML::XS: 0.41
@@ -64,4 +65,4 @@ resources:
homepage: http://netdisco.org/
license: http://opensource.org/licenses/bsd-license.php
repository: git://git.code.sf.net/p/netdisco/netdisco-ng
version: 2.017000
version: 2.018000

View File

@@ -10,10 +10,10 @@ requires 'App::local::lib::helper' => 0.07;
requires 'DBD::Pg' => 0;
requires 'DBD::SQLite' => 1.37;
requires 'DBIx::Class' => 0.08210;
requires 'DBIx::Class::Helpers' => 2.016006;
requires 'DBIx::Class::Helpers' => 2.018004;
requires 'Daemon::Control' => 0.001000;
requires 'Dancer' => 1.3112;
requires 'Dancer::Plugin::DBIC' => 0.1802;
requires 'Dancer::Plugin::DBIC' => 0.1803;
requires 'Dancer::Plugin::Auth::Extensible' => 0.20;
requires 'File::ShareDir' => 1.03;
requires 'HTML::Parser' => 3.70;

View File

@@ -7,7 +7,7 @@ use 5.010_000;
use File::ShareDir 'dist_dir';
use Path::Class;
our $VERSION = '2.017000';
our $VERSION = '2.018000';
BEGIN {
if (not ($ENV{DANCER_APPDIR} || '')

View File

@@ -3,7 +3,6 @@ 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';
@@ -11,7 +10,7 @@ use Time::HiRes 'gettimeofday';
use base 'Exporter';
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);
=head1 NAME
@@ -44,12 +43,10 @@ sub do_arpnip {
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);
my @v4 = _get_arps($device, $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);
my @v6 = _get_arps($device, $snmp->ipv6_n2p_mac, $snmp->ipv6_n2p_addr);
# get directly connected networks
my @subnets = _gather_subnets($device, $snmp);
@@ -78,23 +75,23 @@ sub do_arpnip {
# get an arp table (v4 or v6)
sub _get_arps {
my ($device, $port_macs, $paddr, $netaddr) = @_;
my ($device, $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)];
next unless check_mac($device, $node);
push @arps, [$node, $ip];
}
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)
and DNS host name.
Stores a new entry to the C<node_ip> table with the given MAC, and IP (v4 or
v6).
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
sub store_arp {
my ($mac, $ip, $name, $now) = @_;
my ($mac, $ip, $now) = @_;
$now ||= 'now()';
schema('netdisco')->txn_do(sub {
@@ -122,7 +119,6 @@ sub store_arp {
->search({'me.mac' => $mac, 'me.ip' => $ip})
->update_or_create(
{
dns => $name,
active => \'true',
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;

View File

@@ -94,6 +94,10 @@ sub do_macsuck {
$device->ip, $port, $vlan, scalar 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
delete $fwtable->{0}->{$_}->{$mac}
for keys %{ $fwtable->{0} };
@@ -130,8 +134,7 @@ sub store_node {
my $nodes = schema('netdisco')->resultset('Node');
# TODO: probably needs changing if we're to support VTP domains
my $old = $nodes->search(
{
my $old = $nodes->search({
mac => $mac,
vlan => $vlan,
-bool => 'active',
@@ -146,37 +149,33 @@ sub store_node {
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(
{
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 => [0, undef]})->delete();
# new data
$new->update_or_create({
$new->update_or_create(
{
vlan => $vlan,
active => \'true',
oui => substr($mac,0,8),
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');
}
++$cache->{$port}->{$mac};
$cache->{$port}->{$mac} = ($fw_vlan->{$idx} || '0');
}
return $cache;

View File

@@ -100,6 +100,10 @@ sub delete {
# avoid letting DBIC delete nodes
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}) {
# avoid letting DBIC delete nodes
return 0E0;

View File

@@ -22,7 +22,7 @@ sub capacity_for {
debug "checking local capacity for action $action";
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/],
};

View File

@@ -13,7 +13,7 @@ my $fqdn = hostfqdn || 'localhost';
my $role_map = {
(map {$_ => 'Poller'}
qw/discoverall discover arpwalk arpnip macwalk macsuck/),
qw/discoverall discover arpwalk arpnip nodenames macwalk macsuck/),
(map {$_ => 'Interactive'}
qw/location contact portcontrol portname vlan power/)
};

View File

@@ -1,9 +1,17 @@
package App::Netdisco::Daemon::Worker::Poller::Arpnip;
use Dancer::Plugin::DBIC 'schema';
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 Class::Method::Modifiers;
use namespace::clean;
with 'App::Netdisco::Daemon::Worker::Poller::Common';
@@ -15,4 +23,43 @@ sub arpnip_layer { 3 }
sub arpwalk { (shift)->_walk_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;

View File

@@ -444,6 +444,22 @@ Value: Number. Default: 0.
Sets the minimum amount of time in seconds which must elapse between any two
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>
Value: Boolean. Default: C<true>.

View File

@@ -18,7 +18,7 @@ the L<documentation|App::Netdisco>. Then:
git clone git://git.code.sf.net/p/netdisco/netdisco-ng netdisco-ng
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
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
You should be able to work out something similar for
C<bin/netdisco-daemon-fg>, too. Happy hacking!
You might also want to set C<check_userlog> to C<true> in your config to quieten
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

View File

@@ -35,6 +35,21 @@ but they are backwards compatible.
=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
=head2 General Notices

View File

@@ -307,18 +307,18 @@ App::Netdisco:
=item C<search_node>
A base url which links to the Node tab of the Search page, together with the
correct default search options set.
A path and query string which links to the Node tab of the Search page,
together with the correct default search options set.
=item C<search_device>
A base url which links to the Device tab of the Search page, together with the
correct default search options set.
A path and query string which links to the Device tab of the Search page,
together with the correct default search options set.
=item C<device_ports>
A base url which links to the Ports tab of the Device page, together with
the correct default column view options set.
A path and query sting which links to the Ports tab of the Device page,
together with the correct default column view options set.
=item C<uri_base>

View File

@@ -10,8 +10,10 @@ our @EXPORT = ();
our @EXPORT_OK = qw/
get_device
check_no
check_only
is_discoverable
is_arpnipable
can_nodenames
is_macsuckable
/;
our %EXPORT_TAGS = (all => \@EXPORT_OK);
@@ -57,42 +59,11 @@ sub get_device {
->find_or_new({ip => $ip});
}
=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.
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) = @_;
sub _check_acl {
my ($ip, $config) = @_;
my $device = get_device($ip) or return 0;
my $addr = NetAddr::IP::Lite->new($device->ip);
my $config = setting($setting_name) || [];
return 0 unless scalar @$config;
foreach my $item (@$config) {
if ($item =~ m/^(.*)\s*:\s*(.*)$/) {
my $prop = $1;
@@ -116,6 +87,86 @@ sub check_no {
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? )
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');
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 :-)
return 1 if not $device->in_storage;
@@ -181,7 +232,7 @@ sub is_arpnipable {
if check_no($device, 'arpnip_no');
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")
if not $device->in_storage;
@@ -195,6 +246,32 @@ sub is_arpnipable {
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 )
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');
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")
if not $device->in_storage;

View File

@@ -44,22 +44,25 @@ MAC address is well-formed (according to common formats)
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 *
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);
$port_macs ||= {};
# incomplete MAC addresses (BayRS frame relay DLCI, etc)
if ($mac->get_error) {

View File

@@ -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
hook after_error_render => sub { setting('layout' => 'main') };
# this hook should be loaded _after_ all plugins
hook 'before_template' => sub {
my $tokens = shift;
@@ -61,6 +62,10 @@ hook 'before_template' => sub {
# access to logged in user's roles
$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
$Template::Directive::WHILE_MAX = 10_000;

View File

@@ -55,7 +55,7 @@ foreach my $jobtype (keys %jobs_all, keys %jobs) {
if exists $jobs{$jobtype} and not param('device');
add_job($jobtype, param('device'), param('extra'));
redirect uri_for('/admin/jobqueue')->as_string;
redirect uri_for('/admin/jobqueue')->path;
};
}

View File

@@ -159,7 +159,7 @@ get '/device' => require_login sub {
});
if (!defined $dev) {
return redirect uri_for('/', {nosuchdevice => 1})->as_string();
return redirect uri_for('/', {nosuchdevice => 1})->path_query;
}
params->{'tab'} ||= 'details';

View File

@@ -66,7 +66,7 @@ get '/search' => require_login sub {
if (not param('tab')) {
if (not $q) {
return redirect uri_for('/')->as_string;
return redirect uri_for('/')->path;
}
# pick most likely tab for initial results
@@ -83,7 +83,7 @@ get '/search' => require_login sub {
tab => 'details',
q => ($nd->first->dns || $nd->first->ip),
f => '',
})->as_string;
})->path_query;
}
# multiple devices

View File

@@ -86,6 +86,8 @@ macsuck_min_age: 0
arpnip_no: []
arpnip_only: []
arpnip_min_age: 0
nodenames_no: []
nodenames_only: []
store_wireless_clients: true
store_modules: true
ignore_interfaces:

View File

@@ -14,3 +14,12 @@ body {
margin-left: 0px !important;
margin-right: 0px !important;
}
.floatThead-table {
position: inherit;
top: 45px;
}
a:after {
content: "" !important;
}

32
TODO
View File

@@ -1,31 +1 @@
FRONTEND
========
* 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
See: https://sourceforge.net/p/netdisco/netdisco2/