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

local dirs = ntop.getDirs()
local info = ntop.getInfo()
package.path = dirs.installdir .. "/scripts/lua/modules/?.lua;" .. package.path

require "label_utils"

local script_manager = require("script_manager")
local template_utils = require("template_utils")
local recording_utils = require("recording_utils")
local alert_entities = require("alert_entities")

local page_utils = {}

local is_nedge = ntop.isnEdge()

-- #################################

local active_section = nil
local active_entry = nil
local active_sub_entry = nil


local zoneinfo = 'null'
local temp_zoneinfo = ntop.getInfo().zoneinfo
local newline = string.find(temp_zoneinfo, "\n")

-- WARNING: in FreeBSD,	the zoneinfo ends with a newline character, so remove the newline and everything works fine
if newline then
  temp_zoneinfo = string.sub(temp_zoneinfo, 1, newline - 1)
end

if temp_zoneinfo ~= nil then
  zoneinfo = "( /^[\\x00-\\xFF]*$/.test('" .. temp_zoneinfo .."') && moment.tz.zone('" .. temp_zoneinfo .."') != null ? '".. temp_zoneinfo .. "' : function() { console.warn('zoneinfo not valid:' + (/^[\\x00-\\xFF]*$/.test('" .. temp_zoneinfo .."') ? '" .. temp_zoneinfo .."' : 'Invalid string') ); return null; }())"
end

-- #################################

page_utils.menu_sections = {
   monitoring        = {key = "monitoring", i18n_title = "monitoring", icon = "fas fa-satellite-dish"},
   dashboard     = {key = "dashboard", i18n_title = "index_page.dashboard", icon = "fas fa-tachometer-alt"},
   alerts        = {key = "alerts", i18n_title = "details.alerts", icon = "fas fa-exclamation-triangle"},
   flows         = {key = "flows", i18n_title = "flows", icon = "fas fa-stream"},
   hosts         = {key = "hosts", i18n_title = "hosts", icon = "fas fa-laptop"},
   maps          = {key = "maps", i18n_title = "maps", icon = "fas fa-map"},
   collection    = {key = "collection", i18n_title = "flow_devices.exporters", icon = "fas fa-file-export", zmq_only = true --[[ only for non-packet ZMQ interfaces --]] },
   if_stats      = {key = "if_stats", i18n_title = "interface", icon = "fas fa-ethernet"},
   system_stats  = {key = "system_stats", i18n_title = "system", icon = "fas fa-desktop"},
   admin         = {key = "admin", i18n_title = "settings", icon = "fas fa-cog"},
   alert_exclusions  = {key = "alert_exclusions", i18n_title = "edit_check.exclusion_list", icon = "fas fa-bell-slash"},
   dev           = {key = "dev", i18n_title = "developer", icon = "fas fa-code"},
   about         = {key = "about", i18n_title = "help", icon = "fas fa-life-ring"},
   chatbot       = {key = "chatbot", i18n_title = "chatbot", icon = "fa-solid fa-headset"},
   health        = {key = "health", i18n_title = "health", icon = "fas fa-laptop-medical"},
   pollers       = {key = "pollers", i18n_title = "pollers", icon = "fas fa-heartbeat"},
   tools         = {key = "tools", i18n_title = "tools", icon = "fas fa-cogs"},
   pools         = {key = "pools", i18n_title = "pools.pools", icon = "fas fa-users"},
   notifications = {key = "notifications", i18n_title = "endpoint_notifications.notifications", icon = "fas fa-bell"},
   checks  = {key = "checks", i18n_title = "about.checks" },
   -- nEdge
   views         = {key = "hosts", i18n_title = "views", icon = "fas fa-bars"},
}

-- #################################

-- help_link: this variable contains the contextual link to the documentation

