EVOLUTION-MANAGER
Edit File: scripts-list-utils.js
// 2020 - ntop.org String.prototype.titleCase = function () { return this.toLowerCase().split(' ').map(function(word) { return word.replace(word[0], word[0].toUpperCase()); }).join(' '); } /* ******************************************************* */ const reloadPageAfterPOST = () => { if(location.href.indexOf("user_script=") > 0) { /* Go back to the alerts page */ //location.href = page_url + location.hash; window.history.back(); } else { /* The URL is still the same as before, need to force a reload */ location.reload(); } } /* ******************************************************* */ const hasConfigDialog = (item) => { return !(item.all_hooks.length > 0 && item.input_handler == undefined); } /* ******************************************************* */ /** * This function select the correct tab for script filtering */ const select_script_filter = (enabled_count) => { // get hash from url let hash = window.location.hash; if (hash == undefined || hash == null || hash == "") { // if no tab is active, show the "enabled" tab if there are any enabled // scripts, otherwise show the "all-scripts" tab hash = enabled_count ? '#enabled' : '#all-scripts'; } // redirect to correct tab if (hash == "#enabled") { $(`#enabled-scripts`).addClass("active").trigger("click"); } else if (hash == "#disabled") { $(`#disabled-scripts`).addClass("active").trigger("click"); } else { $(`#all-scripts`).addClass("active").trigger("click"); } } /* ******************************************************* */ // Templates and template builder const generate_checkbox_enabled = (id, enabled, callback) => { const $checkbox_enabled = $(` <div class="custom-control custom-switch"> <input id='${id}' name='enabled' class="custom-control-input" type="checkbox" ${enabled ? "checked" : ""} /> <label class="custom-control-label" for="${id}"></label> </div> `); // bind check event on checkboxes $checkbox_enabled.find(`input[type='checkbox']`).change(callback); return $checkbox_enabled; } /* ******************************************************* */ /** * Generate a multi select with groups */ const generate_multi_select = (params, has_container = true) => { const $select = $(`<select id='multiple-select' multiple class='form-control'></select>`); // add groups and items if(params.groups.length == 1) { params.groups[0].elements.forEach((element) => { $select.append($(`<option value='${element[0]}'>${element[1]}</option>`)) }); } else { params.groups.forEach((category) => { const $group = $(`<optgroup label='${category.label}'></optgroup>`); category.elements.forEach((element) => { $group.append($(`<option value='${element[0]}'>${element[1]}</option>`)) }); $select.append($group); }); } // add attributes if (params.name != undefined) $select.attr('name', params.name); if (params.enabled != undefined && !params.enabled) $select.attr('disabled', ''); if (params.id != undefined) $select.attr('id', params.id); if (params.selected_values != undefined) { $select.val(params.selected_values); } if (has_container) { return $(` <div class='form-group mt-3'> <label>${params.label || 'Default Label'}</label> </div> `).append($select); } return $select; } /* ******************************************************* */ const generate_input_box = (input_settings, has_container = true) => { const $input_box = $(`<input required type='number' name='${input_settings.name}' class='form-control' />`); // set attributes and values if (input_settings.max != undefined) $input_box.attr("max", input_settings.max); if (input_settings.min != undefined) $input_box.attr("min", input_settings.min); if (input_settings.current_value != undefined) $input_box.val(input_settings.current_value); if (input_settings.enabled != undefined && !input_settings.enabled) { $input_box.attr("readonly", ""); } if (has_container) { const $input_container = $(`<div class='form-row mb-2'></div>`); return $input_container.append($(`<div class='col-2'></div>`).append($input_box)); } return $input_box; } /* ******************************************************* */ const generate_single_select = (params, has_container = true) => { const $select = $(`<select name='${params.name}' class='form-control' />`); if (params.enabled != undefined && !params.enabled) { $select.attr("disabled", ""); } params.elements.forEach((element) => { $select.append($(`<option value='${element[0]}'>${element[1]}</option>`)) }); if (params.current_value != undefined) $select.val(params.current_value); if (has_container) { const $input_container = $(`<div class='form-row mb-2'></div>`); return $input_container.append( $(`<div class='col-10'><label class='p-2'>${params.label}</label></div>`), $(`<div class='col-2'></div>`).append($select), ); } return $input_box; } /* ******************************************************* */ const generate_textarea = (textarea_settings) => { const $textarea = $(` <div class='form-group mt-3'> <label class='pl-2'>${textarea_settings.label}</label> <textarea ${textarea_settings.class} ${textarea_settings.enabled ? '' : 'readonly'} name='${textarea_settings.name}' placeholder='${textarea_settings.placeholder}' class='form-control ml-2'>${textarea_settings.value}</textarea> <div class="invalid-feedback"></div> </div> `); return $textarea; }; /* ******************************************************* */ const generate_radio_buttons = (params, has_container = true) => { const active_first_button = params.granularity.labels[0] == params.granularity.label; const active_second_button = params.granularity.labels[1] == params.granularity.label; const active_third_button = params.granularity.labels[2] == params.granularity.label; const $radio_buttons = $(` <div class="btn-group float-right btn-group-toggle" data-toggle="buttons"> <label class="btn ${active_first_button ? 'active btn-primary' : 'btn-secondary'} ${params.enabled ? '' : 'disabled'}"> <input ${params.enabled ? '' : 'disabled'} ${active_first_button ? 'checked' : ''} value='${params.granularity.values[0]}' type="radio" name="${params.name}"> ${params.granularity.labels[0]} </label> <label class="btn ${active_second_button ? 'active btn-primary' : 'btn-secondary'} ${params.enabled ? '' : 'disabled'}"> <input ${params.enabled ? '' : 'disabled'} ${active_second_button ? 'checked' : ''} value='${params.granularity.values[1]}' type="radio" name="${params.name}"> ${params.granularity.labels[1]} </label> <label class="btn ${active_third_button ? 'active btn-primary' : 'btn-secondary'} ${params.enabled ? '' : 'disabled'}"> <input ${params.enabled ? '' : 'disabled'} ${active_third_button ? 'checked' : ''} value='${params.granularity.values[2]}' type="radio" name="${params.name}"> ${params.granularity.labels[2]} </label> </div> `); $radio_buttons.find(`input[type='radio']`).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 $radio_buttons.find('input').removeAttr('checked'); // add active class and btn-primary to the new one $(this).prop('checked', '').parent().addClass('active btn-primary').removeClass('btn-secondary'); }); if (has_container) { const $radio_container = $(`<div class='col-3'></div>`); 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]; } }; /* ******************************************************* */ const apply_edits_script = (template_data, script_subdir, script_key) => { const $apply_btn = $('#btn-apply'); const $error_label = $("#apply-error"); // remove dirty class from form $('#edit-form').removeClass('dirty') $apply_btn.attr('disabled', ''); $.post(`${http_prefix}/lua/edit_user_script_config.lua`, { script_subdir: script_subdir, script_key: script_key, csrf: csrf_edit_config, JSON: JSON.stringify(template_data), confset_id: confset_id }) .done((d, status, xhr) => { if (check_status_code(xhr.status, xhr.statusText, $error_label)) return; if (!d.success) { $error_label.text(d.error).show(); // update token csrf_edit_config = d.csrf; // re enable button $apply_btn.removeAttr('disabled'); } // if the operation was successfull then reload the page if (d.success) reloadPageAfterPOST(); }) .fail(({ status, statusText }) => { 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, script_subdir, callback_reset) => { const $error_label = $('#apply-error'); $.get(`${http_prefix}/lua/get_user_script_config.lua`, { script_subdir: script_subdir, script_key: script_key }) .done((reset_data, status, xhr) => { // if there is an error about the http request if (check_status_code(xhr.status, xhr.statusText, $error_label)) return; // call callback function to reset fields callback_reset(reset_data); // add dirty class to form $('#edit-form').addClass('dirty'); }) .fail(({ status, statusText }) => { check_status_code(status, statusText, $error_label); // hide modal if there is error $("#modal-script").modal("toggle"); }) } /* ******************************************************* */ const ThresholdCross = (gui, hooks, script_subdir, script_key) => { const $table_editor = $("#script-config-editor"); const render_select_operator = (operators, key, hook) => { const $select = $(` <select name='${key}-select' required ${hook.enabled ? '' : 'disabled'} class='btn btn-outline-secondary'></select> `); operators.forEach((op) => { $select.append($(`<option value="${op}">&${op}</option>`)); }); // 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 = $(`<span class='input-group-text'>&${field_operator}</span>`).data('value', field_operator); } const $field = $(`<div class='input-group template w-50'></div>`); $field.append($(`<div class='input-group-prepend'></div>`).append($select)); $field.append(`<input type='number' class='form-control text-right' required name='${key}-input' ${hook.enabled ? '' : 'readonly'} value='${hook.script_conf.threshold == undefined ? '' : hook.script_conf.threshold}' min='${field_min == undefined ? '' : field_min}' max='${field_max == undefined ? '' : field_max}'>`); $field.append(`<span class='mt-auto mb-auto ml-2 mr-2'>${fields_unit ? fields_unit : ""}</span>`); $field.append(`<div class='invalid-feedback'></div>`); const $input_container = $(`<tr id='${key}'></tr>`); const $checkbox = $(` <div class="custom-control custom-switch"> <input class="custom-control-input" id="id-${key}-check" name="${key}-check" type="checkbox" ${hook.enabled ? "checked" : ""} > <label class="custom-control-label" for="id-${key}-check"></label> </div> `); // 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( $(`<td class='text-center'></td>`).append($checkbox), $(`<td>${(hook.label ? hook.label.titleCase() : "")}</td>`), $(`<td></td>`).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(`<tr><th class='text-center'>${i18n.enabled}</th></tr>`) 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, script_subdir, script_key); }; const reset_event = (event) => { reset_script_defaults(script_key, script_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, script_subdir, script_key) => { const $table_editor = $("#script-config-editor"); const render_template = () => { const $component_container = $(`<tr></tr>`); 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', hooks.all.enabled, callback_checkbox ); const items_list = hooks.all.script_conf.items || []; const $text_area = $(` <td> <div class='form-group template w-100'> <textarea ${!hooks.all.enabled ? "readonly" : ""} name='items-list' id='itemslist-textarea' class="w-100 form-control" style="height: 5rem;">${items_list.length > 0 ? items_list.join(',') : ''}</textarea> <small>${gui.input_description || i18n.blacklisted_country}</small> <div class="invalid-feedback"></div> </div> </td> `); $component_container.append($(`<td class='text-center'></td>`).append($checkbox_enabled), $text_area); $table_editor.empty(); $table_editor.append(`<tr><th class='text-center w-25'>${i18n.enabled}</th><th>${gui.input_title || i18n.scripts_list.templates.blacklisted_country_list}:</th></tr>`) $table_editor.append($component_container); } const apply_event = (event) => { const special_char_regexp = /[\@\#\<\>\\\/\?\'\"\`\~\|\:\;\!\&\*\(\)\{\}\[\]\_\-\+\=\%\$\^]/g; const hook_enabled = $('#itemslist-checkbox').prop('checked'); let $error_label = $('#itemslist-textarea').parent().find('.invalid-feedback'); $error_label.fadeOut(); const textarea_value = $('#itemslist-textarea').val().trim(); // if the textarea value contains special characters such as #, @, ... then alert the user if (special_char_regexp.test(textarea_value)) { $error_label.fadeIn().text(`${i18n.items_list_comma}`); return; } // hide label $error_label.hide(); const items_list = textarea_value ? textarea_value.split(',').map(x => x.trim()) : []; const template_data = { all: { enabled: hook_enabled, script_conf: { items: items_list } } }; // make post request to save edits apply_edits_script(template_data, script_subdir, script_key); } const reset_event = (event) => { reset_script_defaults(script_key, script_subdir, (reset_data) => { const items_list = reset_data.hooks.all.script_conf.items; const enabled = reset_data.hooks.all.enabled; // set textarea value with default's one $('#itemslist-textarea').val(items_list.join(',')); $('#itemslist-checkbox').prop('checked', enabled); // turn on readonly to textarea if enabled is false if (!enabled) { $('#itemslist-textarea').attr('readonly', ''); } }); } return { apply_click_event: apply_event, reset_click_event: reset_event, render: render_template, } } /* ******************************************************* */ const LongLived = (gui, hooks, script_subdir, script_key) => { const $table_editor = $("#script-config-editor"); $("#script-config-editor").empty(); const render_template = () => { const enabled = hooks.all.enabled; const items_list = hooks.all.script_conf.items || []; const current_value = hooks.all.script_conf.min_duration || 60; const times_unit = get_unit_times(current_value); const max_time = (times_unit[0] == `${i18n.metrics.minutes}` ? 59 : (times_unit[0] == `${i18n.metrics.hours}` ? 23 : 365)); const input_settings = { name: 'duration_value', current_value: times_unit[1], min: 1, max: max_time, enabled: enabled, }; const $time_input_box = generate_input_box(input_settings); const $multiselect_ds = generate_multi_select({ enabled: enabled, name: 'item_list', label: `${i18n.scripts_list.templates.excluded_applications}:`, selected_values: items_list, groups: apps_and_categories }); // time-ds stands for: time duration selection const radio_values = { labels: [`${i18n.metrics.minutes}`, `${i18n.metrics.hours}`, `${i18n.metrics.days}`], label: times_unit[0], values: [60, 3600, 86400] } const $time_radio_buttons = generate_radio_buttons({ name: 'ds_time', enabled: enabled, granularity: radio_values }); // clamp values on radio change $time_radio_buttons.find(`input[type='radio']`).on('change', function() { const time_selected = $(this).val(); // set min/max bounds to input box if (time_selected == 60) { $time_input_box.find('input').attr("max", 59); } else if (time_selected == 3600) { $time_input_box.find('input').attr("max", 23); } else { $time_input_box.find('input').attr("max", 365); } }); const callback_checkbox = function (e) { const checked = $(this).prop('checked'); // if the checked option is false the disable the elements if (!checked) { const $duration_input = $time_input_box.find(`input[name='duration_value']`); $duration_input.attr("readonly", ""); $time_radio_buttons.find(`input[type='radio']`).attr("disabled", "").parent().addClass('disabled'); $multiselect_ds.find('select').attr("disabled", ""); // if the user left the input box empty then reset previous values if ($duration_input.val() == "") { $duration_input.val(times_unit[1]); $duration_input.attr('max', max_time); reset_radio_button('ds_time', times_unit[2]); } return; } $time_input_box.find(`input[name='duration_value']`).removeAttr("readonly"); $time_radio_buttons.find(`input[type='radio']`).removeAttr("disabled").parent().removeClass('disabled'); $multiselect_ds.find('select').removeAttr("disabled"); }; const $checkbox_enabled = generate_checkbox_enabled( 'ds-checkbox', hooks.all.enabled, callback_checkbox ); // append elements on table const $input_container = $(`<td></td>`); $input_container.append( $time_input_box.prepend($time_radio_buttons).prepend( $(`<div class='col-7'><label class='p-2'>${i18n.scripts_list.templates.flow_duration_threshold}:</label></div>`) ), $multiselect_ds ); // initialize table row const $container = $(`<tr></tr>`).append( $(`<td class='text-center'></td>`).append($checkbox_enabled), $input_container ); $table_editor.append(` <tr class='text-center'> <th>${i18n.enabled}</th> </tr> `); $table_editor.append($container); } const apply_event = (event) => { const hook_enabled = $('#ds-checkbox').prop('checked'); const items_list = $(`select[name='item_list']`).val(); // get the bytes_unit const times_unit = $(`input[name='ds_time']:checked`).val(); const min_duration_input = $(`input[name='duration_value']`).val(); const parsed_duration = parseInt(min_duration_input); const template_data = { all: { enabled: hook_enabled, script_conf: { items: items_list, min_duration: parseInt(times_unit) * parsed_duration, } } } // make post request to save data apply_edits_script(template_data, script_subdir, script_key); } const reset_event = (event) => { reset_script_defaults(script_key, script_subdir, (data_reset) => { // reset textarea content const items_list = data_reset.hooks.all.script_conf.items || []; $(`select[name='item_list']`).val(items_list); // get min_duration value const min_duration = data_reset.hooks.all.script_conf.min_duration || 60; const times_unit = get_unit_times(min_duration); $(`input[name='duration_value']`).val(times_unit[1]); // select the correct radio button reset_radio_button('ds_time', times_unit[2]); const enabled = data_reset.hooks.all.enabled || false; $('#ds-checkbox').prop('checked', enabled); if (!enabled) { $(`input[name='duration_value']`).attr('readonly', ''); $(`select[name='item_list'],input[name='ds_time']`).attr('disabled', '').parent().addClass('disabled'); } else { $(`input[name='duration_value']`).removeAttr('readonly'); $(`select[name='item_list'],input[name='ds_time']`).removeAttr('disabled').parent().removeClass('disabled'); } }); } return { apply_click_event: apply_event, reset_click_event: reset_event, render: render_template, } } /* ******************************************************* */ const ElephantFlows = (gui, hooks, script_subdir, script_key) => { const $table_editor = $("#script-config-editor"); $("#script-config-editor").empty(); const render_template = () => { const enabled = hooks.all.enabled; const items_list = hooks.all.script_conf.items || []; let l2r_bytes_value = hooks.all.script_conf.l2r_bytes_value; let r2l_bytes_value = hooks.all.script_conf.r2l_bytes_value; // get units and values const l2r_unit = get_unit_bytes(l2r_bytes_value); const r2l_unit = get_unit_bytes(r2l_bytes_value); // configure local to remote input const input_settings_l2r = { min: 1, max: 1023, current_value: l2r_unit[1], name: 'l2r_value', enabled: enabled }; const $input_box_l2r = generate_input_box(input_settings_l2r); // configure remote to locale input const input_settings_r2l = { min: 1, max: 1023, current_value: r2l_unit[1], name: 'r2l_value', enabled: enabled }; const $input_box_r2l = generate_input_box(input_settings_r2l); // create textarea to append const $multiselect_bytes = generate_multi_select({ enabled: enabled, name: 'item_list', label: `${i18n.scripts_list.templates.excluded_applications}:`, selected_values: items_list, groups: apps_and_categories }); // create radio button with its own values const radio_values_l2r = { labels: ["KB", "MB", "GB"], label: l2r_unit[0], values: [1024, 1048576, 1073741824] }; const radio_values_r2l = { labels: ["KB", "MB", "GB"], label: r2l_unit[0], values: [1024, 1048576, 1073741824] }; const $radio_button_l2r = generate_radio_buttons({ name: 'bytes_l2r', enabled: enabled, granularity: radio_values_l2r } ); const $radio_button_r2l = generate_radio_buttons({ name: 'bytes_r2l', enabled: enabled, granularity: radio_values_r2l } ); const $checkbox_enabled = generate_checkbox_enabled( 'elephant-flows-checkbox', enabled, function (e) { const checked = $(this).prop('checked'); const $r2l_input = $input_box_r2l.find(`input[name='r2l_value']`); const $l2r_input = $input_box_l2r.find(`input[name='l2r_value']`); // if the checked option is false the disable the elements if (!checked) { $r2l_input.attr("readonly", ""); $l2r_input.attr("readonly", ""); $radio_button_l2r.find(`input[type='radio']`).attr("disabled", "").parent().addClass('disabled'); $radio_button_r2l.find(`input[type='radio']`).attr("disabled", "").parent().addClass('disabled'); $multiselect_bytes.find('select').attr("disabled", ""); // if the user left the input box empty then reset previous values if ($r2l_input.val() == "") { $r2l_input.val(r2l_unit[1]); reset_radio_button('bytes_r2l', r2l_unit[2]); } if ($l2r_input.val() == "") { $l2r_input.val(l2r_unit[1]); reset_radio_button('bytes_l2r', l2r_unit[2]); } return; } $r2l_input.removeAttr("readonly", ""); $l2r_input.removeAttr("readonly", ""); $radio_button_l2r.find(`input[type='radio']`).removeAttr("disabled").parent().removeClass('disabled'); $radio_button_r2l.find(`input[type='radio']`).removeAttr("disabled").parent().removeClass('disabled'); $multiselect_bytes.find('select').removeAttr("disabled"); } ); // append elements on table const $input_container = $(`<td></td>`); $input_container.append( $input_box_l2r .prepend($radio_button_l2r) .prepend($(`<div class='col-7'><label class='pl-2'>${i18n.scripts_list.templates.elephant_flows_l2r}</label></div>`)), $input_box_r2l .prepend($radio_button_r2l) .prepend($(`<div class='col-7'><label class='pl-2'>${i18n.scripts_list.templates.elephant_flows_r2l}</label></div>`)), $multiselect_bytes ); // initialize table row const $container = $(`<tr></tr>`).append( $(`<td class='text-center'></td>`).append($checkbox_enabled), $input_container ); $table_editor.append(`<tr class='text-center'><th>${i18n.enabled}</th></tr>`); // append all inside the table $table_editor.append($container); } const apply_event = (event) => { const hook_enabled = $('#elephant-flows-checkbox').prop('checked'); const items_list = $(`select[name='item_list']`).val(); // get the bytes_unit const unit_l2r = $(`input[name='bytes_l2r']:checked`).val(); const unit_r2l = $(`input[name='bytes_r2l']:checked`).val(); const input_l2r = $(`input[name='l2r_value']`).val(); const input_r2l = $(`input[name='r2l_value']`).val(); const template_data = { all: { enabled: hook_enabled, script_conf: { items: items_list, l2r_bytes_value: parseInt(unit_l2r) * parseInt(input_l2r), r2l_bytes_value: parseInt(unit_r2l) * parseInt(input_r2l) } } } apply_edits_script(template_data, script_subdir, script_key); } const reset_event = (event) => { reset_script_defaults(script_key, script_subdir, (data_reset) => { // reset textarea content const items_list = data_reset.hooks.all.script_conf.items || []; $(`select[name='item_list']`).val(items_list); // get min_duration value const bytes_l2r = data_reset.hooks.all.script_conf.l2r_bytes_value || 1024; const bytes_r2l = data_reset.hooks.all.script_conf.r2l_bytes_value || 1024; const bytes_unit_l2r = get_unit_bytes(bytes_l2r); const bytes_unit_r2l = get_unit_bytes(bytes_r2l); $(`input[name='l2r_value']`).val(bytes_unit_l2r[1]); $(`input[name='r2l_value']`).val(bytes_unit_r2l[1]); // select the correct radio button reset_radio_button('bytes_l2r', bytes_unit_l2r[2]); reset_radio_button('bytes_r2l', bytes_unit_r2l[2]); const enabled = data_reset.hooks.all.enabled || false; $('#elephant-flows-checkbox').prop('checked', enabled); if (!enabled) { $(`input[name='l2r_value'],input[name='r2l_value']`).attr('readonly', ''); $(`select[name='item_list'],input[name='bytes_l2r']`).attr('disabled', '').parent().addClass('disabled'); $(`input[name='bytes_r2l']`).attr('disabled', '').parent().addClass('disabled'); } else { $(`input[name='l2r_value'],input[name='r2l_value']`).removeAttr('readonly'); $(`select[name='item_list'],input[name='bytes_l2r']`).removeAttr('disabled').parent().removeClass('disabled'); $(`input[name='bytes_r2l']`).removeAttr('disabled').parent().removeClass('disabled'); } }); } return { apply_click_event: apply_event, reset_click_event: reset_event, render: render_template, } } /* ******************************************************* */ const FlowMud = (gui, hooks, script_subdir, script_key) => { const $table_editor = $("#script-config-editor"); $("#script-config-editor").empty(); const render_template = () => { const enabled = hooks.all.enabled; const items_list = (hooks.all.script_conf ? hooks.all.script_conf.device_types : null) || []; const max_recording = (hooks.all.script_conf ? hooks.all.script_conf.max_recording : null) || 3600; const $multiselect_ds = generate_multi_select({ enabled: enabled, name: 'item_list', selected_values: items_list, groups: device_types }, false /* no container */); const $max_recording = generate_single_select({ enabled: enabled, label: i18n.scripts_list.templates.max_mud_recording, name: 'max_recording', current_value: max_recording, elements: mud_max_recording, }); const $checkbox_enabled = generate_checkbox_enabled( 'mud-checkbox', enabled, function (e) { const checked = $(this).prop('checked'); if (!checked) { $multiselect_ds.attr("disabled", ""); $max_recording.find('select').attr("disabled", ""); } else { $multiselect_ds.removeAttr("disabled"); $max_recording.find('select').removeAttr("disabled"); } } ); const $container = $(`<tr></tr>`).append( $(`<td class='text-center'></td>`).append($checkbox_enabled), $(`<td></td>`).append( $(`<div class='form-group'> <label>${i18n.scripts_list.templates.mud_enabled_devices}:</label> </div>`).append($multiselect_ds), $max_recording ) ); $table_editor.append(`<tr class='text-center'><th>${i18n.enabled}</th></tr>`); // append all inside the table $table_editor.append($container); } const apply_event = (event) => { const hook_enabled = $('#mud-checkbox').prop('checked'); const items_list = $(`select[name='item_list']`).val(); const max_recording = parseInt($(`select[name='max_recording']`).val()); const template_data = { all: { enabled: hook_enabled, script_conf: { device_types: items_list, max_recording: max_recording, } } } // make post request to save data apply_edits_script(template_data, script_subdir, script_key); } const reset_event = (event) => { reset_script_defaults(script_key, script_subdir, (reset_data) => { const max_recording = reset_data.hooks.all.script_conf.max_recording || 3600; const items_list = reset_data.hooks.all.script_conf.device_types || []; const enabled = reset_data.hooks.all.enabled; // set textarea value with default's one $(`select[name='item_list']`).val(items_list.join(',')); $('#mud-checkbox').prop('checked', enabled); $(`select[name='max_recording']`).val(max_recording); if (!enabled) { $(`select[name='item_list']`).attr('disabled', ''); $(`select[name='max_recording']`).attr('disabled', ''); } else { $(`select[name='item_list']`).removeAttr('disabled', ''); $(`select[name='max_recording']`).removeAttr('disabled', ''); } }) } return { apply_click_event: apply_event, reset_click_event: reset_event, render: render_template, } }; /* ******************************************************* */ const EmptyTemplate = (gui = null, hooks = null, script_subdir = null, script_key = null) => { return { apply_click_event: function() {}, reset_click_event: function() {}, render: function() {}, } } /* ******************************************************* */ // get script key and script name const initScriptConfModal = (script_key, script_title, script_desc) => { // change title to modal $("#script-name").html(`<b>${script_title}</b>`); $('#script-description').text(script_desc); $("#modal-script form").off('submit'); $("#modal-script").on("submit", "form", function (e) { e.preventDefault(); $('#edit-form').trigger('reinitialize.areYouSure').removeClass('dirty'); $("#btn-apply").trigger("click"); }); $.get(`${http_prefix}/lua/get_user_script_config.lua`, { script_subdir: script_subdir, confset_id: confset_id, script_key: script_key } ) .then((data, status, xhr) => { // check status code if (check_status_code(xhr.status, xhr.statusText, null)) return; // hide previous error $("#apply-error").hide(); const template = TemplateBuilder(data, script_subdir, script_key); // render template template.render(); // bind on_apply event on apply button $("#edit-form").off("submit").on('submit', template.apply_click_event); $("#btn-reset").off("click").on('click', template.reset_click_event); // bind are you sure to form $('#edit-form').trigger('rescan.areYouSure').trigger('reinitialize.areYouSure'); }) .fail(({status, statusText}) => { check_status_code(status, statusText, null); // hide modal if there is error $("#modal-script").modal("toggle"); }) } /* ******************************************************* */ /** * This function return the search criteria for the datatable * 'true': apply filter categories criteria only on enabled scripts * 'false': apply filter categories criteria only on disabled scripts * '': apply filter categories criteria for all scripts * * @returns {string} 'true'|'false'|'' */ const get_search_toggle_value = hash => hash == "#enabled" ? 'true' : (hash == "#disabled" ? 'false' : ''); /* ******************************************************* */ const TemplateBuilder = ({gui, hooks}, script_subdir, script_key) => { // get template name const template_name = gui.input_builder; const templates = { threshold_cross: ThresholdCross(gui, hooks, script_subdir, script_key), items_list: ItemsList(gui, hooks, script_subdir, script_key), long_lived: LongLived(gui, hooks, script_subdir, script_key), elephant_flows: ElephantFlows(gui, hooks, script_subdir, script_key), flow_mud: FlowMud(gui, hooks, script_subdir, script_key), } let template_chosen = templates[template_name]; if (!template_chosen) { template_chosen = EmptyTemplate(); throw(`${i18n.scripts_list.templates.template_not_implemented}`); } return template_chosen; } /* ******************************************************* */ // End templates and template builder const create_enabled_button = (row_data) => { const {is_enabled} = row_data; const $button = $(`<button type='button' class='badge border-0'></button>`); if (!is_enabled) { const has_all_hook = row_data.all_hooks.find(e => e.key == 'all'); if (!has_all_hook && hasConfigDialog(row_data)) $button.css('visibility', 'hidden'); $button.text(`${i18n.enable}`); $button.addClass('badge-success'); } else { if (row_data.enabled_hooks.length < 1) $button.css('visibility', 'hidden'); $button.text(`${i18n.disable}`); $button.addClass('badge-danger'); } $button.off('click').on('click', function() { $.post(`${http_prefix}/lua/toggle_user_script.lua`, { script_subdir: script_subdir, script_key: row_data.key, csrf: csrf_toggle_buttons, action: (is_enabled) ? 'disable' : 'enable', confset_id: confset_id }) .done((d, status, xhr) => { if (!d.success) { $("#alert-row-buttons").text(d.error).removeClass('d-none').show(); // update csrf csrf_toggle_buttons = d.csrf; } if (d.success) reloadPageAfterPOST(); }) .fail(({ status, statusText }) => { check_status_code(status, statusText, $("#alert-row-buttons")); // if the csrf has expired if (status == 200) { $("#alert-row-buttons").text(`${i18n.expired_csrf}`).removeClass('d-none').show(); } // re eanble buttons $button.removeAttr("disabled").removeClass('disabled'); }); }) return $button; }; $(document).ready(function() { const CATEGORY_COLUMN_INDEX = 1; const VALUES_COLUMN_INDEX = 3; const add_filter_categories_dropdown = () => { const $dropdown = $(` <div id='category-filter-menu' class='dropdown d-inline'> <button class='btn btn-link dropdown-toggle' data-toggle='dropdown' type='button'> <span>${i18n.filter_categories}</span> </button> <div id='category-filter' class='dropdown-menu'> </div> </div> `); $dropdown.find('#category-filter').append( scripts_categories.map((c, index) => { // list element to append inside the dropdown selector const $list_element = $(`<li class='dropdown-item pointer'>${c.label}</li>`); // 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(`<i class='fas fa-filter'></i> ${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_user_scripts.lua?confset_id=${confset_id}&script_subdir=${script_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(`<i class='fas fa-filter'></i> ${loaded_filter}`); } const [enabled_count, disabled_count] = count_scripts(); // select the correct tab select_script_filter(enabled_count); // 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(); }); // update the tabs counters 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})`); 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: [ { extend: "filterScripts", attr: { id: "all-scripts", }, text: "All" }, { extend: "filterScripts", attr: { id: "enabled-scripts" }, text: "Enabled" }, { extend: "filterScripts", attr: { id: "disabled-scripts" }, text: "Disabled" } ], columns: [ { data: 'title', render: function (data, type, row) { if (type == 'display') return `<b>${data}</b>`; return data; }, }, { data: null, sortable: true, searchable: true, className: 'text-center', render: function (data, type, row) { const icon = (!row.category_icon) ? '' : `<i class='fa ${row.category_icon}'></i>`; if (type == "display") return `${icon}`; return row.category_title; } }, { data: 'description', render: function (data, type, row) { if (type == "display") { return `<span ${data.length >= 72 ? `data-toggle='popover' data-placement='top' data-html='true'` : ``} title="${row.title}" data-content="${data}" > ${truncate_string(data, 72, true)} </span>`; } return data; }, }, { data: 'enabled_hooks', sortable: false, className: 'text-left', 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') ? ` <span title="${i18n.values}" ${row.value_description.length >= 32 ? `data-toggle='popover' data-placement='top'` : ``} data-content='${row.value_description}'> ${row.value_description.substr(0, 32)}${row.value_description.length >= 32 ? '...' : ''} </span> ` : '';; }, }, { targets: -1, data: null, name: 'actions', className: 'text-center', width: '200px', sortable: false, render: function (data, type, row) { const edit_script_btn = ` <a href='#' title='${i18n.edit_script}' class='badge badge-info' style="visibility: ${!row.input_handler ? 'hidden' : 'visible'}" data-toggle="modal" data-target="#modal-script"> ${i18n.edit} </a> `; const edit_url_btn = ` <a href='${data.edit_url}' class='badge badge-secondary' style="visibility: ${!data.edit_url ? 'hidden' : 'visible'}" title='${i18n.view_src_script}'> ${i18n.view} </a> `; return `${edit_script_btn}${edit_url_btn}`; }, createdCell: function(td, cellData, row) { const enabled_button = create_enabled_button(row); $(td).prepend(enabled_button); } } ] }); // 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', 'a[data-target="#modal-script"]', function(e) { const row_data = $script_table.row($(this).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]; } });