EVOLUTION-MANAGER
Edit File: edit_profiles.lua
-- -- (C) 2013-20 - ntop.org -- local dirs = ntop.getDirs() package.path = dirs.installdir .. "/scripts/lua/modules/?.lua;" .. package.path package.path = dirs.installdir .. "/scripts/lua/pro/modules/?.lua;" .. package.path package.path = dirs.installdir .. "/scripts/lua/pro/?.lua;" .. package.path package.path = dirs.installdir .. "/pro/scripts/callbacks/?.lua;" .. package.path require "lua_utils" local template = require "template_utils" local os_utils = require "os_utils" local page_utils = require("page_utils") sendHTTPContentTypeHeader('text/html') page_utils.set_active_menu_entry(page_utils.menu_entries.profiles) dofile(dirs.installdir .. "/scripts/lua/inc/menu.lua") local ntop_info = ntop.getInfo() local max_profiles_num = ntop_info["constants.max_num_profiles"] print [[ <script> var MAX_PROFILES_NUM = ]] print(max_profiles_num.."") print[[; function submitSettings(profile_settings) { // reset ays so that we can submit a custom form var form = $("#profilesForm"); aysResetForm(form); // now create a custom form with appropriate parameters and submit it var params = paramsPairsEncode(profile_settings); params.edit_profiles = ""; params.csrf = "]] print(ntop.getRandomCSRFValue()) print[["; paramsToForm('<form method="post"></form>', params).appendTo('body').submit(); } function checkProfilesForm(ev) { var form = $("#profilesForm"); var profile_settings = {}; var now_exit = false; $('input', form).each(function() { var name = $(this). attr("name"); var parts = name.split("profile_"); if (parts.length == 2) { var profile_name = parts[1]; var filter_name = "filter_" + profile_name; var filter_value = $("input[name='"+filter_name+"']", form).val(); // Take the profile name directly from the input, since it may be different var profile_name = $(this).val(); profile_settings[profile_name] = filter_value; } }); if (now_exit) return false; for (var filter in filters_to_validate) { var status = filters_to_validate[filter]; // do not allow submit if there are fields checks in progress if (status.timer || status.ajax_obj) { status.submit_remind = true; return false; } } if (! ev.isDefaultPrevented()) // isDefaultPrevented is true when the form is invalid submitSettings(profile_settings); return false; } </script> <hr> ]] hashname = "ntopng.prefs.profiles" function forEachRedisCounter(ifid, callback) local key = "ntopng.profiles_counters.ifid_"..ifid local counters = ntop.getHashKeysCache(key) if counters ~= nil then for profile,_ in pairs(counters) do if not callback(profile) then ntop.delHashCache(key, profile) end end end end function forEachRRDCounter(ifid, callback) local base = os_utils.fixPath(dirs.workingdir .. "/" .. ifid .. '/profilestats') for profile, _ in pairs(ntop.readdir(base)) do if not callback(profile) then ntop.rmdir(base.."/"..profile) end end end if _POST["delete_profile"] ~= nil then -- delete a single profile ntop.delHashCache(hashname, _POST["delete_profile"]) -- delete the associated counter forEachRedisCounter(ifId, function(profile) if profile == _POST["delete_profile"] then -- remove return false else return true end end) -- delete the associated RRDs forEachRRDCounter(ifId, function(profile) if profile == _POST["delete_profile"] then -- remove return false else return true end end) ntop.reloadProfiles() elseif _POST["edit_profiles"] ~= nil then local config = paramsPairsDecode(_POST, true) -- delete unused counters forEachRedisCounter(ifId, function(profile) if config[profile] == nil then -- remove return false else return true end end) -- delete unused RRDs forEachRRDCounter(ifId, function(profile) if config[profile] == nil then -- remove return false else return true end end) -- Delete existing profiles ntop.delCache(hashname) local num = 0 for profile, filter in pairs(config) do if http_lint.validateTrafficProfile(profile) and ntop.checkProfileSyntax(filter) then ntop.setHashCache(hashname, profile, filter) num = num + 1 end if num >= max_profiles_num then break end end ntop.reloadProfiles() end print( template.gen("modal_confirm_dialog.html", { dialog={ id = "delete_profile_dialog", action = "deleteProfile(delete_profile_name)", title = i18n("traffic_profiles.delete_profile"), message = i18n("traffic_profiles.confirm_delete_profile") .. ' "<span id=\"delete_profile_dialog_profile\"></span>" ', confirm = i18n("delete"), } }) ) print [[ <form id="profilesForm" method="post"> <div id="table-profiles"></div> <div class="text-right"><button id="profilesSubmit" class="btn btn-primary" style="margin-right:1em;" type="submit" disabled="disabled">]] print(i18n("save_settings")) print[[</button></div> </form> <form id="deleteProfileForm" method="post"> <input type="hidden" name="csrf" value="]] print(ntop.getRandomCSRFValue()) print[["/> <input type="hidden" name="delete_profile" /> </form> <br> ]] print(i18n("traffic_profiles.simple_filter_examples")) print[[: <ul> <li>]] print(i18n("traffic_profiles.http_traffic")) print[[: <i>tcp and port 80</i></li> <li>]] print(i18n("traffic_profiles.host_traffic")) print[[: <i>host 192.168.1.2</i></li> <li>]] print(i18n("traffic_profiles.facebook_traffic")) print[[: <i>l7proto 119</i> ]] print(i18n("traffic_profiles.see_ndpi_protos", {option="<code>ntopng --print-ndpi-protocols</code>"})) print[[</li> </ul> ]] print(i18n("traffic_profiles.advanced_filter_examples")) print[[: <ul> <li>]] print(i18n("traffic_profiles.traffic_between")) print[[: <i>ip host 192.168.1.1 and 192.168.1.2</i></li> <li>]] print(i18n("traffic_profiles.traffic_from_to")) print[[: <i>ip src 192.168.1.1 and dst 192.168.1.2</i></li> <li>]] print(i18n("traffic_profiles.destination_network")) print[[: <i>ip dest net 192.168.1.0/24</i></li> <li>]] print(i18n("traffic_profiles.host_http_https")) print[[: <i>ip host 192.168.1.1 and tcp port (80 or 443)</i></li> <li>]] print(i18n("traffic_profiles.source_ethernet")) print[[: <i>ether src host 00:11:22:33:44:55</i></li> </ul> ]] print(i18n("traffic_profiles.note")) print[[: <ul> <li>]] print(i18n("traffic_profiles.note_0")) print[[. <li>]] print(i18n("traffic_profiles.note_1")) print[[. ]] if not ntop.isEnterprise() then print[[<li>]] print(i18n("traffic_profiles.max_profiles_num", {maxnum=max_profiles_num})) print[[.]] end print[[ </ul> <script> function deleteProfile(profile_name) { // perform the submit var delete_form = $("#deleteProfileForm"); $("input[name='delete_profile']", delete_form).val(profile_name); delete_form.submit(); } function makeProfilesRowEditable(tr_obj) { var profile_name = $("td:nth-child(1)", $(tr_obj)); var profile_filter = $("td:nth-child(2)", $(tr_obj)); // TODO proper escape sequences to support more UTF-8 characters var profile_name_input = $('<input class="form-control input-sm" data-unique="unique" autocomplete="off" spellcheck="false" placeholder="]] print(i18n("traffic_profiles.enter_profile_name")) print[[" pattern="^[_a-zA-Z0-9 ]*$" required/>').attr("name", "profile_" + profile_name.html()).val(profile_name.html()); var profile_filter_input = $('<input class="form-control input-sm" data-bpf="bpf" autocomplete="off" spellcheck="false" placeholder="]] print(i18n("traffic_profiles.enter_profile_filter")) print[[" required/>').attr("name", "filter_" + profile_name.html()).val(profile_filter.html()); var profile_validation_div = $('<div class="form-group has-feedback" style="margin-bottom:0;"></div>'); profile_validation_div.html(profile_name_input); $('<div class="help-block with-errors" style="margin-bottom:0;"></div>').appendTo(profile_validation_div); var filter_validation_div = $('<div class="form-group has-feedback" style="margin-bottom:0;"></div>'); filter_validation_div.html(profile_filter_input); $('<div class="help-block with-errors" style="margin-bottom:0;"></div>').appendTo(filter_validation_div); profile_name.html(profile_validation_div); profile_filter.html(filter_validation_div); } function addNewProfile() { if (datatableIsEmpty("#table-profiles")) datatableRemoveEmptyRow("#table-profiles"); var tr = $('<tr id="new_added_row"><td></td><td></td><td class="text-center" style="vertical-align: middle;"></td></tr>'); makeProfilesRowEditable(tr); $("#table-profiles table").append(tr); datatableAddDeleteButtonCallback.bind(tr)(3, "datatableUndoAddRow('#new_added_row', ']] print(i18n("traffic_profiles.no_profiles")) print[[', '#addNewProfileBtn')", "]] print(i18n("undo")) print[["); $("#addNewProfileBtn").attr('disabled', true); aysRecheckForm('#profilesForm'); } $("#table-profiles").datatable({ url: "]] print (ntop.getHttpPrefix()) print [[/lua/get_traffic_profiles.lua", showPagination: false, perPage: MAX_PROFILES_NUM, hidePerPage: true, hideDetails: true, title: "]] print(i18n("traffic_profiles.edit_traffic_profiles")) print[[", forceTable: true, buttons: [ '<a id="addNewProfileBtn" onclick="addNewProfile()" role="button" class="add-on btn" data-toggle="modal"><i class="fas fa-plus" aria-hidden="true"></i></a>' ], columns: [ { title: "]] print(i18n("traffic_profiles.profile_name")) print[[", field: "column_profile", css: { width: "18%", } }, { title: "]] print(i18n("traffic_profiles.traffic_filter_bpf")) print[[", field: "column_filter", }, { title: "]] print(i18n("actions")) print[[", css: { width: "15%", textAlign: "center", verticalAlign: "middle" } } ], tableCallback: function() { /* Only enable add button if we are in the last page */ var lastpage = $("#dt-bottom-details .pagination li:nth-last-child(3)", $("#table-profiles")); var num_profiles = $("#table-profiles tr").length; $("#addNewProfileBtn").attr("disabled", (((lastpage.length == 1) && (lastpage.hasClass("active") == false)) || (num_profiles > MAX_PROFILES_NUM))); if(datatableIsEmpty("#table-profiles")) { datatableAddEmptyRow("#table-profiles", "]] print(i18n("traffic_profiles.no_profiles")) print[["); } else { datatableForEachRow("#table-profiles", function() { makeProfilesRowEditable(this); var value = $("td:nth-child(1) input", $(this)).val(); datatableAddDeleteButtonCallback.bind(this)(3, "delete_profile_name ='" + value + "'; $('#delete_profile_dialog_profile').html('" + value + "'); $('#delete_profile_dialog').modal('show');", "]] print(i18n('delete')) print[["); }); aysResetForm('#profilesForm'); } $("#profilesForm") .validator(validatorOptions) .on('submit', checkProfilesForm); } }); aysHandleForm("#profilesForm", { handle_datatable: true, ays_options: {addRemoveFieldsMarksDirty: true} }); /* * This function performs form validation asyncronously. * It requires bootstrap 1000hz validation plugin. * * The input field should have a name, which is used as an identifier. * * The timeout parameter is used to limit the number of requests sent. It will * delay the ajax request for the specified time. */ var filters_to_validate = {}; function bpfValidator(filter_field) { var filter = filter_field.val(); var key = filter_field.attr("name"); // configuration var debug = false; var timeout = 250; if(debug) console.log("Filter: " + filter); if (! filters_to_validate[key]) filters_to_validate[key] = {ajax_obj:null, valid:true, timer:null, submit_remind:false, last_val:null}; var status = filters_to_validate[key]; var sendAjax = function () { status.timer = null; var finally_check = function (valid) { status.ajax_obj = null; // Update validation status status.valid = valid; status.last_val = filter; $("#profilesForm").validator('validate'); if(status.submit_remind) { status.submit_remind = false; $("#profilesForm").submit(); } } if (status.last_val !== filter) { if (status.ajax_obj) status.ajax_obj.abort(); status.ajax_obj = $.ajax({ type: "GET", url: ']] print(ntop.getHttpPrefix().."/lua/pro/check_profile.lua") print [[', data: { query: filter, }, error: function() { if(debug) console.log("\tAn error occurred"); finally_check(status.valid); }, success: function(data) { var valid = data.response ? true : false; if (valid) { if(debug) console.log("\tFilter valid"); } else { if(debug) console.log("\tFilter NOT valid"); } finally_check(valid); } }); } else { // possibly process the reminder finally_check(status.valid); } } if (status.last_val === filter) { if(debug) console.log("\tIgnoring..."); } else { if (status.timer) { clearTimeout(status.timer); status.submit_remind = false; } status.timer = setTimeout(sendAjax, timeout); if(debug) console.log("\tScheduling in the future..."); } return status.valid; } var validatorOptions = { disable: true, /* This does not troubles ays since it uses css classes instead of "disabled" attribute */ custom: { bpf: bpfValidator, unique: makeUniqueValidator(function(field) { return $('input[name^="profile_"]', $("#profilesForm")); }), }, errors: { bpf: "]] print(i18n("traffic_profiles.invalid_bpf")) print[[.", unique: "]] print(i18n("traffic_profiles.duplicate_profile")) print[[.", } } /* Retrigger the validation every second to clear outdated errors */ setInterval(function() { $("form:data(bs.validator)").each(function(){ $(this).data("bs.validator").validate(); }); }, 1000); </script> ]] dofile(dirs.installdir .. "/scripts/lua/inc/footer.lua")