page_utils.menu_entries = {
   -- Monitoring
    active_monitoring     = {key = "active_monitoring", i18n_title = "active_monitoring_stats.active_monitoring", section = "monitoring" },
    vulnerability_scan    = {key = "vulnerability_scan", i18n_title = "scan_hosts", section = "monitoring" },
   -- Dashboard
    traffic_dashboard     = {key = "traffic_dashboard", i18n_title = "dashboard.traffic_dashboard", section = "dashboard", help_link = "https://www.ntop.org/guides/ntopng/web_gui/dashboard.html"},
    network_discovery     = {key = "network_discovery", i18n_title = "discover.network_discovery",  section = "dashboard"},
    traffic_report        = {key = "traffic_report",    i18n_title = "report.traffic_report",    section = "dashboard", help_link = "https://www.ntop.org/guides/ntopng/web_gui/report.html"},
    traffic_analysis      = {key = "traffic_analysis", i18n_title = "db_explorer.traffic_analysis", section = "dashboard", help_link = "https://www.ntop.org/guides/ntopng/clickhouse.html?#historical-flows-explorer"},

    -- Alerts
    detected_alerts       = {key = "detected_alerts", i18n_title = "show_alerts.detected_alerts", section = "alerts", help_link = "https://www.ntop.org/guides/ntopng/web_gui/alerts.html"},
    alerts_dashboard      = {key = "alerts_dashboard", i18n_title = "alerts_dashboard.alerts_dashboard", section = "alerts"},
    flow_alerts_explorer  = {key = "flow_alerts_explorer", i18n_title = "flow_alerts_explorer.label", section = "alerts"},
    alerts_list           = {key = "alerts_list", i18n_title = "details.alerts_list", section = "alerts"},
    alerts_analysis       = {key = "alerts_analysis", i18n_title = "details.alerts_analysis", section = "alerts"},
    
    -- Flows
    flows                 = {key = "flows", i18n_title = "flows", section = "flows", help_link = "https://www.ntop.org/guides/ntopng/web_gui/flows.html"},
    active_flows          = {key = "active_flows", i18n_title = "active_flows", section = "flows" },
    flow_details          = {key = "flow_details", i18n_title = "flow_details.flow_details", section = "flows"},
    db_explorer           = {key = "db_explorer", i18n_title = "db_explorer.historical_data_explorer", section = "flows", help_link = "https://www.ntop.org/guides/ntopng/clickhouse.html?#historical-flows-explorer"},

    -- Hosts
    hosts                 = {key = "hosts", i18n_title = "hosts", section = "hosts", help_link = "https://www.ntop.org/guides/ntopng/web_gui/hosts.html#"},
    devices               = {key = "devices", i18n_title = "layer_2", section = "hosts"},
    networks              = {key = "networks", i18n_title = "networks", section = "hosts", help_link = "https://www.ntop.org/guides/ntopng/web_gui/hosts.html?#networks"},
    vlans                 = {key = "vlans", i18n_title = "vlan_stats.vlans", section = "hosts"},
    host_pools            = {key = "host_pools", i18n_title = "host_pools.host_pools", section = "hosts"},
    autonomous_systems    = {key = "autonomous_systems", i18n_title = "as_stats.autonomous_systems", section = "hosts", help_link = "https://www.ntop.org/guides/ntopng/web_gui/hosts.html?#autonomous-systems"},
    countries             = {key = "countries", i18n_title = "countries", section = "hosts", help_link = "https://www.ntop.org/guides/ntopng/web_gui/hosts.html?#countries"},
    operating_systems     = {key = "operating_systems", i18n_title = "operating_systems", section = "hosts", help_link = "https://www.ntop.org/guides/ntopng/web_gui/hosts.html?#operating-systems"},
    http_servers          = {key = "http_servers", i18n_title = "http_servers_stats.local_http_servers", section = "hosts", help_link = "https://www.ntop.org/guides/ntopng/web_gui/hosts.html?#http-servers-local"},
    server_ports_analysis = {key = "server_ports_analysis", i18n_title = "server_ports_analysis", section = "hosts", help_link = "https://www.ntop.org/guides/ntopng/web_gui/hosts.html?#http-servers-local"},
    top_hosts             = {key = "top_hosts", i18n_title = "processes_stats.top_hosts", section = "hosts", help_link = "https://www.ntop.org/guides/ntopng/web_gui/hosts.html?#top-hosts-local"},
    hosts_treemap         = {key = "hosts_treemap", i18n_title = "tree_map.hosts_treemap", section = "hosts"},
    containers            = {key = "containers", i18n_title = "containers_stats.containers", section = "hosts"},
    pods                  = {key = "pods", i18n_title = "containers_stats.pods", section = "hosts"},
    host_rules            = {key = "host_rules", i18n_title = "if_stats_config.traffic_rules", section = "hosts", help_link = "https://www.ntop.org/guides/ntopng/advanced_features/host_rules.html"},

    -- Chatbot 
    chatbot             = {key = "chatbot", i18n_title = "chatbot", section = "chatbot"},
    -- Interface
    interface             = {key = "interface", i18n_title = "interface_details", section = "if_stats"},
    ports_analysis        = {key = "ports_analysis", i18n_title = "ports_analysis.server_ports", section = "if_stats"},

    -- Pollers
    snmp                  = {key = "snmp", i18n_title = "prefs.snmp", section = "pollers"},
    snmp_monitoring        = {key = "snmp_monitoring", i18n_title = "prefs.snmp", section = "monitoring"},
    infrastructure_dashboard = {key = "infrastructure_dashboard", i18n_title = "infrastructure_dashboard.infrastructure_dashboard", section = "pollers"},

    -- Status (Health)
    system_status         = {key = "system_status", i18n_title = "system_status", section = "health"},
    interfaces_status     = {key = "interfaces_status", i18n_title = "system_interfaces_status", section = "health"},
    alerts_status         = {key = "alerts_status", i18n_title = "system_alerts_status", section = "health"},
    influxdb_status       = {key = "influxdb_status", i18n_title = "InfluxDB", section = "health"},
    redis_status          = {key = "redis_monitor", i18n_title = "Redis", section = "health"},
    clickhouse_status     = {key = "clickhouse", i18n_title = "ClickHouse", section = "health"},

    -- exporters
    event_exporters       = {key = "event_exporters", i18n_title = "event_exporters.event_exporters", section = "collection"},
    sflow_exporters       = {key = "sflow_exporters", i18n_title = "flows_page.sflow_devices", section = "collection"},
    exporters             = {key = "exporters", i18n_title = "flow_devices.flow_exporters", section = "collection", help_link = "https://www.ntop.org"},
    nprobe               = {key = "nprobe", i18n_title = "flow_devices.nprobe_instances", section = "collection"},
    observation_points    = {key = "observation_points", i18n_title = "flow_devices.observation_points", section = "collection", help_link = "https://www.ntop.org"},

   -- Map
   analysis_map           = {key = "analysis_map", i18n_title="analysis", section = "maps"},
   geo_map                = {key = "geo_map", i18n_title = "geo_map.geo_map", section = "maps", help_link = "https://www.ntop.org/guides/ntopng/web_gui/hosts.html?#geo-map"},
   hosts_map              = {key = "hosts_map", i18n_title = "flows_page.hosts", section = "maps"},
   
   -- Settings
   nedge_users            = {key = "nedge_users", i18n_title = "manage_users.manage_users", section = "admin", help_link = "https://www.ntop.org/guides/nedge/users.html#"},
   manage_users           = {key = "manage_users", i18n_title = ternary(is_nedge, "nedge.system_users", "manage_users.manage_users"), section = "admin", help_link = "https://www.ntop.org/guides/ntopng/web_gui/settings.html#manage-users"},
   preferences            = {key = "preferences", i18n_title = "prefs.preferences", section = "admin", help_link = "https://www.ntop.org/guides/ntopng/web_gui/settings.html#preferences"},
   scripts_config         = {key = "scripts_config", i18n_title = "about.checks", section = "admin", help_link = "https://www.ntop.org/guides/ntopng/web_gui/checks.html"},
   scripts_config_all     = {key = "scripts_config", subkey="all", i18n_title = "all", section = "admin", help_link = "https://www.ntop.org/guides/ntopng/web_gui/checks.html"},
   scripts_config_hosts   = {key = "scripts_config", subkey="hosts", i18n_title = alert_entities.host.i18n_label, section = "admin", help_link = "https://www.ntop.org/guides/ntopng/web_gui/checks.html"},
   scripts_config_interfaces = {key = "scripts_config", subkey="interfaces", i18n_title = alert_entities.interface.i18n_label, section = "admin", help_link = "https://www.ntop.org/guides/ntopng/web_gui/checks.html"},
   scripts_config_networks = {key = "scripts_config", subkey="networks", i18n_title = alert_entities.network.i18n_label, section = "admin", help_link = "https://www.ntop.org/guides/ntopng/web_gui/checks.html"},
   scripts_config_snmp_devices = {key = "scripts_config", subkey="snmp_devices", i18n_title = alert_entities.snmp_device.i18n_label, section = "admin", help_link = "https://www.ntop.org/guides/ntopng/web_gui/checks.html"},
   scripts_config_flows   = {key = "scripts_config", subkey="flows", i18n_title = alert_entities.flow.i18n_label, section = "admin", help_link = "https://www.ntop.org/guides/ntopng/web_gui/checks.html"},
   scripts_config_system  = {key = "scripts_config", subkey="system", i18n_title = alert_entities.system.i18n_label, section = "admin", help_link = "https://www.ntop.org/guides/ntopng/web_gui/checks.html"},
   scripts_config_syslog  = {key = "scripts_config", subkey="syslog", i18n_title = "syslog.syslog", section = "admin", help_link = "https://www.ntop.org/guides/ntopng/web_gui/checks.html"},
   scripts_config_active_monitoring  = {key = "scripts_config", subkey="active_monitoring", i18n_title = "active_monitoring", section = "admin", help_link = "https://www.ntop.org/guides/ntopng/web_gui/checks.html"},
   device_exclusions      = {key = "device_exclusions", i18n_title = "edit_check.device_exclusion", section = "hosts"},
   alert_exclusions       = {key = "alert_exclusions", i18n_title = "edit_check.exclusion_list", section = "admin", help_link = "https://www.ntop.org/guides/ntopng/web_gui/checks.html"},
   alert_exclusions_hosts = {key = "alert_exclusions", subkey="hosts", i18n_title = alert_entities.host.i18n_label, section = "alert_exclusions", help_link = "https://www.ntop.org/guides/ntopng/web_gui/checks.html"},
   alert_exclusions_flows = {key = "alert_exclusions", subkey="flows", i18n_title = alert_entities.flow.i18n_label, section = "alert_exclusions", help_link = "https://www.ntop.org/guides/ntopng/web_gui/checks.html"},
   alert_exclusions_domains = {key = "alert_exclusions", subkey="domains", i18n_title = alert_entities.domain.i18n_label, section = "alert_exclusions", help_link = "https://www.ntop.org/guides/ntopng/web_gui/checks.html"},
   license                = {key = "license", i18n_title = "license_page.license", section = "admin"},
   profiles               = {key = "profiles", i18n_title = "traffic_profiles.traffic_profiles", section = "admin", help_link = "https://www.ntop.org/guides/ntopng/advanced_features/traffic_profiles.html"},
   categories             = {key = "categories", i18n_title = "custom_categories.apps_and_categories", section = "admin", help_link = "https://www.ntop.org/guides/ntopng/web_gui/categories.html#custom-applications"},
   category_lists         = {key = "category_lists", i18n_title = "category_lists.category_lists", section = "admin"},
   device_protocols       = {key = "device_protocols", i18n_title = "device_protocols.device_protocols", section = "admin"},
   conf_backup            = {key = "conf_backup", i18n_title = "conf_backup.conf_backup", section = "admin"},
   conf_restore           = {key = "conf_restore", i18n_title = "conf_backup.conf_restore", section = "admin"},
   manage_data            = {key = "manage_data", i18n_title = "manage_data.manage_data", section = "admin", help_link = "https://www.ntop.org/guides/ntopng/web_gui/settings.html#manage-data"},
   manage_configurations  = {key = "manage_configurations", i18n_title = "manage_configurations.manage_configurations", section = "admin"},

   -- Notifications
   endpoint_notifications = {key = "endpoint_notifications", i18n_title = "endpoint_notifications.endpoint_list", section="notifications", help_link = "https://www.ntop.org/guides/ntopng/scripts/alert_endpoints.html"},
   endpoint_recipients    = {key = "endpoint_recipients", i18n_title = "endpoint_notifications.enpoint_recipients_list", section="notifications", help_link = "https://www.ntop.org/guides/ntopng/scripts/alert_endpoints.html"},

   -- Tools
   export_data            = {key = "export_data", i18n_title = "manage_data.export", section = "tools"},

   -- Pools
   host_members           = {key = "host_members", i18n_title = "host_pools.host_members", section = "pools"},
   manage_pools           = {key = "manage_pools", i18n_title = "pools.pools", section = "pools"},

   -- Home
   live_capture           = {key = "live_capture", i18n_title = "live_capture.active_live_captures", section = "home"},

   -- Developer
   directories            = {key = "directories", i18n_title = "about.directories", section = "dev", help_link = "https://www.ntop.org/guides/ntopng/scripts/distributing_scripts.html"},
   checks_dev             = {key = "checks_dev", i18n_title = "about.checks", section = "dev", help_link = "https://www.ntop.org/guides/ntopng/scripts/checks.html"},
   analyze_db            = {key = "analyze_db", i18n_title = "about.analyze_db", section = "dev", help_link = "https://www.ntop.org/guides/ntopng/advanced_features/index.html"},
   analyze_pcap           = {key = "analyze_pcap", i18n_title = "about.analyze_pcap", section = "dev", help_link = "https://www.ntop.org/guides/ntopng/advanced_features/index.html"},
   alert_definitions      = {key = "alert_definitions", i18n_title = "about.alert_defines", section = "dev", help_link = "https://www.ntop.org/guides/ntopng/scripts/alert_definitions.html"},
   api                    = {key = "api", i18n_title = "lua_c_api", section = "dev"},
   rest_api               = {key = "rest_api", i18n_title = "swagger_api", section = "dev"},
   
   -- Help
   about                  = {key = "about", i18n_title = "about.about", section = "about", help_link = "https://www.ntop.org/guides/ntopng/web_gui/help_menu.html?#about"},
   blog                   = {key = "blog", i18n_title = "about.ntop_blog", section = "about"},
   telegram               = {key = "telegram", i18n_title = "about.telegram", section = "about"},
   report_issue           = {key = "report_issue", i18n_title = "about.report_issue", section = "about"},
   manual                 = {key = "manual", i18n_title = "about.readme_and_manual", section = "about"},
   suggest_feature        = {key = "suggest_feature", i18n_title = "about.suggest_feature", section = "about"},

   -- Just a divider for horizontal rows in the menu
   divider                = {key = "divider"},

   -- nEdge
   gateways_users         = {key = "gateways_users", i18n_title = "dashboard.gateways_users", section = "dashboard"},
   nedge_flows            = {key = "nedge_flows", i18n_title = "flows", section = "hosts"},
   nedge_interfaces       = {key = "nedge_interfaces", i18n_title = "interfaces", section = "dashboard"},
   users                  = {key = "users", i18n_title = "users.users", section = "hosts", help_link = "https://www.ntop.org/guides/ntopng/advanced_features/authentication.html"},
   system_setup           = {key = "system_setup", i18n_title = "nedge.system_setup", section = "system_stats", help_link = "https://www.ntop.org/guides/nedge/get_started.html"},
   dhcp_static_leases     = {key = "dhcp_static_leases", i18n_title = "nedge.static_dhcp_leases", section = "system_stats"},
   dhcp_active_leases     = {key = "dhcp_active_leases", i18n_title = "nedge.active_dhcp_leases", section = "system_stats"},
   port_forwarding        = {key = "port_forwarding", i18n_title = "nedge.port_forwarding", section = "system_stats"},
   rules_config           = {key = "rules_config", i18n_title = "nedge.rules_config_title", section = "system_stats"},
   forwarders_config       = {key = "forwarders_config", i18n_title = "nedge.repeaters_config_title", section = "system_stats"}
}

