ntopng/scripts/lua/modules/lua_utils.lua
Simone Mainardi c7082a4647 Implements new alerts database and drilldown
alert store skeleton

Alert database type changes

Implement alert store for host alerts. All alert store skeletons.

Fix class method access

Enable tracing

Implements simple queries for host alerts

Implement flow alert store

Fixes escaping of INSERT queries

Flow alerts database schema fixes

Adds escaping for alert JSON in flows and hosts

Implements queries .select() for alerts store

Adds limit and offset to perform paginated queries

Adds new REST getter for flow alerts

Name changes alert_severity to severity, alert_json to json

Fixes alert message not shown

Implement active monitoring alerts store

Implements sort of queries in the new dataabase

Changes alert_type to alert_id

Implement mac alerts store

Fixes flow alert messages

Implement system alerts store

Implement snmp alert store

Add missing items to the flow alerts

Add missing items to the host alerts

Add missing items to the mac alerts

Implements COUNT aplerts api

Add device_name to snmp alerts

add flow alerts templates

updated gitignore

fix for missing order field

add families defined in `alert_store_schema.sql`

Reworks and simplifies alert store subclasses

Implements REST API to fetch alert timeseries

Fixes date in flow REST api

Host alert json fix

Add get/host/alert/list.lua

Move alert/list.lua to alert/past/list.lua

Add alert/past/list.lua for all alert families

Add entity_id to system table to identify the alert type based on <alert_id, entity_id>

Add missing field

Implements facilities to query engaged alerts via REST

Handle both historical and past alerts in alert/list.lua

Fix count

Update params of select_historical

Implement method to add family-specific filters

Add alert/ts.lua for all alert families

Implements facilities and REST endpoints to delete alerts

Implements ordering of alert queries

Fix add_order_by group_by

Rest API tests update

Tests output update

Remove debug trace

Use alert_id instead of type. Add more flow alert info.

Update http lint

Format obsolete tlv version alerts. Add more host info.

Add row_id to list of alerts

Fix selection of engaged alerts

Add test for mac alerts (bcast domains)

Removes attempt to format alerts as flow alerts

Fixes interface selection for active monitoring

Update test output with rest changes

Add more fields to be ignores

Set alert count to 1 for the time being

add bar timeseries chart

add apexcharts

improvements on timeseries bar chart

registered chart callbakcs

working on alert page

fix for date format

Fixes acrive monitoring REST API

Fixes alignment of grouped alert data

Additional fix for alert histogram

remove useless if

formatting alerts page

fixes on flows alert stats table

rename local networks to device

format host pagie in alert_stats

add tag support for hosts and flow

implemented single delete action

add release modal

Fixes format of threshold cross interface alerts

Fixes wrong increase of dropped alerts

Implements exclusion list for invalid dns queries

Reworks exclusions lists for hosts and flows

Addresses #5212
Addresses #5113

Adds host alert keys in host callbacks definitions

Adds alert ids to flow callbacks

