ntopng/scripts/lua/admin/edit_profiles.lua
2020-04-02 12:36:34 +00:00

434 lines
15 KiB
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.isEnterpriseM() 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")