#1009 neighbour map depth and/or until end of lldp chains

* sidebar and style changes

* switch server code to new params and values

* working algo for cloud and neighbor depth

* store map positions for lldp cloud with depth

* color the root node uniquely

* restrict all devices, depth, cloud map to < 1000 devices
This commit is contained in:
Oliver Gorwits
2023-11-09 19:54:34 +00:00
committed by GitHub
parent b893d71e66
commit e52f789b45
9 changed files with 83 additions and 17 deletions

View File

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

View File

@@ -17,6 +17,8 @@ __PACKAGE__->add_columns(
{ data_type => "text[]", is_nullable => 0 }, { data_type => "text[]", is_nullable => 0 },
"vlan", "vlan",
{ data_type => "integer", is_nullable => 0, default => 0 }, { data_type => "integer", is_nullable => 0, default => 0 },
"depth",
{ data_type => "integer", is_nullable => 0, default => 0 },
"positions", "positions",
{ data_type => "text", is_nullable => 0 }, { data_type => "text", is_nullable => 0 },
); );

View File

@@ -86,6 +86,7 @@ get '/device' => require_login sub {
template 'device', { template 'device', {
netdisco_device => $first, netdisco_device => $first,
display_name => ($others ? $first->ip : ($first->dns || $first->ip)), display_name => ($others ? $first->ip : ($first->dns || $first->ip)),
device_count => schema(vars->{'tenant'})->resultset('Device')->count(),
lgroup_list => [ schema(vars->{'tenant'})->resultset('Device')->get_distinct_col('location') ], lgroup_list => [ schema(vars->{'tenant'})->resultset('Device')->get_distinct_col('location') ],
hgroup_list => setting('host_group_displaynames'), hgroup_list => setting('host_group_displaynames'),
device => params->{'tab'}, device => params->{'tab'},

View File

@@ -30,7 +30,9 @@ ajax '/ajax/data/device/netmappositions' => require_login sub {
undef $vlan if (defined $vlan and $vlan !~ m/^\d+$/); undef $vlan if (defined $vlan and $vlan !~ m/^\d+$/);
my $mapshow = param('mapshow'); my $mapshow = param('mapshow');
return if !defined $mapshow or $mapshow !~ m/^(?:all|neighbors)$/; return if !defined $mapshow or $mapshow !~ m/^(?:all|cloud|depth)$/;
my $depth = param('depth') || 1;
return if $depth !~ m/^\d+$/;
# list of groups selected by user and passed in param # list of groups selected by user and passed in param
my $hgroup = (ref [] eq ref param('hgroup') ? param('hgroup') : [param('hgroup')]); my $hgroup = (ref [] eq ref param('hgroup') ? param('hgroup') : [param('hgroup')]);
@@ -56,7 +58,8 @@ ajax '/ajax/data/device/netmappositions' => require_login sub {
return unless scalar keys %clean; return unless scalar keys %clean;
my $posrow = schema(vars->{'tenant'})->resultset('NetmapPositions')->find({ my $posrow = schema(vars->{'tenant'})->resultset('NetmapPositions')->find({
device => (($mapshow eq 'neighbors') ? $qdev->ip : undef), device => ($mapshow ne 'all' ? $qdev->ip : undef),
depth => ($mapshow eq 'depth' ? $depth : 0),
host_groups => \[ '= ?', [host_groups => [sort @hgrplist]] ], host_groups => \[ '= ?', [host_groups => [sort @hgrplist]] ],
locations => \[ '= ?', [locations => [sort @lgrplist]] ], locations => \[ '= ?', [locations => [sort @lgrplist]] ],
vlan => ($vlan || 0), vlan => ($vlan || 0),
@@ -67,7 +70,8 @@ ajax '/ajax/data/device/netmappositions' => require_login sub {
} }
else { else {
schema(vars->{'tenant'})->resultset('NetmapPositions')->create({ schema(vars->{'tenant'})->resultset('NetmapPositions')->create({
device => (($mapshow eq 'neighbors') ? $qdev->ip : undef), device => ($mapshow ne 'all' ? $qdev->ip : undef),
depth => ($mapshow eq 'depth' ? $depth : 0),
host_groups => [sort @hgrplist], host_groups => [sort @hgrplist],
locations => [sort @lgrplist], locations => [sort @lgrplist],
vlan => ($vlan || 0), vlan => ($vlan || 0),
@@ -143,8 +147,9 @@ ajax '/ajax/data/device/netmap' => require_login sub {
undef $vlan if (defined $vlan and $vlan !~ m/^\d+$/); undef $vlan if (defined $vlan and $vlan !~ m/^\d+$/);
my $colorby = (param('colorby') || 'speed'); my $colorby = (param('colorby') || 'speed');
my $mapshow = (param('mapshow') || 'neighbors'); my $mapshow = (param('mapshow') || 'depth');
$mapshow = 'neighbors' if $mapshow !~ m/^(?:all|neighbors)$/; my $depth = (param('depth') || 1);
$mapshow = 'depth' if $mapshow !~ m/^(?:all|cloud|depth)$/;
$mapshow = 'all' unless $qdev->in_storage; $mapshow = 'all' unless $qdev->in_storage;
# list of groups selected by user and passed in param # list of groups selected by user and passed in param
@@ -169,7 +174,7 @@ ajax '/ajax/data/device/netmap' => require_login sub {
my %seen_link = (); my %seen_link = ();
my $links = schema(vars->{'tenant'})->resultset('Virtual::DeviceLinks')->search({ my $links = schema(vars->{'tenant'})->resultset('Virtual::DeviceLinks')->search({
($mapshow eq 'neighbors' ? ( -or => [ (($mapshow eq 'depth' and $depth == 1) ? ( -or => [
{ left_ip => $qdev->ip }, { left_ip => $qdev->ip },
{ right_ip => $qdev->ip }, { right_ip => $qdev->ip },
]) : ()) ]) : ())
@@ -193,10 +198,41 @@ ajax '/ajax/data/device/netmap' => require_login sub {
++$seen_link{$link->{left_ip} ."\0". $link->{right_ip}}; ++$seen_link{$link->{left_ip} ."\0". $link->{right_ip}};
} }
# filter by lldp cloud or depth
# this is O(N^2) or worse
my %cloud = ($qdev->ip => 1);
my $seen_cloud = scalar keys %cloud;
my $passes = ($mapshow eq 'cloud' ? 999 : $depth);
if ($mapshow eq 'cloud' or ($mapshow eq 'depth' and $depth > 1)) {
while ($seen_cloud > 0 and $passes > 0) {
--$passes;
$seen_cloud = 0;
foreach my $cip (keys %cloud) {
foreach my $okip (keys %ok_dev) {
next if exists $cloud{$okip};
if (exists $seen_link{$cip ."\0". $okip}
or exists $seen_link{$okip ."\0". $cip}) {
++$cloud{$okip};
++$seen_cloud;
}
}
}
}
}
elsif ($mapshow eq 'depth' and $depth == 1) {
%cloud = %ok_dev;
}
# DEVICES (NODES) # DEVICES (NODES)
my $posrow = schema(vars->{'tenant'})->resultset('NetmapPositions')->find({ my $posrow = schema(vars->{'tenant'})->resultset('NetmapPositions')->find({
device => (($mapshow eq 'neighbors') ? $qdev->ip : undef), device => ($mapshow ne 'all' ? $qdev->ip : undef),
depth => ($mapshow eq 'depth' ? $depth : 0),
host_groups => \[ '= ?', [host_groups => [sort @hgrplist]] ], host_groups => \[ '= ?', [host_groups => [sort @hgrplist]] ],
locations => \[ '= ?', [locations => [sort @lgrplist]] ], locations => \[ '= ?', [locations => [sort @lgrplist]] ],
vlan => ($vlan || 0), vlan => ($vlan || 0),
@@ -219,8 +255,8 @@ ajax '/ajax/data/device/netmap' => require_login sub {
DEVICE: while (my $device = $devices->next) { DEVICE: while (my $device = $devices->next) {
# if in neighbors mode then use %ok_dev to filter # if in neighbors mode then use %ok_dev to filter
next DEVICE if ($device->ip ne $qdev->ip) next DEVICE if ($device->ip ne $qdev->ip)
and ($mapshow eq 'neighbors') and ($mapshow ne 'all')
and (not $ok_dev{$device->ip}); # showing only neighbors but no link and (not $cloud{$device->ip}); # showing only neighbors but no link
# if location picked then filter # if location picked then filter
next DEVICE if ((scalar @lgrplist) and ((!defined $device->location) next DEVICE if ((scalar @lgrplist) and ((!defined $device->location)
@@ -249,6 +285,7 @@ ajax '/ajax/data/device/netmap' => require_login sub {
ID => $device->ip, ID => $device->ip,
SIZEVALUE => (param('dynamicsize') ? $color_lkp{speed} : 3000), SIZEVALUE => (param('dynamicsize') ? $color_lkp{speed} : 3000),
((exists $color_lkp{$colorby}) ? (COLORVALUE => $color_lkp{$colorby}) : ()), ((exists $color_lkp{$colorby}) ? (COLORVALUE => $color_lkp{$colorby}) : ()),
(($device->ip eq $qdev->ip) ? (COLORVALUE => 'ROOTNODE') : ()),
LABEL => (param('showips') ? ($device->ip .' '. $name) : $name), LABEL => (param('showips') ? ($device->ip .' '. $name) : $name),
ORIG_LABEL => $name, ORIG_LABEL => $name,
INFOSTRING => make_node_infostring($device), INFOSTRING => make_node_infostring($device),

View File

@@ -159,7 +159,9 @@ sidebar_defaults:
device_netmap: device_netmap:
showips: { default: null } showips: { default: null }
showspeed: { default: null } showspeed: { default: null }
mapshow: { default: neighbors } mapshow: { default: depth }
depth: { default: 1 }
too_many_devices: { default: 1000 }
colorby: { default: speed } colorby: { default: speed }
dynamicsize: { default: checked } dynamicsize: { default: checked }
report_moduleinventory: report_moduleinventory:

View File

@@ -494,8 +494,13 @@ td > form.nd_inline-form {
vertical-align: text-bottom; vertical-align: text-bottom;
} }
#nd_vlan-entry { #nd_vlan-entry {
margin-top: 7px;
width: 45px; width: 45px;
} }
#nd_mapshow-hops {
width: 30px;
margin-bottom: 0px;
}
/* netmap maximise icon */ /* netmap maximise icon */
#nd2_netmap-fullscreen { #nd2_netmap-fullscreen {

View File

@@ -0,0 +1,7 @@
BEGIN;
ALTER TABLE netmap_positions ADD COLUMN "depth" integer DEFAULT 0 NOT NULL;
UPDATE netmap_positions SET depth = 0 WHERE device IS NOT NULL;
COMMIT;

View File

@@ -216,7 +216,7 @@ function saveMapPositions() {
graph.inspect().main.nodes.each(function(n) { n.fixed = true }); graph.inspect().main.nodes.each(function(n) { n.fixed = true });
$.post( $.post(
'[% uri_for('/ajax/data/device/netmappositions') | none %]' '[% uri_for('/ajax/data/device/netmappositions') | none %]'
,$("#nd_vlan-entry, #nd_hgroup-select, #nd_lgroup-select, #nq, input[name='mapshow']").serialize() ,$("#nd_vlan-entry, #nd_mapshow-hops, #nd_hgroup-select, #nd_lgroup-select, #nq, input[name='mapshow']").serialize()
+ '&positions=' + JSON.stringify(graph.positions()) + '&positions=' + JSON.stringify(graph.positions())
); );
// toastr.success('Saved map positions.'); // toastr.success('Saved map positions.');

View File

@@ -60,26 +60,37 @@
<hr class="nd_sidebar-hr"/> <hr class="nd_sidebar-hr"/>
[% IF device_count < (vars.sidebar_defaults.device_netmap.too_many_devices || 1000) %]
<div class="radio radio-success"> <div class="radio radio-success">
<input type="radio" name="mapshow" id="nd_mapshow-all" <input type="radio" name="mapshow" id="nd_mapshow-all"
[% 'checked' IF vars.sidebar_defaults.device_netmap.mapshow == 'all' %] value="all"> [% 'checked' IF vars.sidebar_defaults.device_netmap.mapshow == 'all' %] value="all">
<label for="nd_mapshow-all">All Devices</label> <label for="nd_mapshow-all">All Devices</label>
</div> </div>
<div class="radio radio-success"> <div class="radio radio-success">
<input type="radio" name="mapshow" id="nd_mapshow-neighbors" <input type="radio" name="mapshow" id="nd_mapshow-cloud"
[% 'checked' IF vars.sidebar_defaults.device_netmap.mapshow == 'neighbors' %] value="neighbors"> [% 'checked' IF vars.sidebar_defaults.device_netmap.mapshow == 'cloud' %] value="cloud">
<label for="nd_mapshow-neighbors">Only Neighbors</label> <label for="nd_mapshow-cloud">Neighbor Cloud</label>
</div> </div>
<div class="radio radio-success">
<input type="radio" name="mapshow" id="nd_mapshow-depth"
[% 'checked' IF vars.sidebar_defaults.device_netmap.mapshow == 'depth' %] value="depth">
<label for="nd_mapshow-depth">Neighbor Hops:
<input name="depth" id="nd_mapshow-hops" class="input-mini" type="number" placeholder="1"
value="[% params.depth || vars.sidebar_defaults.device_netmap.hops || 1 | html_entity %]"/>
</label>
</div>
[% END %]
<label><span id="nd_vlan-label-text">Carrying VLAN: </span> <label><span id="nd_vlan-label-text">Carrying VLAN: </span>
<input name="vlan" id="nd_vlan-entry" class="input-mini" type="number" placeholder="ID" <input name="vlan" id="nd_vlan-entry" class="input-mini" type="number" placeholder="ID"
value="[% params.vlan | html_entity %]" type="text"/> value="[% params.vlan | html_entity %]"/>
</label> </label>
[% IF hgroup_list.size %] [% IF hgroup_list.size %]
<em class="muted">Device Groups:</em><br/>
<select class="nd_side-select" size="[% hgroup_list.size > 4 ? 4 : hgroup_list.size %]" <select class="nd_side-select" size="[% hgroup_list.size > 4 ? 4 : hgroup_list.size %]"
multiple name="hgroup" id="nd_hgroup-select" multiple name="hgroup" id="nd_hgroup-select"
rel="tooltip" data-placement="left" data-offset="5" data-title="Host Groups"> rel="tooltip" data-placement="left" data-offset="5" data-title="Device Groups">
[% FOREACH opt IN hgroup_list.pairs %] [% FOREACH opt IN hgroup_list.pairs %]
<option[% ' selected="selected"' IF hgroup_lkp.exists(opt.key) %] <option[% ' selected="selected"' IF hgroup_lkp.exists(opt.key) %]
value="[% opt.key | html_entity %]">[% opt.value | html_entity %]</option> value="[% opt.key | html_entity %]">[% opt.value | html_entity %]</option>
@@ -87,6 +98,7 @@
</select> </select>
[% END %] [% END %]
[% IF lgroup_list.size %] [% IF lgroup_list.size %]
<em class="muted">Device Locations:</em><br/>
<select class="nd_side-select" size="[% lgroup_list.size > 4 ? 4 : lgroup_list.size %]" <select class="nd_side-select" size="[% lgroup_list.size > 4 ? 4 : lgroup_list.size %]"
multiple name="lgroup" id="nd_lgroup-select" multiple name="lgroup" id="nd_lgroup-select"
rel="tooltip" data-placement="left" data-offset="5" data-title="Device Locations"> rel="tooltip" data-placement="left" data-offset="5" data-title="Device Locations">