fix for not working button (#5215)

Fixes reported timeseries name

removed any additional button inside chart's toolbar (#5200)

Add tables for interfaces, networks, users to the schema. Skeleton alert_store classes

fixed broken range picker layout in firefox (#5199)

Alert insert fixes

Add more info to network alerts

Add rest endpoint for interface, network, user alerts

Fix endpoint selection in alerts_stats

Unifies columns between engaged and past alerts

Fixes Missing mandatory 'alert_granularity'

Minor fixes for missing alert_severity

Fixes arithmetic on a nil value (field 'last_seen')

Fixes get/system/alert/list.lua

use tstamp for column names (#5221)

Implements host alert formatter

Add alerts_store format_record_common

Use common format_record for am, system alerts

Use common format_record for all alerts

Fixes formatting of alerts of all types

Fixes nil in function 'hostinfo2label'

fixes on disable modal

add pages for network, user and interface endpoint (#5224)

Set alert_entity in all classes

Unifies influxdb alerts into system alerts

Addresses #5224

Unifies process alerts into system alerts

Addresses #5224

Cleanup unused periodicActivityEntity

Unifies category lists alerts into system alerts

Addresses #5224

Aligns new alert enums

Addresses #5224

Fixes alert page links

Fixes insertion of interface alerts

Implement filters for Host alerts

Fixes active monitoring alerts not triggering

Implement filters on flow alerts

Fixes for internal alerts timestamp and subtype

implements disable for the alerts

formatted alert disable label

Add address and device type to mac alert records

fix for delete alert toggle

Fix access to entity_val in alert_unexpected_new_device

add mac address and device type inside table

Fixes for new alert fields not handled

Fixes alert_definitions to handle new fields

Add ip/port to snmp alert records

Implements deletion of stored flow alerts

Add alert_name to all alert records via rest. Fix duration.

fixes for snmp tab

Implements delete of past host alerts

Add name to snmp alert records

fixes on system tab

Fix Date column

fixes link

Update menu

Fixes bad argument #3 to 'format' in snmp alerts

updated interface link

new alerts url for host (#5228)

Fixes sort of engaged alert

Minor cleanup

Fixes data returned for local network alerts

Fix duration for one shot. Note.

Fix duration override

Fixes interface selection for system alerts

Move host alert page

fixes for local network tab

Minor fix

Fix engaged host alerts

fixes on user tab

Fix alert_user_activity message
2021-04-26 19:41:34 +02:00

4399 lines
138 KiB
Lua

--
-- (C) 2014-21 - ntop.org
--
-- Hack to avoid include loops
if(pragma_once_lua_utils == true) then
-- avoid multiple inclusions
return
end
pragma_once_lua_utils = true
-- ###############################################
dirs = ntop.getDirs()
package.path = dirs.installdir .. "/scripts/lua/modules/i18n/?.lua;" .. package.path
package.path = dirs.installdir .. "/scripts/lua/modules/timeseries/?.lua;" .. package.path
package.path = dirs.installdir .. "/scripts/lua/modules/flow_dbms/?.lua;" .. package.path
package.path = dirs.installdir .. "/scripts/lua/modules/pools/?.lua;" .. package.path
require "lua_trace"
require "ntop_utils"
locales_utils = require "locales_utils"
local os_utils = require "os_utils"
local format_utils = require "format_utils"
local dscp_consts = require "dscp_consts"
-- TODO: replace those globals with locals everywhere
secondsToTime = format_utils.secondsToTime
msToTime = format_utils.msToTime
bytesToSize = format_utils.bytesToSize
formatPackets = format_utils.formatPackets
formatFlows = format_utils.formatFlows
formatValue = format_utils.formatValue
pktsToSize = format_utils.pktsToSize
bitsToSize = format_utils.bitsToSize
round = format_utils.round
bitsToSizeMultiplier = format_utils.bitsToSizeMultiplier
-- ##############################################
-- Note: Regexs are applied by default. Pass plain=true to disable them.
function string.contains(str, start, is_plain)
if type(str) ~= 'string' or type(start) ~= 'string' then
return false
end
local i, _ = string.find(str, start, 1, is_plain)
return(i ~= nil)
end
-- ##############################################
function shortenString(name, max_len)
if(name == nil) then return("") end
if max_len == nil then
max_len = ntop.getPref("ntopng.prefs.max_ui_strlen")
max_len = tonumber(max_len)
if(max_len == nil) then max_len = 24 end
end
if(string.len(name) < max_len) then
return(name)
else
return(string.sub(name, 1, max_len).."...")
end
end
-- ##############################################
function convertDate(vardate)
local m,d,y,h,i,s = string.match(vardate, '(%d+)/(%d+)/(%d+) (%d+):(%d+):(%d+)')
return string.format('%s/%s/%s %s:%s:%s', d,m,y,h,i,s)
end
-- ##############################################
-- See also getHumanReadableInterfaceName
function getInterfaceName(interface_id, windows_skip_description)
if(interface_id == getSystemInterfaceId()) then
return(getSystemInterfaceName())
end
local ifnames = interface.getIfNames()
local iface = ifnames[tostring(interface_id)]
if iface ~= nil then
if(windows_skip_description ~= true and string.contains(iface, "{")) then -- Windows
local old_iface = interface.getId()
-- Use the interface description instead of the name
interface.select(tostring(iface))
iface = interface.getStats().description
interface.select(tostring(old_iface))
end
return(iface)
end
return("")
end
-- ##############################################
function getInterfaceId(interface_name)
if(interface_name == getSystemInterfaceName()) then
return(getSystemInterfaceId())
end
local ifnames = interface.getIfNames()
for if_id, if_name in pairs(ifnames) do
if if_name == interface_name then
return tonumber(if_id)
end
end
return(-1)
end
-- ##############################################
function getFirstInterfaceId()
local ifid = interface.getFirstInterfaceId()
if ifid ~= nil then
return ifid, getInterfaceName(ifid)
end
return -1, ""
end
-- ##############################################
function isAllowedSystemInterface()
return ntop.isAllowedInterface(tonumber(getSystemInterfaceId()))
end
-- ##############################################
local cached_allowed_networks_set = nil
function hasAllowedNetworksSet()
if(cached_allowed_networks_set == nil) then
local nets = ntop.getAllowedNetworks()
local allowed_nets = string.split(nets, ",") or {nets}
cached_allowed_networks_set = false
for _, net in pairs(allowed_nets) do
if((not isEmptyString(net)) and (net ~= "0.0.0.0/0") and (net ~= "::/0")) then
cached_allowed_networks_set = true
break
end
end
end
return(cached_allowed_networks_set)
end
-- ##############################################
-- Note that ifname can be set by Lua.cpp so don't touch it if already defined
if((ifname == nil) and (_GET ~= nil)) then
ifname = _GET["ifid"]
if(ifname ~= nil) then
if(ifname.."" == tostring(tonumber(ifname)).."") then
-- ifname does not contain the interface name but rather the interface id
ifname = getInterfaceName(ifname, true)
if(ifname == "") then ifname = nil end
end
end
if(debug_session) then traceError(TRACE_DEBUG,TRACE_CONSOLE, "Session => Session:".._SESSION["session"]) end
if((ifname == nil) and (_SESSION ~= nil)) then
if(debug_session) then traceError(TRACE_DEBUG,TRACE_CONSOLE, "Session => set ifname by _SESSION value") end
ifname = _SESSION["ifname"]
if(debug_session) then traceError(TRACE_DEBUG,TRACE_CONSOLE, "Session => ifname:"..ifname) end
else
if(debug_session) then traceError(TRACE_DEBUG,TRACE_CONSOLE, "Session => set ifname by _GET value") end
end
end
-- See Utils::l4proto2name()
l4_keys = {
{ "IP", "ip", 0 },
{ "ICMP", "icmp", 1 },
{ "IGMP", "igmp", 2 },
{ "TCP", "tcp", 6 },
{ "UDP", "udp", 17 },
{ "IPv6", "ipv6", 41 },
{ "RSVP", "rsvp", 46 },
{ "GRE", "gre", 47 },
{ "ESP", "esp", 50 },
{ "IPv6-ICMP", "ipv6icmp", 58 },
{ "OSPF", "ospf", 89 },
{ "PIM", "pim", 103 },
{ "VRRP", "vrrp", 112 },
{ "HIP", "hip", 139 },
{ "ICMPv6", "icmpv6", 58 },
{ "IGMP", "igmp", 2 },
{ "Other IP", "other_ip", -1 }
}
L4_PROTO_KEYS = {tcp=6, udp=17, icmp=1, other_ip=-1}
function __FILE__() return debug.getinfo(2,'S').source end
function __LINE__() return debug.getinfo(2, 'l').currentline end
-- ##############################################
local http_status_code_map = {
[200] = "OK",
[400] = "Bad Request",
[401] = "Unauthorized",
[403] = "Forbidden",
[404] = "Not Found",
[405] = "Method Not Allowed",
[406] = "Not Acceptable",
[408] = "Request timeout",
[409] = "Conflict",
[410] = "Gone",
[412] = "Precondition Failed",
[415] = "Unsupported Media Type",
[423] = "Locked",
[428] = "Precondition Required",
[429] = "Too many requests",
[500] = "Internal Server Error",
[501] = "Not Implemented",
[503] = "Service Unavailable",
}
-- ##############################################
function sendHTTPHeaderIfName(mime, ifname, maxage, content_disposition, extra_headers, status_code)
info = ntop.getInfo(false)
local cookie_attr = ntop.getCookieAttributes()
local lines = {
'Cache-Control: max-age=0, no-cache, no-store',
'Server: ntopng '..info["version"]..' ['.. info["platform"]..']',
'Pragma: no-cache',
'X-Frame-Options: DENY',
'X-Content-Type-Options: nosniff',
'Content-Type: '.. mime,
'Last-Modified: '..os.date("!%a, %m %B %Y %X %Z"),
}
if(_SESSION ~= nil) then
lines[#lines + 1] = 'Set-Cookie: session='.._SESSION["session"]..'; max-age=' .. maxage .. '; path=/; ' .. cookie_attr
end
if(ifname ~= nil) then
lines[#lines + 1] = 'Set-Cookie: ifname=' .. ifname .. '; path=/' .. cookie_attr
end
if(content_disposition ~= nil) then
lines[#lines + 1] = 'Content-Disposition: '..content_disposition
end
if type(extra_headers) == "table" then
for hname, hval in pairs(extra_headers) do
lines[#lines + 1] = hname..': '..hval
end
end
if not status_code then
status_code = 200
end
local status_descr = http_status_code_map[status_code]
if not status_descr then
status_descr = "Unknown"
end
-- Buffer the HTTP reply and write it in one "print" to avoid fragmenting
-- it into multiple packets, to ease HTTP debugging with wireshark.
print("HTTP/1.1 " .. status_code .. " " .. status_descr .. "\r\n" .. table.concat(lines, "\r\n") .. "\r\n\r\n")
end
-- ##############################################
function sendHTTPHeaderLogout(mime, content_disposition)
sendHTTPHeaderIfName(mime, nil, 0, content_disposition)
end
-- ##############################################
function sendHTTPHeader(mime, content_disposition, extra_headers, status_code)
sendHTTPHeaderIfName(mime, nil, 3600, content_disposition, extra_headers, status_code)
end
-- ##############################################
function sendHTTPContentTypeHeader(content_type, content_disposition, charset, extra_headers, status_code)
local charset = charset or "utf-8"
local mime = content_type.."; charset="..charset
sendHTTPHeader(mime, content_disposition, extra_headers, status_code)
end
-- ##############################################
function printGETParameters(get)
for key, value in pairs(get) do
io.write(key.."="..value.."\n")
end
end
-- ##############################################
function findString(str, tofind)
if(str == nil) then return(nil) end
if(tofind == nil) then return(nil) end
str1 = string.lower(string.gsub(str, "-", "_"))
tofind1 = string.lower(string.gsub(tofind, "-", "_"))
return(string.find(str1, tofind1, 1))
end
-- ##############################################
function findStringArray(str, tofind)
if(str == nil) then return(nil) end
if(tofind == nil) then return(nil) end
local rsp = false
for k,v in pairs(tofind) do
str1 = string.gsub(str, "-", "_")
tofind1 = string.gsub(v, "-", "_")
if(str1 == tofind1) then
rsp = true
end
end
return(rsp)
end
-- ##############################################
function printASN(asn, asname)
asname = asname:gsub('"','')
if(asn > 0) then
return("<A HREF='http://as.robtex.com/as"..asn..".html' title='"..asname.."'>"..asname.."</A> <i class='fas fa-external-link-alt fa-lg'></i>")
else
return(asname)
end
end
-- ##############################################
function urlencode(str)
str = string.gsub (str, "\r?\n", "\r\n")
str = string.gsub (str, "([^%w%-%.%_%~ ])",
function (c) return string.format ("%%%02X", string.byte(c)) end)
str = string.gsub (str, " ", "+")
return str
end
-- ##############################################
function getPageUrl(base_url, params)
if table.empty(params) then
return base_url
end
local encoded = {}
for k, v in pairs(params) do
encoded[k] = urlencode(v)
end
local delim = "&"
if not string.find(base_url, "?") then
delim = "?"
end
return base_url .. delim .. table.tconcat(encoded, "=", "&")
end
-- ##############################################
function printIpVersionDropdown(base_url, page_params)
local ipversion = _GET["version"]
local ipversion_filter
if not isEmptyString(ipversion) then
ipversion_filter = '<span class="fas fa-filter"></span>'
else
ipversion_filter = ''
end
-- table.clone needed to modify some parameters while keeping the original unchanged
local ipversion_params = table.clone(page_params)
ipversion_params["version"] = nil
print[[\
<button class="btn btn-link dropdown-toggle" data-toggle="dropdown">]] print(i18n("flows_page.ip_version")) print[[]] print(ipversion_filter) print[[<span class="caret"></span></button>\
<ul class="dropdown-menu scrollable-dropdown" role="menu" id="flow_dropdown">\
<li><a class="dropdown-item" href="]] print(getPageUrl(base_url, ipversion_params)) print[[">]] print(i18n("flows_page.all_ip_versions")) print[[</a></li>\
<li><a class="dropdown-item ]] if ipversion == "4" then print('active') end print[[" href="]] ipversion_params["version"] = "4"; print(getPageUrl(base_url, ipversion_params)); print[[">]] print(i18n("flows_page.ipv4_only")) print[[</a></li>\
<li><a class="dropdown-item ]] if ipversion == "6" then print('active') end print[[" href="]] ipversion_params["version"] = "6"; print(getPageUrl(base_url, ipversion_params)); print[[">]] print(i18n("flows_page.ipv6_only")) print[[</a></li>\
</ul>]]
end
-- ##############################################
function printVLANFilterDropdown(base_url, page_params)
local vlans = interface.getVLANsList()
if vlans == nil then vlans = {VLANs={}} end
vlans = vlans["VLANs"]
local ids = {}
for _, vlan in ipairs(vlans) do
ids[#ids + 1] = vlan["vlan_id"]
end
local vlan_id = _GET["vlan"]
local vlan_id_filter = ''
if not isEmptyString(vlan_id) then
vlan_id_filter = '<span class="fas fa-filter"></span>'
end
-- table.clone needed to modify some parameters while keeping the original unchanged
local vlan_id_params = table.clone(page_params)
vlan_id_params["vlan"] = nil
print[[\
<button class="btn btn-link dropdown-toggle" data-toggle="dropdown">]] print(i18n("flows_page.vlan")) print[[]] print(vlan_id_filter) print[[<span class="caret"></span></button>\
<ul class="dropdown-menu scrollable-dropdown" role="menu" id="flow_dropdown">\
<li><a class="dropdown-item" href="]] print(getPageUrl(base_url, vlan_id_params)) print[[">]] print(i18n("flows_page.all_vlan_ids")) print[[</a></li>\]]
for _, vid in ipairs(ids) do
vlan_id_params["vlan"] = vid
print[[
<li>\
<a class="dropdown-item ]] print(vlan_id == tostring(vid) and 'active' or '') print[[" href="]] print(getPageUrl(base_url, vlan_id_params)) print[[">VLAN ]] print(tostring(vid)) print[[</a></li>\]]
end
print[[
</ul>]]
end
-- ##############################################
function printDSCPDropdown(base_url, page_params, dscp_list)
local dscp = _GET["dscp"]
local dscp_filter
if not isEmptyString(dscp) then
dscp_filter = '<span class="fas fa-filter"></span>'
else
dscp_filter = ''
end
-- table.clone needed to modify some parameters while keeping the original unchanged
local dscp_params = table.clone(page_params)
dscp_params["dscp"] = nil
-- Used to possibly remove tcp state filters when selecting a non-TCP l4 protocol
local dscp_params_non_filter = table.clone(dscp_params)
if dscp_params_non_filter["dscp"] then
dscp_params_non_filter["dscp"] = nil
end
local ordered_dscp_list = {}
for key, value in pairs(dscp_list) do
local name = dscp_consts.dscp_descr(key)
ordered_dscp_list[name] = {}
ordered_dscp_list[name]["id"] = key
ordered_dscp_list[name]["count"] = value.count
end
print[[\
<button class="btn btn-link dropdown-toggle" data-toggle="dropdown">]] print(i18n("flows_page.dscp")) print[[]] print(dscp_filter) print[[<span class="caret"></span></button>\
<ul class="dropdown-menu dropdown-menu-right scrollable-dropdown" role="menu" id="flow_dropdown">\
<li><a class="dropdown-item" href="]] print(getPageUrl(base_url, dscp_params_non_filter)) print[[">]] print(i18n("flows_page.all_dscp")) print[[</a></li>]]
for key, value in pairsByKeys(ordered_dscp_list, asc) do
print[[<li]]
print([[><a class="dropdown-item ]].. (tonumber(dscp) == value.id and 'active' or '') ..[[" href="]])
local dscp_table = ternary(key ~= 6, dscp_params_non_filter, dscp_params)
dscp_table["dscp"] = value.id
print(getPageUrl(base_url, dscp_table))
print[[">]] print(key) print [[ (]] print(string.format("%d", value.count)) print [[)</a></li>]]
end
print[[</ul>]]
end
-- ###################################
function printHostPoolDropdown(base_url, page_params, host_pool_list)
local host_pools = require "host_pools"
local host_pools_instance = host_pools:create()
local host_pool = _GET["host_pool_id"]
local host_pool_filter
if not isEmptyString(host_pool) then
host_pool_filter = '<span class="fas fa-filter"></span>'
else
host_pool_filter = ''
end
-- table.clone needed to modify some parameters while keeping the original unchanged
local host_pool_params = table.clone(page_params)
host_pool_params["host_pool_id"] = nil
-- Used to possibly remove tcp state filters when selecting a non-TCP l4 protocol
local host_pool_params_non_filter = table.clone(host_pool_params)
if host_pool_params_non_filter["host_pool_id"] then
host_pool_params_non_filter["host_pool_id"] = nil
end
local ordered_host_pool_list = {}
if host_pool then
local id = tonumber(host_pool)
ordered_host_pool_list[id] = {}
ordered_host_pool_list[id]["count"] = host_pool_list[id]["count"]
else
for key, value in pairs(host_pool_list) do
ordered_host_pool_list[key] = {}
ordered_host_pool_list[key]["count"] = value.count
end
end
print[[\
<button class="btn btn-link dropdown-toggle" data-toggle="dropdown">]] print(i18n("details.host_pool")) print[[]] print(host_pool_filter) print[[<span class="caret"></span></button>\
<ul class="dropdown-menu dropdown-menu-right scrollable-dropdown" role="menu" id="flow_dropdown">\
<li><a class="dropdown-item" href="]] print(getPageUrl(base_url, host_pool_params_non_filter)) print[[">]] print(i18n("flows_page.all_host_pool")) print[[</a></li>]]
for key, value in pairsByKeys(ordered_host_pool_list, asc) do
print[[<li]]
print([[><a class="dropdown-item ]].. (tonumber(host_pool) == key and 'active' or '') ..[[" href="]])
local host_pool_table = ternary(key ~= 6, host_pool_params_non_filter, host_pool_params)
host_pool_table["host_pool_id"] = key
print(getPageUrl(base_url, host_pool_table))
print[[">]] print(host_pools_instance:get_pool_name(key)) print [[ (]] print(string.format("%d", value.count)) print [[)</a></li>]]
end
print[[</ul>]]
end
-- ##############################################
function printTrafficTypeFilterDropdown(base_url, page_params)
local traffic_type = _GET["traffic_type"]
local traffic_type_filter = ''
if not isEmptyString(traffic_type) then
traffic_type_filter = '<span class="fas fa-filter"></span>'
end
-- table.clone needed to modify some parameters while keeping the original unchanged
local traffic_type_params = table.clone(page_params)
traffic_type_params["traffic_type"] = nil
print[[\
<button class="btn btn-link dropdown-toggle" data-toggle="dropdown">]] print(i18n("flows_page.direction")) print[[]] print(traffic_type_filter) print[[<span class="caret"></span></button>\
<ul class="dropdown-menu scrollable-dropdown" role="menu" id="flow_dropdown">\
<li><a class="dropdown-item" href="]] print(getPageUrl(base_url, traffic_type_params)) print[[">]] print(i18n("hosts_stats.traffic_type_all")) print[[</a></li>\]]
-- now forthe one-way
traffic_type_params["traffic_type"] = "one_way"
print[[
<li>\
<a class="dropdown-item ]] if traffic_type == "one_way" then print('active') end print[[" href="]] print(getPageUrl(base_url, traffic_type_params)) print[[">]] print(i18n("hosts_stats.traffic_type_one_way")) print[[</a></li>\]]
traffic_type_params["traffic_type"] = "bidirectional"
print[[
<li>\
<a class="dropdown-item ]] if traffic_type == "bidirectional" then print('active') end print[[" href="]] print(getPageUrl(base_url, traffic_type_params)) print[[">]] print(i18n("hosts_stats.traffic_type_two_ways")) print[[</a></li>\]]
print[[
</ul>]]
end
-- ##############################################
--
-- Returns indexes to be used for string shortening. The portion of to_shorten between
-- middle_start and middle_end will be inside the bounds.
--
-- to_shorten: string to be shorten
-- middle_start: middle part begin index
-- middle_end: middle part begin index
-- maxlen: maximum length
--
function shortenInTheMiddle(to_shorten, middle_start, middle_end, maxlen)
local maxlen = maxlen - (middle_end - middle_start)
if maxlen <= 0 then
return 0, string.len(to_shorten)
end
local left_slice = math.max(middle_start - math.floor(maxlen / 2), 1)
maxlen = maxlen - (middle_start - left_slice - 1)
local right_slice = math.min(middle_end + maxlen, string.len(to_shorten))
return left_slice, right_slice
end
-- ##############################################
function shortHostName(name)
local chunks = {name:match("(%d+)%.(%d+)%.(%d+)%.(%d+)")}
if(#chunks == 4) then
return(name)
else
local max_len = ntop.getPref("ntopng.prefs.max_ui_strlen")
max_len = tonumber(max_len)
if(max_len == nil) then max_len = 24 end
chunks = {name:match("%w+:%w+:%w+:%w+:%w+:%w+")}
--io.write(#chunks.."\n")
if(#chunks == 1) then
return(name)
end
if(string.len(name) < max_len) then
return(name)
else
tot = 0
n = 0
ret = ""
for token in string.gmatch(name, "([%w-]+).") do
if(tot < max_len) then
if(n > 0) then ret = ret .. "." end
ret = ret .. token
tot = tot+string.len(token)
n = n + 1
end
end
return(ret .. "...")
end
end
return(name)
end
-- ##############################################
function _handleArray(name, sev)
local id
for id, _ in ipairs(name) do
local l = name[id][1]
local key = name[id][2]
if(string.upper(key) == string.upper(sev)) then
return(l)
end
end
return(firstToUpper(sev))
end
-- ##############################################
function l4Label(proto)
return(_handleArray(l4_keys, proto))
end
function l4_proto_to_id(proto_name)
for _, proto in pairs(l4_keys) do
if proto[2] == proto_name then
return(proto[3])
end
end
end
function l4_proto_to_string(proto_id)
proto_id = tonumber(proto_id)
for _, proto in pairs(l4_keys) do
if proto[3] == proto_id then
return proto[1], proto[2]
end
end
return string.format("%d", proto_id)
end
-- ##############################################
function noHtml(s)
if s == nil then return nil end
local gsub, char = string.gsub, string.char
local entityMap = {lt = "<", gt = ">" , amp = "&", quot ='"', apos = "'"}
local entitySwap = function(orig, n, s)
return (n == '' and entityMap[s])
or (n == "#" and tonumber(s)) and string.char(s)
or (n == "#x" and tonumber(s,16)) and string.char(tonumber(s,16))
or orig
end
local function unescape(str)
return (gsub( str, '(&(#?x?)([%d%a]+);)', entitySwap ))
end
local cleaned = s:gsub("<[aA] .->(.-)</[aA]>","%1")
:gsub("<abbr .->(.-)</abbr>","%1")
:gsub("<span .->(.-)</span>","%1")
:gsub("%s*<[iI].->(.-)</[iI]>","%1")
:gsub("<.->(.-)</.->","%1") -- note: keep as last as this does not handle nested tags
:gsub("^%s*(.-)%s*$", "%1")
:gsub('&nbsp;', " ")
return unescape(cleaned)
end
-- ##############################################
function areAlertsEnabled()
if(__alert_enabled == nil) then
-- Not too nice as changes will be read periodically as new VMs are reloaded
-- but at least we avoid breaking up the performance
__alert_enabled = (ntop.getPref("ntopng.prefs.disable_alerts_generation") ~= "1")
end
return (__alert_enabled)
end
-- ##############################################
function isScoreEnabled()
return(ntop.isEnterpriseM())
end
-- ##############################################
function hasTrafficReport()
local ts_utils = require("ts_utils_core")
local is_pcap_dump = interface.isPcapDumpInterface()
return((not is_pcap_dump) and (ts_utils.getDriverName() == "rrd") and ntop.isEnterpriseM())
end
function mustScanAlerts(ifstats)
return areAlertsEnabled()
end
function hasAlertsDisabled()
_POST = _POST or {}
return ((_POST["disable_alerts_generation"] ~= nil) and (_POST["disable_alerts_generation"] == "1")) or
((_POST["disable_alerts_generation"] == nil) and (ntop.getPref("ntopng.prefs.disable_alerts_generation") == "1"))
end
function hasNindexSupport()
if not ntop.isPro() or ntop.isWindows() then
return false
end
-- TODO optimize
if prefs == nil then
prefs = ntop.getPrefs()
end
if prefs.is_nindex_enabled then
return true
end
return false
end
-- NOTE: global nindex support may be enabled but some disable on some interfaces
function interfaceHasNindexSupport()
return(hasNindexSupport() and interface.nIndexEnabled())
end
--for _key, _value in pairsByKeys(vals, rev) do
-- print(_key .. "=" .. _value .. "\n")
--end
function truncate(x)
return x<0 and math.ceil(x) or math.floor(x)
end
-- Note that the function below returns a string as returning a number
-- would not help as a new float would be returned
function toint(num)
return string.format("%u", truncate(num))
end
function capitalize(str)
return (str:gsub("^%l", string.upper))
end
local function starstring(len)
local s = ""
while(len > 0) do
s = s .."*"
len = len -1
end
return(s)
end
function obfuscate(str)
local len = string.len(str)
local in_clear = 2
if(len <= in_clear) then
return(starstring(len))
else
return(string.sub(str, 0, in_clear)..starstring(len-in_clear))
end
end
function isnumber(str)
if((str ~= nil) and (string.len(str) > 0) and (tonumber(str) ~= nil)) then
return(true)
else
return(false)
end
end
function split(pString, pPattern)
local Table = {} -- NOTE: use {n = 0} in Lua-5.0
local fpat = "(.-)" .. pPattern
local last_end = 1
local s, e, cap = pString:find(fpat, 1)
while s do
if s ~= 1 or cap ~= "" then
table.insert(Table,cap)
end
last_end = e+1
s, e, cap = pString:find(fpat, last_end)
end
if last_end <= #pString then
cap = pString:sub(last_end)
table.insert(Table, cap)
end
return Table
end
-- returns the MAXIMUM value found in a table t, together with the corresponding
-- index argmax. a pair argmax, max is returned.
function tmax(t)
local argmx, mx = nil, nil
if (type(t) ~= "table") then return nil, nil end
for k, v in pairs(t) do
-- first iteration
if mx == nil and argmx == nil then
mx = v
argmx = k
elseif (v == mx and k > argmx) or v > mx then
-- if there is a tie, prefer the greatest argument
-- otherwise grab the maximum
argmx = k
mx = v
end
end
return argmx, mx
end
-- returns the MINIMUM value found in a table t, together with the corresponding
-- index argmin. a pair argmin, min is returned.
function tmin(t)
local argmn, mn = nil, nil
if (type(t) ~= "table") then return nil, nil end
for k, v in pairs(t) do
-- first iteration
if mn == nil and argmn == nil then
mn = v
argmn = k
elseif (v == mn and k > argmn) or v < mn then
-- if there is a tie, prefer the greatest argument
-- otherwise grab the minimum
argmn = k
mn = v
end
end
return argmn, mn
end
function formatEpoch(epoch)
return(format_utils.formatEpoch(epoch))
end
function starts(String,Start)
if((String == nil) or (Start == nil)) then
return(false)
end
return string.sub(String,1,string.len(Start))==Start
end
function ends(String,End)
return End=='' or string.sub(String,-string.len(End))==End
end
-- #################################################################
function bit(p)
return 2 ^ (p - 1) -- 1-based indexing
end
-- Typical call: if hasbit(x, bit(3)) then ...
function hasbit(x, p)
return x % (p + p) >= p
end
function setbit(x, p)
return hasbit(x, p) and x or x + p
end
function clearbit(x, p)
return hasbit(x, p) and x - p or x
end
function isBroadMulticast(ip)
if(ip == "0.0.0.0") then
return true
end
-- print(ip)
t = string.split(ip, "%.")
-- print(table.concat(t, "\n"))
if(t == nil) then
return false -- Might be an IPv6 address
else
if(tonumber(t[1]) >= 224) then
return true
end
end
return false
end
function isBroadcastMulticast(ip)
local ainfo = interface.getAddressInfo(ip)
if(ainfo.is_multicast or ainfo.is_broadcast) then
return true
else
return false
end
end
function isIPv4(address)
-- Reuse the for loop to check the address validity
local checkAddress = (function(chunks)
for _, v in pairs(chunks) do
if (tonumber(v) < 0) or (tonumber(v) > 255) then
return false
end
end
return true
end)
local chunks = {address:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")}
local chunksWithPort = {address:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)%:(%d+)$")}
if #chunks == 4 then
return checkAddress(chunks)
elseif #chunksWithPort == 5 then
table.remove(chunksWithPort, 5)
return checkAddress(chunksWithPort)
end
return false
end
function isIPv4Network(address)
local parts = split(address, "/")
if #parts == 2 then
local prefix = tonumber(parts[2])
if (prefix == nil) or (math.floor(prefix) ~= prefix) or (prefix < 0) or (prefix > 32) then
return false
end
elseif #parts ~= 1 then
return false
end
return isIPv4(parts[1])
end
function addGoogleMapsScript()
local g_maps_key = ntop.getCache('ntopng.prefs.google_apis_browser_key')
if g_maps_key ~= nil and g_maps_key~= "" then
g_maps_key = "&key="..g_maps_key
else
g_maps_key = ""
end
print("<script src=\"https://maps.googleapis.com/maps/api/js?v=3.exp"..g_maps_key.."\"></script>\n")
end
function addLogoLightSvg()
return ([[
<div id='ntopng-logo'>
<svg
id="ntopng-logo"
data-name="ntopng logo"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512.74 512.84"
height="56"
width="56">
<defs>
<style>
.cls-1{fill:none;}
.cls-2{fill:#cecece;}
.cls-2,.cls-3,.cls-4{fill-rule:evenodd;}
.cls-3{fill:#b7b7b7;}
.cls-4{fill:#ee751b;}
</style>
</defs>
<title>ntopng logo</title>
<path class="cls-1" d="M1,513.94V1.1H513.78V513.94ZM497.86,172c0-22.84.08-44.16-.07-65.48a100.07,100.07,0,0,0-1.34-16.31c-6.87-40.3-39-71.17-81.23-71.68-105.79-1.27-211.6-.44-317.4-.35a60.46,60.46,0,0,0-14.33,1.62c-39.52,9.78-65.2,42.87-65.34,85-.24,75.82-.07,151.63,0,227.44,0,1.2.25,2.39.47,4.31,2.18-1.71,3.83-2.82,5.26-4.17,14.23-13.54,28.39-27.15,42.67-40.65,2.17-2.06,2.09-3.89,1.38-6.64-1.58-6.05-3.65-12.31-3.45-18.41.91-28.45,25.61-49.36,51.92-48.1,25.77,1.23,50.24,25.65,46.44,55.08-.59,4.64.85,6,4.18,6.6,38.67-46.29,85.62-58.59,141.88-36.58a15.05,15.05,0,0,0,1-1.31c17.37-26.37,34.78-52.72,52-79.21.92-1.42.69-4.55-.28-6.07-9.33-14.54-11.6-29.7-5.46-46.12,8.26-22.11,33.59-36,56.46-30.51,26.38,6.29,42.42,30.41,37.84,57.12-.55,3.25.22,5.15,3.1,6.94,9,5.62,17.81,11.66,26.81,17.34C485.7,165.2,491.33,168.21,497.86,172Zm-87.1,325.77c22.32,1.05,41.19-5.51,57.26-19.17,19-16.16,30.07-36.25,30-62.13-.33-67.15-.11-134.31-.11-201.46v-7.13c-2.44-1.43-4.62-2.59-6.68-3.93-17.43-11.35-34.88-22.65-52.21-34.14-3.45-2.28-6-2.68-9.66,0-10.77,7.8-23.06,9.91-36.05,7.52-3.8-.69-6,.23-8.17,3.57-11.63,18-23.54,35.89-35.31,53.83-5.55,8.46-11,17-16.52,25.5.85.9,1.39,1.52,2,2.09,36.21,34.78,46.28,81.33,27.45,127.7-1.17,2.87-.64,4.43,1.45,6.41q19.77,18.75,39.33,37.71c7.71,7.43,15.11,15.06,17.48,26.13C423.92,474.13,420.3,486.24,410.76,497.74Zm-58.57.12c-16-16.38-31-31.78-46.18-47-1-1-3.94-1.17-5.55-.57a115.89,115.89,0,0,1-53,7c-61.43-6.45-106.54-62-99.74-123.42,1-9,3.34-17.91,5.11-27.08-4.26-3.2-4.21-3.14-8.65.1-16.12,11.79-33.27,13.91-51.71,5.79-1.78-.78-5.22-.36-6.6.91-10.52,9.69-20.74,19.71-31,29.66Q37.1,360.42,19.44,377.69a5.83,5.83,0,0,0-1.3,3.83c-.07,12.5-.27,25,.06,37.48,1.09,41.31,39.69,78.85,80.9,78.85H352.19Zm-92.67-262c-40.78,2.35-73.75,18.09-93.85,55.48-4.3,8-8.56,16.21-11.36,24.81-4.59,14.08-5.25,28.73-3.21,43.46q5.52,39.68,35.05,66.66c18.62,17,40.75,26.23,65.8,28.17A98.42,98.42,0,0,0,301.13,446c3.13-1.44,4.55-1.23,6.73,1,15.72,16.08,31.71,31.91,47.27,48.14,14.32,14.93,38,15,54-1.07,12.43-12.52,12.43-37.07-.58-50.18-8.62-8.7-17.68-17-26.63-25.35-7.53-7-15.19-14-22.78-21-1.8-1.66-3.1-3.34-1.46-6,7.47-12.09,10.36-25.7,10.79-39.49,1.28-41.07-13.57-74.32-49.22-96.78-5.73-3.61-11.25-7.75-17.38-10.52C288.55,238.72,274.35,236,259.52,235.83Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-2" d="M497.86,172c-6.53-3.76-12.16-6.77-17.56-10.17-9-5.67-17.77-11.72-26.81-17.33-2.88-1.79-3.65-3.69-3.1-6.94,4.58-26.71-11.46-50.84-37.84-57.12-22.87-5.45-48.2,8.4-56.46,30.5-6.14,16.43-3.87,31.58,5.46,46.13,1,1.52,1.19,4.65.28,6.07-17.19,26.49-34.59,52.83-52,79.2a15.5,15.5,0,0,1-1,1.32c-56.27-22-103.21-9.71-141.89,36.58-3.32-.6-4.77-2-4.17-6.6,3.8-29.44-20.67-53.85-46.45-55.08-26.3-1.26-51,19.65-51.91,48.09-.2,6.11,1.87,12.37,3.45,18.42.71,2.75.79,4.58-1.39,6.64-14.27,13.5-28.43,27.11-42.66,40.64-1.43,1.36-3.09,2.47-5.26,4.18-.22-1.92-.47-3.12-.47-4.31,0-75.82-.19-151.63,0-227.44.13-42.12,25.82-75.2,65.34-85a60.46,60.46,0,0,1,14.33-1.62c105.8-.09,211.61-.92,317.4.35,42.2.5,74.36,31.38,81.23,71.67a101.41,101.41,0,0,1,1.34,16.32C497.94,127.81,497.86,149.13,497.86,172Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-3" d="M410.76,497.74c9.54-11.5,13.17-23.61,10.19-37.49-2.37-11.06-9.77-18.7-17.48-26.13q-19.61-18.92-39.33-37.71c-2.09-2-2.62-3.54-1.45-6.41,18.83-46.37,8.76-92.92-27.45-127.7-.59-.57-1.14-1.19-2-2.09,5.51-8.52,11-17,16.52-25.5,11.77-17.94,23.68-35.8,35.31-53.83,2.15-3.34,4.37-4.27,8.17-3.57,13,2.39,25.28.28,36.05-7.52,3.71-2.69,6.21-2.29,9.66,0,17.33,11.48,34.78,22.79,52.21,34.13,2.06,1.35,4.23,2.51,6.68,3.94V215c0,67.15-.22,134.31.11,201.46.12,25.88-11,46-30,62.12C452,492.23,433.08,498.79,410.76,497.74Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-3" d="M352.19,497.86H99.1c-41.21,0-79.81-37.54-80.9-78.85-.33-12.48-.13-25-.06-37.48a5.88,5.88,0,0,1,1.3-3.83q17.61-17.31,35.42-34.43c10.29-9.95,20.51-20,31-29.67,1.37-1.26,4.81-1.68,6.59-.9,18.44,8.12,35.59,6,51.72-5.79,4.43-3.24,4.39-3.3,8.64-.1-1.77,9.17-4.12,18.05-5.11,27.07-6.8,61.43,38.31,117,99.74,123.42a115.75,115.75,0,0,0,53-7c1.61-.6,4.51-.48,5.55.57C321.19,466.08,336.16,481.48,352.19,497.86Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-4" d="M259.52,235.83c14.83.18,29,2.89,42.38,8.92,6.13,2.77,11.65,6.9,17.38,10.51C354.93,277.72,369.79,311,368.5,352c-.43,13.8-3.32,27.41-10.79,39.5-1.64,2.66-.33,4.34,1.47,6,7.59,7,15.24,13.93,22.78,21,8.94,8.38,18,16.65,26.62,25.35,13,13.1,13,37.66.58,50.17-16,16.11-39.71,16-54,1.08-15.57-16.23-31.56-32.06-47.28-48.15-2.18-2.23-3.6-2.43-6.73-1A98.32,98.32,0,0,1,252,454.41c-25-1.94-47.17-11.18-65.8-28.17q-29.52-26.94-35-66.66c-2.05-14.73-1.39-29.38,3.2-43.46,2.81-8.6,7.06-16.8,11.37-24.81C185.77,253.92,218.74,238.18,259.52,235.83Zm74.75,109.92c0-41.77-33.56-74.92-75.46-75.17-43.6-.26-75,36.34-75.13,75.47-.15,42.12,33.35,75.3,75.65,75.23C301.1,421.21,334.32,387.73,334.27,345.75Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-1" d="M334.27,345.75c.05,42-33.17,75.46-74.94,75.53-42.3.07-75.8-33.11-75.65-75.23.15-39.13,31.53-75.73,75.13-75.47C300.71,270.83,334.23,304,334.27,345.75Zm-23.81-51.17C306.21,301,302,307,298.26,313.26a5.89,5.89,0,0,0,.08,5.24c7.05,10,9.43,21,8.85,33-1.21,25.15-26,50.24-57.27,44.86-27-4.64-44.41-28.79-41.21-54.87.19-1.58-.23-4.18-1.28-4.83-5.73-3.55-11.74-6.64-17.77-10-9.73,33.58,7.86,70.39,40,84.81A72.56,72.56,0,0,0,320.85,384C340.92,352,331.39,313.58,310.46,294.58ZM285.84,278.5c-24.8-13.2-67.88-1.62-82.65,22,5.81,3.19,11.73,6.18,17.36,9.64,2.33,1.43,3.62,1.13,5.61-.47,12.3-9.84,26.1-14,41.84-10.79,1.62.33,4.36-.28,5.21-1.45C277.65,291.33,281.66,284.88,285.84,278.5Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-2" d="M310.46,294.58c20.93,19,30.46,57.41,10.39,89.43a72.56,72.56,0,0,1-91.19,27.52c-32.14-14.42-49.73-51.23-40-84.81,6,3.32,12,6.41,17.77,10,1,.65,1.47,3.25,1.27,4.83-3.19,26.08,14.25,50.23,41.22,54.87,31.26,5.38,56.06-19.71,57.27-44.86.58-12-1.8-23.06-8.85-33a5.89,5.89,0,0,1-.08-5.24C302,307,306.21,301,310.46,294.58Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-2" d="M285.84,278.5c-4.18,6.38-8.19,12.83-12.63,19-.85,1.17-3.58,1.78-5.21,1.45-15.74-3.18-29.54.95-41.84,10.79-2,1.6-3.28,1.9-5.61.47-5.63-3.46-11.55-6.45-17.36-9.64C218,276.88,261,265.3,285.84,278.5Z" transform="translate(-1.03 -1.1)"/>
</svg>
</div>
]])
end
function addLogoDarkSvg()
return ([[
<div id='ntopng-logo'>
<svg
id="ntopng-logo-svg"
data-name="ntopng logo"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512.74 512.84"
height="56"
width="56">
<defs>
<style>
.cls-1{fill:none;}
.cls-2{fill:#333;}
.cls-2,.cls-3{fill-rule:evenodd;}
.cls-3{fill:#ee751b;}
</style>
</defs>
<title>ntopng logo</title>
<path class="cls-1" d="M1,513.94V1.1H513.78V513.94ZM497.86,172c0-22.84.08-44.16-.07-65.48a100.07,100.07,0,0,0-1.34-16.31c-6.87-40.3-39-71.17-81.23-71.68-105.79-1.27-211.6-.44-317.4-.35a60.46,60.46,0,0,0-14.33,1.62c-39.52,9.78-65.2,42.87-65.34,85-.24,75.82-.07,151.63,0,227.44,0,1.2.25,2.39.47,4.31,2.18-1.71,3.83-2.82,5.26-4.17,14.23-13.54,28.39-27.15,42.67-40.65,2.17-2.06,2.09-3.89,1.38-6.64-1.58-6.05-3.65-12.31-3.45-18.41.91-28.45,25.61-49.36,51.92-48.1,25.77,1.23,50.24,25.65,46.44,55.08-.59,4.64.85,6,4.18,6.6,38.67-46.29,85.62-58.59,141.88-36.58a15.05,15.05,0,0,0,1-1.31c17.37-26.37,34.78-52.72,52-79.21.92-1.42.69-4.55-.28-6.07-9.33-14.54-11.6-29.7-5.46-46.12,8.26-22.11,33.59-36,56.46-30.51,26.38,6.29,42.42,30.41,37.84,57.12-.55,3.25.22,5.15,3.1,6.94,9,5.62,17.81,11.66,26.81,17.34C485.7,165.2,491.33,168.21,497.86,172Zm-87.1,325.77c22.32,1.05,41.19-5.51,57.26-19.17,19-16.16,30.07-36.25,30-62.13-.33-67.15-.11-134.31-.11-201.46v-7.13c-2.44-1.43-4.62-2.59-6.68-3.93-17.43-11.35-34.88-22.65-52.21-34.14-3.45-2.28-6-2.68-9.66,0-10.77,7.8-23.06,9.91-36.05,7.52-3.8-.69-6,.23-8.17,3.57-11.63,18-23.54,35.89-35.31,53.83-5.55,8.46-11,17-16.52,25.5.85.9,1.39,1.52,2,2.09,36.21,34.78,46.28,81.33,27.45,127.7-1.17,2.87-.64,4.43,1.45,6.41q19.77,18.75,39.33,37.71c7.71,7.43,15.11,15.06,17.48,26.13C423.92,474.13,420.3,486.24,410.76,497.74Zm-58.57.12c-16-16.38-31-31.78-46.18-47-1-1-3.94-1.17-5.55-.57a115.89,115.89,0,0,1-53,7c-61.43-6.45-106.54-62-99.74-123.42,1-9,3.34-17.91,5.11-27.08-4.26-3.2-4.21-3.14-8.65.1-16.12,11.79-33.27,13.91-51.71,5.79-1.78-.78-5.22-.36-6.6.91-10.52,9.69-20.74,19.71-31,29.66Q37.1,360.42,19.44,377.69a5.83,5.83,0,0,0-1.3,3.83c-.07,12.5-.27,25,.06,37.48,1.09,41.31,39.69,78.85,80.9,78.85H352.19Zm-92.67-262c-40.78,2.35-73.75,18.09-93.85,55.48-4.3,8-8.56,16.21-11.36,24.81-4.59,14.08-5.25,28.73-3.21,43.46q5.52,39.68,35.05,66.66c18.62,17,40.75,26.23,65.8,28.17A98.42,98.42,0,0,0,301.13,446c3.13-1.44,4.55-1.23,6.73,1,15.72,16.08,31.71,31.91,47.27,48.14,14.32,14.93,38,15,54-1.07,12.43-12.52,12.43-37.07-.58-50.18-8.62-8.7-17.68-17-26.63-25.35-7.53-7-15.19-14-22.78-21-1.8-1.66-3.1-3.34-1.46-6,7.47-12.09,10.36-25.7,10.79-39.49,1.28-41.07-13.57-74.32-49.22-96.78-5.73-3.61-11.25-7.75-17.38-10.52C288.55,238.72,274.35,236,259.52,235.83Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-2" d="M497.86,172c-6.53-3.76-12.16-6.77-17.56-10.17-9-5.67-17.77-11.72-26.81-17.33-2.88-1.79-3.65-3.69-3.1-6.94,4.58-26.71-11.46-50.84-37.84-57.12-22.87-5.45-48.2,8.4-56.46,30.5-6.14,16.43-3.87,31.58,5.46,46.13,1,1.52,1.19,4.65.28,6.07-17.19,26.49-34.59,52.83-52,79.2a15.5,15.5,0,0,1-1,1.32c-56.27-22-103.21-9.71-141.89,36.58-3.32-.6-4.77-2-4.17-6.6,3.8-29.44-20.67-53.85-46.45-55.08-26.3-1.26-51,19.65-51.91,48.09-.2,6.11,1.87,12.37,3.45,18.42.71,2.75.79,4.58-1.39,6.64-14.27,13.5-28.43,27.11-42.66,40.64-1.43,1.36-3.09,2.47-5.26,4.18-.22-1.92-.47-3.12-.47-4.31,0-75.82-.19-151.63,0-227.44.13-42.12,25.82-75.2,65.34-85a60.46,60.46,0,0,1,14.33-1.62c105.8-.09,211.61-.92,317.4.35,42.2.5,74.36,31.38,81.23,71.67a101.41,101.41,0,0,1,1.34,16.32C497.94,127.81,497.86,149.13,497.86,172Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-2" d="M410.76,497.74c9.54-11.5,13.17-23.61,10.19-37.49-2.37-11.06-9.77-18.7-17.48-26.13q-19.61-18.92-39.33-37.71c-2.09-2-2.62-3.54-1.45-6.41,18.83-46.37,8.76-92.92-27.45-127.7-.59-.57-1.14-1.19-2-2.09,5.51-8.52,11-17,16.52-25.5,11.77-17.94,23.68-35.8,35.31-53.83,2.15-3.34,4.37-4.27,8.17-3.57,13,2.39,25.28.28,36.05-7.52,3.71-2.69,6.21-2.29,9.66,0,17.33,11.48,34.78,22.79,52.21,34.13,2.06,1.35,4.23,2.51,6.68,3.94V215c0,67.15-.22,134.31.11,201.46.12,25.88-11,46-30,62.12C452,492.23,433.08,498.79,410.76,497.74Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-2" d="M352.19,497.86H99.1c-41.21,0-79.81-37.54-80.9-78.85-.33-12.48-.13-25-.06-37.48a5.88,5.88,0,0,1,1.3-3.83q17.61-17.31,35.42-34.43c10.29-9.95,20.51-20,31-29.67,1.37-1.26,4.81-1.68,6.59-.9,18.44,8.12,35.59,6,51.72-5.79,4.43-3.24,4.39-3.3,8.64-.1-1.77,9.17-4.12,18.05-5.11,27.07-6.8,61.43,38.31,117,99.74,123.42a115.75,115.75,0,0,0,53-7c1.61-.6,4.51-.48,5.55.57C321.19,466.08,336.16,481.48,352.19,497.86Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-3" d="M259.52,235.83c14.83.18,29,2.89,42.38,8.92,6.13,2.77,11.65,6.9,17.38,10.51C354.93,277.72,369.79,311,368.5,352c-.43,13.8-3.32,27.41-10.79,39.5-1.64,2.66-.33,4.34,1.47,6,7.59,7,15.24,13.93,22.78,21,8.94,8.38,18,16.65,26.62,25.35,13,13.1,13,37.66.58,50.17-16,16.11-39.71,16-54,1.08-15.57-16.23-31.56-32.06-47.28-48.15-2.18-2.23-3.6-2.43-6.73-1A98.32,98.32,0,0,1,252,454.41c-25-1.94-47.17-11.18-65.8-28.17q-29.52-26.94-35-66.66c-2.05-14.73-1.39-29.38,3.2-43.46,2.81-8.6,7.06-16.8,11.37-24.81C185.77,253.92,218.74,238.18,259.52,235.83Zm74.75,109.92c0-41.77-33.56-74.92-75.46-75.17-43.6-.26-75,36.34-75.13,75.47-.15,42.12,33.35,75.3,75.65,75.23C301.1,421.21,334.32,387.73,334.27,345.75Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-1" d="M334.27,345.75c.05,42-33.17,75.46-74.94,75.53-42.3.07-75.8-33.11-75.65-75.23.15-39.13,31.53-75.73,75.13-75.47C300.71,270.83,334.23,304,334.27,345.75Zm-23.81-51.17C306.21,301,302,307,298.26,313.26a5.89,5.89,0,0,0,.08,5.24c7.05,10,9.43,21,8.85,33-1.21,25.15-26,50.24-57.27,44.86-27-4.64-44.41-28.79-41.21-54.87.19-1.58-.23-4.18-1.28-4.83-5.73-3.55-11.74-6.64-17.77-10-9.73,33.58,7.86,70.39,40,84.81A72.56,72.56,0,0,0,320.85,384C340.92,352,331.39,313.58,310.46,294.58ZM285.84,278.5c-24.8-13.2-67.88-1.62-82.65,22,5.81,3.19,11.73,6.18,17.36,9.64,2.33,1.43,3.62,1.13,5.61-.47,12.3-9.84,26.1-14,41.84-10.79,1.62.33,4.36-.28,5.21-1.45C277.65,291.33,281.66,284.88,285.84,278.5Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-2" d="M310.46,294.58c20.93,19,30.46,57.41,10.39,89.43a72.56,72.56,0,0,1-91.19,27.52c-32.14-14.42-49.73-51.23-40-84.81,6,3.32,12,6.41,17.77,10,1,.65,1.47,3.25,1.27,4.83-3.19,26.08,14.25,50.23,41.22,54.87,31.26,5.38,56.06-19.71,57.27-44.86.58-12-1.8-23.06-8.85-33a5.89,5.89,0,0,1-.08-5.24C302,307,306.21,301,310.46,294.58Z" transform="translate(-1.03 -1.1)"/>
<path class="cls-2" d="M285.84,278.5c-4.18,6.38-8.19,12.83-12.63,19-.85,1.17-3.58,1.78-5.21,1.45-15.74-3.18-29.54.95-41.84,10.79-2,1.6-3.28,1.9-5.61.47-5.63-3.46-11.55-6.45-17.36-9.64C218,276.88,261,265.3,285.84,278.5Z" transform="translate(-1.03 -1.1)"/>
</svg>
</div>
]])
end
function addLogoSvg()
return ([[
<div id='ntop-logo'>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg8"
version="1.1"
viewBox="0 0 13.758333 13.758334"
height="52"
width="52">
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"></dc:type>
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1">
<g
style="font-style:normal;font-weight:normal;font-size:16.9333px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ff7500;fill-opacity:1;stroke:none;stroke-width:0.264583"
id="text835"
aria-label="n">
<path
d="M 2.7739989,9.5828812 V 4.216811 q 0,-0.9839173 0.3224603,-1.4552054 0.3307285,-0.4795564 1.008722,-0.4795564 0.4051424,0 0.7193345,0.2149735 Q 5.1387078,2.7037281 5.378486,3.1336751 5.808433,2.662387 6.3706715,2.4474135 6.93291,2.2324399 7.7349267,2.2324399 q 1.5792286,0 2.4143183,0.9012352 0.835089,0.9012352 0.835089,2.6210235 v 3.8281826 q 0,0.9839178 -0.330728,1.4634738 -0.330729,0.479556 -1.0087222,0.479556 -0.6779934,0 -1.0087219,-0.479556 Q 8.3054333,10.566799 8.3054333,9.5828812 V 6.5649835 q 0,-1.1162088 -0.3389967,-1.5874969 -0.3307285,-0.4795563 -1.0996723,-0.4795563 -0.7276027,0 -1.0748677,0.4960927 -0.3472649,0.4878246 -0.3472649,1.5378876 v 3.0509706 q 0,0.9839178 -0.3307285,1.4634738 -0.3307286,0.479556 -1.008722,0.479556 -0.6779935,0 -1.008722,-0.479556 Q 2.7739989,10.566799 2.7739989,9.5828812 Z"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'VAGRounded BT';-inkscape-font-specification:'VAGRounded BT';fill:#ff7500;fill-opacity:1;stroke-width:0.264583"
id="path873"></path>
</g>
</g>
</svg>
</div>
]])
end
function addGauge(name, url, maxValue, width, height)
if(url ~= nil) then print('<A HREF="'..url..'">') end
print [[
<div class="progress">
<div id="]] print(name) print [[" class="progress-bar bg-warning"></div>
</div>
]]
if(url ~= nil) then print('</A>\n') end
end
function getCategoriesWithProtocols()
local protocol_categories = interface.getnDPICategories()
for k,v in pairsByKeys(protocol_categories) do
protocol_categories[k] = {id=v, protos=interface.getnDPIProtocols(tonumber(v)), count=0}
for proto,_ in pairs(protocol_categories[k].protos) do
protocol_categories[k].count = protocol_categories[k].count + 1
end
end
return protocol_categories
end
--
-- Members supported format
-- 192.168.1.10/32@10
-- 00:11:22:33:44:55
--
function isValidPoolMember(member)
if isEmptyString(member) then
return false
end
if isMacAddress(member) then
return true
end
-- vlan is mandatory here
local vlan_idx = string.find(member, "@")
if ((vlan_idx == nil) or (vlan_idx == 1)) then
return false
end
local other = string.sub(member, 1, vlan_idx-1)
local vlan = tonumber(string.sub(member, vlan_idx+1))
if (vlan == nil) or (vlan < 0) then
return false
end
-- prefix is mandatory here
local address, prefix = splitNetworkPrefix(other)
if prefix == nil then
return false
end
if isIPv4(address) and (tonumber(prefix) >= 0) and (tonumber(prefix) <= 32) then
return true
elseif isIPv6(address) and (tonumber(prefix) >= 0) and (tonumber(prefix) <= 128) then
return true
end
return false
end
function host2member(ip, vlan, prefix)
if prefix == nil then
if isIPv4(ip) then
prefix = 32
else
prefix = 128
end
end
return ip .. "/" .. tostring(prefix) .. "@" .. tostring(vlan)
end
function isLocal(host_ip)
host = interface.getHostInfo(host_ip)
if((host == nil) or (host['localhost'] ~= true)) then
return(false)
else
return(true)
end
end
-- Return the first 'howmany' hosts
function getTopInterfaceHosts(howmany, localHostsOnly)
hosts_stats = interface.getHostsInfo()
hosts_stats = hosts_stats["hosts"]
ret = {}
sortTable = {}
n = 0
for k,v in pairs(hosts_stats) do
if((not localHostsOnly) or ((v["localhost"] == true) and (v["ip"] ~= nil))) then
sortTable[v["bytes.sent"]+v["bytes.rcvd"]+n] = k
n = n +0.01
end
end
n = 0
for _v,k in pairsByKeys(sortTable, rev) do
if(n < howmany) then
ret[k] = hosts_stats[k]
n = n+1
else
break
end
end
return(ret)
end
-- Windows fixes for interfaces with "uncommon chars"
function purifyInterfaceName(interface_name)
-- io.write(debug.traceback().."\n")
interface_name = string.gsub(interface_name, "@", "_")
interface_name = string.gsub(interface_name, ":", "_")
interface_name = string.gsub(interface_name, "/", "_")
return(interface_name)
end
-- See datatype AggregationType in ntop_typedefs.h
function aggregation2String(value)
if(value == 0) then return("Client Name")
elseif(value == 1) then return("Server Name")
elseif(value == 2) then return("Domain Name")
elseif(value == 3) then return("Operating System")
elseif(value == 4) then return("Registrar Name")
else return(value)
end
end
-- #################################
-- Aggregates items below some edge
-- edge: minimum percentage value to create collision
-- min_col: minimum collision groups to aggregate
function aggregatePie(values, values_sum, edge, min_col)
local edge = edge or 0.09
min_col = min_col or 2
local aggr = {}
local other = i18n("other")
local below_edge = {}
-- Initial lookup
for k,v in pairs(values) do
if v / values_sum <= edge then
-- too small
below_edge[#below_edge + 1] = k
else
aggr[k] = v
end
end
-- Decide if to aggregate
for _,k in pairs(below_edge) do
if #below_edge >= min_col then
-- aggregate
aggr[other] = aggr[other] or 0
aggr[other] = aggr[other] + values[k]
else
-- do not aggregate
aggr[k] = values[k]
end
end
return aggr
end
-- #################################
function hostVisualization(ip, name, vlan)
if (ip ~= name) then
if isIPv6(ip) then
name = name.." [IPv6]"
end
else
if vlan ~= nil and tonumber(vlan) > 0 then
name = name.."@"..vlan
end
end
return name
end
-- #################################
-- This function actively resolves an host if there is not information about it.
-- NOTE: prefer the host2name on this function
function resolveAddress(hostinfo, allow_empty)
local alt_name = ip2label(hostinfo["host"])
if(not isEmptyString(alt_name) and (alt_name ~= hostinfo["host"])) then
-- The host label has priority
return(alt_name)
end
local hostname = ntop.resolveName(hostinfo["host"])
if isEmptyString(hostname) then
-- Not resolved
if allow_empty == true then
return hostname
else
-- this function will take care of formatting the IP
return hostinfo2label(hostinfo)
end
end
return hostVisualization(hostinfo["host"], hostname, hostinfo["vlan"])
end
-- #################################
function getIpUrl(ip)
if isIPv6(ip) then
-- https://www.ietf.org/rfc/rfc2732.txt
return "["..ip.."]"
end
return ip
end
-- #################################
function getApplicationIcon(name)
local icon = ""
if(name == nil) then name = "" end
if(findString(name, "Skype")) then icon = '<i class=\'fab fa-skype\'></i>'
elseif(findString(name, "Unknown")) then icon = '<i class=\'fas fa-question\'></i>'
elseif(findString(name, "Twitter")) then icon = '<i class=\'fab fa-twitter\'></i>'
elseif(findString(name, "DropBox")) then icon = '<i class=\'fab fa-dropbox\'></i>'
elseif(findString(name, "Spotify")) then icon = '<i class=\'fab fa-spotify\'></i>'
elseif(findString(name, "Apple")) then icon = '<i class=\'fab fa-apple\'></i>'
elseif(findString(name, "Google") or
findString(name, "Chrome")) then icon = '<i class=\'fab fa-google-plus-g\'></i>'
elseif(findString(name, "FaceBook")) then icon = '<i class=\'fab fa-facebook\'></i>'
elseif(findString(name, "Youtube")) then icon = '<i class=\'fab fa-youtube\'></i>'
elseif(findString(name, "thunderbird")) then icon = '<i class=\'fas fa-paper-plane\'></i>'
end
return(icon)
end
-- #################################
function getApplicationLabel(name)
local icon = getApplicationIcon(name)
name = name:gsub("^%l", string.upper)
return(icon.." "..name)
end
-- #################################
function getCategoryLabel(cat_name)
if isEmptyString(cat_name) then
return("")
end
local v = i18n("ndpi_categories." .. cat_name)
if v then
-- Localized string found
return(v)
end
cat_name = cat_name:gsub("^%l", string.upper)
return(cat_name)
end
-- ###########################################
function computeL7Stats(stats, show_breed, show_ndpi_category)
local _ifstats = {}
if(show_breed) then
local breed_stats = {}
for key, value in pairs(stats["ndpi"]) do
local b = stats["ndpi"][key]["breed"]
local traffic = stats["ndpi"][key]["bytes.sent"] + stats["ndpi"][key]["bytes.rcvd"]
if(breed_stats[b] == nil) then
breed_stats[b] = traffic
else
breed_stats[b] = breed_stats[b] + traffic
end
end
for key, value in pairs(breed_stats) do
_ifstats[key] = value
end
elseif(show_ndpi_category) then
local ndpi_category_stats = {}
for key, value in pairs(stats["ndpi_categories"]) do
key = getCategoryLabel(key)
local traffic = value["bytes"]
if(ndpi_category_stats[key] == nil) then
ndpi_category_stats[key] = traffic
else
ndpi_category_stats[key] = ndpi_category_stats[key] + traffic
end
end
for key, value in pairs(ndpi_category_stats) do
_ifstats[key] = value
end
else
-- Add ARP to stats
local arpBytes = 0
if(stats["eth"] ~= nil) then
arpBytes = stats["eth"]["ARP_bytes"]
if(arpBytes > 0) then
_ifstats["ARP"] = arpBytes
end
end
for key, value in pairs(stats["ndpi"]) do
local traffic = value["bytes.sent"] + value["bytes.rcvd"]
if(key == "Unknown") then
traffic = traffic - arpBytes
end
if(traffic > 0) then
if(show_breed) then
_ifstats[value["breed"]] = traffic
else
_ifstats[key] = traffic
end
end
end
end
return _ifstats
end
-- ###########################################
function getItemsNumber(n)
tot = 0
for k,v in pairs(n) do
--io.write(k.."\n")
tot = tot + 1
end
--io.write(tot.."\n")
return(tot)
end
-- ###########################################
function getHostCommaSeparatedList(p_hosts)
hosts = {}
hosts_size = 0
for i,host in pairs(split(p_hosts, ",")) do
hosts[i] = host
hosts_size = hosts_size + 1
end
return hosts,hosts_size
end
-- ##############################################
function splitNetworkPrefix(net)
local prefix = tonumber(net:match("/(.+)"))
local address = net:gsub("/.+","")
return address, prefix
end
-- ##############################################
function splitNetworkWithVLANPrefix(net_mask_vlan)
local vlan = tonumber(net_mask_vlan:match("@(.+)"))
local net_mask = net_mask_vlan:gsub("@.+","")
local prefix = tonumber(net_mask:match("/(.+)"))
local address = net_mask:gsub("/.+","")
return address, prefix, vlan
end
-- ##############################################
function splitProtocol(proto_string)
local parts = string.split(proto_string, "%.")
local app_proto
local master_proto
if parts == nil then
master_proto = proto_string
app_proto = nil
else
master_proto = parts[1]
app_proto = parts[2]
end
return master_proto, app_proto
end
-- ##############################################
function getHostAltNamesKey(host_key)
if(host_key == nil) then return(nil) end
return "ntopng.cache.host_labels."..host_key
end
function getHostAltName(host_info)
local host_key
if type(host_info) == "table" then
host_key = host_info["host"]
else
host_key = host_info
end
local alt_name = ntop.getCache(getHostAltNamesKey(host_key))
if isEmptyString(alt_name) and type(host_info) == "table" and host_info["vlan"] then
-- Check if there is an alias for the host@vlan
host_key = hostinfo2hostkey(host_info)
alt_name = ntop.getCache(getHostAltNamesKey(host_key))
end
return alt_name
end
function setHostAltName(host_info, alt_name)
local host_key
if type(host_info) == "table" then
-- Note: we are not using hostinfo2hostkey which includes the
-- vlan for backward compatibility, compatibility with
-- the backend, and compatibility with the vpn plugins
host_key = host_info["host"] -- hostinfo2hostkey(host_info)
else
host_key = host_info
end
ntop.setCache(getHostAltNamesKey(host_key), alt_name)
end
-- ##############################################
function getDhcpNameKey(ifid, mac)
return string.format("ntopng.dhcp.%d.cache.%s", ifid, mac)
end
-- ##############################################
local function add_vlan_to_alt_name(alt_name, host_info, show_vlan)
-- Adding the vlan if requested
if show_vlan then
if host_info["vlan"] ~= 0 then
alt_name = alt_name .. "@".. host_info["vlan"]
end
end
return alt_name
end
-- ##############################################
-- Retrieve an host label from an host_info. The minimum fields of
-- the host_info are "host" and "vlan", however a full JSON from Host::lua
-- is needed to provide an accurate result.
--
-- The following order is used to determine the label:
-- MAC label (LBD hosts only), IP label, MDNS/DHCP name from C, resolved IP
--
function hostinfo2label(host_info, show_vlan)
local alt_name = nil
local ip = host_info["ip"] or host_info["host"]
-- If local broadcast domain host and DHCP, try to get the label associated
-- to the MAC address
if host_info["mac"] and (host_info["broadcast_domain_host"] or host_info["dhcpHost"]) then
alt_name = getHostAltName(host_info["mac"])
if not isEmptyString(alt_name) then
-- Adding the vlan if requested
return add_vlan_to_alt_name(alt_name, host_info, show_vlan)
end
end
alt_name = getHostAltName(ip)
if not isEmptyString(alt_name) then
alt_name = add_vlan_to_alt_name(alt_name, host_info, show_vlan)
if isIPv6(ip) == true then
alt_name = alt_name .. " [IPv6]"
end
-- Adding the vlan if requested
return alt_name
end
-- Name info from C (e.g. DHCP name)
if not isEmptyString(host_info["name"]) then
return add_vlan_to_alt_name(host_info["name"], host_info, show_vlan)
end
-- Try to get the resolved name
local hostname = ntop.getResolvedName(ip)
local rname = hostVisualization(ip, hostname, host_info["vlan"])
if not isEmptyString(rname) then
return rname
end
-- Fallback: just the IP and VLAN
return(hostinfo2hostkey(host_info))
end
-- ##############################################
-- Just a convenience function for hostinfo2label with only IP and VLAN
function ip2label(ip, vlan)
return hostinfo2label({host = ip, vlan = (vlan or 0)})
end
-- ##############################################
function mac2label(mac)
local alt_name = getHostAltName(mac)
if not isEmptyString(alt_name) and (alt_name ~= mac) then
return(alt_name)
end
alt_name = ntop.getCache(getDhcpNameKey(interface.getId(), mac))
if not isEmptyString(alt_name) and (alt_name ~= mac) then
return(alt_name)
end
-- Fallback: just the MAC
return(mac)
end
-- ##############################################
-- Mac Addresses --
-- A function to give a useful device name
function getDeviceName(device_mac, skip_manufacturer)
local name = mac2label(device_mac)
if name == device_mac then
-- Not found, try with first host
local info = interface.getHostsInfo(false, nil, 1, 0, nil, nil, nil, tonumber(vlan), nil,
nil, device_mac)
if (info ~= nil) then
for x, host in pairs(info.hosts) do
if not isEmptyString(host.name) and host.name ~= host.ip and host.name ~= "NoIP" then
name = host.name
elseif host.ip ~= "0.0.0.0" then
name = ip2label(host.ip)
if name == host.ip then
name = nil
end
end
break
end
else
name = nil
end
end
if isEmptyString(name) then
if (not skip_manufacturer) then
name = get_symbolic_mac(device_mac, true)
else
-- last resort
name = device_mac
end
end
return name
end
local specialMACs = {
"01:00:0C",
"01:80:C2",
"01:00:5E",
"01:0C:CD",
"01:1B:19",
"FF:FF",
"33:33"
}
function isSpecialMac(mac)
for _,key in pairs(specialMACs) do
if(string.contains(mac, key)) then
return true
end
end
return false
end
-- ##############################################
-- @brief Implements the logic to decide whether to show or not the url for a given `host_info`
local function hostdetails_exists(host_info, hostdetails_params)
if not hostdetails_params then
hostdetails_params = {}
end
if hostdetails_params["page"] ~= "historical" and not hostdetails_params["ts_schema"] then
-- If the requested host_details.lua page is not the "historical" page
-- and if no ts_schema has been requested
-- then we check for host existance in memory, to make sure the page host_details.lua
-- won't bring to an empty page.
if not host_info["ipkey"] then
-- host_info hasn't been generated with Host::lua so we can try and
-- see if the host is active
local active_host = interface.getHostInfo(hostinfo2hostkey(host_info))
if not active_host then
return false
end
end
else
-- If the requested page is the "historical" page, or if a ts_schema has been requested,
-- then we assume page host_details.lua
-- exists if the timeseries are enabled and if the requested timeseries exists for the host
if not hostdetails_params["ts_schema"] then
-- Default schema for hosts
hostdetails_params["ts_schema"] = "host:traffic"
end
-- A ts_schema has been requested, let's see if it exists
local ts_utils = require("ts_utils_core")
local tags = table.merge(host_info, hostdetails_params)
if not tags["ifid"] then tags["ifid"] = interface.getId() end
-- If nIndex support is enabled, then there's no need to check for existence of the
-- schema: nIndex flows must be visible from the historical page even when there's no timeseries
-- associated
if not interfaceHasNindexSupport() and not ts_utils.exists(hostdetails_params["ts_schema"], tags) then
-- If here, the requested schema, along with its hostdetails_params doesn't exist
return false
end
end
return true
end
-- ##############################################
-- @brief Generates an host_details.lua url (if available)
-- @param host_info A lua table containing at least keys `host` and `vlan` or a full lua table generated with Host::lua
-- @param href_params A lua table containing params host_details.lua params, e.g., {page = "historical"}
-- @param href_check Performs existance checks on the link to avoid generating links to inactive hosts or hosts without timeseries
-- @return A string containing the url (if available) or an empty string when the url is not available
function hostinfo2detailsurl(host_info, href_params, href_check)
local res = ''
if not href_check or hostdetails_exists(host_info, href_params) then
local url_params = table.tconcat(href_params or {}, "=", "&")
if href_params and href_params.page == "alerts" then
-- Alerts page for the host is under the Alerts menu
res = string.format("%s/lua/alert_stats.lua?page=host&status=engaged&ip=%s",
ntop.getHttpPrefix(),
host_info["ip"])
else
res = string.format("%s/lua/host_details.lua?%s%s%s",
ntop.getHttpPrefix(),
hostinfo2url(host_info),
isEmptyString(url_params) and '' or '&',
url_params,
href_value)
end
end
return res
end
-- ##############################################
-- @brief Generates an host_details.lua a href link (if available), starting from an `host_info` structure
-- @param host_info A lua table containing at least keys `host` and `vlan` or a full lua table generated with Host::lua
-- @param href_params A lua table containing params host_details.lua params, e.g., {page = "historical"}
-- @param href_value A string containing the visible value shown between a href tags
-- @param href_tooltip A string containing a tooltip shown when hovering the mouse on the link
-- @param href_check Performs existance checks on the link to avoid generating links to inactive hosts or hosts without timeseries
-- @param href_only_with_ts True means that a HREF is geneated only of there are timeseries for this host
-- @return A string containing the a href link or a plain string without a href
function hostinfo2detailshref(host_info, href_params, href_value, href_tooltip, href_check, href_only_with_ts)
local show_href = false
if(href_only_with_ts == true) then
local detailLevel = ntop.getCache("ntopng.prefs.hosts_ts_creation")
if(detailLevel == "full") then
local l7 = ntop.getCache("ntopng.prefs.host_ndpi_timeseries_creation")
if(l7 ~= "none") then
show_href = true
end
end
else
show_href = true
end
if(show_href) then
local hostdetails_url = hostinfo2detailsurl(host_info, href_params, href_check)
if not isEmptyString(hostdetails_url) then
res = string.format("<a href='%s' data-toggle='tooltip' title='%s'>%s</a>",
hostdetails_url, href_tooltip or '', href_value or '')
else
res = href_value or ''
end
return res
else
return(href_value)
end
end
-- ##############################################
-- @brief Generates an host_details.lua a href link (if available), starting from an ip and a vlan
-- @param ip A string with a valid ip address
-- @param vlan A string or a number with a VLAN or nil when VLAN information is not available
-- @param href_params A lua table containing params host_details.lua params, e.g., {page = "historical"}
-- @param href_value A string containing the visible value shown between a href tags
-- @param href_tooltip A string containing a tooltip shown when hovering the mouse on the link
-- @param href_check Performs existance checks on the link to avoid generating links to inactive hosts or hosts without timeseries
-- @return A string containing the a href link or a plain string without a href
function ip2detailshref(ip, vlan, href_params, href_value, href_tooltip, href_check)
return hostinfo2detailshref({host = ip, vlan = tonumber(vlan) or 0}, href_params, href_value, href_tooltip, href_check)
end
-- ##############################################
-- Flow Utils --
function flowinfo2hostname(flow_info, host_type, alerts_view)
local name
local orig_name
if alerts_view and not hasNindexSupport() then
-- do not return resolved name as it will hide the IP address
return(flow_info[host_type..".ip"])
end
if(host_type == "srv") then
if flow_info["host_server_name"] ~= nil and flow_info["host_server_name"] ~= "" and flow_info["host_server_name"]:match("%w") then
-- remove possible ports from the name
return(flow_info["host_server_name"]:gsub(":%d+$", ""))
end
if(flow_info["protos.tls.certificate"] ~= nil and flow_info["protos.tls.certificate"] ~= "") then
return(flow_info["protos.tls.certificate"])
end
end
local hostinfo = {
host = flow_info[host_type..".ip"],
mac = flow_info[host_type..".mac"],
dhcpHost = flow_info[host_type..".dhcpHost"],
broadcast_domain_host = flow_info[host_type..".broadcast_domain_host"],
vlan = flow_info["vlan"],
}
return(hostinfo2label(hostinfo))
end
function flowinfo2process(process, host_info_to_url)
local fmt, proc_name, proc_user_name = '', '', ''
if process then
-- TODO: add links back once restored
if not isEmptyString(process["name"]) then
local full_clean_name = process["name"]:gsub("'",'')
local t = split(full_clean_name, "/")
clean_name = t[#t]
proc_name = string.format("<A HREF='%s/lua/process_details.lua?%s&pid_name=%s&pid=%u'><i class='fas fa-terminal'></i> %s</A>",
ntop.getHttpPrefix(),
host_info_to_url,
full_clean_name,
process["pid"],
clean_name)
end
-- if not isEmptyString(process["user_name"]) then
-- local clean_user_name = process["user_name"]:gsub("'", '')
-- proc_user_name = string.format("<A HREF='%s/lua/username_details.lua?%s&username=%s&uid=%u'><i class='fas fa-linux'></i> %s</A>",
-- ntop.getHttpPrefix(),
-- host_info_to_url,
-- clean_user_name,
-- process["uid"],
-- clean_user_name)
-- end
fmt = string.format("[%s]", table.concat({proc_user_name, proc_name}, ' '))
end
return fmt
end
-- ##############################################
function flowinfo2container(container)
local fmt, cont_name, pod_name = '', '', ''
if container then
cont_name = string.format("<A HREF='%s/lua/flows_stats.lua?container=%s'><i class='fas fa-ship'></i> %s</A>",
ntop.getHttpPrefix(),
container["id"], format_utils.formatContainer(container))
-- local formatted_pod = format_utils.formatPod(container)
-- if not isEmptyString(formatted_pod) then
-- pod_name = string.format("<A HREF='%s/lua/containers_stats.lua?pod=%s'><i class='fas fa-crosshairs'></i> %s</A>",
-- ntop.getHttpPrefix(),
-- formatted_pod,
-- formatted_pod)
-- end
fmt = string.format("[%s]", table.concat({cont_name, pod_name}, ''))
end
return fmt
end
-- ##############################################
function getLocalNetworkAliasKey()
return "ntopng.network_aliases"
end
-- ##############################################
function getLocalNetworkAlias(network)
local alias = ntop.getLocalNetworkAlias(network) or nil
if not alias then
alias = ntop.getHashCache(getLocalNetworkAliasKey(), network)
end
if not isEmptyString(alias) then
return alias
end
return network
end
-- ##############################################
function getFullLocalNetworkName(network)
local alias = getLocalNetworkAlias(network)
if alias ~= network then
return string.format("%s [%s]", alias, network)
end
return network
end
-- ##############################################
function setLocalNetworkAlias(network, alias)
if((network ~= alias) or isEmptyString(alias)) then
ntop.setHashCache(getLocalNetworkAliasKey(), network, alias)
else
ntop.delHashCache(getLocalNetworkAliasKey(), network)
end
end
-- ##############################################
function flow2hostinfo(host_info, host_type)
if host_type == "cli" then
return({host = host_info["cli.ip"], vlan = host_info["cli.vlan"]})
elseif host_type == "srv" then
return({host = host_info["srv.ip"], vlan = host_info["srv.vlan"]})
end
end
-- ##############################################
-- URL Util --
--
-- Split the host key (ip@vlan) creating a new lua table.
-- Example:
-- info = hostkey2hostinfo(key)
-- ip = info["host"]
-- vlan = info["vlan"]
--
function hostkey2hostinfo(key)
local host = {}
local info = split(key,"@")
if(info[1] ~= nil) then host["host"] = info[1] end
if(info[2] ~= nil) then
host["vlan"] = tonumber(info[2])
else
host["vlan"] = 0
end
return host
end
--
-- Analyze the host_info table and return the host key.
-- Example:
-- host_info = interface.getHostInfo("127.0.0.1",0)
-- key = hostinfo2hostkey(host_info)
--
function hostinfo2hostkey(host_info, host_type, show_vlan)
local rsp = ""
if(host_type == "cli") then
if(host_info["cli.ip"] ~= nil) then
rsp = rsp..host_info["cli.ip"]
end
elseif(host_type == "srv") then
if(host_info["srv.ip"] ~= nil) then
rsp = rsp..host_info["srv.ip"]
end
else
if(host_info["ip"] ~= nil) then
rsp = rsp..host_info["ip"]
elseif(host_info["mac"] ~= nil) then
rsp = rsp..host_info["mac"]
elseif(host_info["host"] ~= nil) then
rsp = rsp..host_info["host"]
elseif(host_info["name"] ~= nil) then
rsp = rsp..host_info["name"]
end
end
if((host_info["vlan"] ~= nil and host_info["vlan"] ~= 0) or show_vlan) then
rsp = rsp..'@'..tostring(host_info["vlan"] or 0)
end
if(debug_host) then traceError(TRACE_DEBUG,TRACE_CONSOLE,"HOST2URL => ".. rsp .. "\n") end
return rsp
end
function member2visual(member)
local info = hostkey2hostinfo(member)
local host = info.host
local hlen = string.len(host)
if string.ends(host, "/32") and isIPv4(string.sub(host, 1, hlen-3)) then
host = string.sub(host, 1, hlen-3)
elseif string.ends(host, "/128") and isIPv6(string.sub(host, 1, hlen-4)) then
host = string.sub(host, 1, hlen-4)
end
return hostinfo2hostkey({host=host, vlan=info.vlan})
end
--
-- Analyze the get_info and return a new table containing the url information about an host.
-- Example: url2host(_GET)
--
function url2hostinfo(get_info)
local host = {}
-- Catch when the host key is using as host url parameter
if((get_info["host"] ~= nil) and (string.find(get_info["host"],"@"))) then
get_info = hostkey2hostinfo(get_info["host"])
end
if(get_info["host"] ~= nil) then
host["host"] = get_info["host"]
if(debug_host) then traceError(TRACE_DEBUG,TRACE_CONSOLE,"URL2HOST => Host:"..get_info["host"].."\n") end
end
if(get_info["vlan"] ~= nil) then
host["vlan"] = tonumber(get_info["vlan"])
if(debug_host) then traceError(TRACE_DEBUG,TRACE_CONSOLE,"URL2HOST => Vlan:"..get_info["vlan"].."\n") end
else
host["vlan"] = 0
end
return host
end
--
-- Catch the main information about an host from the host_info table and return the corresponding url.
-- Example:
-- hostinfo2url(host_key), return an url based on the host_key
-- hostinfo2url(host[key]), return an url based on the host value
-- hostinfo2url(flow[key],"cli"), return an url based on the client host information in the flow table
-- hostinfo2url(flow[key],"srv"), return an url based on the server host information in the flow table
--
function hostinfo2url(host_info, host_type, novlan)
local rsp = ''
-- local version = 0
local version = 1
if(host_type == "cli") then
if(host_info["cli.ip"] ~= nil) then
rsp = rsp..'host='..hostinfo2hostkey(flow2hostinfo(host_info, "cli"))
end
elseif(host_type == "srv") then
if(host_info["srv.ip"] ~= nil) then
rsp = rsp..'host='..hostinfo2hostkey(flow2hostinfo(host_info, "srv"))
end
else
if((type(host_info) ~= "table")) then
host_info = hostkey2hostinfo(host_info)
end
if(host_info["host"] ~= nil) then
rsp = rsp..'host='..host_info["host"]
elseif(host_info["ip"] ~= nil) then
rsp = rsp..'host='..host_info["ip"]
elseif(host_info["mac"] ~= nil) then
rsp = rsp..'host='..host_info["mac"]
--Note: the host'name' is not supported (not accepted by lint)
--elseif(host_info["name"] ~= nil) then
-- rsp = rsp..'host='..host_info["name"]
end
end
if(novlan == nil) then
if((host_info["vlan"] ~= nil) and (tonumber(host_info["vlan"]) ~= 0)) then
if(version == 0) then
rsp = rsp..'&vlan='..tostring(host_info["vlan"])
elseif(version == 1) then
rsp = rsp..'@'..tostring(host_info["vlan"])
end
end
end
if(debug_host) then traceError(TRACE_DEBUG,TRACE_CONSOLE,"HOST2URL => ".. rsp .. "\n") end
return rsp
end
--
-- Catch the main information about an host from the host_info table and return the corresponding json.
-- Example:
-- hostinfo2json(host[key]), return a json string based on the host value
-- hostinfo2json(flow[key],"cli"), return a json string based on the client host information in the flow table
-- hostinfo2json(flow[key],"srv"), return a json string based on the server host information in the flow table
--
function hostinfo2json(host_info,host_type)
local rsp = ''
if(host_type == "cli") then
if(host_info["cli.ip"] ~= nil) then
rsp = rsp..'host: "'..host_info["cli.ip"]..'"'
end
elseif(host_type == "srv") then
if(host_info["srv.ip"] ~= nil) then
rsp = rsp..'host: "'..host_info["srv.ip"]..'"'
end
else
if((type(host_info) ~= "table") and (string.find(host_info,"@"))) then
host_info = hostkey2hostinfo(host_info)
end
if(host_info["host"] ~= nil) then
rsp = rsp..'host: "'..host_info["host"]..'"'
elseif(host_info["ip"] ~= nil) then
rsp = rsp..'host: "'..host_info["ip"]..'"'
elseif(host_info["name"] ~= nil) then
rsp = rsp..'host: "'..host_info["name"] ..'"'
elseif(host_info["mac"] ~= nil) then
rsp = rsp..'host: "'..host_info["mac"] ..'"'
end
end
if((host_info["vlan"] ~= nil) and (host_info["vlan"] ~= 0)) then
rsp = rsp..', vlan: "'..tostring(host_info["vlan"]) .. '"'
end
if(debug_host) then traceError(TRACE_DEBUG,TRACE_CONSOLE,"HOST2JSON => ".. rsp .. "\n") end
return rsp
end
--
-- Catch the main information about an host from the host_info table and return the corresponding jqueryid.
-- Example: host 192.168.1.254, vlan0 ==> 1921681254_0
function hostinfo2jqueryid(host_info,host_type)
local rsp = ''
if(host_type == "cli") then
if(host_info["cli.ip"] ~= nil) then
rsp = rsp..''..host_info["cli.ip"]
end
elseif(host_type == "srv") then
if(host_info["srv.ip"] ~= nil) then
rsp = rsp..''..host_info["srv.ip"]
end
else
if((type(host_info) ~= "table") and (string.find(host_info,"@"))) then
host_info = hostkey2hostinfo(host_info)
end
if(host_info["host"] ~= nil) then
rsp = rsp..''..host_info["host"]
elseif(host_info["ip"] ~= nil) then
rsp = rsp..''..host_info["ip"]
elseif(host_info["name"] ~= nil) then
rsp = rsp..''..host_info["name"]
elseif(host_info["mac"] ~= nil) then
rsp = rsp..''..host_info["mac"]
end
end
if((host_info["vlan"] ~= nil) and (host_info["vlan"] ~= 0)) then
rsp = rsp..'@'..tostring(host_info["vlan"])
end
rsp = string.gsub(rsp, "%.", "__")
rsp = string.gsub(rsp, "/", "___")
rsp = string.gsub(rsp, ":", "____")
if(debug_host) then traceError(TRACE_DEBUG,TRACE_CONSOLE,"HOST2KEY => ".. rsp .. "\n") end
return rsp
end
-- NOTE: on index based tables using #table is much more performant
function table.len(table)
local count = 0
if(table == nil) then return(0) end
for k,v in pairs(table) do
count = count + 1
end
return count
end
function table.slice(tbl, first, last, step)
local sliced = {}
for i = first or 1, last or #tbl, step or 1 do
sliced[#sliced+1] = tbl[i]
end
return sliced
end
-- ############################################
-- Redis Utils
-- ############################################
-- Inpur: General prefix (i.e ntopng.pref)
-- Output: User based prefix, if it exists
--
-- Examples:
-- With user: ntopng.pref.user_name
-- Without: ntopng.pref
function getRedisPrefix(str)
if not (isEmptyString(_SESSION["user"] )) then
-- Login enabled
return (str .. '.' .. _SESSION["user"])
else
-- Login disabled
return (str)
end
end
----- End of Redis Utils ------
function isPausedInterface(current_ifname)
if(not isEmptyString(_POST["toggle_local"])) then
return(_POST["toggle_local"] == "0")
end
state = ntop.getCache("ntopng.prefs.ifid_"..tostring(interface.name2id(current_ifname)).."_not_idle")
if(state == "0") then return true else return false end
end
function getThroughputType()
throughput_type = ntop.getCache("ntopng.prefs.thpt_content")
if(throughput_type == "") then
throughput_type = "bps"
end
return throughput_type
end
function processColor(proc)
if(proc == nil) then
return("")
elseif(proc["average_cpu_load"] < 33) then
return("<font color=green>"..proc["name"].."</font>")
elseif(proc["average_cpu_load"] < 66) then
return("<font color=orange>"..proc["name"].."</font>")
else
return("<font color=red>"..proc["name"].."</font>")
end
end
-- Table preferences
function getDefaultTableSort(table_type)
local table_key = getRedisPrefix("ntopng.sort.table")
local value = nil
if(table_type ~= nil) then
value = ntop.getHashCache(table_key, "sort_"..table_type)
end
if((value == nil) or (value == "")) then value = 'column_' end
return(value)
end
function getDefaultTableSortOrder(table_type, force_get)
local table_key = getRedisPrefix("ntopng.sort.table")
local value = nil
if(table_type ~= nil) then
value = ntop.getHashCache(table_key, "sort_order_"..table_type)
end
if((value == nil) or (value == "")) and (force_get ~= true) then value = 'desc' end
return(value)
end
function getDefaultTableSize()
table_key = getRedisPrefix("ntopng.sort.table")
value = ntop.getHashCache(table_key, "rows_number")
if((value == nil) or (value == "")) then value = 10 end
return(tonumber(value))
end
function tablePreferences(key, value, force_set)
table_key = getRedisPrefix("ntopng.sort.table")
if((value == nil) or (value == "")) and (force_set ~= true) then
-- Get preferences
return ntop.getHashCache(table_key, key)
else
-- Set preferences
ntop.setHashCache(table_key, key, value)
return(value)
end
end
function getInterfaceSpeed(ifid)
local ifname = getInterfaceName(ifid)
local ifspeed = ntop.getCache('ntopng.prefs.ifid_'..tostring(ifid)..'.speed')
if not isEmptyString(ifspeed) and tonumber(ifspeed) ~= nil then
ifspeed = tonumber(ifspeed)
else
ifspeed = interface.getMaxIfSpeed(ifid)
end
return ifspeed
end
function getInterfaceRefreshRate(ifid)
local key = "ntopng.prefs.ifid_"..tostring(ifid)..".refresh_rate"
local refreshrate = ntop.getCache(key)
if isEmptyString(refreshrate) or tonumber(refreshrate) == nil then
refreshrate = 3
else
refreshrate = tonumber(refreshrate)
end
return refreshrate
end
function setInterfaceRegreshRate(ifid, refreshrate)
local key = "ntopng.prefs.ifid_"..tostring(ifid)..".refresh_rate"
if isEmptyString(refreshrate) then
ntop.delCache(key)
else
ntop.setCache(key, tostring(refreshrate))
end
end
local function getCustomnDPIProtoCategoriesKey()
return "ntop.prefs.custom_nDPI_proto_categories"
end
function getCustomnDPIProtoCategories()
local ndpi_protos = interface.getnDPIProtocols()
local key = getCustomnDPIProtoCategoriesKey()
local res = {}
for _, app_id in pairs(ndpi_protos) do
local custom_category = ntop.getHashCache(key, tostring(app_id))
if not isEmptyString(custom_category) then
res[tonumber(app_id)] = tonumber(custom_category)
end
end
return res
end
function setCustomnDPIProtoCategory(app_id, new_cat_id)
ntop.setnDPIProtoCategory(app_id, new_cat_id)
local key = getCustomnDPIProtoCategoriesKey(ifid)
-- NOTE: when the ndpi struct changes, the custom associations are
-- reloaded by Ntop::loadProtocolsAssociations
ntop.setHashCache(key, tostring(app_id), tostring(new_cat_id));
end
-- "Some Very Long String" -> "Some Ver...g String"
function shortenCollapse(s, max_len)
local replacement = "..."
local r_len = string.len(replacement)
local s_len = string.len(s)
if max_len == nil then
max_len = ntop.getPref("ntopng.prefs.max_ui_strlen")
max_len = tonumber(max_len)
if(max_len == nil) then max_len = 24 end
end
if max_len <= r_len then
return replacement
end
if s_len > max_len then
local half = math.floor((max_len-r_len) / 2)
return string.sub(s, 1, half) .. replacement .. string.sub(s, s_len-half+1)
end
return s
end
-- ##############################################
function getHumanReadableInterfaceName(interface_name)
local interface_id = nil
if(interface_name == "__system__") then
return(i18n("system"))
elseif tonumber(interface_name) ~= nil then
-- convert ID to name
interface_id = tonumber(interface_name)
interface_name = getInterfaceName(interface_name)
else
-- Parameter is a string, let's take it's id first
interface_id = getInterfaceId(interface_name)
-- and then get the name
interface_name = getInterfaceName(interface_id)
end
local key = 'ntopng.prefs.ifid_'..tostring(interface_id)..'.name'
local custom_name = ntop.getCache(key)
if not isEmptyString(custom_name) then
return(shortenCollapse(custom_name))
end
return interface_name
end
-- ##############################################
function unescapeHTML(s)
local unesc = function (h)
local res = string.char(tonumber(h, 16))
return res
end
-- s = string.gsub(s, "+", " ")
s = string.gsub(s, "%%(%x%x)", unesc)
return s
end
-- ##############################################
function unescapeHttpHost(host)
if isEmptyString(host) then
return(host)
end
return string.gsub(string.gsub(host, "http:__", "http://"), "https:__", "https://")
end
-- ##############################################
function harvestUnusedDir(path, min_epoch)
local files = ntop.readdir(path)
-- print("Reading "..path.."<br>\n")
for k,v in pairs(files) do
if(v ~= nil) then
local p = os_utils.fixPath(path .. "/" .. v)
if(ntop.isdir(p)) then
harvestUnusedDir(p, min_epoch)
else
local when = ntop.fileLastChange(path)
if((when ~= -1) and (when < min_epoch)) then
os.remove(p)
end
end
end
end
end
-- ##############################################
function harvestJSONTopTalkers(days)
local when = os.time() - 86400 * days
ifnames = interface.getIfNames()
for _,ifname in pairs(ifnames) do
interface.select(ifname)
local _ifstats = interface.getStats()
local dirs = ntop.getDirs()
local basedir = os_utils.fixPath(dirs.workingdir .. "/" .. _ifstats.id)
harvestUnusedDir(os_utils.fixPath(basedir .. "/top_talkers"), when)
harvestUnusedDir(os_utils.fixPath(basedir .. "/flows"), when)
end
end
-- ##############################################
function haveAdminPrivileges(isJsonResponse)
if (isAdministrator()) then
return(true)
end
local isJson = isJsonResponse or false
if (isJson) then
local json = require("dkjson")
sendHTTPContentTypeHeader('application/json')
print(json.encode({}))
else
local page_utils = require("page_utils")
page_utils.print_header()
dofile(dirs.installdir .. "/scripts/lua/inc/menu.lua")
print("<div class=\"alert alert-danger\"><img src=".. ntop.getHttpPrefix() .. "/img/warning.png> Access forbidden</div>")
end
return(false)
end
-- ##############################################
function getKeysSortedByValue(tbl, sortFunction)
local keys = {}
for key in pairs(tbl) do
table.insert(keys, key)
end
table.sort(keys, function(a, b)
return sortFunction(tbl[a], tbl[b])
end)
return keys
end
function getKeys(t, col)
local keys = {}
for k,v in pairs(t) do keys[tonumber(v[col])] = k end
return keys
end
-- ##############################################
function formatBreed(breed)
if(breed == "Safe") then
return("<i class='fas fa-lock' alt='Safe Protocol'></i>")
elseif(breed == "Acceptable") then
return("<i class='fas fa-thumbs-up' alt='Acceptable Protocol'></i>")
elseif(breed == "Fun") then
return("<i class='fas fa-smile' alt='Fun Protocol'></i>")
elseif(breed == "Unsafe") then
return("<i class='fas fa-thumbs-down' style='color: red'></i>")
elseif(breed == "Dangerous") then
return("<i class='fas fa-exclamation-triangle'></i>")
else
return("")
end
end
function getFlag(country)
if((country == nil) or (country == "")) then
return("")
else
return(" <a href='" .. ntop.getHttpPrefix() .. "/lua/hosts_stats.lua?country=".. country .."'><img src='".. ntop.getHttpPrefix() .. "/img/blank.gif' class='flag flag-".. string.lower(country) .."'></a> ")
end
end
-- GENERIC UTILS
-- split
function split(s, delimiter)
result = {};
if(s ~= nil) then
if delimiter == nil then
-- No delimiter, split all characters
for match in s:gmatch"." do
table.insert(result, match);
end
else
-- Split by delimiter
for match in (s..delimiter):gmatch("(.-)"..delimiter) do
table.insert(result, match);
end
end
end
return result;
end
-- startswith
function startswith(s, char)
return string.sub(s, 1, string.len(s)) == char
end
-- strsplit
function strsplit(s, delimiter)
result = {};
for match in (s..delimiter):gmatch("(.-)"..delimiter) do
if(match ~= "") then result[match] = true end
end
return result;
end
-- isempty
function isempty(array)
local count = 0
for _,__ in pairs(array) do
count = count + 1
end
return (count == 0)
end
-- isin
function isin(s, array)
if (s == nil or s == "" or array == nil or isempty(array)) then return false end
for _, v in pairs(array) do
if (s == v) then return true end
end
return false
end
-- hasKey
function hasKey(key, theTable)
if((theTable == nil) or (theTable[key] == nil)) then
return(false)
else
return(true)
end
end
function getPasswordInputPattern()
-- maximum len must be kept in sync with MAX_PASSWORD_LEN
return [[^[\w\$\\!\/\(\)= \?\^\*@_\-\u0000-\u0019\u0021-\u00ff]{5,31}$]]
end
-- NOTE: keep in sync with validateLicense()
function getLicensePattern()
return [[^[a-zA-Z0-9\+/=]+$]]
end
function getIPv4Pattern()
return "^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$"
end
function getACLPattern()
local ipv4 = "(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])"
local netmask = "(\\/([0-9]|[1-2][0-9]|3[0-2]))"
local cidr = ipv4..netmask
local yesorno_cidr = "[\\+\\-]"..cidr
return "^"..yesorno_cidr.."(,"..yesorno_cidr..")*$"
end
function getMacPattern()
return "^([0-9a-fA-F][0-9a-fA-F]:){5}[0-9a-fA-F]{2}$"
end
function getURLPattern()
return "^https?://.+$"
end
-- get_mac_classification
function get_mac_classification(m, extended_name)
local short_extended = ntop.getMacManufacturer(m) or {}
if extended_name then
return short_extended.extended or short_extended.short or m
else
return short_extended.short or m
end
return m
end
local magic_macs = {
["00:00:00:00:00:00"] = "",
["FF:FF:FF:FF:FF:FF"] = "Broadcast",
["01:00:0C:CC:CC:CC"] = "CDP",
["01:00:0C:CC:CC:CD"] = "CiscoSTP",
["01:80:C2:00:00:00"] = "STP",
["01:80:C2:00:00:00"] = "LLDP",
["01:80:C2:00:00:03"] = "LLDP",
["01:80:C2:00:00:0E"] = "LLDP",
["01:80:C2:00:00:08"] = "STP",
["01:1B:19:00:00:00"] = "PTP",
["01:80:C2:00:00:0E"] = "PTP"
}
local magic_short_macs = {
["01:00:5E"] = "IPv4mcast",
["33:33:"] = "IPv6mcast"
}
function macInfoWithSymbName(mac, name)
return(' <A HREF="' .. ntop.getHttpPrefix() .. '/lua/mac_details.lua?host='.. mac ..'">'..name..'</A> ')
end
function macInfo(mac)
return(' <A HREF="' .. ntop.getHttpPrefix() .. '/lua/mac_details.lua?host='.. mac ..'">'..mac..'</A> ')
end
-- get_symbolic_mac
function get_symbolic_mac(mac_address, no_href, add_extra_info)
if(magic_macs[mac_address] ~= nil) then
return(magic_macs[mac_address])
else
local m = string.sub(mac_address, 1, 8)
local t = string.sub(mac_address, 10, 17)
if(magic_short_macs[m] ~= nil) then
if(add_extra_info == true) then
return(magic_short_macs[m].."_"..t.." ("..macInfo(mac_address)..")")
else
if no_href then
return(magic_short_macs[m].."_"..t)
else
return(macInfoWithSymbName(mac_address, magic_short_macs[m].."_"..t))
end
end
else
local s = get_mac_classification(m)
if(m == s) then
return '<a href="' .. ntop.getHttpPrefix() .. '/lua/mac_details.lua?host='..mac_address..'">' .. get_mac_classification(m) .. ":" .. t .. '</a>'
else
if(add_extra_info == true) then
return(get_mac_classification(m).."_"..t.." ("..macInfo(mac_address)..")")
else
return(get_mac_classification(m).."_"..t)
end
end
end
end
end
function get_mac_url(mac)
local m = get_symbolic_mac(mac, true)
if isEmptyString(m) then
return ""
end
local url = ntop.getHttpPrefix() .."/lua/mac_details.lua?host="..mac
return string.format('[ <a href=\"%s\">%s</a> ]', url, m)
end
function get_manufacturer_mac(mac_address)
local m = string.sub(mac_address, 1, 8)
local ret = get_mac_classification(m, true --[[ extended name --]])
if(ret == m) then ret = "n/a" end
if ret and ret ~= "" then
ret = ret:gsub("'"," ")
end
return ret or "n/a"
end
-- getservbyport
function getservbyport(port_num, proto)
if(proto == nil) then proto = "TCP" end
port_num = tonumber(port_num)
proto = string.lower(proto)
-- io.write(port_num.."@"..proto.."\n")
return(ntop.getservbyport(port_num, proto))
end
function intToIPv4(num)
return(math.floor(num / 2^24).. "." ..math.floor((num % 2^24) / 2^16).. "." ..math.floor((num % 2^16) / 2^8).. "." ..num % 2^8)
end
function getFlowMaxRate(cli_max_rate, srv_max_rate)
cli_max_rate = tonumber(cli_max_rate)
srv_max_rate = tonumber(srv_max_rate)
if((cli_max_rate == 0) or (srv_max_rate == 0)) then
max_rate = 0
elseif((cli_max_rate == -1) and (srv_max_rate > 0)) then
max_rate = srv_max_rate
elseif((cli_max_rate > 0) and (srv_max_rate == -1)) then
max_rate = cli_max_rate
else
max_rate = math.min(cli_max_rate, srv_max_rate)
end
return(max_rate)
end
-- ###############################################
-- removes trailing/leading spaces
function trimString(s)
return (s:gsub("^%s*(.-)%s*$", "%1"))
end
-- ###############################################
-- removes all spaces
function trimSpace(what)
if(what == nil) then return("") end
return(string.gsub(string.gsub(what, "%s+", ""), "+%s", ""))
end
-- ###############################################
-- TODO: improve this function
function jsonencode(what)
what = string.gsub(what, '"', "'")
-- everything but all ASCII characters from the space to the tilde
what = string.gsub(what, "[^ -~]", " ")
-- cleanup line feeds and carriage returns
what = string.gsub(what, "\n", " ")
what = string.gsub(what, "\r", " ")
-- escape all the remaining backslashes
what = string.gsub(what, "\\", "\\\\")
-- max 1 sequential whitespace
what = string.gsub(what, " +"," ")
return(what)
end
-- ###############################################
function formatWebSite(site)
return("<A target=\"_blank\" HREF=\"http://"..site.."\">"..site.."</A> <i class=\"fas fa-external-link-alt\"></i></th>")
end
-- ###############################################
function formatElephantAlertType(flowalert_info)
local threshold = ""
local res = ""
if not flowalert_info then
return i18n("flow_details.elephant_flow")
end
local l2r_bytes = bytesToSize(flowalert_info["l2r_bytes"])
if flowalert_info["l2r_bytes"] > flowalert_info["l2r_threshold"] then
l2r_bytes = l2r_bytes .." > "..bytesToSize(flowalert_info["l2r_threshold"])
end
local r2l_bytes = bytesToSize(flowalert_info["r2l_bytes"])
if flowalert_info["r2l_bytes"] > flowalert_info["r2l_threshold"] then
r2l_bytes = r2l_bytes .. " > "..bytesToSize(flowalert_info["r2l_threshold"])
end
res = i18n("flow_details.elephant_flow")
res = string.format("%s<sup><i class='fas fa-info-circle' aria-hidden='true' title='"..i18n("flow_details.elephant_flow_descr").."'></i></sup>", res)
res = string.format("%s %s", res, i18n("flow_details.elephant_exceeded", {l2r = l2r_bytes, r2l = r2l_bytes}))
return res
end
-- ###############################################
-- prints purged information for hosts / flows
function purgedErrorString()
local info = ntop.getInfo(false)
return i18n("purged_error_message",{url=ntop.getHttpPrefix()..'/lua/admin/prefs.lua?tab=in_memory', product=info["product"]})
end
-- print TCP flags
function printTCPFlags(flags)
if(hasbit(flags,0x01)) then print('<span class="badge badge-info">FIN</span> ') end
if(hasbit(flags,0x02)) then print('<span class="badge badge-info">SYN</span> ') end
if(hasbit(flags,0x04)) then print('<span class="badge badge-danger">RST</span> ') end
if(hasbit(flags,0x08)) then print('<span class="badge badge-info">PUSH</span> ') end
if(hasbit(flags,0x10)) then print('<span class="badge badge-info">ACK</span> ') end
if(hasbit(flags,0x20)) then print('<span class="badge badge-info">URG</span> ') end
if(hasbit(flags,0x40)) then print('<span class="badge badge-info">ECE</span> ') end
if(hasbit(flags,0x80)) then print('<span class="badge badge-info">CWR</span> ') end
end
-- convert the integer carrying TCP flags in a more convenient lua table
function TCPFlags2table(flags)
local res = {
["FIN"] = 0, ["SYN"] = 0, ["RST"] = 0,
["PSH"] = 0, ["ACK"] = 0, ["URG"] = 0,
["ECE"] = 0, ["CWR"] = 0,
}
if(hasbit(flags,0x01)) then res["FIN"] = 1 end
if(hasbit(flags,0x02)) then res["SYN"] = 1 end
if(hasbit(flags,0x04)) then res["RST"] = 1 end
if(hasbit(flags,0x08)) then res["PSH"] = 1 end
if(hasbit(flags,0x10)) then res["ACK"] = 1 end
if(hasbit(flags,0x20)) then res["URG"] = 1 end
if(hasbit(flags,0x40)) then res["ECE"] = 1 end
if(hasbit(flags,0x80)) then res["CWR"] = 1 end
return res
end
-- ##########################################
function historicalProtoHostHref(ifId, host, l4_proto, ndpi_proto_id, info)
if ntop.isPro() and ntop.getPrefs().is_dump_flows_to_mysql_enabled == true then
local hist_url = ntop.getHttpPrefix().."/lua/pro/db_explorer.lua?search=true&ifid="..ifId
local now = os.time()
local ago1h = now - 3600
hist_url = hist_url.."&epoch_end="..tostring(now)
if((host ~= nil) and (host ~= "")) then hist_url = hist_url.."&"..hostinfo2url(host) end
if((l4_proto ~= nil) and (l4_proto ~= "")) then
hist_url = hist_url.."&l4proto="..l4_proto
end
if((ndpi_proto_id ~= nil) and (ndpi_proto_id ~= "")) then hist_url = hist_url.."&protocol="..ndpi_proto_id end
if((info ~= nil) and (info ~= "")) then hist_url = hist_url.."&info="..info end
print('&nbsp;')
-- print('<span class="badge badge-info">')
print('<a href="'..hist_url..'&epoch_begin='..tostring(ago1h)..'" title="'..i18n("db_explorer.last_hour_flows")..'"><i class="fas fa-history fa-lg"></i></a>')
-- print('</span>')
end
end
-- #############################################
-- Add here the icons you guess based on the Mac address
-- TODO move to discovery stuff
local guess_icon_keys = {
["dell inc."] = "fas fa-desktop",
["vmware, inc."] = "fas fa-desktop",
["xensource, inc."] = "fas fa-desktop",
["lanner electronics, inc."] = "fas fa-desktop",
["nexcom international co., ltd."] = "fas fa-desktop",
["apple, inc."] = "fab fa-apple",
["cisco systems, inc"] = "fas fa-arrows-alt",
["juniper networks"] = "fas fa-arrows-alt",
["brocade communications systems, inc."] = "fas fa-arrows-alt",
["force10 networks, inc."] = "fas fa-arrows-alt",
["huawei technologies co.,ltd"] = "fas fa-arrows-alt",
["alcatel-lucent ipd"] = "fas fa-arrows-alt",
["arista networks, inc."] = "fas fa-arrows-alt",
["3com corporation"] = "fas fa-arrows-alt",
["routerboard.com"] = "fas fa-arrows-alt",
["extreme networks"] = "fas fa-arrows-alt",
["xerox corporation"] = "fas fa-print"
}
function guessHostIcon(key)
local m = string.lower(get_manufacturer_mac(key))
local icon = guess_icon_keys[m]
if((icon ~= nil) and (icon ~= "")) then
return(" <i class='"..icon.." fa-lg'></i>")
else
return ""
end
end
-- ####################################################
-- Functions to set/get a device type of user choice
local function getCustomDeviceKey(mac)
return "ntopng.prefs.device_types." .. string.upper(mac)
end
function getCustomDeviceType(mac)
return tonumber(ntop.getPref(getCustomDeviceKey(mac)))
end
function setCustomDeviceType(mac, device_type)
ntop.setPref(getCustomDeviceKey(mac), tostring(device_type))
end
-- ####################################################
function tableToJsObject(lua_table)
local json = require("dkjson")
return json.encode(lua_table, nil)
end
-- ####################################################
-- @brief The difference, in seconds, between the local time of this instance and GMT
local server_timezone_diff_seconds
-- @brief Compute and return the difference, in seconds, between the local time of this instance and GMT
-- @return A positive or negative number corresponding to the seconds between local time and GMT
local function get_server_timezone_diff_seconds()
if not server_timezone_diff_seconds then
local tmp_time = os.time()
local d1 = os.date("*t", tmp_time)
local d2 = os.date("!*t", tmp_time)
-- Forcefully set isdst to false otherwise difference won't work during DST
d1.isdst = false
-- Use a minus to have the difference between local time and GMT, rather than between GMT and loca ltime
server_timezone_diff_seconds = -os.difftime(os.time(d1), os.time(d2))
end
return server_timezone_diff_seconds
end
-- ####################################################
-- @brief Get the frontend timezone offset in seconds
-- @return The offset of the frontend timezone
function getFrontendTzSeconds()
local frontend_tz_offset = nil
if _COOKIE and _COOKIE.tzoffset then
-- The timezone offset can be passed from the client as a cookie.
-- This allows to format the dates in the frontend timezone.
frontend_tz_offset = tonumber(_COOKIE.tzoffset)
end
if frontend_tz_offset == nil then
-- If timezone is not available in the client _COOKIE,
-- server timezone is used as fallback
return -get_server_timezone_diff_seconds()
end
return frontend_tz_offset
end
-- ####################################################
-- @brief Converts a datetime string into an epoch, adjusted with the client time
function makeTimeStamp(d)
local pattern = "(%d+)%/(%d+)%/(%d+) (%d+):(%d+):(%d+)"
local day, month, year, hour, minute, seconds = string.match(d, pattern);
-- Get the epoch out of d. The epoch gets adjusted by os.time in the server timezone, that is, in
-- the timezone of this running ntopng instance
-- See https://www.lua.org/pil/22.1.html
local server_epoch = os.time({year = year, month = month, day = day, hour = hour, min = minute, sec = seconds});
-- Convert the server_epoch into a gmt_epoch which is adjusted to GMT
local gmt_epoch = server_epoch + get_server_timezone_diff_seconds()
-- Finally, compute a client_epoch by adding the seconds of getFrontendTzSeconds() to the GMT epoch just computed
local client_epoch = gmt_epoch + getFrontendTzSeconds()
-- Now we can compute the deltas to know the extact number of seconds between the server and the client timezone
local server_to_gmt_delta = gmt_epoch - server_epoch
local gmt_to_client_delta = client_epoch - gmt_epoch
local server_to_client_delta = client_epoch - server_epoch
-- Make sure everything is OK...
assert(server_to_client_delta == server_to_gmt_delta + gmt_to_client_delta)
-- tprint({
-- server_ts = server_epoch,
-- gmt_ts = gmt_epoch,
-- server_to_gmt_delta = (server_to_gmt_delta) / 60 / 60,
-- gmt_to_client_delta = (gmt_to_client_delta) / 60 / 60,
-- server_to_client_delta = (server_to_client_delta) / 60 / 60
-- })
-- Return the epoch in the client timezone
return string.format("%u", server_epoch - server_to_client_delta)
end
-- ###########################################
-- Merges table a and table b into a new table. If some elements are presents in
-- both a and b, b elements will have precedence.
-- NOTE: this does *not* perform a deep merge. Only first level is merged.
function table.merge(a, b)
local merged = {}
a = a or {}
b = b or {}
if((a[1] ~= nil) and (b[1] ~= nil)) then
-- index based tables
for _, t in ipairs({a, b}) do
for _,v in pairs(t) do
merged[#merged + 1] = v
end
end
else
-- key based tables
for _, t in ipairs({a, b}) do
for k,v in pairs(t) do
merged[k] = v
end
end
end
return merged
end
-- Performs a deep copy of the table.
function table.clone(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[table.clone(orig_key)] = table.clone(orig_value)
end
setmetatable(copy, table.clone(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
-- From http://lua-users.org/lists/lua-l/2014-09/msg00421.html
-- Returns true if tables are equal
function table.compare(t1, t2, ignore_mt)
local ty1 = type(t1)
local ty2 = type(t2)
if ty1 ~= ty2 then return false end
if ty1 ~= 'table' and ty2 ~= 'table' then return t1 == t2 end
local mt = getmetatable(t1)
if not ignore_mt and mt and mt.__eq then return t1 == t2 end
for k1,v1 in pairs(t1) do
local v2 = t2[k1]
if v2 == nil or not table.compare(v1, v2) then return false end
end
for k2,v2 in pairs(t2) do
local v1 = t1[k2]
if v1 == nil or not table.compare(v1, v2) then return false end
end
return true
end
function toboolean(s)
if((s == "true") or (s == true)) then
return true
elseif((s == "false") or (s == false)) then
return false
else
return nil
end
end
--
-- Find the highest divisor which divides input value.
-- val_idx can be used to index divisors values.
-- Returns the highest_idx
--
function highestDivisor(divisors, value, val_idx, iterator_fn)
local highest_idx = nil
local highest_val = nil
iterator_fn = iterator_fn or ipairs
for i, v in iterator_fn(divisors) do
local cmp_v
if val_idx ~= nil then
v = v[val_idx]
end
if((highest_val == nil) or ((v > highest_val) and (value % v == 0))) then
highest_val = v
highest_idx = i
end
end
return highest_idx
end
-- ###########################################
-- Note: the base unit is Kbit/s here
FMT_TO_DATA_RATES_KBPS = {
["k"] = {label="kbit/s", value=1},
["m"] = {label="Mbit/s", value=1000},
["g"] = {label="Gbit/s", value=1000*1000},
}
FMT_TO_DATA_BYTES = {
["b"] = {label="B", value=1},
["k"] = {label="KB", value=1024},
["m"] = {label="MB", value=1024*1024},
["g"] = {label="GB", value=1024*1024*1024},
}
FMT_TO_DATA_TIME = {
["s"] = {label=i18n("metrics.secs"), value=1},
["m"] = {label=i18n("metrics.mins"), value=60},
["h"] = {label=i18n("metrics.hours"), value=3600},
["d"] = {label=i18n("metrics.days"), value=3600*24},
}
-- ###########################################
-- Note: use data-min and data-max to setup ranges
function makeResolutionButtons(fmt_to_data, ctrl_id, fmt, value, extra, max_val)
local extra = extra or {}
local html_lines = {}
local divisors = {}
-- fill in divisors
if tonumber(value) ~= nil then
-- foreach character in format
string.gsub(fmt, ".", function(k)
local v = fmt_to_data[k]
if v ~= nil then
divisors[#divisors + 1] = {k=k, v=v.value}
end
end)
end
local selected = nil
if tonumber(value) ~= 0 then
selected = highestDivisor(divisors, value, "v")
end
if selected ~= nil then
selected = divisors[selected].k
else
selected = string.sub(fmt, 1, 1)
end
local style = table.merge({display="flex"}, extra.style or {})
html_lines[#html_lines+1] = [[<div class="btn-group btn-group-toggle ]] .. table.concat(extra.classes or {}, "") .. [[" id="]] .. ctrl_id .. [[" data-toggle="buttons" style="]] .. table.tconcat(style, ":", "; ", ";") .. [[">]]
-- foreach character in format
string.gsub(fmt, ".", function(k)
local v = fmt_to_data[k]
if v ~= nil then
local line = {}
if((max_val == nil) or (v.value < max_val)) then
line[#line+1] = [[<label class="btn]]
if selected == k then
line[#line+1] = [[ btn-primary active]]
else
line[#line+1] = [[ btn-secondary]]
end
line[#line+1] = [[ btn-sm"><input data-resol="]] .. k .. [[" value="]] .. truncate(v.value) .. [[" title="]] .. v.label .. [[" name="opt_resbt_]] .. k .. [[_]] .. ctrl_id .. [[" autocomplete="off" type="radio"]]
if selected == k then line[#line+1] = [[ checked="checked"]] end
line[#line+1] = [[/>]] .. v.label .. [[</label>]]
html_lines[#html_lines+1] = table.concat(line, "")
end
end
end)
html_lines[#html_lines+1] = [[</div>]]
-- Note: no // comment below, only /* */
local js_init_code = [[
var _resol_inputs = [];
function resol_selector_get_input(a_button) {
return $("input", $(a_button).closest(".form-group")).last();
}
function resol_selector_get_buttons(an_input) {
return $(".btn-group", $(an_input).closest(".form-group")).first().find("input");
}
/* This function scales values wrt selected resolution */
function resol_selector_reset_input_range(selected) {
var selected = $(selected);
var input = resol_selector_get_input(selected);
var raw = parseInt(input.attr("data-min"));
if (! isNaN(raw))
input.attr("min", Math.sign(raw) * Math.ceil(Math.abs(raw) / selected.val()));
raw = parseInt(input.attr("data-max"));
if (! isNaN(raw))
input.attr("max", Math.sign(raw) * Math.ceil(Math.abs(raw) / selected.val()));
var step = parseInt(input.attr("data-step-" + selected.attr("data-resol")));
if (! isNaN(step)) {
input.attr("step", step);
/* Align value */
input.val(input.val() - input.val() % step);
} else
input.attr("step", "");
resol_recheck_input_range(input);
}
function resol_selector_change_selection(selected) {
selected.attr('checked', 'checked')
.closest("label").removeClass('btn-secondary').addClass('btn-primary')
.siblings().removeClass('active').removeClass('btn-primary').addClass('btn-secondary').find("input").removeAttr('checked');
resol_selector_reset_input_range(selected);
}
function resol_recheck_input_range(input) {
var value = input.val();
if (input[0].hasAttribute("min"))
value = Math.max(value, input.attr("min"));
if (input[0].hasAttribute("max"))
value = Math.min(value, input.attr("max"));
var old_val = input.val();
if ((old_val != "") && (old_val != value))
input.val(value);
}
function resol_selector_change_callback(event) {
resol_selector_change_selection($(this));
}
function resol_selector_on_form_submit(event) {
var form = $(this);
if (event.isDefaultPrevented() || (form.find(".has-error").length > 0))
return false;
resol_selector_finalize(form);
return true;
}
/* Helper function to set a selector value by raw value */
function resol_selector_set_value(input_id, its_value) {
var input = $(input_id);
var buttons = resol_selector_get_buttons($(input_id));
var values = [];
buttons.each(function() {
values.push(parseInt($(this).val()));
});
var new_value;
var new_i;
if (its_value > 0) {
/* highest divisor */
var highest_i = 0;
for (var i=1; i<values.length; i++) {
if(((values[i] > values[highest_i]) && (its_value % values[i] == 0)))
highest_i = i;
}
new_value = its_value / values[highest_i];
new_i = highest_i;
} else {
/* smallest value */
new_value = Math.max(its_value, -1);
new_i = values.indexOf(Math.min.apply(Math, values));
}
/* Set */
input.val(new_value);
resol_selector_change_selection($(buttons[new_i]));
/* This must be set manually on initialization */
$(buttons[new_i]).closest("label").addClass("active");
}
function resol_selector_get_raw(input) {
var buttons = resol_selector_get_buttons(input);
var selected = buttons.filter(":checked");
return parseInt(selected.val()) * parseInt(input.val());
}
function resol_selector_finalize(form) {
$.each(_resol_inputs, function(i, elem) {
/* Skip elements which are not part of the form */
if (! $(elem).closest("form").is(form))
return;
var selected = $(elem).find("input[checked]");
var input = resol_selector_get_input(selected);
resol_recheck_input_range(input);
/* transform in raw units */
var new_input = $("<input type=\"hidden\"/>");
new_input.attr("name", input.attr("name"));
input.removeAttr("name");
new_input.val(resol_selector_get_raw(input));
new_input.appendTo(form);
});
/* remove added input names */
$("input[name^=opt_resbt_]", form).removeAttr("name");
}]]
local js_specific_code = [[
$("#]] .. ctrl_id .. [[ input").change(resol_selector_change_callback);
$(function() {
var elemid = "#]] .. ctrl_id .. [[";
_resol_inputs.push(elemid);
var selected = $(elemid + " input[checked]");
resol_selector_reset_input_range(selected);
/* setup the form submit callback (only once) */
var form = selected.closest("form");
if (! form.attr("data-options-handler")) {
form.attr("data-options-handler", 1);
form.submit(resol_selector_on_form_submit);
}
});
]]
-- join strings and strip newlines
local html = string.gsub(table.concat(html_lines, ""), "\n", "")
js_init_code = string.gsub(js_init_code, "\n", "")
js_specific_code = string.gsub(js_specific_code, "\n", "")
if tonumber(value) ~= nil then
-- returns the new value with selected resolution
return {html=html, init=js_init_code, js=js_specific_code, value=tonumber(value) / fmt_to_data[selected].value}
else
return {html=html, init=js_init_code, js=js_specific_code, value=nil}
end
end
-- ###########################################
--
-- Extracts parameters from a lua table.
-- This function performs the inverse conversion of javascript paramsPairsEncode.
--
-- Note: plain parameters (not encoded with paramsPairsEncode) remain unchanged only
-- when strict mode is *not* enabled
--
function paramsPairsDecode(params, strict_mode)
local res = {}
for k,v in pairs(params) do
local sp = split(k, "key_")
if #sp == 2 then
local keyid = sp[2]
local value = "val_"..keyid
if params[value] then
res[v] = params[value]
end
end
if((not strict_mode) and (res[v] == nil)) then
-- this is a plain parameter
res[k] = v
end
end
return res
end
function isBridgeInterface(ifstats)
return ifstats.inline
end
function hasSnmpDevices(ifid)
if (not ntop.isEnterpriseM()) or (not isAdministrator()) then
return false
end
return has_snmp_devices(ifid)
end
function getTopFlowPeers(hostname_vlan, max_hits, detailed, other_options)
local detailed = detailed or false
local paginator_options = {
sortColumn = "column_bytes",
a2zSortOrder = false,
detailedResults = detailed,
maxHits = max_hits,
}
if other_options ~= nil then
paginator_options = table.merge(paginator_options, other_options)
end
local res = interface.getFlowsInfo(hostname_vlan, paginator_options)
if ((res ~= nil) and (res.flows ~= nil)) then
return res.flows
else
return {}
end
end
function stripVlan(name)
local key = string.split(name, "@")
if((key ~= nil) and (#key == 2)) then
-- Verify that the host is actually an IP address and the VLAN actually
-- a number to avoid stripping things that are not vlans (e.g. part of an host name)
local addr = key[1]
if((tonumber(key[2]) ~= nil) and (isIPv6(addr) or isIPv4(addr))) then
return(addr)
end
end
return(name)
end
function getSafeChildIcon()
return("&nbsp;<font color='#5cb85c'><i class='fas fa-lg fa-child' aria-hidden='true'></i></font>")
end
-- ###########################################
function getNtopngRelease(ntopng_info)
local release
if ntopng_info.oem or ntopng_info["version.nedge_edition"] then
release = ""
elseif(ntopng_info["version.enterprise_l_edition"]) then
release = "Enterprise L"
elseif(ntopng_info["version.enterprise_m_edition"]) then
release = "Enterprise M"
elseif(ntopng_info["version.enterprise_edition"]) or (ntopng_info["version.nedge_enterprise_edition"]) then
release = "Enterprise"
elseif(ntopng_info["pro.release"]) then
release = "Professional"
elseif(ntopng_info["version.embedded_edition"]) then
release = "/Embedded"
else
release = "Community"
end
-- E.g., ntopng edge v.4.3.210112 (Ubuntu 16.04.6 LTS)
local res = string.format("%s %s v.%s (%s)", ntopng_info.product, release, ntopng_info.version, ntopng_info.OS)
if not ntopng_info.oem then
local vers = string.split(ntopng_info["version.git"], ":")
if vers and vers[2] then
local ntopng_git_url = "<A HREF=\"https://github.com/ntop/ntopng/commit/".. vers[2] .."\"><i class='fab fa-github'></i></A>"
res = string.format("%s | %s", res, ntopng_git_url)
end
end
return res
end
-- ###########################################
-- avoids manual HTTP prefix and /lua concatenation
function page_url(path)
return ntop.getHttpPrefix().."/lua/"..path
end
-- extracts a page url from the path
function path_get_page(path)
local prefix = ntop.getHttpPrefix() .. "/lua/"
if string.find(path, prefix) == 1 then
return string.sub(path, string.len(prefix) + 1)
end
return path
end
-- ###########################################
function swapKeysValues(tbl)
local new_tbl = {}
for k, v in pairs(tbl or {}) do
new_tbl[v] = k
end
return new_tbl
end
-- ###########################################
-- A redis hash mac -> first_seen
function getFirstSeenDevicesHashKey(ifid)
return "ntopng.seen_devices.ifid_" .. ifid
end
-- ###########################################
function getHideFromTopSet(ifid)
return "ntopng.prefs.iface_" .. ifid .. ".hide_from_top"
end
-- ###########################################
function getGwMacsSet(ifid)
return "ntopng.prefs.iface_" .. ifid .. ".gw_macs"
end
-- ###########################################
function printWarningAlert(message)
print[[<div class="alert alert-warning alert-dismissable" role="alert">]]
print[[<a class="close" data-dismiss="alert" aria-label="close">&times;</a>]]
print[[<i class="fas fa-exclamation-triangle fa-sm"></i> ]]
print[[<strong>]] print(i18n("warning")) print[[</strong> ]]
print(message)
print[[</div>]]
end
-- ###########################################
function tsQueryToTags(query)
local tags = {}
for _, part in pairs(split(query, ",")) do
local sep_pos = string.find(part, ":")
if sep_pos then
local k = string.sub(part, 1, sep_pos-1)
local v = string.sub(part, sep_pos+1)
tags[k] = v
end
end
return tags
end
function tsTagsToQuery(tags)
return table.tconcat(tags, ":", ",")
end
-- ###########################################
function splitUrl(url)
local params = {}
local parts = split(url, "?")
if #parts == 2 then
url = parts[1]
parts = split(parts[2], "&")
for _, param in pairs(parts) do
local p = split(param, "=")
if #p == 2 then
params[p[1]] = p[2]
end
end
end
return {
url = url,
params = params,
}
end
-- ###########################################
function getDeviceProtocolPoliciesUrl(params_str)
local url, sep
if ntop.isnEdge() then
url = "/lua/pro/nedge/admin/nf_edit_user.lua?page=device_protocols"
sep = "&"
else
url = "/lua/admin/edit_device_protocols.lua"
sep = "?"
end
if not isEmptyString(params_str) then
return ntop.getHttpPrefix() .. url .. sep .. params_str
end
return ntop.getHttpPrefix() .. url
end
-- ###########################################
-- Banner format: {type="success|warning|danger", text="..."}
function printMessageBanners(banners)
for _, msg in ipairs(banners) do
print[[
<div class="alert alert-]] print(msg.type) print([[ alert-dismissible" style="margin-top:2em; margin-bottom:0em;">
<button type="button" class="close" data-dismiss="alert" aria-label="]]..i18n("close")..[[">
<span aria-hidden="true">&times;</span>
</button>]])
if (msg.type == "warning") then
print("<b>".. i18n("warning") .. "</b>: ")
elseif (msg.type == "danger") then
print("<b>".. i18n("error") .. "</b>: ")
end
print(msg.text)
print[[
</div>]]
end
end
-- ###########################################
function visualTsKey(tskey)
if ends(tskey, "_v4") or ends(tskey, "_v6") then
local ver = string.sub(tskey, string.len(tskey)-1, string.len(tskey))
local address = string.sub(tskey, 1, string.len(tskey)-3)
local visual_addr
if ver == "v4" then
visual_addr = address
else
visual_addr = address .. " (" .. ver ..")"
end
return visual_addr
end
return tskey
end
-- ###########################################
-- Returns the size of a folder (size is in bytes)
--! @param path the path to compute the size for
--! @param timeout the maxium time to compute the size. If nil, it defaults to 15 seconds.
function getFolderSize(path, timeout)
local folder_size_key = "ntopng.cache.folder_size"
local now = os.time()
local expiration = 30 -- sec
local size = nil
if ntop.isWindows() then
size = 0 -- TODO
else
local MAX_TIMEOUT = tonumber(timeout) or 15 -- default
-- Check if timeout is present on the system to cap the execution time of the subsequent du,
-- which may be very time consuming, especially when the number of files is high
local has_timeout = ntop.getCache("ntopng.cache.has_gnu_timeout")
if isEmptyString(has_timeout) then
-- Cache the timeout
-- Check timeout existence with which. If no timeout is found, command will return nil
has_timeout = (os_utils.execWithOutput("which timeout >/dev/null 2>&1") ~= nil)
ntop.setCache("ntopng.cache.has_gnu_timeout", tostring(has_timeout), 3600)
else
has_timeout = has_timeout == "true"
end
-- Check the cache for a recent value
local time_size = ntop.getHashCache(folder_size_key, path)
if not isEmptyString(time_size) then
local values = split(time_size, ',')
if #values >= 2 and tonumber(values[1]) >= (now - expiration) then
size = tonumber(values[2])
end
end
if size == nil then
size = 0
-- Read disk utilization
local periodic_activities_utils = require "periodic_activities_utils"
if ntop.isdir(path) and not periodic_activities_utils.have_degraded_performance() then
local du_cmd = string.format("du -s %s 2>/dev/null", path)
if has_timeout then
du_cmd = string.format("timeout %u%s %s", MAX_TIMEOUT, "s", du_cmd)
end
-- use POSIXLY_CORRECT=1 to guarantee results is returned in 512-byte blocks
-- both on BSD and Linux
local line = os_utils.execWithOutput(string.format("POSIXLY_CORRECT=1 %s", du_cmd))
local values = split(line, '\t')
if #values >= 1 then
local used = tonumber(values[1])
if used ~= nil then
size = math.ceil(used * 512)
-- Cache disk utilization
ntop.setHashCache("ntopng.cache.folder_size", path, now..","..size)
end
end
end
end
end
return size
end
-- ##############################################
--- Return an HTML `select` element with passed options.
--
function generate_select(id, name, is_required, is_disabled, options, additional_classes)
local required_flag = (is_required and "required" or "")
local disabled_flag = (is_disabled and "disabled" or "")
local name_attr = (name ~= "" and "name='" .. name .. "'" or "")
local parsed_options = ""
for i, option in ipairs(options) do
parsed_options = parsed_options .. ([[
<option ]].. (i == 1 and "selected" or "") ..[[ value="]].. option.value ..[[">]].. option.title ..[[</option>
]])
end
return ([[
<select id="]].. id ..[[" class="form-control ]] .. (additional_classes or "") .. [[" ]].. name_attr ..[[ ]].. required_flag ..[[ ]] .. disabled_flag ..[[>
]].. parsed_options ..[[
</select>
]])
end
-- ###########################################
function getHttpUrlPrefix()
if starts(_SERVER["HTTP_HOST"], 'https://') then
return "https://"
else
return "http://"
end
end
-- ###########################################
-- Compares IPv4 / IPv6 addresses
function ip_address_asc(a, b)
return(ntop.ipCmp(a, b) < 0)
end
function ip_address_rev(a, b)
return(ntop.ipCmp(a, b) > 0)
end
-- ###########################################
-- @brief Deletes all the cache/prefs keys matching the pattern
function deleteCachePattern(pattern)
local keys = ntop.getKeysCache(pattern)
for key in pairs(keys or {}) do
ntop.delCache(key)
end
end
-- ###########################################
-- NOTE: '~= "0"' is used for prefs which are enabled by default
function areInterfaceTimeseriesEnabled(ifid)
return((ntop.getPref("ntopng.prefs.interface_rrd_creation") ~= "0"))
end
function areInterfaceL7TimeseriesEnabled(ifid)
return(areInterfaceTimeseriesEnabled(ifid) and
(ntop.getPref("ntopng.prefs.interface_ndpi_timeseries_creation") ~= "per_category"))
end
function areInterfaceCategoriesTimeseriesEnabled(ifid)
local rv = ntop.getPref("ntopng.prefs.interface_ndpi_timeseries_creation")
-- note: categories are disabled by default
return(areInterfaceTimeseriesEnabled(ifid) and
((rv == "per_category") or (rv == "both")))
end
function areHostTimeseriesEnabled(ifid)
local rv = ntop.getPref("ntopng.prefs.hosts_ts_creation")
if isEmptyString(rv) then rv = "light" end
return((rv == "light") or (rv == "full"))
end
function areHostL7TimeseriesEnabled(ifid)
local rv = ntop.getPref("ntopng.prefs.host_ndpi_timeseries_creation")
-- note: host protocols are disabled by default
return((ntop.getPref("ntopng.prefs.hosts_ts_creation") == "full") and
((rv == "per_protocol") or (rv == "both")))
end
function areHostCategoriesTimeseriesEnabled(ifid)
local rv = ntop.getPref("ntopng.prefs.host_ndpi_timeseries_creation")
-- note: host protocols are disabled by default
return((ntop.getPref("ntopng.prefs.hosts_ts_creation") == "full") and
((rv == "per_category") or (rv == "both")))
end
function areSystemTimeseriesEnabled()
return(ntop.getPref("ntopng.prefs.system_probes_timeseries") ~= "0")
end
function areHostPoolsTimeseriesEnabled(ifid)
return(ntop.isPro() and (ntop.getPref("ntopng.prefs.host_pools_rrd_creation") == "1"))
end
function areASTimeseriesEnabled(ifid)
return(ntop.getPref("ntopng.prefs.asn_rrd_creation") == "1")
end
function areInternalTimeseriesEnabled(ifid)
-- NOTE: no separate preference so far
return(areSystemTimeseriesEnabled())
end
function areCountryTimeseriesEnabled(ifid)
return((ntop.getPref("ntopng.prefs.country_rrd_creation") == "1"))
end
function areOSTimeseriesEnabled(ifid)
return((ntop.getPref("ntopng.prefs.os_rrd_creation") == "1"))
end
function areVlanTimeseriesEnabled(ifid)
return(ntop.getPref("ntopng.prefs.vlan_rrd_creation") == "1")
end
function areMacsTimeseriesEnabled(ifid)
return(ntop.getPref("ntopng.prefs.l2_device_rrd_creation") == "1")
end
function areContainersTimeseriesEnabled(ifid)
-- NOTE: no separate preference so far
return(true)
end
function areSnmpTimeseriesEnabled(device, port_idx)
return(ntop.getPref("ntopng.prefs.snmp_devices_rrd_creation") == "1")
end
function areFlowdevTimeseriesEnabled(ifid, device)
return(ntop.getPref("ntopng.prefs.flow_device_port_rrd_creation") == "1")
end
-- ###########################################
-- version is major.minor.veryminor
function version2int(v)
if(v == nil) then return(0) end
e = string.split(v, "%.");
if(e ~= nil) then
major = e[1]
minor = e[2]
veryminor = e[3]
if(major == nil or tonumber(major) == nil or type(major) ~= "string") then major = 0 end
if(minor == nil or tonumber(minor) == nil or type(minor) ~= "string") then minor = 0 end
if(veryminor == nil or tonumber(veryminor) == nil or type(veryminor) ~= "string") then veryminor = 0 end
version = tonumber(major)*1000 + tonumber(minor)*100 -- + tonumber(veryminor)
return(version)
else
return(0)
end
end
function get_version_update_msg(info, latest_version)
local version_elems = split(info["version"], " ")
local new_version = version2int(latest_version)
local this_version = version2int(version_elems[1])
if (new_version > this_version) then
return i18n("about.new_major_available", {
product = info["product"], version = latest_version,
url = "http://www.ntop.org/get-started/download/"
})
end
return ""
end
--- Check if there is a new major release
--- @return string message If there is a new major release then return a non-nil string
--- containing the update message.
function check_latest_major_release()
-- get the latest major release
local latest_version = ntop.getCache("ntopng.cache.major_release")
-- tprint(debug.traceback())
if isEmptyString(latest_version) then
local rsp = ntop.httpGet("https://www.ntop.org/ntopng.version", "", "", 10 --[[ seconds ]])
if (not isEmptyString(rsp)) and (not isEmptyString(rsp["CONTENT"])) then
latest_version = trimSpace(string.gsub(rsp["CONTENT"], "\n", ""))
else
-- a value that won't trigger an update message
latest_version = "0.0.0"
end
ntop.setCache("ntopng.cache.major_release", latest_version, 86400 --[[ recheck interval]])
end
return get_version_update_msg(info, latest_version)
end
-- ###########################################
-- To be called inside the flows tableCallback
function initFlowsRefreshRows()
print[[
datatableInitRefreshRows($("#table-flows"), "key_and_hash", 10000, {
/* List of rows with trend icons */
"column_thpt": ]] print(ternary(getThroughputType() ~= "bps", "NtopUtils.fpackets", "NtopUtils.bitsToSize")) print[[,
"column_bytes": NtopUtils.bytesToSize,
});
$("#dt-bottom-details > .float-left > p").first().append('. ]]
print(i18n('flows_page.idle_flows_not_listed'))
print[[');]]
end
-- ###########################################
function canRestoreHost(ifid, ip, vlan)
local ip_to_mac = string.format("ntopng.ip_to_mac.ifid_%u__%s@%d", ifid, ip, vlan)
local key_to_check
-- Check if there is a MAC address associated
local mac = ntop.getCache(ip_to_mac)
if not isEmptyString(mac) then
key_to_check = string.format("ntopng.serialized_hostsbymac.ifid_%u__%s_%s", ifid, mac, ternary(isIPv4(ip), "v4", "v6"))
else
key_to_check = string.format("ntopng.serialized_hosts.ifid_%u__%s@%d", ifid, ip, vlan)
end
return(not table.empty(ntop.getKeysCache(key_to_check)))
end
-- ###########################################
--- Test if each element inside the table t satisfies the predicate function
--- @param t table The table containing values to test
--- @param predicate function The function that return a boolean value (true|false)
--- @return boolean
function table.all(t, predicate)
if type(t) ~= 'table' then
traceError(TRACE_DEBUG, TRACE_CONSOLE, "the first paramater is not a table!")
return false
end
if type(predicate) ~= 'function' then
traceError(TRACE_DEBUG, TRACE_CONSOLE, "the passed predicate is not a function!")
return false
end
if t == nil then return false end
for _, value in pairs(t) do
-- check if the value satisfies the boolean predicate
local term = predicate(value)
-- if the return value is valid and true then do nothing
-- otherwise stop the loop and return false
if term == nil then
-- inform the client about the nil value
traceError(TRACE_DEBUG, TRACE_CONSOLE, "a null term has been returned from the predicate function!")
return false
elseif not term then
return false
end
end
-- each entry satisfies the predicate
return true
end
-- ###########################################
--- Perform a linear search to check if an element is inside a table
--- @param t table The table to scan
--- @param needle any The element to search
--- @param comp function The compare function used to compare the searched element with others
--- @return boolean True if the element is insie the table, False otherwise
function table.contains(t, needle, comp)
if (t == nil) then return false end
if (type(t) ~= "table") then return false end
if (#t == 0) then return false end
local default_compare = (function(e) return e == needle end)
comp = comp or default_compare
for _, element in ipairs(t) do
if comp(element) then return true end
end
return false
end
-- ###########################################
function build_query_url(excluded)
local query = "?"
for key, value in pairs(_GET) do
if not(table.contains(excluded, key)) then
query = query .. string.format("%s=%s&", key, value)
end
end
return query
end
-- ###########################################
function build_query_params(params)
local query = "?"
local t = {}
for key, value in pairs(params) do
t[#t+1] = string.format("%s=%s", key, value)
end
return query .. table.concat(t, '&')
end
-- ###########################################
function create_ndpi_proto_name(v)
local app = ""
if v["proto.ndpi"] then
app = getApplicationLabel(v["proto.ndpi"])
else
local master_proto = interface.getnDPIProtoName(tonumber(v["l7_master_proto"]))
local app_proto = interface.getnDPIProtoName(tonumber(v["l7_proto"]))
if master_proto == app_proto then
app = app_proto
elseif master_proto == "Unknown" then
app = app_proto
else
app = master_proto
if app_proto ~= "Unknown" then
app = app .. "." .. app_proto
end
end
app = getApplicationLabel(app)
end
return app
end
-- ###########################################
--- Insert an element inside the table if is not present
function table.insertIfNotPresent(t, element, comp)
if table.contains(t, element, comp) then return end
t[#t+1] = element
end
-- ###########################################
--- Fold right table with a custom function
--- @param t table Table to fold
--- @param func function Function to execute on table values
--- @param val any The returned default value
function table.foldr(t, func, val)
for i,v in pairs(t) do
val = func(val, v)
end
return val
end
-- ###########################################
function table.has_key(table, key)
return table[key] ~= nil
end
-- ###########################################
local cache = {}
function buildHostHREF(ip_address, vlan_id, page)
local stats = cache[ip_address]
if(stats == nil) then
stats = interface.getHostInfo(ip_address, vlan_id)
cache[ip_address] = { stats = stats }
else
stats = stats.stats
end
if(stats == nil) then
return(ip_address)
else
local hinfo = hostkey2hostinfo(ip_address)
local name = hostinfo2label(hinfo)
local res
if((name == nil) or (name == "")) then name = ip_address end
res = '<A HREF="'..ntop.getHttpPrefix()..'/lua/host_details.lua?host='..ip_address
if(vlan_id and (vlan_id ~= 0)) then res = res .. "@"..vlan_id end
res = res ..'&page='..page..'">'..name..'</A>'
return(res)
end
end
function builMapHREF(ip_address, vlan_id, map, default_page)
local stats = cache[ip_address]
if(stats == nil) then
stats = interface.getHostInfo(ip_address, vlan_id)
cache[ip_address] = { stats = stats }
else
stats = stats.stats
end
if(stats == nil) then
return(ip_address)
else
local hinfo = hostkey2hostinfo(ip_address)
local hmininfo = interface.getHostMinInfo(hinfo.host, hinfo.vlan)
for key, value in pairs(hmininfo) do
hinfo[key] = value
end
local name = hostinfo2label(hinfo)
local res
if((name == nil) or (name == "")) then name = ip_address end
res = '<a href="'..ntop.getHttpPrefix()..'/lua/pro/enterprise/'..map..'_map.lua?host='..ip_address
if(vlan_id and (vlan_id ~= 0)) then res = res .. "@"..vlan_id end
res = res ..'&page='..default_page..'">'..name..'</A>'
return(res)
end
end
-- #####################
local iec104_typeids = {
M_SP_NA_1=0x01,
M_SP_TA_1=0x02,
M_DP_NA_1=0x03,
M_DP_TA_1=0x04,
M_ST_NA_1=0x05,
M_ST_TA_1=0x06,
M_BO_NA_1=0x07,
M_BO_TA_1=0x08,
M_ME_NA_1=0x09,
M_ME_TA_1=0x0A,
M_ME_NB_1=0x0B,
M_ME_TB_1=0x0C,
M_ME_NC_1=0x0D,
M_ME_TC_1=0x0E,
M_IT_NA_1=0x0F,
M_IT_TA_1=0x10,
M_EP_TA_1=0x11,
M_EP_TB_1=0x12,
M_EP_TC_1=0x13,
M_PS_NA_1=0x14,
M_ME_ND_1=0x15,
M_SP_TB_1=30,
M_DP_TB_1=31,
M_ST_TB_1=32,
M_BO_TB_1=33,
M_ME_TD_1=34,
M_ME_TE_1=35,
M_ME_TF_1=36,
M_IT_TB_1=37,
M_EP_TD_1=38,
M_EP_TE_1=39,
M_EP_TF_1=40,
ASDU_TYPE_41=41,
ASDU_TYPE_42=42,
ASDU_TYPE_43=43,
ASDU_TYPE_44=44,
C_SC_NA_1=45,
C_DC_NA_1=46,
C_RC_NA_1=47,
C_SE_NA_1=48,
C_SE_NB_1=49,
C_SE_NC_1=50,
C_BO_NA_1=51,
C_SC_TA_1=58,
C_DC_TA_1=59,
C_RC_TA_1=60,
C_SE_TA_1=61,
C_SE_TB_1=62,
C_SE_TC_1=63,
C_BO_TA_1=64,
M_EI_NA_1=70,
C_IC_NA_1=100,
C_CI_NA_1=101,
C_RD_NA_1=102,
C_CS_NA_1=103,
C_TS_NA_1=104,
C_RP_NA_1=105,
C_CD_NA_1=106,
C_TS_TA_1=107,
P_ME_NA_1=110,
P_ME_NB_1=111,
P_ME_NC_1=112,
P_AC_NA_1=113,
F_FR_NA_1=120,
F_SR_NA_1=121,
F_SC_NA_1=122,
F_LS_NA_1=123,
F_FA_NA_1=124,
F_SG_NA_1=125,
F_DR_TA_1=126,
}
function iec104_typeids2str(c)
if(c == nil) then return end
for s,v in pairs(iec104_typeids) do
if(v == c) then
return(s.." (".. v ..")")
end
end
return(c)
end
function table.slice(t, start_table, end_table)
if t == nil then
error("The array to slice cannot be nil!")
end
if end_table > #t then
end_table = #t
end
if start_table < 1 then
error("Invalid bounds!")
end
local res = {}
for i = start_table, end_table, 1 do
res[#res + 1] = t[i]
end
return res
end
--
-- IMPORTANT
-- Leave it at the end so it can use the functions
-- defined in this file
--
http_lint = require "http_lint"