-- Extend the menu entries with the scripts
local menu, entries_data = script_manager.getMenuEntries()
page_utils.scripts_menu = menu or {}

if entries_data then
    for k, v in pairs(entries_data) do
       page_utils.menu_entries[k] = v
    end
end

-- #################################

-- NOTE: this function must be called after page_utils.print_header_and_set_active_menu_entry
function page_utils.print_page_title(title, mini_title)
   local help_link = page_utils.menu_entries[active_entry].help_link or ""
   print("<header class='mb-3 d-flex align-items-center'>")
   print("<h2 class='d-inline-block'>".. title .."</h2>")
   if (not isEmptyString(help_link)) then
      print("<a data-bs-toggle='tooltip' title='".. i18n("open_documentation") .."' target='_newtab' href='".. help_link .."' class='text-muted ms-auto'><i class='fas fa-question-circle'></i></a>")
   end
   
   if mini_title and not isEmptyString(mini_title) then
    print("<small style='margin-left: auto;'>" .. mini_title .. "</small>")
   end
   print("</header>")

end

-- #################################

-- NOTE: this function is called by the web pages in order to
-- set the active entry and section and highlight it into the menu
function page_utils.print_header_and_set_active_menu_entry(entry, i18n_params, alt_title)
   require "locales_utils"
   entry = entry or page_utils.menu_entries.traffic_dashboard

   active_section = entry.section
   active_entry = entry.key
   if entry.subkey then
      active_sub_entry = entry.subkey
   end

   page_utils.print_header(alt_title or i18n(entry.i18n_title, i18n_params) or entry.i18n_title)
