--
-- (C) 2013-23 - ntop.org
--

local dirs = ntop.getDirs()
package.path = dirs.installdir .. "/scripts/lua/modules/?.lua;" .. package.path
if((dirs.scriptdir ~= nil) and (dirs.scriptdir ~= "")) then package.path = dirs.scriptdir .. "/lua/modules/?.lua;" .. package.path end

local top_sites_update
local snmp_utils

if ntop.isPro() then
   package.path = dirs.installdir .. "/scripts/lua/pro/modules/?.lua;" .. package.path
   package.path = dirs.installdir .. "/pro/scripts/callbacks/?.lua;" .. package.path
   snmp_utils = require "snmp_utils"
   top_sites_update = require("host_sites_update")
end

require "db_utils"
require "lua_utils"
require "prefs_utils"

local json = require "dkjson"
local template = require "template_utils"
local format_utils  = require "format_utils"
local top_talkers_utils = require "top_talkers_utils"
local internals_utils = require "internals_utils"
local page_utils = require("page_utils")
local ui_utils = require("ui_utils")
local auth = require "auth"
local behavior_utils = require("behavior_utils")
local graph_utils = require "graph_utils"
local recording_utils = require "recording_utils"
local companion_interface_utils = require "companion_interface_utils"
local storage_utils = require "storage_utils"
local have_nedge = ntop.isnEdge()
local sites_granularities = nil
local show_zmq_encryption_public_key = false

if ntop.isPro() then
   shaper_utils = require("shaper_utils")
end

sendHTTPContentTypeHeader('text/html')

page = _GET["page"]
ifid = _GET["ifid"]

-- parse interface names and possibly fall back to the selected interface:
-- priority goes to the interface id
if ifid ~= nil and ifid ~= "" then
   if_name = getInterfaceName(ifid)
else
   -- fall-back to the default interface
   if_name = ifname
   ifid = interface.name2id(ifname)
end

interface.select(ifname)

ifname_clean = "iface_"..tostring(ifid)
msg = ""

local is_packet_interface = interface.isPacketInterface()
local is_pcap_dump = interface.isPcapDumpInterface()

local ifstats = interface.getStats()
local is_sub_interface = interface.isSubInterface()

-- tprint(ifstats.stats.hosts_rcvd_only .. " / " .. ifstats.stats.hosts)

if page == "syslog_producers" and not ifstats.isSyslog then
   page = "overview"
end

local is_mirrored_traffic = false
local is_mirrored_traffic_pref = string.format("ntopng.prefs.ifid_%d.is_traffic_mirrored", interface.getId())
if not ntop.isnEdge() and is_packet_interface then
   is_mirrored_traffic = ternary(ntop.getPref(is_mirrored_traffic_pref) == '1', true, false)
end

local service_map_available = false
local periodicity_map_available = false
service_map_available, periodicity_map_available = behavior_utils.mapsAvailable()

local disaggregation_criterion_key  = "ntopng.prefs.dynamic_sub_interfaces.ifid_"..tostring(ifid)..".mode"
local host_threshold_rules_key = "ntopng.prefs.ifid_"..tostring(ifid)..".host_threshold_rules"

local charts_available = areInterfaceTimeseriesEnabled(ifid)

function percentage(value, total)
   if(total > 0) then
      local pctg = round((value*100)/total, 2)
      if(pctg > 0) then return(" [ " .. pctg .. " % ] ") end
   end

   return("") 
end

