* arp matrix graph and fix

* bug fix

* graph fix

* bug fix

* tests on arp graph

* merged heatmap.js and map.js

* graph dinamic resize

* minor fix

* graph dinamic width

* graph performance improvement

* clean code

* fix manual refresh

* message for empty graph

* clean code

* more clean

* update arpMap and host details

* migrate to v4.min version of d3.js
This commit is contained in:
Francesco Staccini 2019-04-17 13:23:10 +02:00 committed by simonemainardi
parent 94b143c8a3
commit 1d9b35be42
13 changed files with 1247 additions and 224 deletions

View file

@ -1,252 +1,199 @@
--
-- (C) 2013-19 - ntop.org
-- (C) 2018 - ntop.org
--
local dirs = ntop.getDirs()
dirs = ntop.getDirs()
package.path = dirs.installdir .. "/scripts/lua/modules/?.lua;" .. package.path
require "lua_utils"
if((dirs.scriptdir ~= nil) and (dirs.scriptdir ~= "")) then package.path = dirs.scriptdir .. "/lua/modules/?.lua;" .. package.path end
ignore_post_payload_parse = 1
require "lua_utils"
local json = require("dkjson")
local matrix = interface.getArpStatsMatrixInfo()
sendHTTPContentTypeHeader('application/json')
--sendHTTPContentTypeHeader('text/html')
--g: the type og graph ( 1 -sigma graph, 2 -heb graph )
--t: the type of data visualized (1-broadcast,2-replies,3-requests)
--local g,t = _GET["g"], _GET["t"]
--print(g..t)
--MISSING VALIDATION!
sendHTTPContentTypeHeader('Application/json')
--========UTILS=======(but not currently used)==============================
--[[
--chack if inside "t" there is a mac named "name", if true return the index, nil otherwise
local function containName(t,name)
for i,v in pairs(t) do
if v.labels == name then return i end
--------------------------------------------------------------------------
---------------------------------------------------------------------------
local ga_module = {}
local request = {}
local response = {}
--"suggestions_strings" must be a string array, and "card" must be created with create_card()
local function fill_response(speech_text, display_text, expect_response, suggestions_strings, card)
if display_text == nil or display_text == "" then display_text = speech_text end
if expect_response == nil then expect_response = true end
local mysuggestions = {}--MAX 10 (imposed by google)
if suggestions_strings then
for i = 1, #suggestions_strings do
table.insert( mysuggestions, {title = suggestions_strings[i]} )
end
return nil
end
end
--split the string "s" with the "sep" separator
local function split(s,sep)
local sep, fields = sep, {}
local pattern = string.format("([^%s]+)", sep)
s:gsub(pattern, function(c) fields[#fields+1] = c end)
return fields
end
local myitems = {}
function tableLength(t)
local count = 0
for _ in pairs(t) do count = count + 1 end
return count
end
--]]
--=======================================================================
if card then
--tprint(card)
myitems = {
{
simpleResponse = {
textToSpeech = speech_text,
displayText = display_text
}
},
{basicCard = card}
}
else
myitems[1] = {
simpleResponse = {
textToSpeech = speech_text,
displayText = display_text,
}
}
end
local r = {}
--if a context was created, consume it
local mycontext = ga_module.getContext()
if mycontext then
--[[ JSON SCHEMA for sigma.js graph
{
"nodes": [
{
"id": "chr1",
"x": 0,
"y": 0,
"label": "Bob",
"size": 8.75
},
{
"id": "chr10",
"label": "Alice",
"x": 3,
"y": 1,
"size": 14.75
r = {
fulfillmentText = display_text,
payload = {
google = {
expectUserResponse = expect_response,
richResponse = {
items = myitems,
suggestions = mysuggestions
}
}
},
outputContexts = mycontext
}
],
"edges": [{
"id": "1",
"source": "chr1",
"target": "chr10"
}]
]]--
ga_module.deleteContext()
else
r = {
fulfillmentText = display_text,
payload = {
google = {
expectUserResponse = expect_response,
richResponse = {
items = myitems,
suggestions = mysuggestions
}
}
}
}
--TODO: normalize pkt size (eg: size = (pkt_snt+rcv / tot_pkt_seen) * max_node_size ) ? )
--but maybe is not necessary, sigma.js already make an average
end
--the value of a t_nodes element is the size (not normalized) of the node (size = #request_pkt_sent )
--if "broadcast" is false all the broadcast requests will be ignored
--AT THE MOMENT THE CREATED GRAPH REPRESENT ONLY THE ARP REQUESTS (not replies)
local function createNodesAndEdges(matrix, broadcast)
local t_nodes = {}
local x,y = 10,10
local num, e_id = 0, 0
local t = { nodes = {}, edges = {} }
local source, target
for _, m_elem in ipairs(matrix) do
for src_mac, s_elem in pairs(m_elem)do
for dst_mac, stats in pairs(s_elem) do
--add dst_mac node and edges if broadcast is true or the pkt isn't broadcast
if broadcast or (dst_mac ~= "FF:FF:FF:FF:FF:FF") then
if t_nodes[src_mac] then
t_nodes[src_mac] = t_nodes[src_mac] + stats["src2dst.requests"]
else
t_nodes[src_mac] = stats["src2dst.requests"]
end
if t_nodes[dst_mac] then
t_nodes[dst_mac] = t_nodes[dst_mac] + stats["dst2src.requests"]
else
t_nodes[dst_mac] = stats["dst2src.requests"]
end
if stats["src2dst.requests"] > 0 then
table.insert( t.edges,
{ id = e_id,
source = src_mac,
target = dst_mac,
size = stats["src2dst.requests"],
label = stats["src2dst.requests"].." req snt"
}
)
end
e_id = e_id + 1
if stats["dst2src.requests"] > 0 then
table.insert( t.edges,
{ id = e_id,
source = dst_mac,
target = src_mac,
size = stats["dst2src.requests"],
label = stats["dst2src.requests"].." req snt"
}
)
end
e_id = e_id + 1
end--end if
end
end
end
for i,v in pairs(t_nodes) do
x = math.floor(math.random(0,500))
y = math.floor(math.random(0,350))
table.insert( t.nodes, { id = i, label = i, x = x , y = y, size = v })
end
return t
return json.encode(r)
end
----WIP--------WIP--------WIP--------WIP--------WIP--------WIP--------WIP--------WIP--------
--------------------------------------------------------------------------------------------
----------------------------Hierarchical Edge Bundling--------------------------------------
--------------------------------------------------------------------------------------------
--PROBLEMA: le "foglie", cioè l'ultima parola dopo il punto. devono essere uniche
-- ma se A invia una req a B e poi B invia una req ad A, ciò viene meno
--IDEA: creo una finta gerarchia in base alle comunicazioni:
--se A invia a B, allora il nome di B diventa "A.B". e così per ogni comunicazione
--TODO: cards allow many things (like buttons), more info ---> [ https://dialogflow.com/docs/rich-messages#card ]
function ga_module.create_card(card_title, card_url_image, accessibility_image_text, button_title, button_open_url_action )
--TODO: unire i 3 cici dove possibile
local function createHierarchyAndImport(matrix,broadcast)
local t_names = {}
local tbl = {}
local pkt_num = 0
local myButton = {}
myButton = {
{
title = button_title,
openUrlAction = { url = button_open_url_action}
}
}
--creo i nodi del grafo
for _, m_elem in ipairs(matrix) do
for src_mac, s_elem in pairs(m_elem)do
for dst_mac, stats in pairs(s_elem) do
local myCard = {}
myCard = {
title = card_title,
image = { url = card_url_image, accessibilityText = accessibility_image_text },
buttons = myButton
}
--i due punti separatori dei byte del mac danno noia allo script js, metto il trattino
src_mac = string.gsub(src_mac, ":", "-")
dst_mac = string.gsub(dst_mac, ":", "-")
return myCard
end
t_names[src_mac] = {name = src_mac, imports = {} }
--To set an arbitrary context (and overwrite the old one) call setContext()
--To cancel an existing/outgoing context ---> set the lifespan to 0
--For complex structures use as many prefs as there are fields to save
function ga_module.setContext(name, lifespan, parameter) --TODO: support for more parameters
if dst_mac ~= "FF-FF-FF-FF-FF-FF" then
t_names[dst_mac] = {name = dst_mac, imports = {} }
end
end
end
end
--ho la mappa dei mac dentro t_names, ora compongo la gerarchia fittizia
for _, m_elem in ipairs(matrix) do
for src_mac, s_elem in pairs(m_elem)do
for dst_mac, stats in pairs(s_elem) do
src_mac = string.gsub(src_mac, ":", "-")
dst_mac = string.gsub(dst_mac, ":", "-")
pkt_num = stats["src2dst.requests"]
if pkt_num > 0 then
if dst_mac ~= "FF-FF-FF-FF-FF-FF" then
t_names[dst_mac].name = src_mac.."."..t_names[dst_mac].name
end
end
pkt_num = stats["dst2src.requests"]
if pkt_num > 0 then
t_names[src_mac].name = dst_mac.."."..t_names[src_mac].name
end
end
end
end
--ho i nomi "lunghi", aggiungo gli import
for _, m_elem in ipairs(matrix) do
for src_mac, s_elem in pairs(m_elem)do
for dst_mac, stats in pairs(s_elem) do
src_mac = string.gsub(src_mac, ":", "-")
dst_mac = string.gsub(dst_mac, ":", "-")
if name then
ntop.setCache("context_name", name, 60 * 20) --(max context lifespan: 20 min)
end
if lifespan then
ntop.setCache("context_lifespan", tostring(lifespan), 60 * 20)
end
if parameter then
ntop.setCache("context_param", parameter, 60*20)
end
end
pkt_num = stats["src2dst.requests"]
if pkt_num > 0 then
if dst_mac ~= "FF-FF-FF-FF-FF-FF" and (t_names[dst_mac].name ~= t_names[src_mac].name) then
table.insert( t_names[src_mac].imports, t_names[dst_mac].name )
end
end
pkt_num = stats["dst2src.requests"]
if pkt_num > 0 then
if t_names[dst_mac].name ~= t_names[src_mac].name then
table.insert( t_names[dst_mac].imports, t_names[src_mac].name )
end
end
function ga_module.deleteContext()
ntop.delCache("context_name")
ntop.delCache("context_lifespan")
ntop.delCache("context_param")
end
end
end
end
function ga_module.getContext()
--genero la tabella pronta per divenire il file json
for i,v in pairs(t_names) do
--tprint(v)
--"size" non viene preso in considerazione per lo spessore dell'arco
table.insert(tbl, { name = v.name, size = math.floor(math.random(100,10000)), imports = v.imports } )
end
local name = ntop.getCache("context_name")
if name == "" then return nil end
local lifespan = ntop.getCache("context_lifespan")
return tbl
if lifespan == "" then lifespan = 2 end
local mycontext = {
{
name = name,
lifespanCount = lifespan,
parameters = {param = ntop.getCache("context_param") }
}
}
return mycontext
end
function ga_module.send(speech_text, display_text, expect_response, suggestions_strings, card )
res = fill_response(speech_text, display_text,expect_response, suggestions_strings, card)
print(res.."\n")
io.write("\n")
io.write("NTOPNG RESPONSE\n")
tprint(res)
io.write("\n---------------------------------------------------------\n")
end
--print( json.encode( createNodesAndEdges(matrix, false), {indent = true} ) )
print( json.encode( createHierarchyAndImport(matrix, false), {indent = true} ) )
function ga_module.receive()
--print( json.encode( matrix, {indent = true} ) )
local info, pos, err = json.decode(_POST["payload"], 1, nil)--I assume only ONE outputContext
response["responseId"] = info.responseId
response["queryText"] = info.queryResult.queryText
if info.queryResult.parameters ~= nil then response["parameters"] = info.queryResult.parameters end
if info.queryResult.outputContexts and info.queryResult.outputContexts[1].name then response["context"] = info.queryResult.outputContexts[1].name end
---response["outputContext_name"] = info.queryResult.outputContexts[1].name
--response["outputContext_parameters"] = info.queryResult.outputContexts[1].parameters.number
response["intent_name"] = info.queryResult.intent.displayName
response["session"] = info.session
ntop.setCache("session_id", info.session )
io.write("\n")
io.write("DIALOGFLOW REQUEST")
tprint(response)
io.write("\n")
return response
end
return ga_module