end

-- #################################

function page_utils.get_active_section()
    return(active_section)
end

function page_utils.get_active_entry()
    return(active_entry)
end

function page_utils.get_active_sub_entry()
    return(active_sub_entry)
end

-- #################################

-- @brief Encode the HTML entities in a string.
--        Useful when printing dissected data which may result in XSS
--        e.g., curl -u admin:admin1 "http://devel:3000/</a><script>alert(1);</script><a>"
-- @param s The string to escape.
-- @return The string with HTML entities properly escaped
function page_utils.safe_html(s)
   if not s then
      return ''
   end

   ret = string.gsub(s, "[}{\">/<'&]", {
            ["&"] = "&amp;",
            ["<"] = "&lt;",
            [">"] = "&gt;",
            ['"'] = "&quot;",
            ["'"] = "&#39;",
            ["/"] = "&#47;"
   })

   return(ret)
end

-- #################################

function page_utils.is_dark_mode_enabled(theme)

   local dark_mode
   local theme = theme or ntop.getPref("ntopng.user." .. _SESSION["user"] .. ".theme")

   if (isEmptyString(theme)) then
      dark_mode = false
   else
      dark_mode = (theme == "dark")
   end

   return dark_mode
end

-- #################################

function page_utils.print_header(title, addLoginJS)
  local http_prefix = ntop.getHttpPrefix()
  local static_file_epoch = ntop.getStaticFileEpoch()..""

  local dark_mode = page_utils.is_dark_mode_enabled(_POST["toggle_theme"])
  local page_title = i18n("welcome_to", { product=info.product })
  local favicon_path = nil

  local admin_lang = ntop.getPref("ntopng.user.admin.language")
  local language = ternary(isEmptyString(admin_lang), "en", admin_lang)
  local locale_path = dirs.installdir.."/scripts/locales/"..language..".lua"
  local locale_when = ntop.fileLastChange(locale_path)

  if title ~= nil then
    page_title = info.product .. " - " .. title
  end

  print [[
   <!DOCTYPE html>
   <html>
   <head>
    <title>]] print(page_title) print[[</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <link href="]] print(http_prefix) print[[/dist/third-party.css?]] print(static_file_epoch) print[[" rel="stylesheet">
    <link href="]] print(http_prefix) print[[/dist/ntopng.css?]] print(static_file_epoch) print[[" rel="stylesheet">
    <link href="]] print(http_prefix) print[[/dist/white-mode.css?]] print(static_file_epoch) print[[" rel="stylesheet">]]
    
    if (dark_mode) then
      print[[<link href="]] print(http_prefix) print[[/dist/dark-mode.css?]] print(static_file_epoch) print[[" rel="stylesheet">]]
    end

   if (ntop.isPro() or ntop.isnEdge()) and ntop.exists(dirs.installdir .. "/httpdocs/img/custom_favicon.ico") then
      favicon_path = http_prefix.."/img/custom_favicon.ico"
   end
   if (favicon_path ~= nil) then
    print[[<link href="]] print(favicon_path) print[[" rel="icon">]]
   else
    print[[<link href="]] print(http_prefix) print[[/favicon.ico" rel="icon">]]
   end

   print[[
    <style>
      .flag {
        width: 16px;
        height: 11px;
        margin-top: -5px;
        background:url(]] print(http_prefix) print[[/dist/images/flags.png) no-repeat
      }
    </style>

    ]]
          
    if addLoginJS then
      print[[<script type="application/javascript" src="]] print(http_prefix) print[[/dist/login.js?]] print(static_file_epoch) print[["></script>]]
    end

    if(ntop.isPro() and ntop.exists(dirs.installdir.."/httpdocs/css/custom-theme.css")) then
      print [[
        <link href="]] print(http_prefix) print[[/css/custom-theme.css?]] print(static_file_epoch) print[[" rel="stylesheet">
      ]]
    end
    
    print[[
    <script type="application/javascript" src="]] print(http_prefix) print("/lua/locale.lua?"..locale_when .. "&user_language=" ..language); print[["> </script>
    <script type="application/javascript" src="]] print(http_prefix) print[[/dist/third-party.js?]] print(static_file_epoch) print[["></script>
    <script type="application/javascript" src="]] print(http_prefix) print[[/dist/ntopng.js?]] print(static_file_epoch) print[["></script>
    <script>
    const ntop_zoneinfo = ]] print(zoneinfo) print[[;
    const http_prefix = "]] print(ntop.getHttpPrefix()) print [[";
    </script>
    </head>]]
  print([[
     <body class="body ]].. (dark_mode and "dark" or "") ..[[">
  ]])
