mirror of
https://github.com/ntop/ntopng.git
synced 2026-05-03 09:20:10 +00:00
This commit lets the hosts interaction map show most relevant
hosts first. This plays along well with the limit introduced
with commit 7d3bbc1. This addresses issue #42.
580 lines
15 KiB
Lua
580 lines
15 KiB
Lua
--
|
|
-- (C) 2013-15 - ntop.org
|
|
--
|
|
|
|
dirs = ntop.getDirs()
|
|
package.path = dirs.installdir .. "/scripts/lua/modules/?.lua;" .. package.path
|
|
|
|
require "lua_utils"
|
|
|
|
-- host_info = url2hostinfo(_GET)
|
|
|
|
|
|
if(host_ip == nil) then
|
|
host_info = url2hostinfo(_GET)
|
|
else
|
|
-- print("host_ip:"..host_ip.."<br>")
|
|
host_info = {}
|
|
host_info = hostkey2hostinfo(host_ip)
|
|
end
|
|
|
|
if(mode == nil) then
|
|
mode = _GET["mode"]
|
|
end
|
|
|
|
if(host_name == nil) then
|
|
host_name = _GET["name"]
|
|
end
|
|
|
|
if(mode ~= "embed") then
|
|
sendHTTPHeader('text/html; charset=iso-8859-1')
|
|
ntop.dumpFile(dirs.installdir .. "/httpdocs/inc/header.inc")
|
|
active_page = "hosts"
|
|
dofile(dirs.installdir .. "/scripts/lua/inc/menu.lua")
|
|
end
|
|
|
|
num_top_hosts = 10
|
|
|
|
if(host_info["host"] ~= nil) then
|
|
num = 1
|
|
else
|
|
interface.select(ifname)
|
|
hosts_stats = interface.getHostsInfo()
|
|
num = 0
|
|
for key, value in pairs(hosts_stats) do
|
|
num = num + 1
|
|
end
|
|
end
|
|
|
|
if(num > 0) then
|
|
if(mode ~= "embed") then
|
|
if(host_info["host"] == nil) then
|
|
print("<hr><h2>Top Hosts Interaction</H2>")
|
|
else
|
|
name = host_name
|
|
if(name == nil) then name = host_info["host"] end
|
|
print("<hr><h2>"..name.." Interactions</H2><i class=\"fa fa-chevron-left fa-lg\"></i><small><A onClick=\"javascript:history.back()\">Back</A></small>")
|
|
end
|
|
end
|
|
|
|
print [[
|
|
<style>
|
|
svg {
|
|
font: 10px sans-serif;
|
|
}
|
|
|
|
.axis path, .axis line {
|
|
fill: none;
|
|
stroke: #000;
|
|
shape-rendering: crispEdges;
|
|
}
|
|
|
|
sup, sub {
|
|
line-height: 0;
|
|
}
|
|
|
|
q:before, blockquote:before {
|
|
content: "?";
|
|
}
|
|
|
|
q:after, blockquote:after {
|
|
content: "?";
|
|
}
|
|
|
|
blockquote:before {
|
|
position: absolute;
|
|
left: 2em;
|
|
}
|
|
|
|
blockquote:after {
|
|
position: absolute;
|
|
}
|
|
|
|
</style>
|
|
<style>
|
|
#chart {
|
|
height:
|
|
]]
|
|
|
|
if(mode ~= "embed") then
|
|
print("600")
|
|
elseif(interface.getNumAggregatedHosts() > 0) then
|
|
print("400")
|
|
else
|
|
print("300")
|
|
end
|
|
|
|
print [[
|
|
px;
|
|
}
|
|
.node rect {
|
|
cursor: move;
|
|
fill-opacity: .9;
|
|
shape-rendering: crispEdges;
|
|
}
|
|
.node text {
|
|
pointer-events: none;
|
|
text-shadow: 0 1px 0 #fff;
|
|
}
|
|
.link {
|
|
fill: none;
|
|
stroke: #000;
|
|
stroke-opacity: .2;
|
|
}
|
|
.link:hover {
|
|
stroke-opacity: .5;
|
|
}
|
|
|
|
circle.node-dot {
|
|
fill: DarkSlateGray;
|
|
stroke: SlateGray;
|
|
stroke-width: 1px;
|
|
}
|
|
|
|
|
|
path.link {
|
|
fill: none;
|
|
stroke: SlateGray;
|
|
stroke-width: 1.5px;
|
|
}
|
|
|
|
marker#defaultMarker {
|
|
fill: SlateGray;
|
|
}
|
|
|
|
path.link.defaultMarker {
|
|
stroke: SlateGray;
|
|
}
|
|
|
|
circle {
|
|
fill: #ccc;
|
|
stroke: #333;
|
|
stroke-width: 1.5px;
|
|
}
|
|
|
|
text {
|
|
pointer-events: none;
|
|
}
|
|
|
|
text.shadow {
|
|
stroke: #fff;
|
|
stroke-width: 3px;
|
|
stroke-opacity: .8;
|
|
}
|
|
|
|
</style><style>path.link.proposer{stroke:red;}
|
|
marker#bus{fill:blue;}
|
|
marker#manual{fill:red;}
|
|
path.link.direct{stroke:green; }
|
|
path.link.bus{stroke:blue;}
|
|
path.link.manual{stroke:red;stroke-dasharray: 0, 2 1;} </style><script>
|
|
/**
|
|
* do the force vizualization
|
|
* @param {string} divName name of the div to hold the tree
|
|
* @param {object} inData the source data
|
|
*/
|
|
function doTheTreeViz(divName, inData) {
|
|
// tweak the options
|
|
var options = $.extend({
|
|
stackHeight : 12,
|
|
radius : 5,
|
|
fontSize : 12,
|
|
labelFontSize : 8,
|
|
nodeLabel : null,
|
|
markerWidth : 0,
|
|
markerHeight : 0,
|
|
width : $(divName).outerWidth(),
|
|
gap : 1.5,
|
|
nodeResize : "",
|
|
linkDistance : 30,
|
|
charge : -120,
|
|
styleColumn : null,
|
|
styles : null,
|
|
linkName : null,
|
|
height : $(divName).outerHeight()
|
|
}, inData.d3.options);
|
|
// set up the parameters
|
|
options.gap = options.gap * options.radius;
|
|
var width = options.width;
|
|
var height = options.height;
|
|
var data = inData.d3.data;
|
|
var nodes = data.nodes;
|
|
var links = data.links;
|
|
var color = d3.scale.category10();
|
|
|
|
color["local"] = "#aec7e8";
|
|
color["remote"] = "#bcbd22";
|
|
color["sun"] = "#fd8d3c";
|
|
color["aggregation"] = "#008d3c";
|
|
|
|
var force = d3.layout.force().nodes(nodes).links(links).size([width, height]).linkDistance(options.linkDistance).charge(options.charge).on("tick", tick).start();
|
|
|
|
var main = d3.select(divName).append("svg:svg").attr("width", width).attr("height", height);
|
|
|
|
var svg = main.append('svg:g')
|
|
.call(d3.behavior.zoom().on("zoom", rescale))
|
|
.on("dblclick.zoom", null);
|
|
|
|
function rescale() {
|
|
trans=d3.event.translate;
|
|
scale=d3.event.scale;
|
|
|
|
svg.attr("transform",
|
|
"translate(" + trans + ")"
|
|
+ " scale(" + scale + ")");
|
|
}
|
|
|
|
// get list of unique values in stylecolumn
|
|
linkStyles = [];
|
|
if (options.styleColumn) {
|
|
var x;
|
|
for (var i = 0; i < links.length; i++) {
|
|
if (linkStyles.indexOf( x = links[i][options.styleColumn].toLowerCase()) == -1)
|
|
linkStyles.push(x);
|
|
}
|
|
} else
|
|
linkStyles[0] = "defaultMarker";
|
|
|
|
// do we need a marker?
|
|
|
|
if (options.markerWidth) {
|
|
svg.append("svg:defs").selectAll("marker").data(linkStyles).enter().append("svg:marker").attr("id", String).attr("viewBox", "0 -5 10 10").attr("refX", 15).attr("refY", -1.5).attr("markerWidth", options.markerWidth).attr("markerHeight", options.markerHeight).attr("orient", "auto").append("svg:path").attr("d", "M0,-5L10,0L0,5");
|
|
}
|
|
|
|
var path = svg.append("svg:g").selectAll("path").data(force.links()).enter().append("svg:path").attr("class", function(d) {
|
|
return "link " + (options.styleColumn ? d[options.styleColumn].toLowerCase() : linkStyles[0]);
|
|
}).attr("marker-end", function(d) {
|
|
return "url(#" + (options.styleColumn ? d[options.styleColumn].toLowerCase() : linkStyles[0] ) + ")";
|
|
});
|
|
|
|
var circle = svg.append("svg:g").selectAll("circle")
|
|
.data(force.nodes())
|
|
.enter()
|
|
.append("svg:circle")
|
|
.attr("r", function(d) {
|
|
return getRadius(d);
|
|
})
|
|
.style("fill", function(d) {
|
|
return color[d.group];
|
|
})
|
|
.call(force.drag)
|
|
//.on("mousedown",
|
|
// function(d) {
|
|
// disable zoom
|
|
// svg.call(d3.behavior.zoom().on("zoom"), null);
|
|
// })
|
|
//.on("mouseup",
|
|
// function(d) {
|
|
// enable zoom
|
|
// svg.call(d3.behavior.zoom().on("zoom"), rescale);
|
|
// })
|
|
;
|
|
|
|
if (options.nodeLabel) {
|
|
circle.append("title").html(function(d) {
|
|
return d[options.nodeLabel];
|
|
});
|
|
}
|
|
|
|
circle.on("dblclick", function(d) { if(d.link.length > 0) { window.location.href = d.link; } } );
|
|
|
|
if (options.linkName) {
|
|
path.append("title").text(function(d) {
|
|
return d[options.linkName];
|
|
});
|
|
}
|
|
var text = svg.append("svg:g").selectAll("g").data(force.nodes()).enter().append("svg:g");
|
|
|
|
// A copy of the text with a thick white stroke for legibility.
|
|
text.append("svg:text").attr("x", options.labelFontSize).attr("y", ".31em").attr("class", "shadow").text(function(d) {
|
|
return d[options.nodeLabel];
|
|
});
|
|
|
|
text.append("svg:text").attr("x", options.labelFontSize).attr("y", ".31em").text(function(d) {
|
|
return d[options.nodeLabel];
|
|
});
|
|
function getRadius(d) {
|
|
return options.radius * (options.nodeResize ? Math.sqrt(d[options.nodeResize]) / Math.PI : 1);
|
|
}
|
|
|
|
// Use elliptical arc path segments to doubly-encode directionality.
|
|
function tick() {
|
|
path.attr("d", function(d) {
|
|
var dx = d.target.x - d.source.x, dy = d.target.y - d.source.y, dr = Math.sqrt(dx * dx + dy * dy);
|
|
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
|
|
});
|
|
|
|
circle.attr("transform", function(d) {
|
|
return "translate(" + d.x + "," + d.y + ")";
|
|
});
|
|
|
|
text.attr("transform", function(d) {
|
|
return "translate(" + d.x + "," + d.y + ")";
|
|
});
|
|
}
|
|
|
|
}
|
|
</script><script>
|
|
|
|
window['ntopData'] = {"d3":{"options":
|
|
{"radius":"16","fontSize":"30","labelFontSize":"30","charge":"-2000","nodeResize":"count","nodeLabel":"label","markerHeight":"6","markerWidth":"6","styleColumn":"styleColumn","linkName":"group"},
|
|
"data":{"links":[
|
|
|
|
]]
|
|
|
|
-- Nodes
|
|
|
|
interface.select(ifname)
|
|
|
|
if(host_info["host"] == nil) then
|
|
hosts_stats = getTopInterfaceHosts(num_top_hosts, true)
|
|
else
|
|
hosts_stats = {}
|
|
hosts_stats[host_info["host"]] = interface.getHostInfo(host_info["host"],host_info["vlan"])
|
|
end
|
|
|
|
hosts_ctx = {}
|
|
hosts_id = {}
|
|
ids = {}
|
|
|
|
num = 0
|
|
links = 0
|
|
local host
|
|
local max_num_hosts = 50
|
|
local host_idx = 0
|
|
|
|
local num_contacts
|
|
|
|
-- Enumerate contacts of each host
|
|
for key, values in pairs(hosts_stats) do
|
|
host = interface.getHostInfo(key)
|
|
if (host ~= nil) then
|
|
num_contacts = 0
|
|
if (host["contacts"]["client"] ~= nil) then
|
|
for k,v in pairs(host["contacts"]["client"]) do
|
|
num_contacts = num_contacts + 1
|
|
end
|
|
end
|
|
if (host["contacts"]["server"] ~= nil) then
|
|
for k,v in pairs(host["contacts"]["server"]) do
|
|
num_contacts = num_contacts + 1
|
|
end
|
|
end
|
|
end
|
|
hosts_ctx[key] = num_contacts
|
|
end
|
|
|
|
for key, values in pairsByValues(hosts_ctx, rev) do
|
|
|
|
host = interface.getHostInfo(key)
|
|
|
|
if(host ~= nil) then
|
|
-- init host
|
|
if(hosts_id[key] == nil) then
|
|
hosts_id[key] = { }
|
|
hosts_id[key]['count'] = 0
|
|
hosts_id[key]['id'] = num
|
|
ids[num] = key
|
|
key_id = num
|
|
num = num + 1
|
|
else
|
|
key_id = hosts_id[key]['id']
|
|
end
|
|
|
|
-- client contacts
|
|
if(host["contacts"]["client"] ~= nil) then
|
|
for k,v in pairs(host["contacts"]["client"]) do
|
|
|
|
if(hosts_id[k] == nil) then
|
|
hosts_id[k] = { }
|
|
hosts_id[k]['count'] = 0
|
|
hosts_id[k]['id'] = num
|
|
ids[num] = k
|
|
peer_id = num
|
|
num = num + 1
|
|
else
|
|
peer_id = hosts_id[k]['id']
|
|
end
|
|
|
|
hosts_id[key]['count'] = hosts_id[key]['count'] + v
|
|
if(links > 0) then print(",") end
|
|
print('\n{"source":'..key_id..',"target":'..peer_id..',"depth":6,"count":'..v..',"styleColumn":"client","linkName":""}')
|
|
links = links + 1
|
|
end
|
|
end
|
|
|
|
-- server contacts
|
|
if(host["contacts"]["server"] ~= nil) then
|
|
for k,v in pairs(host["contacts"]["server"]) do
|
|
|
|
if(hosts_id[k] == nil) then
|
|
hosts_id[k] = { }
|
|
hosts_id[k]['count'] = 0
|
|
hosts_id[k]['id'] = num
|
|
ids[num] = k
|
|
peer_id = num
|
|
num = num + 1
|
|
else
|
|
peer_id = hosts_id[k]['id']
|
|
end
|
|
|
|
hosts_id[key]['count'] = hosts_id[key]['count'] + v
|
|
if(links > 0) then print(",") end
|
|
print('\n{"source":'..key_id..',"target":'..peer_id..',"depth":6,"count":'..v..',"styleColumn":"server"}')
|
|
links = links + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
host_idx = host_idx + 1
|
|
if (host_idx > max_num_hosts) then
|
|
break
|
|
end
|
|
end
|
|
|
|
aggregation_ids = {}
|
|
|
|
if(host_info["host"] ~= nil) then
|
|
aggregations = interface.getAggregationsForHost(host_info["host"])
|
|
else
|
|
aggregations = {}
|
|
end
|
|
|
|
for name,num_contacts in pairs(aggregations) do
|
|
aggregation_ids[name] = num
|
|
|
|
hosts_id[name] = { }
|
|
hosts_id[name]['count'] = num_contacts
|
|
hosts_id[name]['id'] = num
|
|
ids[num] = name
|
|
|
|
if(links > 0) then print(",") end
|
|
print('\n{"source":'..num..',"target": 0,"depth":6,"count":'..num_contacts..',"styleColumn":"aggregation"}')
|
|
links = links + 1
|
|
|
|
num = num + 1
|
|
end
|
|
|
|
tot_hosts = num
|
|
|
|
print [[
|
|
|
|
],"nodes":[
|
|
]]
|
|
|
|
-- Nodes
|
|
|
|
min_size = 5
|
|
maxval = 0
|
|
for k,v in pairs(hosts_id) do
|
|
if(v['count'] > maxval) then maxval = v['count'] end
|
|
end
|
|
|
|
num = 0
|
|
for i=0,tot_hosts-1 do
|
|
k = ids[i]
|
|
v = hosts_id[k]
|
|
k_info = hostkey2hostinfo(k)
|
|
|
|
target_host = interface.getHostInfo(k)
|
|
|
|
if(target_host ~= nil) then
|
|
|
|
name = target_host["name"]
|
|
if(name ~= nil) then
|
|
name = name
|
|
else
|
|
name = ntop.getResolvedAddress(k_info["host"])
|
|
target_host["name"] = name
|
|
end
|
|
|
|
if(target_host['localhost'] ~= nil) then label = "local" else label = "remote" end
|
|
|
|
else
|
|
|
|
name = k
|
|
if(aggregations[k] ~= nil) then label = "aggregation" else label = "remote" end
|
|
|
|
end
|
|
|
|
if ((host_info["host"] ~= nil) and (host_info["host"] == k_info["host"])) then label = "sun" end
|
|
-- f(name == k) then name = ntop.getResolvedAddress(k) end
|
|
if(name == nil) then name = k end
|
|
|
|
if(maxval == 0) then
|
|
tot = maxval
|
|
else
|
|
tot = math.floor(0.5+(v['count']*100)/maxval)
|
|
if(tot < min_size) then tot = min_size end
|
|
end
|
|
|
|
if(num > 0) then print(",") end
|
|
print('\n{"name":"'.. name ..'","count":'.. tot ..',"group":"' .. label .. '","linkCount": '.. tot .. ',"label":"'.. name..'"')
|
|
|
|
if(target_host ~= nil) then
|
|
-- Host still in memory
|
|
if((host_info["host"] == nil) or (k_info["host"] ~= host_info["host"])) then
|
|
print(', "link": "'..ntop.getHttpPrefix()..'/lua/hosts_interaction.lua?"}')
|
|
else
|
|
print(', "link": "'..ntop.getHttpPrefix()..'/lua/host_details.lua?host='.. k.. '"}')
|
|
end
|
|
else
|
|
-- print('->>'..k..'<<-\n')
|
|
if(aggregations[k] ~= nil) then
|
|
print(', "link": "'..ntop.getHttpPrefix()..'/lua/aggregated_host_details.lua?host='.. k .. '"}')
|
|
else
|
|
-- Host purged ?
|
|
print(', "link": ""}')
|
|
end
|
|
|
|
end
|
|
|
|
num = num + 1
|
|
end
|
|
|
|
if ((num == 0) and (host_info["host"] ~= nil)) then
|
|
tot = 1
|
|
label = ""
|
|
name = host_info["host"]
|
|
print('\n{"name":"'.. name ..'","count":'.. tot ..',"group":"' .. label .. '","linkCount": '.. tot .. ',"label":"'.. name..'", "link": "'..ntop.getHttpPrefix()..'/lua/host_details.lua?host='.. hostinfo2hostkey(host_info).. '"}')
|
|
end
|
|
|
|
print [[
|
|
|
|
]
|
|
}}};
|
|
|
|
|
|
</script>
|
|
|
|
<div id="chart"></div>
|
|
|
|
<p> <p><small><b>NOTE</b></small>
|
|
<ol>
|
|
]]
|
|
|
|
if(host_info["host"] ~= nil) then
|
|
print('<li><small>This map is centered on host <font color="#fd8d3c">'.. hostinfo2hostkey(host_info))
|
|
if(host_name ~= nil) then print('('.. host_name .. ')') end
|
|
print('</font>. Clicking on this host you will visualize its details.</small></li>\n')
|
|
else
|
|
print('<li><small>This map depicts the interactions of the top '.. num_top_hosts .. ' hosts.</small></li>\n')
|
|
end
|
|
print('<li><small>Color map: <font color=#aec7e8><b>local</b></font>, <font color=#bcbd22><b>remote</b></font>, <font color=#008d3c><b>aggregation</b></font>, <font color=#fd8d3c><b>focus</b></font> host.</small></li>\n')
|
|
print [[
|
|
<li> <small>Click is enabled only for hosts that have not been purged from memory.</small></li>
|
|
</ol>
|
|
|
|
<!-- http://ramblings.mcpher.com/Home/excelquirks/d3 -->
|
|
<script type="text/javascript">
|
|
doTheTreeViz("#chart", ntopData);
|
|
</script>
|
|
|
|
]]
|
|
else
|
|
print("<div class=\"alert alert-danger\"><img src=".. ntop.getHttpPrefix() .. "/img/warning.png> No results found</div>")
|
|
end
|
|
|
|
if(mode ~= "embed") then
|
|
dofile(dirs.installdir .. "/scripts/lua/inc/footer.lua")
|
|
end
|