function inline_input_form(name, placeholder, tooltip, value, can_edit, input_opts, input_class, measure_unit)
   if(can_edit) then
      print('<input style="width:36em;" title="'..tooltip..'" '..(input_opts or "")..' class="form-control '..(input_class or "")..'" name="'..name..'" placeholder="'..placeholder..'" value="')
      if(value ~= nil) then print(value.."") end
      print[[">]]
      if (measure_unit) then
         print([[<span class='ms-1 align-middle'>]].. i18n(measure_unit) ..[[</span>]])
      end
   else
      if(value ~= nil) then print(value) end
   end
end

function inline_select_form(name, keys, values, curval)
   print[[<select class="form-select" style="width:12em; display:inline;" name="]] print(name) print[[">]]
   for idx, k in ipairs(keys) do
      local v = values[idx]
      print[[<option value="]] print(v) print[[" ]]

      if curval == v then
         print("selected")
      end

      print[[>]] print(k) print[[</option>]]
   end
   print[[</select>]]
end

-- this is a user-browseable page, so we must return counters from
-- the latest reset as the user may have chosen to reset statistics at some point
if ifstats.stats and ifstats.stats_since_reset then
   -- override stats with the values calculated from the latest user reset
   for k, v in pairs(ifstats.stats_since_reset) do
      ifstats.stats[k] = v
   end
end

if ifstats.zmqRecvStats and ifstats.zmqRecvStats_since_reset then
   -- override stats with the values calculated from the latest user reset
   for k, v in pairs(ifstats.zmqRecvStats_since_reset) do
      ifstats.zmqRecvStats[k] = v
   end
end

local ext_interfaces = {}

-- refresh traffic recording availability as one may have installed n2disk
-- with a running instance of ntopng
recording_utils.checkAvailable()

if recording_utils.isAvailable() and recording_utils.isSupportedZMQInterface(ifid) then
   ext_interfaces = recording_utils.getExtInterfaces(ifid)
end

if (isAdministrator()) then
   if (page == "config") and (not table.empty(_POST)) then
      local custom_name = _POST["custom_name"]

      if starts(custom_name, "tcp:__") then
        -- Was mangled by sanitization
        custom_name = "tcp://" .. string.sub(custom_name, 7)
      end

      ntop.setCache('ntopng.prefs.ifid_'..ifstats.id..'.name', custom_name)

      local ifspeed_cache = 'ntopng.prefs.ifid_'..ifstats.id..'.speed'
      if isEmptyString(_POST["ifSpeed"]) then
         ntop.delCache(ifspeed_cache)
      else
         ntop.setCache(ifspeed_cache, _POST["ifSpeed"])
      end

      if is_packet_interface then
         if _POST["gw_macs"] ~= nil then
            -- Gw MAC addresses - used to compute traffic direction
            local gw_macs_set = getGwMacsSet(ifstats.id)
            ntop.delCache(gw_macs_set)
            for _, mac in pairs(split(_POST["gw_macs"] or "", ",")) do
               mac = trimSpace(mac)
               if not isEmptyString(mac) then
                  ntop.setMembersCache(gw_macs_set, mac)
               end
            end
            interface.reloadGwMacs()
         end
      end

      if not ntop.isnEdge() and is_packet_interface then
         if _POST["is_mirrored_traffic"] == "1" then
             is_mirrored_traffic = true
         else
             is_mirrored_traffic = false
         end

         ntop.setPref(is_mirrored_traffic_pref, ternary(is_mirrored_traffic == true, '1', '0'))
         interface.updateTrafficMirrored()
      end

      local sf = tonumber(_POST["scaling_factor"])
      if(sf == nil) then sf = 1 end
      ntop.setCache("ntopng.prefs.iface_" .. tostring(ifid)..'.scaling_factor',tostring(sf))
      interface.loadScalingFactorPrefs()
   end
end

page_utils.print_header_and_set_active_menu_entry(page_utils.menu_entries.interface, { ifname=getHumanReadableInterfaceName(if_name) })

dofile(dirs.installdir .. "/scripts/lua/inc/menu.lua")

--print(msg)

if _SERVER["REQUEST_METHOD"] == "POST" and _POST["companion_interface"] ~= nil then
   companion_interface_utils.setCompanion(ifstats.id, _POST["companion_interface"])
end

if _SERVER["REQUEST_METHOD"] == "POST" and _POST["disaggregation_criterion"] ~= nil then
   if _POST["disaggregation_criterion"] == "none" then
      ntop.delCache(disaggregation_criterion_key)
   else
      ntop.setCache(disaggregation_criterion_key, _POST["disaggregation_criterion"])
   end
end

if _SERVER["REQUEST_METHOD"] == "POST" and _POST["host_threshold_rules"] ~= nil then
   if _POST["host_threshold_rules"] == "none" then
      ntop.delCache(host_threshold_rules_key)
   else
      ntop.setCache(host_threshold_rules_key, _POST["host_threshold_rules"])
   end
end

if _SERVER["REQUEST_METHOD"] == "POST" and not isEmptyString(_POST["traffic_recording_provider"]) then
   local prev_provider = recording_utils.getCurrentTrafficRecordingProvider(ifstats.id)

   -- if the current provider is the builtin ntopng and we are changing to another provider
   -- then it may be necessary to stop the builtin ntopng
   if prev_provider == "ntopng" and _POST["traffic_recording_provider"] ~= "ntopng" then
      recording_utils.stop(ifstats.id)
      ntop.setCache('ntopng.prefs.ifid_'..ifstats.id..'.traffic_recording.enabled', "false")
   end

   recording_utils.setCurrentTrafficRecordingProvider(ifstats.id, _POST["traffic_recording_provider"])
end

local has_traffic_recording_page = (recording_utils.isAvailable()
          and (is_packet_interface
                  or (ifstats.isView) -- Allows view interfaces (e.g., views of ZMQ)
                  or ((recording_utils.isSupportedZMQInterface(ifid) and not table.empty(ext_interfaces)))
                  or (recording_utils.getCurrentTrafficRecordingProvider(ifid) ~= "ntopng")))

local dismiss_recording_providers_reminder = recording_utils.isExternalProvidersReminderDismissed(ifstats.id)

local url = ntop.getHttpPrefix()..'/lua/if_stats.lua?ifid=' .. ifid

--  Added global javascript variable, in order to disable the refresh of pie chart in case
--  of historical interface
print('\n<script>var refresh = '..interface.getStatsUpdateFreq(ifstats.id)..' * 1000; /* ms */;</script>\n')

local short_name = getHumanReadableInterfaceName(ifname)
local title = i18n("interface") .. ": " .. shortenCollapse(short_name)

if (ntop.isPro()) then
   sites_granularities = top_sites_update.getGranularitySites(nil, nil, ifid, true)
end

page_utils.print_navbar(title, url,
                           {
                              {
                                 hidden = only_historical,
                                 active = page == "overview" or page == nil,
                                 page_name = "overview",
                                 label = "<i class=\"fas fa-lg fa-home\"></i>",
                              },
                              {
                                 hidden = is_packet_interface or ntop.isnEdge() or  interface.isView(),
                                 active = page == "nprobe",
                                 page_name = "nprobe",
                                 label = "nProbe",
                              },
                              {
                                 hidden = not is_packet_interface or ntop.isnEdge() or interface.isView(),
                                 active = page == "networks",
                                 page_name = "networks",
                                 label = i18n("networks"),
                              },
                              {
                                 hidden = have_nedge or not ifstats or ifstats.stats.packets == 0 or ntop.isnEdge(),
                                 active = page == "packets",
                                 page_name = "packets",
                                 label = i18n("packets"),
                              },
                              {
                                 hidden = not ifstats or not ifstats["has_macs"] or ntop.isnEdge(),
                                 active = page == "DSCP",
                                 page_name = "DSCP",
                                 label = i18n("dscp"),
                              },
                              {
                                 active = page == "ndpi",
                                 page_name = "ndpi",
                                 label = i18n("applications"),
                              },
                              {
                                 hidden = have_nedge,
                                 active = page == "ICMP",
                                 page_name = "ICMP",
                                 label = i18n("icmp"),
                              },
                              {
                                 hidden = not ifstats or not ifstats["has_macs"] or ntop.isnEdge(),
                                 active = page == "ARP",
                                 page_name = "ARP",
                                 label = i18n("arp"),
                              },
                              {
                                 hidden = (sites_granularities == nil or table.len(sites_granularities) == 0),
                                 active = page == "sites",
                                 page_name = "sites",
                                 label = i18n("sites_page.sites"),
                              },
                              {
                                 hidden = not charts_available,
                                 active = page == "historical",
                                 page_name = "historical",
                                 label = "<i class='fas fa-lg fa-chart-area' title='"..i18n("historical").."'></i>",
                              },
                              {
                                 hidden = have_nedge or not ifstats or table.empty(ifstats.profiles),
                                 active = page == "trafficprofiles",
                                 page_name = "trafficprofiles",
                                 label = "<i class=\"fas fa-lg fa-user-md\"></i>",
                              },
                              {
                                 hidden = not has_traffic_recording_page,
                                 active = page == "traffic_recording",
                                 page_name = "traffic_recording",
                                 label = "<i class='fas fa-lg fa-hdd' title='"..i18n("traffic_recording.traffic_recording").."'></i>",
                              },
                              {
                                 hidden = not areAlertsEnabled() or not auth.has_capability(auth.capabilities.alerts),
                                 active = page == "alerts",
                                 page_name = "alerts",
                                 url = ntop.getHttpPrefix() .. "/lua/alert_stats.lua?&page=interface",
                                 label = "<i class='fas fa-lg fa-exclamation-triangle' title='"..i18n("alerts_dashboard.alerts").."'></i>",
                              },
                              {
                                 hidden = not hasTrafficReport(),
                                 active = page == "traffic_report",
                                 page_name = "traffic_report",
                                 label = "<i class='fas fa-lg fa-file-alt report-icon' title='"..i18n("report.traffic_report").."'></i>",
                              },
                              {
                                 hidden = not isAdministrator() or is_pcap_dump,
                                 active = page == "config",
                                 page_name = "config",
                                 label = "<i class='fas fa-lg fa-cog' title='"..i18n("settings").."'></i>",
                              },
                              {
                                 active = page == "internals",
                                 page_name = "internals",
                                 label = "<i class='fas fa-lg fa-wrench' title='"..i18n("status").."'></i>",
                              },
                              {
                                 hidden = have_nedge or not isAdministrator() or not ntop.isEnterpriseM() or is_sub_interface or ifstats.isView,
                                 active = page == "sub_interfaces",
                                 page_name = "sub_interfaces",
                                 label = "<i class='fas fa-lg fa-code-branch' title='"..i18n("sub_interfaces.disaggregation").."'></i>",
                              },
                              {
                                 hidden = have_nedge or not isAdministrator() or not ifstats.isSyslog,
                                 active = page == "syslog_producers",
                                 page_name = "syslog_producers",
                                 label = "<i class='fas fa-lg fa-hand-holding' title='"..i18n("syslog.producers").."'></i>",
                              },
                              {
                                 hidden = not isAdministrator() or not ifstats.has_macs or have_nedge,
                                 active = page == "unassigned_pool_devices",
                                 page_name = "unassigned_pool_devices",
                                 label = "<i class='fas fa-lg fa-user-slash' title='"..i18n("unknown_devices.unassigned_devices").."'></i>",
                              },
                              {
                                 hidden = not isAdministrator() or is_pcap_dump or interface.isZMQInterface(),
                                 active = page == "dhcp",
                                 page_name = "dhcp",
                                 label = "<i class='fas fa-lg fa-bolt' title='"..i18n("dhcp.dhcp").."'></i>",
                              },
                              {
                                 hidden = (not periodicity_map_available),
                                 page_name = "periodicity_map",
                                 url = ntop.getHttpPrefix() .. "/lua/pro/enterprise/network_maps.lua?map=periodicity_map",
                                 label = "<i class=\"fas fa-lg fa-clock\"></i>",
                              },
                              {
                                 hidden = (not service_map_available),
                                 page_name = "service_map",
                                 url = ntop.getHttpPrefix() .. "/lua/pro/enterprise/network_maps.lua?map=service_map",
                                 label = "<i class=\"fas fa-lg fa-concierge-bell\"></i>",
                              },
                           }
)

print(
      template.gen("modal_confirm_dialog.html", {
                      dialog={
                         id      = "reset_stats_dialog",
                         action  = "resetCounters(false)",
                         title   = i18n("reset_if_title"),
                         message = i18n("reset_if_message"),
                         confirm = i18n("reset"),
                         confirm_button = "btn-danger",
                      }
      })
)

print(
      template.gen("modal_confirm_dialog.html", {
                      dialog={
                         id      = "reset_drops_dialog",
                         action  = "resetCounters(true)",
                         title   = i18n("reset_drops_if_title"),
                         message = i18n("reset_drops_if_message"),
                         confirm = i18n("reset"),
                         confirm_button = "btn-danger",
                      }
      })
)

if((page == "overview") or (page == nil)) then
   local tags = {ifid = ifstats.id}
   print("<div class='table-responsive-xl'>")
  
   print("<table class=\"table table-striped table-bordered mb-0\">\n")
   print("<tr><th width=15%>"..i18n("if_stats_overview.id").."</th><td colspan=6>" .. ifstats.id .. " ")

   if(ifstats.description ~= ifstats.name) then print(" ("..ifstats.description..")") end
   print("</td></tr>\n")

   if not is_pcap_dump and ifstats["type"] ~= "netfilter" then
      print("<tr><th width=250>"..i18n("if_stats_overview.state").."</th><td colspan=6>")
      state = toggleTableButton("", "", i18n("if_stats_overview.active"), "1","primary", i18n("if_stats_overview.paused"), "0","primary", "toggle_local", "ntopng.prefs.ifid_"..tostring(ifid).."_not_idle")

      if(state == "0") then
         on_state = true
      else
         on_state = false
      end

      interface.setInterfaceIdleState(on_state)

      print("</td></tr>\n")
   end

   local nprobe_interface, nprobe_version, nprobe_probe_ip, nprobe_probe_public_ip, nprobe_edition, nprobe_license, nprobe_maintenance = {}, {}, {}, {}, {}, {}, {}

   local tot_num_nprobes = table.len(ifstats.probes or {})
   local max_num_nprobes = 4
   local cur_num_nprobes = 0

   for k, v in pairsByKeys(ifstats.probes or {}, asc) do
      cur_num_nprobes = cur_num_nprobes + 1
      if cur_num_nprobes > max_num_nprobes then
         break
      end

      local cur_interface = i18n("if_stats_overview.remote_probe_collector_mode")

      if v["remote.name"] ~= "none" then
         cur_interface = string.format("%s [%s]", v["remote.name"], bitsToSize(v["remote.ifspeed"]*1000000))
      end
      nprobe_interface[#nprobe_interface + 1] = cur_interface

      local cur_version = v["probe.probe_version"]

      if not isEmptyString(v["probe.probe_os"]) then
         cur_version = string.format("%s (%s)", cur_version, v["probe.probe_os"])
      end
      nprobe_version[#nprobe_version + 1] = cur_version

      local cur_probe_ip = ip2detailshref(v["probe.ip"], 0, nil, v["probe.ip"], nil, true)
      if not isEmptyString(v["probe.public_ip"]) then
         cur_probe_ip = string.format("%s (%s)", cur_probe_ip, ip2detailshref(v["probe.public_ip"], 0, nil, v["probe.public_ip"], nil, true))
      end
      nprobe_probe_ip[#nprobe_probe_ip + 1] = cur_probe_ip

      nprobe_edition[#nprobe_edition + 1] = v["probe.probe_edition"]
      nprobe_license[#nprobe_license + 1] = v["probe.probe_license"] or i18n("if_stats_overview.no_license")
      nprobe_maintenance[#nprobe_maintenance + 1] = v["probe.probe_maintenance"] or i18n("if_stats_overview.expired_maintenance")
   end

   if tot_num_nprobes > max_num_nprobes then
      local other_probes = tot_num_nprobes - max_num_nprobes
      local other_probes_message = "... (" .. i18n("if_stats_overview.other_probes", {num = other_probes}) .. ")"
      nprobe_interface[#nprobe_interface + 1] = other_probes_message
      nprobe_version[#nprobe_version + 1] = other_probes_message
      nprobe_probe_ip[#nprobe_probe_ip + 1] = other_probes_message
      nprobe_probe_public_ip[#nprobe_probe_public_ip + 1] = other_probes_message
      nprobe_edition[#nprobe_edition + 1] = other_probes_message
      nprobe_license[#nprobe_license + 1] = other_probes_message
      nprobe_maintenance[#nprobe_maintenance + 1] = other_probes_message
   end

   if cur_num_nprobes > 0 then
      print("<tr><th nowrap>".. i18n("if_stats_overview.remote_probe") .."</th>")

      local msg =  i18n("if_stats_overview.remote_probe_collecting_from_x_devices", {
         num = tot_num_nprobes,
         url = ntop.getHttpPrefix() .."/lua/if_stats.lua?page=nprobe"
      })

      if max_num_nprobes < tot_num_nprobes then
         msg = string.format("%s %s", msg, i18n("if_stats_overview.remote_probe_showing_first_x_devices", {num = max_num_nprobes}))
      end

      print("<td colspan=6 nowrap>".. msg .."</td>")

      print("</tr><tr>")

      print("<th nowrap>".. i18n("if_stats_overview.interface_name") .."</th><td nowrap><ol><li>")
      print(table.concat(nprobe_interface, "</li><li>"))
      print("</li></ol></td>\n")

      print("<th nowrap>".. i18n("if_stats_overview.remote_probe") .."</th><td nowrap><ol><li>")
      print(table.concat(nprobe_version, "</li><li>"))
      print("</li></ol></td>\n")

      print("<th nowrap>".. i18n("if_stats_overview.remote_probe_edition") .."</th><td nowrap colspan=2><ol><li>")
      print(table.concat(nprobe_edition, "</li><li>"))
      print("</li></ol></td>\n")

      print("</tr></tr>")

      print("<th nowrap>".. i18n("if_stats_overview.remote_probe_license") .."</th><td nowrap><ol><li>")
      print(table.concat(nprobe_license, "</li><li>"))
      print("</li></ol></td>\n")

      print("<th nowrap>".. i18n("if_stats_overview.probe_ip") .."</th><td nowrap><ol><li>")
      print(table.concat(nprobe_probe_ip, "</li><li>"))
      print("</li></ol></td>\n")

      print("<th nowrap>".. i18n("if_stats_overview.remote_probe_maintenance") .."</th><td nowrap colspan=2><ol><li>")
      print(table.concat(nprobe_maintenance, "</li><li>"))
      print("</li></ol></td>\n")

      print("</tr>")
   end

   if cur_num_nprobes > 0 then
      local max_items_per_row = 3
      local cur_i = 0

      print("<tr>")

      if not isEmptyString(ifstats["remote_pps"]) or not isEmptyString(ifstats["remote_bps"]) then
         if cur_i >= max_items_per_row then print("</tr><tr>"); cur_i = 0 end

         print("<th nowrap>".. i18n("if_stats_overview.probe_throughput").."</th>")
         print('<td nowrap><span id="if_zmq_remote_bps">' .. format_utils.bitsToSize(ifstats["remote_bps"]) .. '</span>')
         print(' [<span id="if_zmq_remote_pps">' .. format_utils.pktsToSize(ifstats["remote_pps"]) .. '</span>]</td>')

         cur_i = cur_i + 1
      end

      if ifstats["timeout.lifetime"] > 0 then
         if cur_i >= max_items_per_row then print("</tr><tr>"); cur_i = 0 end

         print("<th width='15%'><details><summary><span class='ntop_notes' data-bs-toggle='tooltip' data-placement='right' title='" .. i18n("click_to_expand") .. "'>")
         print(i18n("if_stats_overview.probe_timeout_lifetime").." <i class='fas fa-question-circle '></i>")
         print("</span></summary><small><span>" ..i18n("if_stats_overview.note_lifetime_timeout").. "</span></small></details></th>")

         print("<td>")

         if((ifstats["timeout.collected_lifetime"] ~= nil) and (ifstats["timeout.collected_lifetime"] > 0)) then
            -- We're in collector mode on the nProbe side
            print(" "..secondsToTime(ifstats["timeout.lifetime"]).." [".. i18n("if_stats_overview.remote_flow_lifetime")..": "..secondsToTime(ifstats["timeout.collected_lifetime"]).."]")
         else
            -- Modern nProbe in non-flow collector mode or old nProbe
            print(secondsToTime(ifstats["timeout.lifetime"]))
         end
         print("</td>")
         cur_i = cur_i + 1
      end

      if ifstats["timeout.idle"] > 0 then
         if cur_i >= max_items_per_row then print("</tr><tr>"); cur_i = 0 end
         print("<th nowrap>"..i18n("if_stats_overview.probe_timeout_idle").."</th><td nowrap>"..secondsToTime(ifstats["timeout.idle"]).."</td>")
         cur_i = cur_i + 1
      end

      if not isEmptyString(ifstats["probe.local_time"]) and not isEmptyString(ifstats["probe.remote_time"]) then
         local tdiff = math.abs(ifstats["probe.local_time"]-ifstats["probe.remote_time"])

         if tdiff > 0 then
            if cur_i >= max_items_per_row then print("</tr><tr>"); cur_i = 0 end
            print("<th nowrap>"..i18n("if_stats_overview.remote_probe_time")..
                     " <sup><i class='fas fa-question-circle ' title='"..i18n("if_stats_overview.note_remote_probe_time").."'></i></sup>" ..
                     "</th><td>")

            if(tdiff > 10) then print("<font color=red>") end
            print(formatValue(tdiff).." sec")
            if(tdiff > 10) then print("</font>") end

            print("</td>")
            cur_i = cur_i + 1
         end
      end

      local has_drops_export_queue_full = (tonumber(ifstats["zmq.drops.export_queue_full"]) and tonumber(ifstats["zmq.drops.export_queue_full"]) > 0)
      local has_drops_flow_collection_drops = (tonumber(ifstats["zmq.drops.flow_collection_drops"]) or 0) > 0
      local has_drops_flow_collection_udp_socket_drops = (tonumber(ifstats["zmq.drops.flow_collection_udp_socket_drops"]) or 0) > 0
      local has_remote_drops = (has_drops_export_queue_full or has_drops_flow_collection_drops)

      if has_drops_export_queue_full then
         local span_class = ' '

         if cur_i >= max_items_per_row then print("</tr><tr>"); cur_i = 0 end
         print("<th nowrap>"..i18n("if_stats_overview.probe_zmq_drops_export_queue_full").." <sup><i class='fas fa-question-circle ' title='"..i18n("if_stats_overview.note_probe_zmq_drops_export_queue_full").."'></i></sup></th>")
         print("<td nowrap><span "..span_class.." id=if_zmq_drops_export_queue_full>"..formatValue(ifstats["zmq.drops.export_queue_full"]).."</span> <span id=if_zmq_drops_export_queue_full_trend></span></td>")
         cur_i = cur_i + 1
      end

      if has_drops_flow_collection_drops then
         local span_class = ' '

         if cur_i >= max_items_per_row then print("</tr><tr>"); cur_i = 0 end
         print("<th><details><summary><span class='ntop_notes' data-bs-toggle='tooltip' data-placement='right' title='" .. i18n("click_to_expand") .. "'>")
         print(i18n("if_stats_overview.probe_zmq_drops_flow_collection_drops").." <i class='fas fa-question-circle '></i>")
         print("</span></summary><small><span>" ..i18n("if_stats_overview.note_probe_zmq_drops_flow_collection_drops").. "</span></small></details></th>")
         print("<td nowrap><span "..span_class.." id=if_zmq_drops_flow_collection_drops>"..formatValue(ifstats["zmq.drops.flow_collection_drops"]).."</span> <span id=if_zmq_drops_flow_collection_drops_trend></span></td>")
         cur_i = cur_i + 1
      end

      if has_drops_flow_collection_udp_socket_drops then
         local span_class = ' '

         if cur_i >= max_items_per_row then print("</tr><tr>"); cur_i = 0 end
         print("<th nowrap>"..i18n("if_stats_overview.probe_zmq_drops_flow_collection_udp_socket_drops").." <sup><i class='fas fa-question-circle ' title='"..i18n("if_stats_overview.note_probe_zmq_drops_flow_collection_udp_socket_drops").."'></i></sup></th>")
         print("<td nowrap><span "..span_class.." id=if_zmq_drops_flow_collection_udp_socket_drops>"..formatValue(ifstats["zmq.drops.flow_collection_udp_socket_drops"]).."</span> <span id=if_zmq_drops_flow_collection_udp_socket_drops_trend></span></td>")
         cur_i = cur_i + 1
      end

      if cur_i < max_items_per_row then
         -- 7 is the default colspan, then for each element we subtract two columns
         print(string.format("<td colspan=%u></td>", 7 - cur_i * 2))
      end

      print("</tr>")
   end

   local is_physical_iface = is_packet_interface and (not is_pcap_dump)

   local label = getHumanReadableInterfaceName(ifstats.name)
   local s
   if ((not isEmptyString(label)) and (label ~= ifstats.name)) then
      s = label.." (" .. ifstats.name .. ")"
   else
      s = ifstats.name
   end

   if((isAdministrator()) and (not is_pcap_dump)) then
      s = s .. " <a href=\""..url.."&page=config\"><i class=\"fas fa-cog fa-sm\" title=\"Configure Interface Name\"></i></a>"
   end

   print('<tr><th width="250">'..i18n("name")..'</th><td colspan="2"><p style="word-break: break-all">')
   print(s)
   if(ifstats.mac and ifstats.mac ~= "00:00:00:00:00:00") then print(" [" .. ifstats.mac .. "]"); end
   print('</p></td>\n')

   print("<th>"..i18n("if_stats_overview.family").."</th><td colspan=2>")
   if(ifstats.type == "zmq") then
      print("ZMQ")
   else
      print(ifstats.type)
   end

   if(ifstats.inline) then
      print(" "..i18n("if_stats_overview.in_path_interface"))
   end
   if(ifstats.has_traffic_directions) then
      print(" ".. i18n("if_stats_overview.has_traffic_directions") .. " ")
   end
   print("</tr>")

   show_zmq_encryption_public_key = (ifstats.encryption and ifstats.encryption.public_key and isAdministrator())
   
   if show_zmq_encryption_public_key == true then
      print("<tr><th width=280 nowrap>"..i18n("if_stats_overview.zmq_encryption_public_key").."</th><td colspan=6>"..i18n("if_stats_overview.zmq_encryption_alias").."<span>")
      print("<input type='hidden' id='hiddenKey' value='"..ifstats.encryption.public_key.."'>")
      print("<button id='copy' class='btn btn-light border ms-1'>".."<i class='fas fa-copy'></i>".." </button>")
      print("<br><small><b>"..i18n("if_stats_overview.note").."</b>:<ul><li> ".. i18n("if_stats_overview.zmq_encryption_public_key_note", {key="&lt;key&gt;"}).."")
      local zmq_endpoint = ifstats.name
      local probe_mode = ""

      if endswith(zmq_endpoint, 'c') then
         zmq_endpoint = string.sub(zmq_endpoint, 1, -2)
         probe_mode = " --zmq-probe-mode"
      end

      if ntop.isCloud() then
         if(prefs.zmq_publish_events_url == nil) then
            print("<p><b><font color=red>Please restart ntopng with --zmq-publish-events &lt;URL&gt;</font></b>")
         else
            local info = ntop.getHostInformation()
            local elems = string.split(zmq_endpoint, ":")
            local port = elems[3]
            
            zmq_endpoint = "tcp://"..info.ip..":"..port
            
            print("<li>nprobe --cloud "..zmq_endpoint.." --zmq-encryption-key '"..i18n("if_stats_overview.zmq_encryption_alias").."' ...")
         end
      else
        print("<li>nprobe --zmq "..zmq_endpoint.. probe_mode .." --zmq-encryption-key '"..i18n("if_stats_overview.zmq_encryption_alias").."' ...")
      end
      
      print("</small></ul>");

      if(ntop.isCloud() and (prefs.zmq_publish_events_url ~= nil)) then
        -- Sample configuration file
        print [[
        <script type='text/javascript'>
        function configDownload() {
          var filename = 'nprobe-example.conf';
          var content = "";
          content += "# Set the capture interface name\n";
          content += "#-i=INTERFACE_NAME\n";
          content += "\n";
          content += "# Set the ntopng address properly\n";
          content += "--cloud=]] print(zmq_endpoint) print [[\n";
          content += "--instance-name=SET_YOUR_NPROBE_INSTANCE_NAME\n";
          content += "--zmq-encryption-key=']] print(ifstats.encryption.public_key) print [['\n";
          content += "\n";
          content += "# Add more options here...\n";
          content += "\n";
          var element = document.createElement('a');
          element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(content));
          element.setAttribute('download', filename);
          element.style.display = 'none';
          document.body.appendChild(element);
          element.click();
          document.body.removeChild(element);
        }
        </script>
        <button type="button" class="btn btn-sm btn-secondary" onclick="configDownload();">]] print(i18n("if_stats_overview.zmq_download_conf")) print [[</button>
        ]]
      end

      print("</td></tr>\n")
   end

   if is_physical_iface and not ifstats.isView then
      print("<tr>")
      print("<th>"..i18n("mtu").."</th><td colspan=2  nowrap>"..ifstats.mtu.." "..i18n("bytes").."</td>\n")
      local speed_key = 'ntopng.prefs.ifid_'..tostring(interface.name2id(ifname))..'.speed'
      local speed = ntop.getCache(speed_key)
      if (tonumber(speed) == nil) then
              speed = ifstats.speed
      end
      print("<th width=250>"..i18n("speed").."</th><td colspan=2>" .. bitsToSize(speed * 1000000) .. "</td>")
      print("</tr>")
   end

   if (not hasAllowedNetworksSet()) and ((ifstats.num_alerts_engaged > 0) or (ifstats.num_dropped_alerts > 0)) then
      print("<tr>")
      local warning = "<i class='fas fa-exclamation-triangle fa-lg' style='color: #B94A48;'></i> "

      print("<th>".. ternary(ifstats.num_alerts_engaged > 0, warning, "") ..i18n("show_alerts.engaged_alerts")..
               ternary(charts_available, " <A HREF='"..url.."&page=historical&ts_schema=iface:engaged_alerts'><i class='fas fa-chart-area fa-sm'></i></A>", "").."</th><td colspan=2  nowrap><a href='".. ntop.getHttpPrefix() .."/lua/alert_stats.lua?status=engaged&page=host&ifid="..ifstats.id.."'>".. formatValue(ifstats.num_alerts_engaged) .."</a> <span id=engaged_alerts_trend></span></td>\n")
      print("<th width=250>".. ternary(ifstats.num_dropped_alerts > 0, warning, "")..i18n("show_alerts.dropped_alerts")..
               " <i class='fas fa-sm fa-question-circle ' title='".. i18n("if_stats_overview.dropped_alerts_info") .."'></i>"..
               ternary(charts_available, " <A HREF='"..url.."&page=historical&ts_schema=iface:dropped_alerts'><i class='fas fa-chart-area fa-sm'></i></A>", "")
               .."</th><td colspan=2><span id=dropped_alerts>" .. formatValue(ifstats.num_dropped_alerts) .. "</span> <span id=dropped_alerts_trend></span></td>\n</td>")
   end

   label = i18n("pkts")

   print[[</tbody></table>]]
        print[[<table class="table table-striped table-bordered"><tbody>]]

   print[[ <tr><th colspan=1 nowrap>]] print(i18n("if_stats_overview.traffic_breakdown")) print[[</th> ]]

   if(ifstats.type ~= "zmq") then
      print [[ <td colspan=2><div class="pie-chart" id="ifaceTrafficBreakdown"></div></td><td colspan=3> <div class="pie-chart" id="ifaceTrafficDistribution"></div></td></tr> ]]
   else
      print [[ <td colspan=5><div class="pie-chart" id="ifaceTrafficBreakdown"></div></td></tr> ]]
   end

   print [[
        <script type='text/javascript'>
               window.onload=function() {
                                   do_pie("#ifaceTrafficBreakdown", ']]
   print (ntop.getHttpPrefix())
   print [[/lua/iface_local_stats.lua', { ifid: ]] print(ifstats.id .. " }, \"\", refresh); \n")

   if(ifstats.type ~= "zmq") then
      print [[                                   do_pie("#ifaceTrafficDistribution", ']]
      print (ntop.getHttpPrefix())
      print [[/lua/iface_local_stats.lua', { ifid: ]] print(ifstats.id .. ", iflocalstat_mode: \"distribution\" }, \"\", refresh); \n")
   end
   print [[ }

]]
   print("</script>\n")

   if(ifstats.zmqRecvStats ~= nil) then
      print("<tr><th colspan=7 nowrap>"..i18n("if_stats_overview.zmq_rx_statistics").."</th></tr>\n")

      print("<tr>")
      print("<th nowrap>"..i18n("if_stats_overview.collected_flows")..ternary(charts_available, " <A HREF='"..url.."&page=historical&ts_schema=iface:zmq_recv_flows'><i class='fas fa-chart-area fa-sm'></i></A>", "").."</th><td width=20%><span id=if_zmq_flows>"..formatValue(ifstats.zmqRecvStats.flows).."</span></td>")
      print("<th nowrap> <i class='fas fa-tint' aria-hidden='true'></i> "..i18n("if_stats_overview.unhandled_flows").."</th><td width=20%><span id=if_zmq_dropped_flows>"..formatValue(ifstats.zmqRecvStats.dropped_flows).."</span></td>")
      print("<td colspan=2></td>")
      print("</tr>")

      print("<tr>")
      print("<th nowrap>"..i18n("if_stats_overview.zmq_message_rcvd")..ternary(charts_available, " <A HREF='"..url.."&page=historical&ts_schema=custom:zmq_msg_rcvd_vs_drops'><i class='fas fa-chart-area fa-sm'></i></A>", "").."</th><td width=20%><span id=if_zmq_msg_rcvd>"..formatValue(ifstats.zmqRecvStats.zmq_msg_rcvd).."</span></td>")
      print("<th nowrap> <i class='fas fa-tint' aria-hidden='true'></i> "..i18n("if_stats_overview.zmq_message_drops")..ternary(charts_available, " <A HREF='"..url.."&page=historical&ts_schema=custom:zmq_msg_rcvd_vs_drops'><i class='fas fa-chart-area fa-sm'></i></A>", "").."</th><td width=20%><span id=if_zmq_msg_drops>"..formatValue(ifstats.zmqRecvStats.zmq_msg_drops).."</span></td>")
      print("<td nowrap colspan=2> <b>"..i18n("if_stats_overview.zmq_avg_msg_flows").."</b>: <span id=if_zmq_avg_msg_flows></span></td>")
      print("</tr>")

      print("<tr>")
      print("<th nowrap>"..i18n("if_stats_overview.interface_rx_updates").."</th><td width=20%><span id=if_zmq_events>"..formatValue(ifstats.zmqRecvStats.events).."</span></td>")
      print("<th nowrap>"..i18n("if_stats_overview.sflow_counter_updates").."</th><td width=20%><span id=if_zmq_counters>"..formatValue(ifstats.zmqRecvStats.counters).."</span></td>")
      print("<td colspan=2></td>")
      print("</tr>")
   end

   print("<tr><th colspan=7 nowrap>"..i18n("if_stats_overview.traffic_statistics").."</th></tr>\n")

   print("<tr><th nowrap>"..i18n("report.traffic_anomalies")..ternary(charts_available, " <A HREF='"..url.."&page=historical&ts_schema=iface:hosts_anomalies'><i class='fas fa-chart-area fa-sm'></i></A>", "").."</th>")
   print("<th nowrap>"..i18n("report.traffic_anomalies_local_hosts").."</th><td><span id=local_hosts_anomalies>"..formatValue(ifstats.anomalies.num_local_hosts_anomalies).."</span> <span id=local_hosts_anomalies_trend></span></td>")
   print("<th colspan=2 nowrap>"..i18n("report.traffic_anomalies_remote_hosts").."</th><td><span id=remote_hosts_anomalies>"..formatValue(ifstats.anomalies.num_remote_hosts_anomalies).."</span> <span id=remote_hosts_anomalies_trend></span></td>")
   print("</tr>\n")

   
   print("<tr><th nowrap>"..i18n("report.total_traffic")..ternary(charts_available, " <A HREF='"..url.."&page=historical&ts_schema=iface:traffic'><i class='fas fa-chart-area fa-sm'></i></A>", "").."</th><td width=20%><span id=if_bytes>"..bytesToSize(ifstats.stats.bytes).."</span> [<span id=if_pkts>".. formatValue(ifstats.stats.packets) .. " ".. label .."</span>] ")

   print("<span id=pkts_trend></span></td>")

   if not is_sub_interface then
      print("<th width=20%><span id='if_packet_drops_drop'><i class='fas fa-tint' aria-hidden='true'></i></span> ")

      print(i18n("if_stats_overview.dropped_packets")..ternary(charts_available, " <A HREF='"..url.."&page=historical&ts_schema=iface:packets_vs_drops'><i class='fas fa-chart-area fa-sm'></i></A>", "").."</th>")

      print("<td width=20% colspan=3><span id=if_drops>")

      if(ifstats.stats.drops > 0) then
         print('<span class="badge bg-danger">')
      end

      print(formatValue(ifstats.stats.drops).. " " .. label)

      if((ifstats.stats.packets+ifstats.stats.drops) > 0) then
         local pctg = round((ifstats.stats.drops*100)/(ifstats.stats.packets+ifstats.stats.drops), 2)
         if(pctg > 0) then print(" [ " .. pctg .. " % ] ") end
      end

      if(ifstats.stats.drops > 0) then print('</span>') end
      print("</span>&nbsp;<span id=drops_trend></span>")

      if(ifstats.zmqRecvStats ~= nil) then
       print([[
        <small>
        <details class='mt-2'>
         <summary>
            <span class="ntop_notes" data-bs-toggle="tooltip" data-placement="right" title=']].. i18n("click_to_expand") ..[['>
               ]]..i18n("notes")..[[ <i class='fas fa-question-circle '></i>
            </span>
         </summary>
         <p>]]..i18n("if_stats_overview.note_drops_sflow")..[[</p>
        </details>
        </small>
       ]])
      end

      print("</td>")
   else
      print("<td width=20% colspan=3>")
      print("<small><b>"..i18n("if_stats_overview.note")..":</b> "..i18n("if_stats_overview.note_drop_ifstats_dynamic").."</small>")
      print("</td>")
   end

   print("</tr>")

   if(ifstats.has_traffic_directions) then
      local tx = ifstats.eth.egress.bytes
      local rx = ifstats.eth.ingress.bytes
      local tot = rx+tx
      
      print("<tr><th nowrap>"..i18n("http_page.traffic_sent")..ternary(charts_available, " <A HREF='"..url.."&page=historical&ts_schema=iface:traffic_rxtx'><i class='fas fa-chart-area fa-sm'></i></A>", "").."</th><td width=20%><span id=if_out_bytes>"..bytesToSize(tx).."</span> [<span id=if_out_pkts>".. formatValue(ifstats.eth.egress.packets) .. " ".. label .."</span>] <span id=pkts_out_trend></span></td>")
      print("<th nowrap>"..i18n("http_page.traffic_received")..ternary(charts_available, " <A HREF='"..url.."&page=historical&ts_schema=iface:traffic_rxtx'><i class='fas fa-chart-area fa-sm'></i></A>", "").."</th><td width=20%><span id=if_in_bytes>"..bytesToSize(rx).."</span> [<span id=if_in_pkts>".. formatValue(ifstats.eth.ingress.packets) .. " ".. label .."</span>] <span id=pkts_in_trend></span></td>")


      print('<td colspan=2><div class="progress"><div class="progress-bar bg-warning" style="width: ' .. (tx * 100 / tot) .. '%;">'.. i18n("sent") ..'</div>')
      print('<div class="progress-bar bg-success" style="width: ' .. (rx * 100 / tot) .. '%;">'.. i18n("received")..'</div></div></td>')


      print("</tr>")
   end

   if interface.isSyslogInterface() then
      -- Syslog Stats
      print("<tr>")
      print("<th colspan=7 nowrap>"..i18n("if_stats_overview.syslog_statistics").."</th>")
      print("</tr>")

      print("<tr>")
      print("<th nowrap>"..i18n("if_stats_overview.collected_logs").."</th>")
      print("<td><span id=syslog_tot_events>"..formatValue(ifstats.syslog.tot_events).."</span></td>")
      print("<th>"..i18n("if_stats_overview.dispatched_logs").."</th>")
      print("<td><span id=syslog_dispatched>"..formatValue(ifstats.syslog.dispatched).."</span></td>")
      print("<th>"..i18n("if_stats_overview.unhandled_logs").."</th>")
      print("<td><span id=syslog_unhandled>"..formatValue(ifstats.syslog.unhandled).."</span></td>")
      print("</tr>")

      print("<tr>")
      print("<th nowrap>"..i18n("if_stats_overview.malformed_logs").."</th>")
      print("<td><span id=syslog_malformed>"..formatValue(ifstats.syslog.malformed).."</span></td>")
      print("<th>"..i18n("if_stats_overview.host_correlations").."</th>")
      print("<td><span id=syslog_host_correlations>"..formatValue(ifstats.syslog.host_correlations).."</span></td>")
      print("<th>"..i18n("if_stats_overview.alert_events").."</th>")
      print("<td><span id=syslog_alerts>"..formatValue(ifstats.syslog.alerts).."</span></td>")
      print("</tr>")

      -- Additional stats (e.g. Suricata)
      local external_json_stats = ntop.getCache("ntopng.prefs.ifid_"..tostring(ifid)..".external_stats")
      if not isEmptyString(external_json_stats) then
         local external_stats = json.decode(external_json_stats)
         if external_stats ~= nil then
            local external_stats_title = i18n("external_stats.title")
            if external_stats.i18n_title then
              external_stats_title = i18n(external_stats.i18n_title)
              external_stats.i18n_title = nil
            end
            print("<tr><th colspan=7 nowrap>"..external_stats_title.."</th></tr>\n")
            for key, value in pairsByKeys(external_stats, asc) do
               print("<tr>")
               print("<th nowrap>"..ternary(i18n("external_stats."..key), i18n("external_stats."..key), key).."</th>")
               print("<td colspan=4>"..ternary(type(value) == "number", formatValue(value), value).."</td>")
               print("</tr>")
            end
         end
      end
   end

   if prefs.is_dump_flows_enabled and not ifstats.isViewed then
      local dump_to = ""
      
      if prefs.is_dump_flows_to_clickhouse_enabled then
         dump_to = "ClickHouse"
      elseif prefs.is_dump_flows_to_mysql_enabled then
         dump_to = "MySQL"
      elseif prefs.is_dump_flows_to_es_enabled == true then
         dump_to = "ElasticSearch"
      elseif prefs.is_dump_flows_to_syslog_enabled == true then
         dump_to = "Syslog"
      end

      local export_count     = ifstats.stats.flow_export_count
      local export_rate      = ifstats.stats.flow_export_rate
      local export_drops     = ifstats.stats.flow_export_drops
      local export_drops_pct = 0

      if not export_drops or not export_count then
         -- Nothing to do
      elseif export_drops > 0 and export_count > 0 then
         export_drops_pct = export_drops / (export_count + export_drops) * 100
      elseif export_drops > 0 then
         export_drops_pct = 100
      end

      print("<tr><th colspan=7 nowrap>")
      if prefs.is_dump_flows_runtime_enabled then
         print(dump_to.." "..i18n("if_stats_overview.flows_export_statistics"))
      else
         print(i18n("if_stats_overview.export_disabled"))
      end
      print("</th></tr>\n")

      print("<tr>")
      print("<th nowrap>"..i18n("if_stats_overview.exported_flows")..ternary(charts_available, " <A HREF='"..url.."&page=historical&ts_schema=iface:dumped_flows'><i class='fas fa-chart-area fa-sm'></i></A>", "").."</th>")
      print("<td><span id=exported_flows>"..formatValue(export_count).."</span>")
      if export_rate == nil then
         export_rate = 0
      end
      print("&nbsp;[<span id=exported_flows_rate>"..formatValue(round(export_rate, 2)).." fps</span>]</td>")

      print("<th><span id='if_flow_drops_drop'<i class='fas fa-tint' aria-hidden='true'></i></span> ")
      print(i18n("if_stats_overview.dropped_flows")..ternary(charts_available, " <A HREF='"..url.."&page=historical&ts_schema=iface:dumped_flows'><i class='fas fa-chart-area fa-sm'></i></A>", "").."</th>")

      local span_danger = ""
      if not export_drops then
      elseif export_drops > 0 then
         span_danger = ' class="badge bg-danger"'
      end

      print("<td><span id=exported_flows_drops "..span_danger..">"..formatValue(export_drops).."</span>&nbsp;")
      print("<span id=exported_flows_drops_pct "..span_danger..">["
               ..formatValue(round(export_drops_pct, 2)).."%]</span></td>")

      if not is_packet_interface then
         print("<th nowrap>"..i18n("if_stats_overview.direct_mode").."</th>")
         print("<td>"..ternary(prefs.is_dump_flows_direct_enabled, i18n("enabled"), i18n("disabled")).."</td>")
      else
         print("<td colspan=2></td>")
      end

      print("</tr>")
   end

   if ifstats.stats.discarded_probing_packets then
      print("<tr><td colspan=2></td><th nowrap> <i class='fas fa-trash' aria-hidden='true'></i> "..i18n("if_stats_overview.discarded_probing_traffic")..ternary(charts_available, " <A HREF='"..url.."&page=historical&ts_schema=iface:disc_prob_pkts'><i class='fas fa-chart-area fa-sm'></i></A>", "").."</th><td colspan=4width=20%><span id=if_discarded_probing_bytes>"..bytesToSize(ifstats.stats.discarded_probing_bytes).."</span> [<span id=if_discarded_probing_pkts>".. formatPackets(ifstats.stats.discarded_probing_packets) .."</span>] ")

      print("<span id=if_discarded_probing_trend></span></td></tr>\n")
   end

   local an = ifstats.anomalies.tot_num_anomalies or {}
   local tot_an = 0
   
   if table.len(an) > 0 then
      tot_an = an.local_hosts + an.remote_hosts
   end

   if(tot_an > 0) then
      -- TODO: Add JSON update
      print("<tr><th>"..i18n("if_stats_overview.counter_anomalies").."</th>")
      print("<td><b>"..i18n("total").."</b>: <span id=total_anomalies>"..formatValue(tot_an).."</span><span id=total_anomalies_trend></span></td>")
      print("<td><b>"..i18n("local_hosts").."</b>: <span id=local_anomalies>"..formatValue(an.local_hosts).."</span><span id=local_anomalies_trend></span></td>")
      print("<td><b>"..i18n("remote_hosts").."</b>: <span id=remote_anomalies>"..formatValue(an.remote_hosts).."</span><span id=remote_anomalies_trend></span></td>")
      print("<td>&nbsp;</td></tr>\n") -- Cell for future usage
   end

   if isAdministrator() and ifstats.isView == false then

      -- Traffic recording

      if has_traffic_recording_page then
         print("<tr><th colspan=7 nowrap>"..i18n("traffic_recording.traffic_recording").."</th></tr>\n")

         -- Traffic Recording stats

         local stats = recording_utils.stats(ifstats.id)

         local first_epoch = stats['FirstDumpedEpoch']
         local last_epoch = stats['LastDumpedEpoch']
         local start_time = stats['StartEpoch']

         if first_epoch ~= nil then
            print("<tr><th>"..i18n("traffic_recording.dump_window").."</th><td colspan=5>")
            if first_epoch ~= nil and last_epoch ~= nil and 
               first_epoch > 0 and last_epoch > 0 then
               print(formatEpoch(first_epoch).." - "..formatEpoch(last_epoch))
            else
               print(i18n("traffic_recording.no_file"))
            end
            print("</td></tr>\n")
         end

         if start_time ~= nil then
            print("<tr><th>"..i18n("traffic_recording.active_since").."</th><td colspan=5>"..formatEpoch(start_time))
            if (start_time ~= nil) and (first_epoch ~= nil) and (first_epoch > 0) and (start_time > first_epoch) then
               print(' - <i class="fas fa-exclamation-triangle"></i> ')
               print(i18n("traffic_recording.missing_data_msg"))
            end
            print("</td></tr>\n")
         end

         -- Smart Recording stats

         stats = recording_utils.smartStats(ifstats.id)

         first_epoch = stats['FirstDumpedEpoch']
         last_epoch = stats['LastDumpedEpoch']

         if first_epoch ~= nil then
            print("<tr><th>"..i18n("traffic_recording.smart_window").."</th><td colspan=5>")
            if first_epoch ~= nil and last_epoch ~= nil and 
               first_epoch > 0 and last_epoch > 0 then
               print(formatEpoch(first_epoch).." - "..formatEpoch(last_epoch))
            else
               print(i18n("traffic_recording.no_file"))
            end
            print("</td></tr>\n")
         end

      end

      -- Storage utilization

      local ts_utils = require "ts_utils_core"
      local storage_info = storage_utils.interfaceStorageInfo(ifid)
      local storage_items = {}

      if storage_info then
         if ts_utils.getDriverName() == "rrd" then
            if storage_info.rrd ~= nil and storage_info.rrd > 0 then
               table.insert(storage_items, {
                               title = i18n("if_stats_overview.rrd_timeseries"),
                               value = storage_info.rrd,
                               class = "primary",
               })
            end
         end

         if storage_info.flows ~= nil and storage_info.flows > 0 then
            table.insert(storage_items, {
                            title = i18n("flows"),
                            value = storage_info.flows,
                            class = "info",
            })
         end

         if storage_info.pcap ~= nil and storage_info.pcap > 0 then
            local link = nil

            if recording_utils.isAvailable(ifstats.id) then
               link = ntop.getHttpPrefix() .. "/lua/if_stats.lua?ifid=" .. ifid .. "&page=traffic_recording"
            end

            table.insert(storage_items, {
                            title = i18n("traffic_recording.packet_dumps"),
                            value = storage_info.pcap,
                            class = "warning",
                            link = link
            })
         end

         if #storage_items > 0 then
            print("<tr><th>"..i18n("traffic_recording.storage_utilization").."</th><td colspan=5>")
            print(graph_utils.stackedProgressBars(storage_info.total, storage_items, nil, bytesToSize))
            print("</td></tr>\n")
         end

      end
   end

   if ntop.isPcapDownloadAllowed() and ifstats.isView == false and not is_sub_interface and is_packet_interface then
      print("<tr><th>"..i18n("live_capture.live_capture").."&nbsp;<i class=\"fas fa-download fa-lg\"></i></th><td colspan=5>")

      local live_traffic_utils = require("live_traffic_utils")
      live_traffic_utils.printLiveTrafficForm(interface.getId())

      print("</td></tr>\n")
   end

   if isAdministrator() then
      print("<tr><th width=250>"..i18n("if_stats_overview.reset_counters").."</th>")
      print("<td colspan=5>")

      local tot        = ifstats.stats.bytes + ifstats.stats.packets + ifstats.stats.drops
      if(ifstats.stats.flow_export_count ~= nil) then
         tot = tot + ifstats.stats.flow_export_count + ifstats.stats.flow_export_drops
      end

      print('<button id="btn_reset_all" type="button" class="btn btn-danger" onclick="resetInterfaceCounters(false);">'..i18n("if_stats_overview.all_counters")..'</button>&nbsp;')

      print('<button id="btn_reset_drops" type="button" class="btn btn-danger" onclick="resetInterfaceCounters(true);">'..i18n("if_stats_overview.drops_only")..'</button>')
      print("</td>")

      print("</tr>\n")
   end

   if have_nedge and ifstats.type == "netfilter" and ifstats.netfilter then
      local st = ifstats.netfilter

      print("<tr><th nowrap>"..i18n("if_stats_overview.nf_queue_total").."</th>")
      local span_class = ''
      if st.nfq.queue_pct > 80 then
         span_class = "class='badge bg-danger'"
      end
      print("<td width=20%><span id=nfq_queue_total "..span_class..">"..string.format("%s [%s %%]", formatValue(st.nfq.queue_total), formatValue(st.nfq.queue_pct)).." </span> <span id=nfq_queue_total_trend></span></td>")
      print("<th nowrap>"..i18n("if_stats_overview.nf_handle_packet_failed").."</th>")
      print("<td width=20%><span id=nfq_handling_failed>"..formatValue(st.failures.handle_packet).."</span> <span id=nfq_handling_failed_trend></span></td>")
      print("<th nowrap>"..i18n("if_stats_overview.nf_enobufs").."</th>")
      print("<td width=20%><span id=nfq_enobufs>"..formatValue(st.failures.no_buffers).."</span> <span id=nfq_enobufs_trend></span></td>")
      print("</tr>")

      print("<tr><th nowrap>") print(i18n("if_stats_overview.conntrack_flow_entries")) print("</th><td colspan=5>")
      print("<span id=num_conntrack_entries>"..formatValue(st.nfq.num_conntrack_entries).."</span></td>")
      print("</tr>")
   end

   print [[
   <tr><td colspan=7> <small> <b>]] print(i18n("if_stats_overview.note").."</b>:<p>"..i18n("if_stats_overview.note_packets")) print[[</small> </td></tr>
   ]]

   print("</table>\n")
   print("</div>") -- close of table responsive

elseif page == "nprobe" and not is_packet_interface and not interface.isView() then
   local context = {
   }
   print(template.gen("pages/interface_nprobes.template", context))
elseif page == "networks" and is_packet_interface then

   print("<table class=\"table table-striped table-bordered\">")

   if(ifstats.ip_addresses ~= "") then
      local tokens = split(ifstats.ip_addresses, ",")

      if(tokens ~= nil) then
         print("<tr><th width=250>"..i18n("ip_address").."</th><td colspan=5><ul><li>")
         local addresses = {}

         for _,s in pairs(tokens) do
            t = string.split(s, "/")
            host = interface.getHostInfo(t[1])

            if(host ~= nil) then
               addresses[#addresses+1] = hostinfo2detailshref(host, nil, t[1]).."/"..t[2]
            else
               addresses[#addresses+1] = s
            end
         end

         print(table.concat(addresses, "\n<li>"))

         print("</ul></td></tr>")
      end
   else
      print("<tr><th width=250>"..i18n("ip_address").."</th><td colspan=5>")
      print(i18n("if_stats_networks.no_ip_addresses_read"))
      print("</td></tr>")
   end

   local has_ghost_networks = false
   local ghost_icon = '<font color=red><i class="fas fa-ghost" aria-hidden="true"></i></font>'
   if ifstats.bcast_domains and table.len(ifstats.bcast_domains) > 0 then
      print("<tr><th width=250>"..i18n("broadcast_domain").."</th><td colspan=5>")

      local bcast_domains = {}
      for bcast_domain, domain_info in pairsByKeys(ifstats.bcast_domains) do
         bcast_domain = string.format("<a href='%s/lua/hosts_stats.lua?network_cidr=%s'>%s</a>", ntop.getHttpPrefix(), bcast_domain, bcast_domain)

         if domain_info.ghost_network then
            has_ghost_networks = true
            bcast_domain = bcast_domain..' '..ghost_icon
         end

         bcast_domains[#bcast_domains + 1] = bcast_domain
      end

      if #bcast_domains > 0 then
         print("<ul>")
         for _, bcast_domain in ipairs(bcast_domains) do
            print("<li>"..bcast_domain.."</li>")
         end
         print("</ul>")
      end

      print("</td></tr>")
   else
      print("<tr><th width=250>"..i18n("broadcast_domain").."</th><td colspan=5>")
      print(i18n('if_stats_networks.no_broadcast_domains'))
      print("</td></tr>")
   end
   print("</table>")

   print(ui_utils.render_notes({
      {content = i18n("if_stats_networks.note_iface_addresses")},
      {content = i18n("if_stats_networks.note_iface_bcast_domains")},
      {content = i18n("if_stats_networks.note_ghost_bcast_domains", {ghost_icon = ghost_icon}), hidden = not has_ghost_networks}
   }))

elseif((page == "packets")) then
   local nedge_hidden = ternary(have_nedge, 'class="hidden"', '')

   print [[ <table class="table table-bordered table-striped"> ]]
   print("<tr " .. nedge_hidden .. "><th width=30% rowspan=3>" .. i18n("packets_page.tcp_packets_analysis") .. "</th><th>" .. i18n("packets_page.retransmissions") .."</th><td align=right><span id=pkt_retransmissions>".. formatPackets(ifstats.tcpPacketStats.retransmissions) .. percentage(ifstats.tcpPacketStats.retransmissions, ifstats.stats.packets)  .. "</span> <span id=pkt_retransmissions_trend></span></td></tr>\n")
   
   print("<tr " .. nedge_hidden .. "></th><th>" .. i18n("packets_page.out_of_order") .. "</th><td align=right><span id=pkt_ooo>".. formatPackets(ifstats.tcpPacketStats.out_of_order) .. percentage(ifstats.tcpPacketStats.out_of_order, ifstats.stats.packets) .."</span> <span id=pkt_ooo_trend></span></td></tr>\n")
   print("<tr " .. nedge_hidden .. "></th><th>" .. i18n("packets_page.lost") .. "</th><td align=right><span id=pkt_lost>".. formatPackets(ifstats.tcpPacketStats.lost)  .. percentage(ifstats.tcpPacketStats.lost, ifstats.stats.packets) .."</span> <span id=pkt_lost_trend></span></td></tr>\n")

    if(ifstats.type ~= "zmq") then
      print [[<tr ]] print(nedge_hidden) print[[><th class="text-start">]] print(i18n("packets_page.size_distribution")) print [[</th><td colspan=5><div class="pie-chart" id="sizeDistro"></div></td></tr>]]
    end

    print[[
           <tr ]] print(nedge_hidden) print[[><th class="text-start">]] print(i18n("packets_page.version_vs_flags_distribution")) print[[</th>
<td colspan=1><div class="pie-chart" id="ipverDistro"></div></td><td colspan=1><div class="pie-chart" id="flagsDistro"></div></td></tr>
      </table>

        <script type='text/javascript'>
         window.onload=function() {

       do_pie("#sizeDistro", ']]
   print (ntop.getHttpPrefix())
   print [[/lua/if_pkt_distro.lua', { distr: "size", ifid: "]] print(ifstats.id.."\"")
   print [[
           }, "", refresh);

       do_pie("#flagsDistro", ']]
   print (ntop.getHttpPrefix())
   print [[/lua/if_tcpflags_pkt_distro.lua', { ifid: "]] print(ifstats.id.."\"")
   print [[
           }, "", refresh);

      do_pie("#ipverDistro", ']]
   print (ntop.getHttpPrefix())
   print [[/lua/if_pkt_distro.lua', { distr: "ipver", ifid: "]] print(ifstats.id.."\"")
   print [[
           }, "", refresh);
    }

      </script><p>
  ]]
elseif(page == "DSCP") then

  print [[
     <table id="dscp_table" class="table table-bordered table-striped tablesorter">
        <tr>
          <th class="text-start">]] print(i18n("dscp_page.statistics")) print [[</th>
          <td colspan=4><div class="pie-chart" id="dscpGroups"></td>
        </tr>
     </table>
<script>
 do_pie("#dscpGroups", ']] print (ntop.getHttpPrefix()) print [[/lua/rest/v2/get/interface/dscp/stats.lua', { ifid: "]] print(ifid) print [[" }, "", refresh);
</script>

]]

elseif(page == "ndpi") then
print[[
   <div class='card'>
   <div class='card-header'>
  <ul id="ndpiNav" class="nav nav-tabs card-header-tabs" role="tablist">
    <li class="nav-item active"><a class="nav-link active" data-bs-toggle="tab" role="tab" href="#applications" active>]] print(i18n("applications")) print[[</a></li>
    <li class="nav-item"><a class="nav-link" data-bs-toggle="tab" role="tab" href="#categories">]] print(i18n("categories")) print[[</a></li>
  </ul>
  </div>
  <div class='card-body tab-content'>
    <div id="applications" class="tab-pane in active">

      <table class="table table-bordered table-striped">
]]

   if ntop.isPro() and ifstats["custom_apps"] then
      print[[
        <tr>
          <th class="text-start">]] print(i18n("ndpi_page.overview", {what = i18n("ndpi_page.custom_applications")})) print [[</th>
          <td colspan=5><div class="pie-chart" id="topCustomApps"></td>
        </tr>
]]
   end

   print[[
        <tr>
          <th class="text-start">]] print(i18n("ndpi_page.overview", {what = i18n("applications")})) print [[</th>
          <td colspan=3><div class="pie-chart" id="topApplicationProtocols"></td>
          <td colspan=2><div class="pie-chart" id="topApplicationBreeds"></td>
        </tr>
        <tr>
          <th class="text-start">]] print(i18n("ndpi_page.live_flows_count")) print [[</th>
          <td colspan=3><div class="pie-chart" id="topFlowsCount"></td>
          <td colspan=2><div class="pie-chart" id="topTCPFlowsStats">
          <br><small><b>]] print(i18n("ndpi_page.note")) print [[ :</b>]] print(i18n("ndpi_page.note_live_flows_chart")) print [[
          </td>
        </tr>
      </table>
     <table id="if_stats_ndpi" class="table table-bordered table-striped tablesorter">
       <thead>
         <tr>
           <th>]] print(i18n("application")) print[[</th>
           <th class='text-end'>]] print(i18n("ndpi_page.total_since_startup")) print[[</th>
           <th>]] print(i18n("percentage")) print[[</th>
         </tr>
       </thead>
       <tbody id="if_stats_ndpi_tbody"></tbody>
     </table>
    </div>
    <div id="categories" class="tab-pane">
      <table class="table table-bordered table-striped">
        <tr>
          <th class="text-start">]] print(i18n("ndpi_page.overview", {what = i18n("categories")})) print [[</th>
          <td colspan=5><div class="pie-chart" id="topApplicationCategories"></td>
        </tr>
      </table>
     <table id="if_stats_ndpi_categories" class="table table-bordered table-striped tablesorter">
       <thead>
         <tr>
           <th>]] print(i18n("category")) print[[</th>
           <th>]] print(i18n("applications")) print[[</th>
           <th class='text-end'>]] print(i18n("ndpi_page.total_since_startup")) print[[</th>
           <th>]] print(i18n("percentage")) print[[</th>
         </tr>
       </thead>
       <tbody id="if_stats_ndpi_categories_tbody"></tbody>
     </table>
    </div>
    </div>
    </div>

]]

print [[
        <script type='text/javascript'>
         window.onload=function() {]]

   if ntop.isPro() and ifstats["custom_apps"] then
      print[[do_pie("#topCustomApps", ']]
      print (ntop.getHttpPrefix())
      print [[/lua/pro/get_custom_app_stats.lua', { ifid: "]] print(ifid) print [[" }, "", refresh);
]]
   end

   print[[do_pie("#topApplicationProtocols", ']]
   print (ntop.getHttpPrefix())
   print [[/lua/rest/v2/get/interface/l7/stats.lua', { ndpistats_mode: "sinceStartup", ifid: "]] print(ifid) print [[" }, "", refresh);

       do_pie("#topApplicationBreeds", ']]
   print (ntop.getHttpPrefix())
   print [[/lua/rest/v2/get/interface/l7/stats.lua', { breed: "true", ndpistats_mode: "sinceStartup", ifid: "]] print(ifid) print [[" }, "", refresh);

       do_pie("#topApplicationCategories", ']]
   print (ntop.getHttpPrefix())
   print [[/lua/rest/v2/get/interface/l7/stats.lua', { ndpi_category: "true", ndpistats_mode: "sinceStartup", ifid: "]] print(ifid) print [[" }, "", refresh);

       do_pie("#topFlowsCount", ']]
   print (ntop.getHttpPrefix())
   print [[/lua/rest/v2/get/interface/l7/stats.lua', { breed: "true", ndpistats_mode: "count", ifid: "]] print(ifid) print [[" }, "", refresh);

       do_pie("#topTCPFlowsStats", ']]
   print (ntop.getHttpPrefix())
   print [[/lua/iface_tcp_stats.lua', { ifid: "]] print(ifid) print [[" }, "", refresh);
    }

function update_ndpi_table() {
  $.ajax({
    type: 'GET',
    url: ']]
   print (ntop.getHttpPrefix())
   print [[/lua/if_stats_ndpi.lua',
    data: { ifid: "]] print(ifid) print [[" },
    success: function(content) {
      if(content) {
         $('#if_stats_ndpi_tbody').html(content);
         // Let the TableSorter script know that we updated the table
         $('#if_stats_ndpi_tbody').trigger("update");
      }
    }
  });
}
update_ndpi_table();
setInterval(update_ndpi_table, 5000);

function update_ndpi_categories_table() {
  $.ajax({
    type: 'GET',
    url: ']]
   print (ntop.getHttpPrefix())
   print [[/lua/if_stats_ndpi_categories.lua',
    data: { ifid: "]] print(ifid) print [[" },
    success: function(content) {
      if(content) {
         $('#if_stats_ndpi_categories_tbody').html(content);
         // Let the TableSorter script know that we updated the table
         $('#if_stats_ndpi_categories_tbody').trigger("update");
      }
    }
  });
}
update_ndpi_categories_table();
setInterval(update_ndpi_categories_table, 5000);

</script>
]]

elseif(page == "ICMP") then
print[[
  <div class='card'>
   <div class='card-header'>
  <ul id="icmp_nav" class="nav nav-tabs card-header-tabs" role="tablist">
    <li class="nav-item active"><a class="nav-link active" data-bs-toggle="tab" role="tab" href="#icmp" active>]] print(i18n("icmpv4")) print[[</a></li>
    <li class="nav-item"><a class="nav-link" data-bs-toggle="tab" role="tab" href="#icmpv6">]] print(i18n("icmpv6")) print[[</a></li>
  </ul>
  </div>
  <div class="card-body tab-content">
    <div id="icmp" class="tab-pane in active">
       <table id="icmp_table_4" class="table table-bordered table-striped tablesorter">
         <thead><tr><th>]] print(i18n("icmp_page.icmp_message")) print [[</th><th>]] print(i18n("icmp_page.icmp_type")) print [[</th><th>]] print(i18n("icmp_page.icmp_code")) print [[</th><th style='text-align:right;'>]] print(i18n("packets")) print[[</th></tr></thead>
         <tbody id="iface_details_icmp_tbody_4">
         </tbody>
       </table>
    </div>
    <div id="icmpv6" class="tab-pane">
       <table id="icmp_table_6" class="table table-bordered table-striped tablesorter">
         <thead><tr><th>]] print(i18n("icmp_page.icmp_message")) print [[</th><th>]] print(i18n("icmp_page.icmp_type")) print [[</th><th>]] print(i18n("icmp_page.icmp_code")) print [[</th><th style='text-align:right;'>]] print(i18n("packets")) print[[</th></tr></thead>
         <tbody id="iface_details_icmp_tbody_6">
         </tbody>
       </table>
    </div>
    </div>
    </div>

<script>
function update_icmp_table(ip_version) {
  var icmp_table_id = '#icmp_table_' + ip_version;
  var icmp_table_body_id = '#iface_details_icmp_tbody_' + ip_version;

  $.ajax({
    type: 'GET',
    url: ']]
  print(ntop.getHttpPrefix())
  print [[/lua/get_icmp_data.lua',
    data: { ifid: "]] print(interface.getId().."")  print [[", version: ip_version },
    success: function(content) {
      if(content) {
         $(icmp_table_body_id).html(content);
         $(icmp_table_id).trigger("update");
      }
    }
  });
}

function update_icmp_tables() {
  update_icmp_table(4);
  update_icmp_table(6);
}

update_icmp_tables();
setInterval(update_icmp_tables, 5000);
</script>

]]
elseif(page == "ARP") then
   local endpoint = string.format(ntop.getHttpPrefix() .. "/lua/rest/v2/get/interface/arp.lua?ifid=%s", ifId)
   local context = {
      json = json,
      template = template,
      sites = {
         endpoint = endpoint,
      }
   }
   
print(template.gen("pages/arp.template", context))


elseif(page == "sites") then
   if not prefs.are_top_talkers_enabled then
      local msg = i18n("sites_page.top_sites_not_enabled_message",{url=ntop.getHttpPrefix().."/lua/admin/prefs.lua?tab=protocols"})
      print("<div class='alert alert-info'><i class='fas fa-info-circle fa-lg' aria-hidden='true'></i> "..msg.."</div>")

   elseif table.len(sites_granularities) > 0 then
      local endpoint = string.format(ntop.getHttpPrefix() .. "/lua/pro/rest/v2/get/interface/top/sites.lua?ifid=%s", ifid)
      local context = {
         json = json,
         template = template,
         sites = {
            endpoint = endpoint,
            ifid = ifid,
            granularities = sites_granularities,
            default_granularity = "current"
         }
      }

      print(template.gen("pages/top_sites.template", context))

   else
      local msg = i18n("sites_page.top_sites_not_seen")
      print("<div class='alert alert-info'><i class='fas fa-info-circle fa-lg' aria-hidden='true'></i> "..msg.."</div>")
   end

elseif(page == "historical") then
   local source_value_object = { ifid = interface.getId() }
   graph_utils.drawNewGraphs(source_value_object)
elseif(page == "trafficprofiles") then
   
   print("<table class=\"table table-striped table-bordered\">\n")
   print("<tr><th width=15%><a href=\""..ntop.getHttpPrefix().."/lua/pro/admin/edit_profiles.lua\">" .. i18n("traffic_profiles.profile_name") .. "</A></th><th width=5%>" .. i18n("chart") .. "</th><th>" .. i18n("traffic") .. "</th></tr>\n")
   for pname,pbytes in pairsByKeys(ifstats.profiles, asc) do
     local trimmed = trimSpace(pname)
     local statschart_icon = ''

     if areInterfaceTimeseriesEnabled(ifid) then
         statschart_icon = '<A HREF=\"'..ntop.getHttpPrefix()..'/lua/profile_details.lua?profile='..pname..'\"><i class=\'fas fa-chart-area fa-lg\'></i></A>'
     end

     print("<tr><th>"..pname.."</th><td align=center>"..statschart_icon.."</td><td><span id=profile_"..trimmed..">"..bytesToSize(pbytes).."</span> <span id=profile_"..trimmed.."_trend></span></td></tr>\n")
   end

print [[
   </table>
   <script>
   let last_profile = [];
   const traffic_profiles_interval = window.setInterval(function() {
          $.ajax({
                    type: 'GET',
                    url: ']]
   print (ntop.getHttpPrefix())
   print [[/lua/rest/v2/get/interface/data.lua',
                    data: { iffilter: "]] print(tostring(interface.name2id(if_name))) print [[" },
                    success: function(content) {
                        if(content["rc_str"] == "OK" && content["rsp"] && content["rsp"]["profiles"] != null) {
                           const profiles = content["rsp"];

                           for (key in profiles["profiles"]) {
                             let k = '#profile_'+key.replace(" ", "");
                             const v = profiles["profiles"][key];
                             $(k).html(NtopUtils.bytesToVolume(v));
                             k += "_trend";
                             let last = last_profile[key];
                             if(last == null) { last = 0; }
                             $(k).html(NtopUtils.get_trend(last, v));
                           }

                           last_profile = profiles["profiles"];
                          }
                        }
          });
}, ]] print(interface.getStatsUpdateFreq(ifstats.id).."") print[[ * 1000);

   </script>
]]
elseif(page == "traffic_recording" and has_traffic_recording_page) then
   local master_ifid = interface.getMasterInterfaceId()

   if not dismiss_recording_providers_reminder then
      print('<div id="traffic-recording-providers-detected" class="alert alert-info alert-dismissable">'..i18n('traffic_recording.msg_external_providers_detected', {url = ntop.getHttpPrefix().."/lua/if_stats.lua?page=config"})..'<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>')

      print [[
  <script>
    $('#traffic-recording-providers-detected').on('close.bs.alert', function () {
         var dismiss_notice = $.ajax({
          type: 'POST',
          url: ']]
   print (ntop.getHttpPrefix())
   print [[/lua/traffic_recording_config.lua',
          data: { ifid: ]] print(tostring(master_ifid)) print[[,
                  dismiss_external_providers_reminder: true,
                  csrf: "]] print(ntop.getRandomCSRFValue()) print[["},
          success: function()  {},
          complete: function() {},
        });

    });
  </script>
]]
   end

   local tab = _GET["tab"] or "config"
   local recording_enabled = recording_utils.isEnabled(master_ifid)
   -- config tab is only shown when the recording service is managed by ntopng
   -- otherwise it is assumed that the user is managing the service manually with n2disk
   local config_enabled  = (recording_utils.getCurrentTrafficRecordingProvider(master_ifid) == "ntopng")

   if tab == "config" and not config_enabled then
      if recording_enabled then tab = "status" else tab = "" end
   end
   if (tab == "status" or tab == "jobs") and not recording_enabled then
      if config_enabled then tab = "config" else tab = "" end
   end

   if(_SERVER["REQUEST_METHOD"] == "POST") and (tab == "config") then
      recording_enabled = not isEmptyString(_POST["record_traffic"])
   end

   print('<ul id="traffic-recording-nav" class="nav nav-tabs" role="tablist">')

   if config_enabled then
      print('<li class="nav-item '.. ternary(tab == "config", "active", "") ..'"><a class="nav-link '.. ternary(tab == "config", "active", "") ..'" href="?ifid='.. ifstats.id
               ..'&page=traffic_recording"><i class="fas fa-cog"></i> '.. i18n("traffic_recording.settings") ..'</a></li>')
   end

   if recording_enabled then
      print('<li class="nav-item '.. ternary(tab == "status", "active", "") ..'"><a class="nav-link '.. ternary(tab == "status", "active", "") ..'" href="?ifid='.. ifstats.id
         ..'&page=traffic_recording&tab=status">'.. i18n("status") ..'</a></li>')

      if ntop.isEnterpriseM() then
         print('<li class="nav-item '.. ternary(tab == "jobs", "active", "") ..'"><a class="nav-link '.. ternary(tab == "jobs", "active", "") ..'" href="?ifid='.. ifstats.id
            ..'&page=traffic_recording&tab=jobs">'.. i18n("traffic_recording.jobs") ..'</a></li>')
      end
   end

   print('</ul>')
   print('<div class="tab-content">')

   if recording_enabled and tab == "status" then
      dofile(dirs.installdir .. "/scripts/lua/inc/traffic_recording_status.lua")
   elseif recording_enabled and ntop.isEnterpriseM() and tab == "jobs" then
      dofile(dirs.installdir .. "/scripts/lua/inc/traffic_recording_jobs.lua")
   elseif config_enabled and tab == "config" then -- config, default
      dofile(dirs.installdir .. "/scripts/lua/inc/traffic_recording_config.lua")
   end

   print('</div></div>')
elseif(page == "config") then
   if(not isAdministrator()) then
      return
   end

   local messages = {}

   -- Flow dump check
   local interface_flow_dump = true
   if prefs.is_dump_flows_enabled then
      interface_flow_dump = (ntop.getPref("ntopng.prefs.ifid_"..interface.getId()..".is_flow_dump_disabled") ~= "1")

      if _SERVER["REQUEST_METHOD"] == "POST" then
         local new_value = (_POST["interface_flow_dump"] == "1")

         if new_value ~= interface_flow_dump then
            -- Value changed
            interface_flow_dump = new_value
            ntop.setPref("ntopng.prefs.ifid_"..interface.getId()..".is_flow_dump_disabled", ternary(interface_flow_dump, "0", "1"))

            messages[#messages + 1] = {
             type = "warning",
             text = i18n("prefs.restart_needed", {product=info.product}),
           }
         end
      end
   end

   if not table.empty(messages) then
      printMessageBanners(messages)
      print("<br>")
   end

   print[[
   <form id="iface_config" lass="form-inline" method="post">
   <input name="csrf" type="hidden" value="]] print(ntop.getRandomCSRFValue()) print[[" />
   <table id="iface_config_table" class="table table-bordered table-striped">]]

   if ((not is_pcap_dump) and
       (ifstats.name ~= nil) and
       (ifstats.name ~= "dummy")) then
      -- Custom name
      print[[
      <tr>
         <th>]] print(i18n("if_stats_config.custom_name")) print[[</th>
         <td>]]
      local label = getHumanReadableInterfaceName(ifstats.name)
      inline_input_form("custom_name", "Custom Name",
         i18n("if_stats_config.custom_name_popup_msg"),
         label, isAdministrator(), 'autocorrect="off" spellcheck="false"')
      print[[
         </td>
      </tr>]]

      -- Interface speed
      if not have_nedge then
        print[[
        <tr>
           <th>]] print(i18n("if_stats_config.interface_speed")) print[[</th>
           <td>]]
        local ifspeed = getInterfaceSpeed(ifstats.id)
        inline_input_form("ifSpeed", "Interface Speed",
           i18n("if_stats_config.interface_speed_popup_msg"),
           ifspeed, isAdministrator(), 'type="number" min="1"', "d-inline-block", "if_stats_config.interface_speed_measure_unit")
        print[[
           </td>
        </tr>]]
      end
   end

   if not have_nedge then
     -- Scaling factor
     if is_packet_interface and not have_nedge then
        local label = ntop.getCache("ntopng.prefs.iface_"..tostring(ifid)..".scaling_factor")
        if((label == nil) or (label == "")) then label = "1" end

        print[[
        <tr>
           <th>]] print(i18n("if_stats_config.scaling_factor")) print[[</th>
           <td>]]
        inline_input_form("scaling_factor", "Scaling Factor",
           i18n("if_stats_config.scaling_factor_popup_msg"),
           label, isAdministrator(), 'type="number" min="1" step="1"')
        print[[
           </td>
        </tr>]]
     end
   end

   local serialize_by_mac
   local serialize_by_mac_key = string.format("ntopng.prefs.ifid_%u.serialize_local_broadcast_hosts_as_macs", interface.getId())

   if(_POST["lbd_hosts_as_macs"] ~= nil) then
      serialize_by_mac = _POST["lbd_hosts_as_macs"]

      if ntop.getPref(serialize_by_mac_key) ~= serialize_by_mac then
         ntop.setPref(serialize_by_mac_key, serialize_by_mac)
         interface.updateLbdIdentifier()
      end
   else
      serialize_by_mac = ntop.getPref(serialize_by_mac_key)
   end

   -- LBD identifier
     print[[
        <tr>
           <th width="30%">]] print(i18n("prefs.toggle_host_tskey_title")) print[[ <i class="fas fa-question-circle " title="]] print(i18n("prefs.toggle_host_tskey_description")) print[["></i></th>
           <td>]]
      inline_select_form("lbd_hosts_as_macs", {i18n("ip_address"), i18n("mac_address")}, {"0", "1"}, serialize_by_mac)
        print[[
           </td>
        </tr>]]

   -- Automatic Reports
   if isAdministrator() and ntop.isEnterpriseL() then
      package.path = dirs.installdir .. "/pro/scripts/lua/enterprise/modules/?.lua;" .. package.path
      local reports_utils = require "reports_utils"
      
      if toboolean(ntop.getPref("ntopng.prefs.automatic_reports_enabled")) then
         local automatic_reports_creation

         if _SERVER["REQUEST_METHOD"] == "POST" then
            automatic_reports_creation = toboolean(_POST["automatic_reports_creation"])
            reports_utils.toggle_automatic_reports(ifid, automatic_reports_creation)
         else
            automatic_reports_creation = reports_utils.automatic_reports_enabled(ifid)
         end

         print [[<tr>
            <th>]] print(i18n("if_stats_config.automatic_reports_creation")) print[[</th>
            <td>]]

         print(template.gen("on_off_switch.html", {
            id = "automatic_reports_creation",
            checked = automatic_reports_creation,
          }))

         print[[
               </td>
         </tr>]]
      end
   end

   -- per-interface Top-Talkers generation
   local interface_top_talkers_creation = true

   if _SERVER["REQUEST_METHOD"] == "POST" then
      if _POST["interface_top_talkers_creation"] ~= "1" then
         interface_top_talkers_creation = false
         top_talkers_utils.disableTop(interface.getId())
      else
         top_talkers_utils.enableTop(interface.getId())
      end
   else
      if not top_talkers_utils.areTopEnabled(interface.getId()) then
         interface_top_talkers_creation = false
      end
   end

   print [[<tr>
         <th>]] print(i18n("if_stats_config.interface_top_talkers_creation")) print[[</th>
         <td>]]

   print(template.gen("on_off_switch.html", {
         id = "interface_top_talkers_creation",
         checked = interface_top_talkers_creation,
       }))

   print[[
            </td>
      </tr>]]

   -- Flow dump
   if prefs.is_dump_flows_enabled then
      print [[<tr>
         <th>]] print(i18n("if_stats_config.dump_flows_to_database")) print[[</th>
         <td>]]

      print(template.gen("on_off_switch.html", {
         id = "interface_flow_dump",
         checked = interface_flow_dump,
       }))

      print[[
         </td>
      </tr>]]
   end

   -- Mirrored Traffic
   if not ntop.isnEdge() and is_packet_interface then
      print [[<tr>
         <th>]] print(i18n("if_stats_config.is_mirrored_traffic")) print[[</th>
    <td>]]

      print(template.gen("on_off_switch.html", {
         id = "is_mirrored_traffic",
         checked = is_mirrored_traffic,
       }))

      print[[

<script type="text/javascript">
$("#is_mirrored_traffic").change(function(e) {
  const value = $(this).is(":checked");
  if (value) {
    toggle_mirrored_traffic_function_on();
  } else {
    toggle_mirrored_traffic_function_off();
  }
});

function toggle_mirrored_traffic_function_on(){
  $(`#is_mirrored_traffic`).val("1");
  $("#gw_macs_tr").css("display","table-row");
}

function toggle_mirrored_traffic_function_off(){
  $(`#is_mirrored_traffic`).val("0");
  $("#gw_macs_tr").css("display","none");
}
</script>

         </td>
      </tr>]]

      -- Gw Macs for Address-Based Traffic Directions
      local rv = ntop.getMembersCache(getGwMacsSet(ifstats.id)) or {}
      local members = {}

      -- impose sort order
      for _, addr in pairsByValues(rv, asc) do
         members[#members + 1] = addr
      end

      local gw_macs = table.concat(members, ",")

      print([[
        <tr id="gw_macs_tr" style="display: ]] .. ternary(is_mirrored_traffic, "table-row", "none") .. [[;">
           <th>]] .. i18n("if_stats_config.gw_macs") .. [[</th>
           <td>]])

      print('<input style="width:36em;" class="form-control" name="gw_macs" placeholder="'..i18n("if_stats_config.gw_macs_example", {example="00:11:22:33:44:55,00:11:22:33:44:66"})..'" value="' .. gw_macs .. '">')

      print([[
        <small>
        <details class='mt-2'>
         <summary>
            <span class="ntop_notes" data-bs-toggle="tooltip" data-placement="right" title=']].. i18n("click_to_expand") ..[['>
               ]]..i18n("notes")..[[ <i class='fas fa-question-circle '></i>
            </span>
         </summary>
         <p>]]..i18n("if_stats_config.gw_macs_description")..[[</p>
        </details>
        </small>
           </td>
        </tr>]])
   end

   -- Discard Probing Traffic
   if not ntop.isnEdge() and not is_packet_interface then
      local discard_probing_traffic = false
      local discard_probing_traffic_pref = string.format("ntopng.prefs.ifid_%d.discard_probing_traffic", interface.getId())

      if _SERVER["REQUEST_METHOD"] == "POST" then
         if _POST["discard_probing_traffic"] == "1" then
            discard_probing_traffic = true
         end

         ntop.setPref(discard_probing_traffic_pref,
                      ternary(discard_probing_traffic == true, '1', '0'))
         interface.updateDiscardProbingTraffic()
      else
         discard_probing_traffic = ternary(ntop.getPref(discard_probing_traffic_pref) == '1', true, false)
      end

      print [[<tr>
         <th>]] print(i18n("if_stats_config.discard_probing_traffic")) print[[</th>
    <td>]]

    print(template.gen("on_off_switch.html", {
         id = "discard_probing_traffic",
         checked = discard_probing_traffic,
    }))

    print[[
         </td>
      </tr>]]
   end

   -- per-interface Network Discovery
   if interface.isDiscoverableInterface() then
      local discover = require "discover_utils"

      local interface_network_discovery = true

      if _SERVER["REQUEST_METHOD"] == "POST" then
         if _POST["interface_network_discovery"] ~= "1" then
            interface_network_discovery = false
         end

         ntop.setPref(discover.getInterfaceNetworkDiscoveryEnabledKey(interface.getId()), tostring(interface_network_discovery))
      else
         interface_network_discovery = ntop.getPref(discover.getInterfaceNetworkDiscoveryEnabledKey(interface.getId()))

         if interface_network_discovery == "false" then
            interface_network_discovery = false
         end
      end

      print [[<tr>
         <th>]] print(i18n("if_stats_config.interface_network_discovery")) print[[</th>
    <td>]]

    print(template.gen("on_off_switch.html", {
         id = "interface_network_discovery",
         checked = interface_network_discovery,
    }))

    print[[
      </td>
      </tr>]]
   end

   if not is_sub_interface then
      local cur_companion = companion_interface_utils.getCurrentCompanion(ifstats.id)
      local companions = companion_interface_utils.getAvailableCompanions()

      if table.len(companions) > 1 then
         print [[
       <tr>
         <th>]] print(i18n("if_stats_config.companion_interface")) print[[</th>
         <td>
           <select name="companion_interface" class="form-select" style="width:36em; display:inline;">]]

         for _, companion in ipairs(companions) do
            local companion_id = companion["ifid"]
            local companion_name = companion["ifname"]
            local label = companion_name
            if companion_name ~= "None" then
               label = getHumanReadableInterfaceName(companion_name)
            end

            print[[<option value="]] print(companion_id) print[[" ]] if cur_companion == companion_id then print('selected="selected"') end print[[">]] print(label) print[[</option>]]
         end

         print[[
           </select>
         </td>
       </tr>]]
      end
   end

   if has_traffic_recording_page then
      local cur_provider = recording_utils.getCurrentTrafficRecordingProvider(ifstats.id)
      local providers = recording_utils.getAvailableTrafficRecordingProviders()

      -- only 1 provider means there's only the default ntopng
      -- so no need to show this extra menu entry
      if table.len(providers) > 1 then
         print [[
       <tr>
         <th>]] print(i18n("traffic_recording.traffic_recording_provider")) print[[</th>
         <td>
           <select name="traffic_recording_provider" class="form-select" style="width:36em; display:inline;">]]

         for _, provider in pairs(providers) do
            local label = string.format("%s", provider["name"])
            if provider["conf"] then
               label = string.format("%s (%s)", provider["name"], provider["conf"])
            end

            if provider["name"] == "ntopng" and not is_packet_interface then
               -- non-packet interfaces
               label = "None"
            end

            print[[<option value="]] print(provider["name"]) print[[" ]] if cur_provider == provider["name"] then print('selected="selected"') end print[[">]] print(label) print[[</option>]]
         end

         print[[
           </select>
         </td>
       </tr>]]
      end
   end

   if not is_sub_interface and not have_nedge then
      local cur_mode = ntop.getCache(disaggregation_criterion_key)
      if isEmptyString(cur_mode) then
         cur_mode = "none"
      end

      local labels = {
        i18n("prefs.none"),
        i18n("prefs.vlan"),
        i18n("prefs.probe_ip_address"),
        i18n("prefs.ingress_egress_flow_interface"),
        i18n("prefs.ingress_flow_interface"),
        i18n("prefs.ingress_vrf_id"),
        i18n("prefs.probe_ip_and_ingress_iface_idx")
      }

      local values = {}
      if is_packet_interface then
        values = {
           "none",
          "vlan"
        }
      else
        values = {
           "none",
          "vlan",
          "probe_ip",
          "iface_idx",
          "ingress_iface_idx",
          "ingress_vrf_id",
          "probe_ip_and_ingress_iface_idx",
        }
      end

      print [[
       <tr>
         <th>]] print(i18n("prefs.dynamic_interfaces_creation_title")) print[[</th>
         <td>
           <select name="disaggregation_criterion" class="form-select" style="width:36em; display:inline;">]]

         for k, value in ipairs(values) do
            local label = labels[k]
            print[[<option value="]] print(value) print[[" ]] if cur_mode == value then print('selected="selected"') end print[[">]] print(label) print[[</option>]]
         end

         print[[
           </select>
     ]]

     print([[
        <small>
        <details class='mt-2'>
         <summary>
            <span class="ntop_notes" data-bs-toggle="tooltip" data-placement="right" title=']].. i18n("click_to_expand") ..[['>
               ]]..i18n("notes")..[[ <i class='fas fa-question-circle '></i>
            </span>
         </summary>
         <p>]]..i18n("prefs.dynamic_interfaces_creation_description")..[[</p>
         <p>]]..i18n("prefs.dynamic_interfaces_creation_note_0")..[[</p>
         <p>]]..i18n("prefs.dynamic_interfaces_creation_note_4")..[[</p>
         <p>]]..i18n("prefs.dynamic_interfaces_creation_note_1")..[[</p>
         <p>]]..(not is_packet_interface and i18n("prefs.dynamic_interfaces_creation_note_2") or '') ..[[</p>
         <p>]]..(not is_packet_interface and i18n("prefs.dynamic_interfaces_creation_note_3") or '') ..[[</p>
        </details>
        </small>
     ]])

    print[[
         </td>
       </tr>]]

      -- Show dynamic traffic in the master interface
      local show_dyn_iface_traffic = false
      local show_dyn_iface_traffic_pref = string.format("ntopng.prefs.ifid_%d.show_dynamic_interface_traffic", interface.getId())

      if _SERVER["REQUEST_METHOD"] == "POST" then
         if _POST["show_dyn_iface_traffic"] == "1" then
            show_dyn_iface_traffic = true
         end

         ntop.setPref(show_dyn_iface_traffic_pref,
                      ternary(show_dyn_iface_traffic == true, '1', '0'))
         interface.updateDynIfaceTrafficPolicy()
      else
         show_dyn_iface_traffic = ternary(ntop.getPref(show_dyn_iface_traffic_pref) == '1', true, false)
      end

      print [[<tr>
    <th>
    ]] print(i18n("if_stats_config.show_dyn_iface_traffic")) print[[
       <i class='fas fa-question-circle ' data-bs-toggle="tooltip" data-placement="top" title=']] print(i18n("if_stats_config.show_dyn_iface_traffic_note")) print[['></i>
    </th>
    <td>]]

      print(template.gen("on_off_switch.html", {
         id = "show_dyn_iface_traffic",
         checked = show_dyn_iface_traffic,
       }))
       print[[
         </td>
      </tr>]]

      -- Show Push Host Filters to PF_RING toggle
      local push_host_filters = false
      local push_host_filters_pref = string.format("ntopng.prefs.ifid_%d.push_host_filters_to_pfring", interface.getId())

      if _SERVER["REQUEST_METHOD"] == "POST" then
         if _POST["push_host_filters"] == "1" then
            push_host_filters = true
         end

         ntop.setPref(push_host_filters_pref, ternary(push_host_filters == true, '1', '0'))
         interface.updatePushFiltersSettings()
      else
         push_host_filters = ternary(ntop.getPref(push_host_filters_pref) == '1', true, false)
      end

      print [[<tr>
    <th>
    ]] print(i18n("if_stats_config.toggle_push_host_filters")) print[[
       <i class='fas fa-question-circle ' data-bs-toggle="tooltip" data-placement="top" title=']] print(i18n("if_stats_config.toggle_push_host_filters_note")) print[['></i>
    </th>
    <td>]]

      local queue = "pfring."..interface.getId()..".filter.host.queue"

      print(template.gen("on_off_switch.html", {
         id = "push_host_filters",
         checked = push_host_filters,
label="<small>"..i18n("if_stats_config.toggle_push_host_filters_queue")..": <i>"..queue.."</i></small>"
       }))

      print[[
         </td>
      </tr>]]

   end

print[[
</table>
   <button class="btn btn-primary" style="float:right; margin-right:1em; margin-left: auto" disabled="disabled" type="submit">]] print(i18n("save_settings")) print[[</button><br><br>
   </form>
   <script>
      aysHandleForm("#iface_config");
   </script>]]

elseif(page == "internals") then
   internals_utils.printInternals(ifid, true --[[ hash tables ]], true --[[ periodic activities ]], true --[[ checks]], true --[[ queues --]])
print [[
   </table>
]]


elseif(page == "snmp_bind") then
   if ((not hasSnmpDevices(ifstats.id)) or (not is_packet_interface)) then
      return
   end

   local snmp_host = _POST["ip"]
   local snmp_interface = _POST["snmp_port_idx"] or ""

   if (snmp_host ~= nil) then
      -- snmp_host can be empty
      snmp_utils.set_snmp_bound_interface(ifstats.id, snmp_host, snmp_interface)
   else
      local value = snmp_utils.get_snmp_bound_interface(ifstats.id)

      if value ~= nil then
         snmp_host = value.snmp_device
         snmp_interface = value.snmp_port
      end
   end

   local snmp_devices = snmp_utils.get_snmp_devices(ifstats.id)

   print[[
<form id="snmp_bind_form" method="post" style="margin-bottom:3em;">
   <table class="table table-bordered table-striped">]]

   print[[
      <tr>
         <th>]] print(i18n("snmp.snmp_device")) print[[</th>
         <td>
            <select class="form-select" style="width:30em; display:inline;" id="snmp_bind_device" name="ip">
               <option]] if isEmptyString(snmp_host) then print(" selected") end print[[ value="">Not Bound</option>
         ]]

   for _, device in pairs(snmp_devices) do
      print('<option value="'..device.ip..'"')
      if (snmp_host == device.ip) then
         print(' selected')
      end
      print('>'..device.name..' ('..device.ip..')</option>')
   end

   print[[
            </select>
            <a class="btn" id="snmp_device_link" style="padding:0.2em; margin-left:0.3em;" href="#"]]

   if isEmptyString(snmp_host) then
      print(" disabled")
   end

   print[[>]] print(i18n("snmp.view_device"))  print[[</i></a>
         </td>
      </tr>
      <tr>
         <th>]] print(i18n("snmp.snmp_interface")) print[[</th>
            <td>
               <select class="form-select" style="width:30em; display:inline;" id="snmp_bind_interface" name="snmp_port_idx">]]

   if not isEmptyString(snmp_interface) then
      -- This is neeeded to initialized ays form fields
      print('<option value="'..snmp_interface..'" selected></option>')
   end

   print[[
               </select>
               <img id="snmp_loading" style="margin-left:0.5em; visibility:hidden;" src="]] print(ntop.getHttpPrefix()) print[[/img/loading.gif"\>
            </td>
      </tr>
   </table>
   <input type="hidden" name="csrf" value="]] print(ntop.getRandomCSRFValue()) print[[" />
   <button id="snmp_bind_submit" class="btn btn-primary" style="float:right; margin-right:1em; margin-left: auto" disabled="disabled" type="submit">]] print(i18n("save_settings")) print[[</button>
</form>

<b>]] print(i18n("snmp.note") .. ":") print[[</b><br>
<small>]] print(i18n("snmp.bound_interface_description")) print[[</small>

<script>
   var snmp_bind_port_ajax = null;
   var snmp_bind_first_init = true;

   function snmp_set_loading_status(is_loading) {
      if (is_loading) {
         $("#snmp_loading").css("visibility", "");
         $("#snmp_bind_submit").addClass("disabled");
      } else {
         $("#snmp_loading").css("visibility", "hidden");
         $("#snmp_bind_submit").removeClass("disabled");
      }
   }

   function snmp_check_snmp_list() {
      var iflist = $("#snmp_bind_interface");

      if ($("option", iflist).length > 0)
         iflist.removeAttr("disabled");
      else
         iflist.attr("disabled", "disabled");

      aysRecheckForm('#snmp_bind_form');
   }

   function snmp_recheck_selection() {
      var iflist = $("#snmp_bind_interface");
      var selected_device = $("#snmp_bind_device option:selected").val();

      // Remove existing entries
      $("option", iflist).remove();

      if (snmp_bind_port_ajax != null) {
         snmp_bind_port_ajax.abort();
         snmp_bind_port_ajax = null;
      }
      snmp_check_snmp_list();

      if (selected_device) {
         snmp_set_loading_status(true);
         $("#snmp_device_link").removeAttr("disabled");
         $("#snmp_device_link").attr("href", "]] print(ntop.getHttpPrefix()) print[[/lua/pro/enterprise/snmp_device_details.lua?host=" + selected_device);

         snmp_bind_port_ajax = $.ajax({
          type: 'GET',
          url: ']]
   print (ntop.getHttpPrefix())
   print [[/lua/pro/enterprise/get_snmp_device_details.lua',
          data: { ifid: ]] print(ifstats.id) print[[, ip: selected_device, iftype_filter:'snmp_binding' },
          success: function(rsp) {
            if (rsp.interfaces) {
               for (var ifidx in rsp.interfaces) {
                  var snmp_interface = rsp.interfaces[ifidx];
                  var selected = snmp_interface.bound ? " selected" : "";
                  iflist.append('<option value="' + ifidx + '"' + selected + '>' + snmp_interface.label + '</option>');
                  snmp_interface.label;
               }

               snmp_check_snmp_list();
            }
          }, complete: function() {
            snmp_set_loading_status(false);
          }
        });
      } else {
         snmp_set_loading_status(false);
         $("#snmp_device_link").attr("disabled", "disabled");
         snmp_check_snmp_list();
      }
   }

   aysHandleForm("#snmp_bind_form");
   snmp_check_snmp_list();

   $("#snmp_bind_device").change(snmp_recheck_selection);
   $(function() {
      // let it pass some time to ays initialization
      snmp_recheck_selection();
   });
</script>]]
elseif(page == "sub_interfaces") then
   if(isAdministrator() and ntop.isEnterpriseM()) then
      dofile(dirs.installdir .. "/pro/scripts/lua/enterprise/sub_interfaces.lua")
   end
elseif(page == "syslog_producers") then
   if(isAdministrator()) then
      dofile(dirs.installdir .. "/scripts/lua/syslog_producers.lua")
   end
elseif(page == "unassigned_pool_devices") then
   dofile(dirs.installdir .. "/scripts/lua/unknown_devices.lua")
elseif(page == "dhcp") then
    dofile(dirs.installdir .. "/scripts/lua/admin/dhcp.lua")
elseif page == "traffic_report" then
   package.path = dirs.installdir .. "/pro/scripts/lua/enterprise/?.lua;" .. package.path
   local traffic_report = require "traffic_report"

   traffic_report.generate_traffic_report()
end

print("<script>\n")
print("var last_pkts  = " .. ifstats.stats.packets .. ";\n")
print("var last_in_pkts  = " .. ifstats.eth.ingress.packets .. ";\n")
print("var last_out_pkts  = " .. ifstats.eth.egress.packets .. ";\n")
print("var last_drops = " .. ifstats.stats.drops .. ";\n")
print("var last_engaged_alerts = " .. ifstats.num_alerts_engaged .. ";\n")
print("var last_dropped_alerts = " .. ifstats.num_dropped_alerts .. ";\n")
print("var last_num_local_hosts_anomalies = " .. ifstats.anomalies.num_local_hosts_anomalies .. ";\n")
print("var last_num_remote_hosts_anomalies = " .. ifstats.anomalies.num_remote_hosts_anomalies .. ";\n")


if(ifstats.zmqRecvStats ~= nil) then
   print("var last_zmq_time = 0;\n")
   print("var last_zmq_remote_bps = ".. ifstats.remote_bps .. ";\n")
   print("var last_zmq_remote_pps = ".. ifstats.remote_pps .. ";\n")
   print("var last_zmq_flows = ".. ifstats.zmqRecvStats.flows .. ";\n")
   print("var last_zmq_dropped_flows = ".. ifstats.zmqRecvStats.dropped_flows .. ";\n")
   print("var last_zmq_events = ".. ifstats.zmqRecvStats.events .. ";\n")
   print("var last_zmq_counters = ".. ifstats.zmqRecvStats.counters .. ";\n")
   print("var last_zmq_msg_drops = ".. ifstats.zmqRecvStats.zmq_msg_drops .. ";\n")
   print("var last_zmq_msg_rcvd = ".. ifstats.zmqRecvStats.zmq_msg_rcvd .. ";\n")
   print("var last_zmq_drops_export_queue_full = "..(ifstats["zmq.drops.export_queue_full"] or 0).. ";\n")
   print("var last_zmq_drops_flow_collection_drops = "..(ifstats["zmq.drops.flow_collection_drops"] or 0) .. ";\n")
   print("var last_zmq_drops_flow_collection_udp_socket_drops = ".. (ifstats["zmq.drops.flow_collection_udp_socket_drops"] or 0) .. ";\n")
   print("var last_zmq_avg_msg_flows = 1;\n")

   print("var last_probe_zmq_exported_flows = ".. (ifstats["zmq.num_flow_exports"] or 0) .. ";\n")
end

if ifstats.stats.discarded_probing_packets then
   print("var last_discarded_probing_pkts  = " .. ifstats.stats.discarded_probing_packets .. ";\n")
end

print [[
function resetCounters(drops_only) {
  var action = "reset_all";
  if(drops_only) action = "reset_drops";
  $.ajax({ type: 'post',
    url: ']]
print (ntop.getHttpPrefix())
print [[/lua/reset_stats.lua',
    data: {ifid: ]] print(ifstats.id) print [[, resetstats_mode:  action, csrf: "]] print(ntop.getRandomCSRFValue()) print[["},
    success: function(rsp) {},
    complete: function() {
      /* reload the page to generate a new CSRF */
      window.location.href = window.location.href;
    }
  });
}

var resetInterfaceCounters = function(drops_only) {
  if(drops_only) 
    $('#reset_drops_dialog').modal('show');
  else
    $('#reset_stats_dialog').modal('show');
}

setInterval(function() {
      $.ajax({
          type: 'GET',
          url: ']]
print (ntop.getHttpPrefix())
print [[/lua/rest/v2/get/interface/data.lua',
          data: { iffilter: "]] print(tostring(interface.name2id(ifstats.name))) print [[" },
          success: function(content) {
        if(content["rc_str"] != "OK") {
          return;
        }

        const rsp = content["rsp"];
        const v = NtopUtils.bytesToVolume(rsp.bytes);
        $('#if_bytes').html(v);

        $('#if_in_bytes').html(NtopUtils.bytesToVolume(rsp.bytes_download));
        $('#if_out_bytes').html(NtopUtils.bytesToVolume(rsp.bytes_upload));
        $('#if_in_pkts').html(NtopUtils.addCommas(rsp.packets_download) + " Pkts");
        $('#if_out_pkts').html(NtopUtils.addCommas(rsp.packets_upload)  + " Pkts");
        $('#pkts_in_trend').html(NtopUtils.get_trend(rsp.bytes_download, last_in_pkts));
        $('#pkts_out_trend').html(NtopUtils.get_trend(rsp.bytes_upload, last_out_pkts));
        last_in_pkts = rsp.bytes_download;
        last_out_pkts = rsp.bytes_upload;

        if (typeof rsp.zmqRecvStats !== 'undefined') {
           var diff, time_diff, flows_label;
           var now = (new Date()).getTime();

           if(last_zmq_time > 0) {
              time_diff = now - last_zmq_time;
              diff = rsp.zmqRecvStats.flows - last_zmq_flows;

              if(diff > 0) {
                 rate = ((diff * 1000)/time_diff).toFixed(1);
                 flows_label = " [" + NtopUtils.fflows(rate) + "] " + NtopUtils.get_trend(1,0);
              } else {
                 flows_label = " "+NtopUtils.get_trend(0,0);
              }
           } else {
              flows_label = " "+NtopUtils.get_trend(0,0);
           }

           $('#if_zmq_remote_bps').html(NtopUtils.bitsToSize(rsp.remote_bps) + " " + NtopUtils.get_trend(rsp.remote_bps, last_zmq_remote_bps));
           $('#if_zmq_remote_pps').html(NtopUtils.fpackets(rsp.remote_pps) + " " + NtopUtils.get_trend(rsp.remote_pps, last_zmq_remote_pps));
           $('#if_zmq_flows').html(NtopUtils.addCommas(rsp.zmqRecvStats.flows)+flows_label);
           $('#if_zmq_dropped_flows').html(NtopUtils.addCommas(rsp.zmqRecvStats.dropped_flows)+" "+NtopUtils.get_trend(rsp.zmqRecvStats.dropped_flows, last_zmq_dropped_flows));
           $('#if_zmq_events').html(NtopUtils.addCommas(rsp.zmqRecvStats.events)+" "+NtopUtils.get_trend(rsp.zmqRecvStats.events, last_zmq_events));
           $('#if_zmq_counters').html(NtopUtils.addCommas(rsp.zmqRecvStats.counters)+" "+NtopUtils.get_trend(rsp.zmqRecvStats.counters, last_zmq_counters));
           $('#if_zmq_msg_drops').html(NtopUtils.addCommas(rsp.zmqRecvStats.zmq_msg_drops)+" "+NtopUtils.get_trend(rsp.zmqRecvStats.zmq_msg_drops, last_zmq_msg_drops));
           $('#if_zmq_drops_export_queue_full').html(NtopUtils.addCommas(rsp["zmq.drops.export_queue_full"])+" "+NtopUtils.get_trend(rsp["zmq.drops.export_queue_full"], last_zmq_drops_export_queue_full));
           $('#if_zmq_drops_flow_collection_drops').html(NtopUtils.addCommas(rsp["zmq.drops.flow_collection_drops"])+" "+NtopUtils.get_trend(rsp["zmq.drops.flow_collection_drops"], last_zmq_drops_flow_collection_drops));
           $('#if_zmq_drops_flow_collection_udp_socket_drops').html(NtopUtils.addCommas(rsp["zmq.drops.flow_collection_udp_socket_drops"])+" "+NtopUtils.get_trend(rsp["zmq.drops.flow_collection_udp_socket_drops"], last_zmq_drops_flow_collection_udp_socket_drops));
           $('#if_zmq_msg_rcvd').html(NtopUtils.addCommas(rsp.zmqRecvStats.zmq_msg_rcvd)+" "+NtopUtils.get_trend(rsp.zmqRecvStats.zmq_msg_rcvd, last_zmq_msg_rcvd));
           $('#if_zmq_avg_msg_flows').html(NtopUtils.addCommas(NtopUtils.formatValue(rsp.zmqRecvStats.zmq_avg_msg_flows)));
           $('#if_num_remote_zmq_flow_exports').html(NtopUtils.addCommas(rsp["zmq.num_flow_exports"])+" "+NtopUtils.get_trend(rsp["zmq.num_flow_exports"], last_probe_zmq_exported_flows));

           last_remote_pps = rsp.remote_pps;
           last_remote_bps = rsp.remote_bps;
           last_zmq_flows = rsp.zmqRecvStats.flows;
           last_zmq_dropped_flows = rsp.zmqRecvStats.dropped_flows;
           last_zmq_events = rsp.zmqRecvStats.events;
           last_zmq_counters = rsp.zmqRecvStats.counters;
           last_zmq_msg_drops = rsp.zmqRecvStats.zmq_msg_drops;
           last_zmq_drops_export_queue_full = rsp["zmq.drops.export_queue_full"];
           last_zmq_drops_flow_collection_drops = rsp["zmq.drops.flow_collection_drops"];
           last_zmq_drops_flow_collection_udp_socket_drops = rsp["zmq.drops.flow_collection_udp_socket_drops"];
           last_zmq_msg_rcvd = rsp.zmqRecvStats.zmq_msg_rcvd;
           last_zmq_avg_msg_flows = rsp.zmqRecvStats.zmq_avg_msg_flows;
           last_probe_zmq_exported_flows = rsp["zmq.num_flow_exports"];
           last_zmq_time = now;
        }

        $('#if_pkts').html(NtopUtils.addCommas(rsp.packets)+"]]

print(" Pkts\");")

if have_nedge and ifstats.type == "netfilter" and ifstats.netfilter then
   local st = ifstats.netfilter

   print("var last_nfq_queue_total = ".. st.nfq.queue_total .. ";\n")
   print("var last_nfq_handling_failed = ".. st.failures.handle_packet .. ";\n")
   print("var last_nfq_enobufs = ".. st.failures.no_buffers .. ";\n")

   print[[
        if(rsp.netfilter.nfq.queue_pct > 80) {
          $('#nfq_queue_total').addClass("badge bg-danger");
        } else {
          $('#nfq_queue_total').removeClass("badge bg-danger");
        }
        $('#nfq_queue_total').html(NtopUtils.fint(rsp.netfilter.nfq.queue_total) + " [" + NtopUtils.fint(rsp.netfilter.nfq.queue_pct) + " %]");
        $('#nfq_queue_total_trend').html(NtopUtils.get_trend(rsp.netfilter.nfq.queue_total, last_nfq_queue_total));
        $('#nfq_handling_failed').html(NtopUtils.fint(rsp.netfilter.failures.handle_packet));
        $('#nfq_handling_failed_trend').html(NtopUtils.get_trend(rsp.netfilter.failures.handle_packet, last_nfq_handling_failed));
        $('#nfq_enobufs').html(NtopUtils.fint(rsp.netfilter.failures.no_buffers));
        $('#nfq_enobufs_trend').html(NtopUtils.get_trend(rsp.netfilter.failures.no_buffers, last_nfq_enobufs));
        $('#num_conntrack_entries').html(NtopUtils.fint(rsp.netfilter.nfq.num_conntrack_entries)+ " [" + NtopUtils.fint((rsp.netfilter.nfq.num_conntrack_entries*100)/rsp.num_flows) + " %]");
]]
end

if ifstats.stats.discarded_probing_packets then
      print[[
        $('#if_discarded_probing_bytes').html(NtopUtils.bytesToVolume(rsp.discarded_probing_bytes));
        $('#if_discarded_probing_pkts').html(NtopUtils.formatPackets(rsp.discarded_probing_packets));
        $('#if_discarded_probing_trend').html(NtopUtils.get_trend(rsp.discarded_probing_packets, last_discarded_probing_pkts));
        last_discarded_probing_pkts = rsp.discarded_probing_packets;
]]
end

print [[
        var pctg = 0;
        var drops = "";
        var last_pkt_retransmissions = ]] print(tostring(ifstats.tcpPacketStats.retransmissions)) print [[;
        var last_pkt_ooo =  ]] print(tostring(ifstats.tcpPacketStats.out_of_order)) print [[;
        var last_pkt_lost = ]] print(tostring(ifstats.tcpPacketStats.lost)) print [[;

        $('#pkt_retransmissions').html(NtopUtils.fint(rsp.tcpPacketStats.retransmissions)+" Pkts" + NtopUtils.percentage(rsp.tcpPacketStats.retransmissions, rsp.packets));
        $('#pkt_retransmissions_trend').html(NtopUtils.get_trend(rsp.tcpPacketStats.retransmissions, last_pkt_retransmissions));
        $('#pkt_ooo').html(NtopUtils.fint(rsp.tcpPacketStats.out_of_order)+" Pkts" + NtopUtils.percentage(rsp.tcpPacketStats.out_of_order, rsp.packets));
        $('#pkt_ooo_trend').html(NtopUtils.get_trend(rsp.tcpPacketStats.out_of_order, last_pkt_ooo));
        $('#pkt_lost').html(NtopUtils.fint(rsp.tcpPacketStats.lost)+" Pkts");
         $('#pkt_lost_trend').html(NtopUtils.get_trend(rsp.tcpPacketStats.lost, last_pkt_lost) + NtopUtils.percentage(rsp.tcpPacketStats.lost, rsp.packets));
        last_pkt_retransmissions = rsp.tcpPacketStats.retransmissions;
        last_pkt_ooo = rsp.tcpPacketStats.out_of_order;
        last_pkt_lost = rsp.tcpPacketStats.lost;

        $('#pkts_trend').html(NtopUtils.get_trend(rsp.packets, last_pkts));
        $('#drops_trend').html(NtopUtils.get_trend(rsp.drops, last_drops));
        last_pkts = rsp.packets;
        last_drops = rsp.drops;

        $('#engaged_alerts_trend').html(NtopUtils.get_trend(rsp.engaged_alerts, last_engaged_alerts));
        last_engaged_alerts = rsp.engaged_alerts;
        $('#dropped_alerts_trend').html(NtopUtils.get_trend(rsp.dropped_alerts, last_dropped_alerts));
        last_dropped_alerts = rsp.dropped_alerts;
        $('#dropped_alerts').html(NtopUtils.fint(last_dropped_alerts));

        $('#local_hosts_anomalies').html(NtopUtils.fint(rsp.num_local_hosts_anomalies));
        $('#local_hosts_anomalies_trend').html(NtopUtils.get_trend(rsp.num_local_hosts_anomalies, last_num_local_hosts_anomalies));
        last_num_local_hosts_anomalies = rsp.num_local_hosts_anomalies;
        $('#remote_hosts_anomalies').html(NtopUtils.fint(rsp.num_remote_hosts_anomalies));
        $('#remote_hosts_anomalies_trend').html(NtopUtils.get_trend(rsp.num_remote_hosts_anomalies, last_num_remote_hosts_anomalies));
        last_num_remote_hosts_anomalies = rsp.num_remote_hosts_anomalies;

        if((rsp.packets + rsp.drops) > 0) {
          pctg = ((rsp.drops*100)/(rsp.packets+rsp.drops)).toFixed(2);
        }

        if(rsp.drops > 0) {
          drops = '<span class="badge bg-danger">';
        }
        drops = drops + NtopUtils.addCommas(rsp.drops)+" Pkts";

        if(pctg > 0)      { drops  += " [ "+pctg+" % ]"; }
        if(rsp.drops > 0) { drops  += '</span>'; }
        $('#if_drops').html(drops);

        $('#exported_flows').html(NtopUtils.fint(rsp.flow_export_count));
        $('#exported_flows_rate').html(NtopUtils.fflows(rsp.flow_export_rate));
        if(rsp.flow_export_drops > 0) {
          $('#exported_flows_drops')
            .addClass("badge bg-danger")
            .html(NtopUtils.fint(rsp.flow_export_drops));
          if(rsp.flow_export_count > 0) {
            $('#exported_flows_drops_pct')
              .addClass("badge bg-danger")
              .html("[" + NtopUtils.fpercent(rsp.flow_export_drops / (rsp.flow_export_count + rsp.flow_export_drops + 1) * 100) + "]");
          } else {
            $('#exported_flows_drops_pct').addClass("badge bg-danger").html("[100%]");
          }
        } else {
          $('#exported_flows_drops').removeClass().html("0");
          $('#exported_flows_drops_pct').removeClass().html("[0%]");
        }
]]

if interface.isSyslogInterface() then
  print [[
        $('#syslog_tot_events').html(rsp.syslog.tot_events);
]]
end

print [[
           }
               });
       }, ]] print(interface.getStatsUpdateFreq(ifstats.id).."") print[[ * 1000)

</script>

]]

print [[
<script>
$(document).ready(function()
    {
        $("#icmp_table_4").tablesorter();
        $("#icmp_table_6").tablesorter();
        $("#arp_table").tablesorter();
        $("#if_stats_ndpi").tablesorter();
        $("#if_stats_ndpi_categories").tablesorter();
    }
);
</script>
]]

dofile(dirs.installdir .. "/scripts/lua/inc/footer.lua")

if show_zmq_encryption_public_key == true then
   print[[
      <script type='text/javascript'>
      const copyButton = document.getElementById("copy");
      let copyKey=() => {
         const input = document.getElementById("hiddenKey");
         input.type="text";
         input.select();
         document.execCommand("copy");
         input.type='hidden';
      } 

      copyButton.onclick = copyKey;

      </script>
   ]]
end   

print[[
  <script type='text/javascript'>
      $(document).ready(function(){
        $('#copy').tooltip({title: "]] print(i18n("copied")) print[[", trigger: "focus", delay: {"show": 50, "hide": 300}});

      });    
  </script>
]]