end

-- #################################

function page_utils.print_header_minimal(title, addLoginJS)
  local http_prefix = ntop.getHttpPrefix()

  local page_title = i18n("welcome_to", { product=info.product })
  if title ~= nil then
    page_title = info.product .. " - " .. title
  end
  
  local admin_lang = ntop.getPref("ntopng.user.admin.language")
  local language = ternary(isEmptyString(admin_lang), "en", admin_lang)
  local locale_path = dirs.installdir.."/scripts/locales/"..language..".lua"
  local locale_when = ntop.fileLastChange(locale_path)
  local static_file_epoch = ntop.getStaticFileEpoch()..""
  
  print [[
    <!DOCTYPE html>
    <html>
      <head>
        <title>]] print(page_title) print[[</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">

        <link href="]] print(http_prefix) print[[/dist/third-party.css?]] print(static_file_epoch) print[[" rel="stylesheet">
        <link href="]] print(http_prefix) print[[/dist/ntopng.css?]] print(static_file_epoch) print[[" rel="stylesheet">
          ]]

         if (ntop.isPro() or ntop.isnEdge()) and ntop.exists(dirs.installdir .. "/httpdocs/img/custom_favicon.ico") then
            favicon_path = http_prefix.."/img/custom_favicon.ico"
         end
         if (favicon_path ~= nil) then
          print[[<link href="]] print(favicon_path) print[[" rel="icon">]]
         else
          print[[<link href="]] print(http_prefix) print[[/favicon.ico" rel="icon">]]
         end

         print[[
          <style>
          .flag {
            width: 16px;
            height: 11px;
            margin-top: -5px;
            background:url(]] print(http_prefix) print[[/dist/images/flags.png) no-repeat
          }
          </style>
          <script type="application/javascript" src="]] print(http_prefix) print("/lua/locale.lua?"..locale_when .. "&user_language=" ..language); print[["> </script>
          <script type="application/javascript" src="]] print(http_prefix) print[[/dist/third-party.js?]] print(static_file_epoch) print[["></script>
          <script type="application/javascript" src="]] print(http_prefix) print[[/dist/ntopng.js?]] print(static_file_epoch) print[["></script>
          <script> const ntop_zoneinfo = ]] print(zoneinfo) print[[;</script>          
      </head>
      <body>

]]
end

