`);
$radio_buttons.find(`label`).on('click', function (e) {
// Remove active class from every button
$radio_buttons.find('label').removeClass('active').removeClass('btn-primary').addClass('btn-secondary');
// Remove checked from buttons
const checked_input_id = $(this).attr('for');
const checked_input = $("#" + checked_input_id);
// Add attribute checked to the input associated to this label
checked_input.attr('checked', '');
// Set the right classes to this active label
$(this).addClass('active btn-primary').removeClass('btn-secondary');
});
if (has_container) {
const $radio_container = $(``);
return $radio_container.append($radio_buttons);
}
return $radio_buttons;
}
/* ******************************************************* */
const reset_radio_button = (name, value) => {
$(`input[name='${name}']`)
.removeAttr('checked')
.parent()
.removeClass('btn-primary')
.removeClass('active')
.addClass('btn-secondary');
$(`input[name='${name}'][value='${value}']`)
.attr('checked', '')
.parent()
.toggleClass('btn-secondary')
.toggleClass('btn-primary')
.toggleClass('active');
}
/* ******************************************************* */
const get_unit_bytes = (bytes) => {
if (bytes < 1048576 || bytes == undefined || bytes == null) {
return ["KB", bytes / 1024, 1024];
}
else if (bytes >= 1048576 && bytes < 1073741824) {
return ["MB", bytes / 1048576, 1048576];
}
else {
return ["GB", bytes / 1073741824, 1073741824];
}
};
/* ******************************************************* */
const get_unit_times = (seconds) => {
if (seconds < 3600 || seconds == undefined || seconds == null) {
return [`${i18n.metrics.minutes}`, seconds / 60, 60];
}
else if (seconds >= 3600 && seconds < 86400) {
return [`${i18n.metrics.hours}`, seconds / 3600, 3600];
}
else if (seconds >= 86400) {
return [`${i18n.metrics.days}`, seconds / 86400, 86400];
}
};
/* ******************************************************* */
function getSanitizedScriptExList(script_exclusion_list) {
var ex_list_purified;
// IP only are supported at the moment, values are listed without =
// as this is more intuitive for the user, however the backend
// handles = (see also appendExclusionList)
//ex_list_purified = script_exclusion_list.split("\n").join(";");
ex_list_purified = script_exclusion_list.split(" ").join("");
ex_list_purified = ex_list_purified.split("\n").join("");
if (ex_list_purified.length > 0)
ex_list_purified = "ip="+ex_list_purified;
ex_list_purified = ex_list_purified.split(",").join(";ip=");
return ex_list_purified.split(" ").join("");
}
/* ******************************************************* */
const apply_edits_script = (template_data, check_subdir, script_key) => {
const exclusionList = $(`#script-config-editor textarea[name='exclusion-list']`).val();
var script_exclusion_list = exclusionList || undefined;
const $apply_btn = $('#btn-apply');
const $error_label = $("#apply-error");
if (script_exclusion_list !== undefined) {
script_exclusion_list = getSanitizedScriptExList(script_exclusion_list);
}
// remove dirty class from form
$('#edit-form').removeClass('dirty')
$apply_btn.attr('disabled', '');
$.post(`${http_prefix}/lua/edit_check_config.lua`, {
check_subdir: check_subdir,
script_key: script_key,
csrf: pageCsrf,
script_exclusion_list: script_exclusion_list,
JSON: JSON.stringify(template_data)
})
.done((d, status, xhr) => {
if (NtopUtils.check_status_code(xhr.status, xhr.statusText, $error_label)) return;
if (!d.success) {
$error_label.text(d.error).show();
// re enable button
$apply_btn.removeAttr('disabled');
}
// if the operation was successfull then reload the page
if (d.success) reloadPageAfterPOST();
})
.fail(({ status, statusText }, a, b) => {
NtopUtils.check_status_code(status, statusText, $error_label);
if (status == 200) {
$error_label.text(`${i18n.expired_csrf}`).show();
}
$apply_btn.removeAttr('disabled');
});
}
const reset_script_defaults = (script_key, check_subdir, callback_reset) => {
const $error_label = $('#apply-error');
$.get(`${http_prefix}/lua/get_check_config.lua`, {
check_subdir: check_subdir,
script_key: script_key,
factory: 'true'
})
.done((reset_data, status, xhr) => {
// if there is an error about the http request
if (NtopUtils.check_status_code(xhr.status, xhr.statusText, $error_label)) return;
const { metadata } = reset_data;
const exclusionList = $(`#script-config-editor textarea[name='exclusion-list']`).val();
const script_exclusion_list = exclusionList || undefined;
/* Creating the default string for the exclusion list when reset is called */
if (script_exclusion_list) {
let ex_list_str = ""
const scriptConfExList = reset_data.filters;
if (scriptConfExList) {
const scriptConfCurrFil = scriptConfExList.current_filters;
if (scriptConfCurrFil) {
for (const [index, filters] of Object.entries(scriptConfCurrFil)) {
for (const [name, value] of Object.entries(filters)) {
// Concat the string to create a human readable string
if (name === "str_format") {
// Temporary check, needs to be removed in a few time
continue;
}
ex_list_str = ex_list_str + name + "=" + value + ",";
}
ex_list_str = ex_list_str.slice(0, -1);
ex_list_str = ex_list_str + "\n";
}
}
$(`#script-config-editor textarea[name='exclusion-list']`).val(ex_list_str);
}
}
// call callback function to reset fields
callback_reset(reset_data);
// add dirty class to form
$('#edit-form').addClass('dirty');
})
.fail(({ status, statusText }) => {
NtopUtils.check_status_code(status, statusText, $error_label);
// hide modal if there is error
$("#modal-script").modal("toggle");
})
}
/* ******************************************************* */
// TEMPALTES:
const ThresholdCross = (gui, hooks, check_subdir, script_key) => {
const $table_editor = $("#script-config-editor");
const render_select_operator = (operators, key, hook) => {
const $select = $(`
`);
operators.forEach((op) => {
$select.append($(``));
});
// select the right operator
if (hook.script_conf.operator != undefined) {
$select.val(hook.script_conf.operator)
}
return $select;
}
const render_template = () => {
const { field_operator, fields_unit, field_min, field_max } = gui;
const operators = ['gt', 'lt'];
// save input fields
const $input_fields = {};
// iterate over keys to create each hooks
for (const key in hooks) {
if (key == undefined) continue;
// get hook
const hook = hooks[key];
let $select = null;
if (field_operator == undefined) {
$select = render_select_operator(operators, key, hook);
}
else {
$select = $(`&${field_operator}`).data('value', field_operator);
}
const $field = $(``);
$field.append($select);
$field.append(``);
$field.append(`${fields_unit ? fields_unit : ""}`);
$field.append(``);
const $input_container = $(`
`);
const $checkbox = $(`
`);
// bind check event on checkboxes
$checkbox.find(`input[type='checkbox']`).change(function (e) {
const checked = $(this).prop('checked');
// if the checked option is false the disable the elements
if (!checked) {
$field.find(`input[type='number']`).attr("readonly", "");
$select.attr("disabled", "");
return;
}
$field.find(`input[type='number']`).removeAttr("readonly");
$select.removeAttr("disabled");
});
// append label and checkbox inside the row
$input_container.append(
$(`
`).append($checkbox),
$(`
${(hook.label ? hook.label.titleCase() : "")}
`),
$(`
`).append($field)
);
// save input field
$input_fields[key] = $input_container;
}
// clean script editor table from previous state
$table_editor.empty();
// append each hooks to the table
$table_editor.append(`
${i18n.enabled}
`)
if ("min" in $input_fields) {
$table_editor.append($input_fields['min']);
delete $input_fields['min'];
}
if ("5mins" in $input_fields) {
$table_editor.append($input_fields['5mins']);
delete $input_fields['5mins'];
}
if ("hour" in $input_fields) {
$table_editor.append($input_fields['hour']);
delete $input_fields['hour'];
}
if ("day" in $input_fields) {
$table_editor.append($input_fields['day']);
delete $input_fields['day'];
}
let other_keys = [];
for (let key in $input_fields) other_keys.push(key);
/* Guarantees the sort order */
other_keys.sort();
$.each(other_keys, function (idx, item) {
$table_editor.append($input_fields[item]);
});
};
const apply_event = (event) => {
// prepare request to save config
const data = {};
// iterate over granularities
$table_editor.find("tr[id]").each(function (index) {
const id = $(this).attr("id");
const enabled = $(this).find("input[type='checkbox']").is(":checked");
const $template = $(this).find(".template");
const $error_label = $template.find(`.invalid-feedback`);
let operator = $template.find("select").val();
// if operator is undefined it means there isn't any select, so take the value from span
if (operator == undefined) {
operator = $template.find('span.input-group-text').data('value');
}
const $input_box = $template.find("input");
let threshold = parseInt($input_box.val());
// hide before errors
$error_label.hide();
// remove class error
$input_box.removeClass('is-invalid');
// save data into dictonary
data[id] = {
'enabled': enabled,
'script_conf': {
'operator': operator,
'threshold': parseInt(threshold)
}
}
});
apply_edits_script(data, check_subdir, script_key);
};
const reset_event = (event) => {
reset_script_defaults(script_key, check_subdir, (data) => {
const { hooks } = data;
// reset default values
for (key in hooks) {
const granularity = hooks[key];
$(`input[name='${key}-check']`).prop('checked', granularity.enabled);
if (granularity.script_conf.threshold === undefined) {
$(`input[name='${key}-input']`).val('');
}
else {
$(`input[name='${key}-input']`).val(granularity.script_conf.threshold);
}
if (granularity.enabled) {
$(`select[name='${key}-select']`).removeAttr("disabled");
$(`input[name='${key}-input']`).removeAttr("readonly");
}
else {
$(`input[name='${key}-input']`).attr("readonly", "");
$(`select[name='${key}-select']`).attr("disabled", "");
}
$(`select[name='${key}-select']`).val(granularity.script_conf.operator);
}
});
}
return {
apply_click_event: apply_event,
reset_click_event: reset_event,
render: render_template,
}
}
/* ******************************************************* */
const ItemsList = (gui, hooks, check_subdir, script_key) => {
const $table_editor = $("#script-config-editor");
const render_template = () => {
let enabled = undefined;
if(hooks.all)
enabled = hooks.all.enabled
else
enabled = hooks["5mins"] ? hooks["5mins"].enabled : hooks.min.enabled;
const $component_container = $(`
`);
const callback_checkbox = function (e) {
const checked = $(this).prop('checked');
// if the checked option is false the disable the elements
if (!checked) {
$text_area.find(`#itemslist-textarea`).attr("readonly", "");
return;
}
$text_area.find(`#itemslist-textarea`).removeAttr("readonly", "");
};
const $checkbox_enabled = generate_checkbox_enabled(
'itemslist-checkbox', enabled, callback_checkbox
);
const items_list = hooks.all ? hooks.all.script_conf.items : ( (hooks["5mins"] ? hooks["5mins"].script_conf.items : hooks.min.script_conf.items) || []);
const $text_area = $(`
`).append($(``).append($textarea)));
}
$(`#script-config-editor`).append($container);
$(`#script-config-editor Textarea[name='exclusion-list']`).val(ex_list_str);
}
}
function delegateActionButton(gui) {
const $button = $(`#btn-action`);
$button.text(gui.input_action_i18n);
$button.off('click').click(function (e) {
e.preventDefault();
if (gui.input_action_confirm && !window.confirm(gui.input_action_i18n_confirm)) {
return;
}
$button.attr("disabled", "disabled");
const $alert = $(`#action-error`);
$alert.hide();
const req = $.post(`${http_prefix}/${gui.input_action_url}`, { csrf: pageCsrf });
req.then(function ({ rc, rc_str }) {
// if the return code is zero then everything went alright
if (rc == 0) {
$alert.removeClass('alert-danger').addClass('alert-success').html(i18n.rest[rc_str]).show().fadeOut(3000);
return;
}
// otherwise show an error!
$alert.removeClass('alert-success').addClass('alert-danger').html(i18n.rest[rc_str]).show();
});
req.fail(function (jqXHR) {
if (jqXHR.status == 404) {
NtopUtils.check_status_code(jqXHR.status, jqXHR.statusText, $alert);
return;
}
const { rc_str } = jqXHR.responseJSON;
$alert.removeClass('alert-success').addClass('alert-danger').html(i18n.rest[rc_str]).show();
});
req.always(function () {
$button.removeAttr("disabled");
});
});
}
function delegateTooltips() {
$(`span[data-bs-toggle='popover']`).popover({
trigger: 'manual',
html: true,
animation: false,
})
.on('mouseenter', function () {
let self = this;
$(this).popover("show");
$(".popover").on('mouseleave', function () {
$(self).popover('hide');
});
})
.on('mouseleave', function () {
let self = this;
setTimeout(function () {
if (!$('.popover:hover').length) {
$(self).popover('hide');
}
}, 50);
});
}
$(function () {
const CATEGORY_COLUMN_INDEX = 1;
const VALUES_COLUMN_INDEX = 3;
const add_filter_categories_dropdown = () => {
const $dropdown = $(`
`);
$dropdown.find('#category-filter').append(
scripts_categories.map((c, index) => {
// list element to append inside the dropdown selector
const $list_element = $(`
${c.label}
`);
// when a user click the filter category then the datatable
// will be filtered
$list_element.click(function () {
// if the category is not inside the array
// it means the filter category is `All`
if (c.disableFilter) {
$script_table
.column(CATEGORY_COLUMN_INDEX).search('')
.column(VALUES_COLUMN_INDEX).search(get_search_toggle_value(location.hash))
.draw();
$dropdown.find('button span').text(`${i18n.filter_categories}`);
return;
}
$dropdown.find('button span').html(` ${c.label}`);
$script_table
.column(CATEGORY_COLUMN_INDEX).search(c.label)
.column(VALUES_COLUMN_INDEX).search(get_search_toggle_value(location.hash))
.draw();
});
return $list_element;
})
);
$('#scripts-config_filter').prepend($dropdown);
return $dropdown;
}
const hide_categories_dropdown = () => {
// get current category filter
const current_category_filter = $script_table.column(CATEGORY_COLUMN_INDEX).search();
// get alla categories from current datatable instance
const data_rows = $script_table
.column(CATEGORY_COLUMN_INDEX)
.search('')
.rows({ filter: 'applied' }).data();
const categories_set = new Set();
for (let i = 0; i < data_rows.length; i++) {
categories_set.add(data_rows[i].category_title);
}
const enabled_categories = [...categories_set];
if (enabled_categories.indexOf(current_category_filter) == -1) {
$('#category-filter-menu button span').text(`${i18n.filter_categories}`);
$script_table.column(CATEGORY_COLUMN_INDEX).search('').draw();
}
$('#category-filter li').each(function (index, element) {
const value = $(this).text();
// all filter must be always enabled
if (scripts_categories.find(e => e.label == value).disableFilter) return;
// hide category
if (enabled_categories.indexOf(value) == -1) {
$(this).hide();
return;
}
$(this).show();
});
}
const truncate_string = (str, lim, strip_html = false) => {
if (strip_html) {
let str_sub = str.replace(/(<([^>]+)>)/ig, "");
return (str_sub.length > lim) ? str_sub.substr(0, lim) + '...' : str_sub;
}
return (str.length > lim) ? str.substr(0, lim) + '...' : str;
}
// initialize script table
const $script_table = $("#scripts-config").DataTable({
dom: "Bfrtip",
pagingType: 'full_numbers',
language: {
info: i18n.showing_x_to_y_rows,
search: i18n.script_search,
infoFiltered: "",
paginate: {
previous: '<',
next: '>',
first: '«',
last: '»'
}
},
lengthChange: false,
ajax: {
url: `${http_prefix}/lua/get_checks.lua?check_subdir=${check_subdir}`,
type: 'get',
dataSrc: ''
},
stateSave: true,
initComplete: function (settings, json) {
// add categories dropdown
const $categories_filter = add_filter_categories_dropdown();
// check if there is a previous filter
if (settings.oLoadedState != null) {
const loaded_filter = settings.oLoadedState.columns[CATEGORY_COLUMN_INDEX].search.search;
if (loaded_filter != "") $categories_filter.find('button span').html(` ${loaded_filter}`);
}
const [enabled_count, disabled_count] = count_scripts();
// enable the disable all button if there are more than one enabled scripts
if (enabled_count > 0) $(`#btn-disable-all`).removeAttr('disabled');
// clean searchbox
$(".dataTables_filter").find("input[type='search']").val('').trigger('keyup');
// hide category in base selected pill
hide_categories_dropdown();
$('#all-scripts,#enabled-scripts,#disabled-scripts').click(function () {
hide_categories_dropdown();
});
delegateTooltips();
// update the tabs counters
const INDEX_SEARCH_COLUMN = 3;
const $disabled_button = $(`#disabled-scripts`);
const $all_button = $("#all-scripts");
const $enabled_button = $(`#enabled-scripts`);
$all_button.html(`${i18n.all} (${enabled_count + disabled_count})`)
$enabled_button.html(`${i18n.enabled} (${enabled_count})`);
$disabled_button.html(`${i18n.disabled} (${disabled_count})`);
const filterButonEvent = ($button, searchValue, tab) => {
$('.filter-scripts-button').removeClass('active');
$button.addClass('active');
this.DataTable().columns(INDEX_SEARCH_COLUMN).search(searchValue).draw();
window.history.replaceState(undefined, undefined, tab);
delegateTooltips();
}
$all_button.click(function () {
filterButonEvent($(this), "", "#all");
});
$enabled_button.click(function () {
filterButonEvent($(this), "true", "#enabled");
});
$disabled_button.click(function () {
filterButonEvent($(this), "false", "#disabled");
});
// select the correct tab
select_script_filter(enabled_count);
if (script_search_filter) {
this.DataTable().columns(INDEX_SEARCH_COLUMN).search("").draw(true);
this.DataTable().search(script_search_filter).draw(true);
// disable the search box
$(`#scripts-config_filter input[type='search']`).attr("readonly", "");
}
if (script_key_filter) {
let elem = json.filter((x) => { return (x.key == script_key_filter); })[0];
if (elem) {
let title = elem.title;
let desc = elem.description;
this.DataTable().search(title).draw();
if (hasConfigDialog(elem)) {
initScriptConfModal(script_key_filter, title, desc);
$("#modal-script").modal("show");
}
}
}
},
order: [[0, "asc"]],
buttons: {
buttons: [
{
text: '',
className: 'btn-link',
action: function (e, dt, node, config) {
$script_table.ajax.reload(function () {
const [enabled_count, disabled_count] = count_scripts();
// enable the disable all button if there are more than one enabled scripts
if (enabled_count > 0) $(`#btn-disable-all`).removeAttr('disabled');
$("#all-scripts").html(`${i18n.all} (${enabled_count + disabled_count})`)
$(`#enabled-scripts`).html(`${i18n.enabled} (${enabled_count})`);
$(`#disabled-scripts`).html(`${i18n.disabled} (${disabled_count})`);
}, false);
}
}
],
dom: {
button: {
className: 'btn btn-link'
},
container: {
className: 'border-start ms-1 float-end'
}
}
},
columns: [
{
data: 'title',
render: function (data, type, row) {
if (type == 'display') return `${data}`;
return data;
},
},
{
data: null,
sortable: true,
searchable: true,
className: 'text-center',
render: function (data, type, row) {
const icon = (!row.category_icon) ? '' : ``;
if (type == "display") return `${icon}`;
return row.category_title;
}
},
{
data: 'description',
render: function (data, type, row) {
if (type == "display") {
return `= 120 ? `data-bs-toggle='popover' data-placement='top' data-html='true' title="${row.title}" data-bs-content="${data}"` : ``}
data-content="${data}" >
${truncate_string(data, 120, true)}
`;
}
return data;
},
},
{
data: 'enabled_hooks',
sortable: false,
className: 'text-start',
render: function (data, type, row) {
// if the type is flter return true if the data length is greather or equal
// than 0 so the script table can detect if a plugin is enabled
if (data.length <= 0 && type == "filter") return false;
if (data.length > 0 && type == "filter") return true;
return (type == 'display') ? `
= 32 ? `data-bs-toggle='popover' data-placement='top'` : ``}
data-content='${row.value_description}'>
${row.value_description.substr(0, 32)}${row.value_description.length >= 32 ? '...' : ''}
` : '';
},
},
{
targets: -1,
data: null,
name: 'actions',
className: 'text-center',
sortable: false,
width: 'auto',
render: function (data, type, script) {
const isScriptEnabled = script.is_enabled;
const isSubdirFlow = (check_subdir === "flow");
const srcCodeButtonEnabled = data.edit_url && isScriptEnabled ? '' : 'disabled';
const editScriptButtonEnabled = ((!script.input_handler && !isSubdirFlow) || !isScriptEnabled) ? 'disabled' : '';
return DataTableUtils.createActionButtons([
{ class: `btn-info ${editScriptButtonEnabled}`, modal: '#modal-script', icon: 'fa-edit' },
{ class: `btn-secondary ${srcCodeButtonEnabled}`, icon: 'fa-file-code', href: data.edit_url }
]);
},
createdCell: function (td, cellData, row) {
const $enableButton = createScriptStatusButton(row);
$(td).find('div').prepend($enableButton);
}
}
]
});
// initialize are you sure
$("#edit-form").areYouSure({ message: i18n.are_you_sure });
// handle modal-script close event
$("#modal-script").on("hide.bs.modal", function (e) {
// if the forms is dirty then ask to the user
// if he wants save edits
if ($('#edit-form').hasClass('dirty')) {
// ask to user if he REALLY wants close modal
const result = confirm(`${i18n.are_you_sure}`);
if (!result) e.preventDefault();
// remove dirty class from form
$('#edit-form').removeClass('dirty');
}
})
.on("shown.bs.modal", function (e) {
// add focus to btn apply to enable focusing on the modal hence user can press escape button to
// close the modal
$("#btn-apply").trigger('focus');
});
// load templates for the script
$('#scripts-config').on('click', '[href="#modal-script"],[data-bs-target="#modal-script"]', function (e) {
const row_data = $script_table.row($(this).parent().parent()).data();
const script_key = row_data.key;
const script_title = row_data.title;
const script_desc = row_data.description;
initScriptConfModal(script_key, script_title, script_desc);
});
/**
* Count the scripts that are enabled, disabled inside the script table
*/
const count_scripts = () => {
let enabled_count = 0;
let disabled_count = 0;
$script_table.data().each(d => {
if (d.is_enabled) {
enabled_count++;
return;
}
disabled_count++;
});
return [enabled_count, disabled_count];
}
$(`.filter-scripts-button`).click(function () {
$(`#scripts-config a[href^='/lua/code_viewer.lua']`).each(function () {
const encoded = encodeURIComponent(`${location.pathname}${location.search}${location.hash}`);
$(this).attr('href', `${$(this).attr('href')}&referal_url=${encoded}`);
});
});
$(`#disable-all-modal #btn-confirm-action`).click(async function () {
$(this).attr("disabled", "disabled");
$.post(`${http_prefix}/lua/toggle_all_checks.lua`, {
action: 'disable',
check_subdir: check_subdir,
csrf: pageCsrf
})
.then((result) => {
if (result.success) location.reload();
})
.catch((error) => {
console.error(error);
})
.always(() => {
$(`#btn-disable-all`).removeAttr("disabled");
})
})
});