-- #################################

function page_utils.get_navbar_context(title, base_url, items_table, label_url, back_url)
   local help_link = page_utils.menu_entries[active_entry].help_link or nil
   local icon = page_utils.menu_sections[active_section].icon or ""

   local navbar = {
   	 main_icon   = icon,
         main_title  = title,
         base_url    = base_url,
         items_table = items_table,
         label_url   = label_url,
         back_url    = back_url,
         help_link   = help_link
   }
   return navbar
end

-- #################################

function page_utils.get_new_navbar_context(title, base_url, items_table, label_url, back_url)
   local help_link = page_utils.menu_entries[active_entry].help_link or nil
   local icon = page_utils.menu_sections[active_section].icon or ""

   local navbar = {
    main_title = { 
      icon = icon,
      label  = title,
    },
    base_url    = base_url,
    items_table = items_table,
    label_url   = label_url,
    back_url    = back_url,
    help_link   = help_link
   }
   return navbar
end
-- #################################

function page_utils.print_navbar(title, base_url, items_table, label_url, back_url, end_items)
   local help_link = page_utils.menu_entries[active_entry].help_link or nil
   local icon = page_utils.menu_sections[active_section].icon or ""

   local context = {
      navbar = {
         title       = "<i class='".. icon .. "'></i>" .. " " .. title,
         base_url    = base_url,
         items_table = items_table,
         label_url   = label_url,
         back_url    = back_url,
         help_link   = help_link,
         end_items   = end_items
      }
   }
   print(template_utils.gen("pages/components/page-navbar.template", context))
end

-- #################################
local menubar_structure = {}

function page_utils.init_menubar()
   menubar_structure = {}
end

-- #################################

function page_utils.add_menubar_section(section)
   menubar_structure[#menubar_structure + 1] = section
end

-- #################################

local function print_submenu(section, container_list_name)
   local active_subpage = page_utils.get_active_entry()
   local active_subsubpage = page_utils.get_active_sub_entry()
   local section_key = section.section.key
   local section_id = section_key.."-submenu"
   local list_name = section_id.."-list"

   print[[<div data-parent='#]] print(container_list_name) print [[' class='collapse side-collapse' id=']] print(section_id) print[['>
          <ul class='nav flex-column' id=']] print(list_name) print[['>
            ]]

   for _, section_entry in ipairs(section.entries) do
      if not section_entry.hidden then
         if section_entry.custom then
            print(section_entry.custom)
         elseif section_entry.entry.key == "divider" then
            print[[
           <li class="dropdown-divider"></li>
                  ]]
         else
            local external_link = false
            local section_has_submenu = section_entry.entries
	    local active = false

            if section_entry.entry and 
	       (section_entry.entry.key == active_subpage and 
	        (not section_entry.entry.subkey or section_entry.entry.subkey == active_subsubpage)) then
	       active = true
	    end

      if section_entry.url:starts("http") then
         -- Absolute (external) url
      
         external_link = true
      end
            print[[
         <li class=']]
	    if active then
               print("active")
            end
            print[['>

             <a class="]]
            if section_has_submenu then
               print[[submenu ]]
            end
	    if active then
               print[[active ]]
            end
      if external_link == true then 
         print[[ntopng-external-link ]]
      end
	    print[["]]

            if section_has_submenu then
               print[[ data-bs-toggle="collapse" ]]
            end
       print[[href="]]
	    if section_has_submenu then
	       local submenu_section_key = section_entry.section.key
	       local submenu_section_id = submenu_section_key.."-submenu"
               print("#"..submenu_section_id)
	    else 
         if external_link then 
            print(section_entry.url)
         else
            print(ntop.getHttpPrefix()..section_entry.url)
         end
	    end
            print[["]]

            if section_entry.entry.is_modal then
               print(' data-bs-toggle="modal"')
            end
            
            if external_link then
               -- Open in a new page
               print(' target="_blank"')
            end

            print[[>]]
            print(i18n(section_entry.entry.i18n_title) or section_entry.entry.i18n_title)
            if section_entry.suffix then
               print(section_entry.suffix)
            end
            if external_link then
               print(" <i class='fas fa-external-link-alt'></i>")
            end

            print[[</a>]]
            
	    if section_has_submenu then
	       print_submenu(section_entry, list_name)
            end

            print[[</li>
   ]]
         end
      end
   end

   print[[
          </ul>
        </div>
   ]]
end

-- #################################

local function print_section(section, list_name)
   local active_page = page_utils.get_active_section()
   local show_section = false
   local section_has_submenu = (section.entries and #section.entries > 0)

   if(not section.hidden) then
      if not section_has_submenu then
         show_section = true
      else
         -- The section is shown only if at least one of its sub entries
         -- is shown
         for _, section_entry in pairs(section.entries or {}) do
            if not section_entry.hidden then
               show_section = true
               break
            end
     end
      end
   end

   if show_section then
      local section_key = section.section.key
      local section_id = section_key.."-submenu"

      print[[
      <li class="nav-item ]] print(active_page == section_key and 'active' or '') print[[">
    <a class="]]

      if section_has_submenu then
         print[[submenu ]]
      end

      if active_page == section_key then
         print[[active ]]
      end

      print[[" ]]

     if section_has_submenu then
        print[[ data-bs-toggle="collapse" ]]
     end

     print[[href="]]

     if section.url then
        print(ntop.getHttpPrefix()..section.url)
     else
        print("#"..section_id)
     end

     print[[">
      <span class="]] print(section.section.icon) print[["></span> ]] print(i18n(section.section.i18n_title)) print[[
    </a>]]

     if section_has_submenu then
        print_submenu(section, list_name)
     end
   end
end

-- #################################

function page_utils.print_menubar()
   local is_system_interface = page_utils.is_system_view()
   local logo_path = nil
   local list_name = "sidebar"

   if (ntop.isPro() or ntop.isnEdge()) and ntop.exists(dirs.installdir .. "/httpdocs/img/custom_logo.png") then
      logo_path = ntop.getHttpPrefix().."/img/custom_logo.png"
   end

   local navbar_style = _POST["toggle_theme"] or ntop.getPref("ntopng.user." .. _SESSION["user"] .. ".theme")

   if ((navbar_style == nil) or (navbar_style == "white") or (navbar_style == "")) then
      navbar_style = "default"
   end

   if (navbar_style == "default") then
      navbar_style = "dark"
   end

   print('<nav id="n-sidebar" class="bg-'.. navbar_style ..' py-0 px-2">')
   print([[
      <div class="mobile-menu-button d-flex">
            <form class='w-100' method='post' action="/?">
               <input hidden name='switch_interface' value='1' />
               <input hidden name='csrf' value=']].. ntop.getRandomCSRFValue() ..[[' />
               ]]
               .. template_utils.gen("pages/components/ifaces-select.template", {
                  ifaces = page_utils.get_interface_list(),
                  is_sys_iface = is_system_interface
               })
               ..[[
            </form>
            <button data-bs-toggle="]] .. list_name .. [[" class='ms-5 my-auto btn btn-close'></button>
      </div>
      <div class="mobile-menu-stats">
      ]])

      -- the network load will not be displayed in system view mode
      if not is_system_interface then
         print([[<div class="network-load mb-2 w-100"></div>]])
      end

      print([[
         <div class="info-stats w-100">]].. page_utils.generate_info_stats() ..[[</div>
      </div>
   ]])
   print("<h3 class='muted'><a href='"..ntop.getHttpPrefix().."/'>")

   if (logo_path ~= nil) then
      print("<img class=\"logo-brand\" height=\"52px\" src=\""..logo_path.."\" alt='Custom Logo' />")
   else
      print(addLogoSvg())
   end

   print([[
           </a>
     </h3>

     <ul class="nav-side mb-4" id=']] .. list_name .. [['>
]])

   for _, section in ipairs(menubar_structure) do
     print_section(section, list_name)
   end

   print([[
     </ul>
   </nav>
]])
end

-- ##############################################

function page_utils.get_interface_list()
   local interfaces = {}
   local iface_names = interface.getIfNames()
   local is_system_interface = page_utils.is_system_view()

   local num_ifaces = 0
   for k,v in pairs(iface_names) do
      num_ifaces = num_ifaces+1
   end

   local ifs = interface.getStats()
   local is_pcap_dump = interface.isPcapDumpInterface()
   local is_packet_interface = interface.isPacketInterface()
   local views = {}
   local drops = {}
   local recording = {}
   local packetinterfaces = {}
   local ifnames = {}
   local ifdescr = {}
   local ifHdescr = {}
   local ifCustom = {}
   local dynamic = {}

   for v,k in pairs(iface_names) do
      interface.select(k)
      local _ifstats = interface.getStats()
      ifnames[_ifstats.id] = k
      ifdescr[_ifstats.id] = _ifstats.description
      --io.write("["..k.."/"..v.."][".._ifstats.id.."] "..ifnames[_ifstats.id].."=".._ifstats.id.."\n")
      if(_ifstats.isView == true) then views[k] = true end
      if(interface.isSubInterface()) then dynamic[k] = true end
      if(recording_utils.isEnabled(_ifstats.id)) then recording[k] = true end
      if(interface.isPacketInterface()) then packetinterfaces[k] = true end
      if(_ifstats.stats_since_reset.drops * 100 > _ifstats.stats_since_reset.packets) then
         drops[k] = true
      end
      ifHdescr[_ifstats.id] = getHumanReadableInterfaceName(_ifstats.description.."")
      ifCustom[_ifstats.id] = _ifstats.customIftype
   end

   -- First round: only physical interfaces
   -- Second round: only virtual interfaces

   for round = 1, 2 do

      for k,_ in pairsByValues(ifHdescr, asc) do

         if ((round == 1) and (ifCustom[k] ~= nil)) or (((round == 2) and (ifCustom[k] == nil))) then
            goto continue
         end

         local v = ifnames[k]

         -- table.clone needed to modify some parameters while keeping the original unchanged
         local page_params = table.clone(_GET)
         page_params.ifid = k

         local url_query = getPageUrl("", page_params)

         -- NOTE: the actual interface switching is performed in C in LuaEngine::handle_script_request
         local action_url = ""

         if(is_system_interface) then
            action_url = ntop.getHttpPrefix() .. '/' .. url_query
         else
            action_url = url_query
         end

         local descr = getHumanReadableInterfaceName(v.."")

         if(string.contains(descr, "{")) then -- Windows
            descr = ifdescr[k]
         else

            if(descr ~= ifdescr[k]) and (not views[v]) then

               if(descr == shortenCollapse(ifdescr[k])) then
                  descr = ifdescr[k]
               else
                  descr = descr .. " (".. ifdescr[k] ..")" -- Add description
               end
            end

         end

         interfaces[#interfaces+1] = {
            is_virtual = (round == 2),                                     -- Is the interface virtual or physical?
            is_paused = isPausedInterface(v),                              -- Is the interfaces paused?
            is_selected = (v == ifname) and not is_system_interface,        -- Is the in
            is_dynamic = (dynamic[v]),
            is_recording = (recording[v]),
            has_drops = (drops[v]),
            has_views = (views[v]),
            ifid = k,
            action_url = action_url,
            human_name = descr
         }

         ::continue::
      end
   end

   interface.select(ifs.id.."")

   return interfaces
end

function page_utils.generate_info_stats()

   local _ifstats = interface.getStats()

   local upload_stats = ""
   local download_stats = ""

   for _, v in pairs(_ifstats["download_stats"]) do
      download_stats = download_stats .. tostring(-v * 1000 * 8) .. "," -- push the values in bit, they are stored as KB in c++
   end
   
   for _, v in pairs(_ifstats["upload_stats"]) do
      upload_stats = upload_stats .. tostring(v * 1000 * 8) .. "," -- push the values in bit, they are stored as KB in c++
   end

   download_stats = download_stats:sub(1, -2)
   upload_stats = upload_stats:sub(1, -2)
   if (_ifstats.id == -1) then
      return ""
   end
   if _ifstats.has_traffic_directions then
      return ([[
         <a href=']].. ntop.getHttpPrefix() ..[[/lua/if_stats.lua'>
            <div class='up'>
               <i class="fas fa-arrow-up"></i>
               <span style='display: none;' class="network-load-chart-upload">]] .. upload_stats .. [[</span>
               <span class="text-end chart-upload-text"></span>
            </div>
            <div class='down'>
               <i class="fas fa-arrow-down"></i>
               <span style='display: none;' class="network-load-chart-download">]] .. download_stats.. [[</span>
               <span class="text-end chart-download-text"></span>
            </div>
         </a>
      ]])
   else
      return ([[
         <a href=']].. ntop.getHttpPrefix() ..[[/lua/if_stats.lua'>
            <span style='display: none;' class="network-load-chart-total">0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0</span>
            <span class="text-end chart-total-text"></span>
         </a>
      ]])
   end

end

-- ##############################################

function page_utils.is_system_view()
   return interface.getId() == tonumber(getSystemInterfaceId()) and isAdministrator()
end

-- ##############################################

--- Check if the current page is valid
--- @return boolean True if the current page is in available_pages, false otherwise
function page_utils.is_valid_page(selected_page, available_pages)

   if selected_page == nil then
      traceError(TRACE_WARNING, TRACE_DEBUG, "selected_pages is nil!")
      return false
   end
   if available_pages == nil then
      traceError(TRACE_WARNING, TRACE_DEBUG, "available_pages is nil!")
      return false
   end
   if type(available_pages) ~= 'table' then
      traceError(TRACE_WARNING, TRACE_DEBUG, "available_pages is not a table!")
      return false
   end

   -- do a linear search
   for _, page in ipairs(available_pages) do
      if (page == selected_page) then return true end
   end

   return false

end

-- ##############################################

-- @brief Prepares the URL which will be used for the redirect after interface switch
-- @param ifid The interface identifier of the interface that will be switched
-- @param if_type The type of the interface that will be switched
-- @return The URL
function page_utils.switch_interface_form_action_url(active_ifid, switch_ifid, switch_if_type)
   local action_url = ""
   local page_params = table.clone(_GET)

   -- Read the currently active page
   local active_page = page_utils.get_active_section()

   if active_ifid == tonumber(getSystemInterfaceId()) then
      -- If the switch is between the system interface and a non-system interface
      -- the URL is just the home
      action_url = ntop.getHttpPrefix() .. '/'
   elseif page_utils.menu_sections[active_page] and page_utils.menu_sections[active_page]["zmq_only"] and switch_if_type ~= "zmq" and switch_if_type ~= "custom" then
      -- If the interface that will be switched is non-ZMQ, and the currently
      -- selected page is for ZMQ-only interfaces, then a redirection to root
      -- is performed, rather than preserving the current page
      action_url = ntop.getHttpPrefix() .. '/'
   end

   -- Attach the interface id of the interface that will be switched
   page_params.ifid = switch_ifid

   page_params["observationPointId"] = nil

   -- Return the url, preserving all existing page parameters (e.g., host=xx)
   local res = getPageUrl(action_url, page_params)

   return res
end

-- ##############################################

return page_utils

