// 2019 - ntop.org var schema_2_label = {}; var data_2_label = {}; var graph_i18n = {}; function initLabelMaps(_schema_2_label, _data_2_label, _graph_i18n) { schema_2_label = _schema_2_label; data_2_label = _data_2_label; graph_i18n = _graph_i18n; }; function getSerieLabel(schema, serie, visualization, serie_index) { var data_label = serie.label; var new_label = data_2_label[data_label]; if(visualization && visualization.metrics_labels && visualization.metrics_labels[serie_index]) return visualization.metrics_labels[serie_index]; if(serie.ext_label) return serie.ext_label; else if((schema == "top:local_senders") || (schema == "top:local_receivers")) { if(serie.ext_label) return serie.ext_label; else return serie.tags.host } else if(schema.startsWith("top:")) { // topk graphs if(serie.tags.protocol) return serie.tags.protocol; else if(serie.tags.category) return serie.tags.category; else if(serie.tags.l4proto) return serie.tags.l4proto; else if(serie.tags.device && serie.tags.if_index) { // SNMP interface if(serie.ext_label != "") return serie.ext_label; else return "(" + serie.tags.if_index + ")"; } else if(serie.tags.device && serie.tags.port) // Flow device return serie.tags.port; else if(serie.tags.exporter && serie.tags.ifname) // Event exporter return serie.tags.ifname; else if(serie.tags.profile) return serie.tags.profile; else if(serie.tags.user_script) return serie.tags.user_script; else if(serie.tags.command) return serie.tags.command.substring(4).toUpperCase(); } else if(data_label != "bytes") { // single series if(serie.tags.protocol) return serie.tags.protocol + " (" + new_label + ")"; else if(serie.tags.category) return serie.tags.category + " (" + new_label + ")"; else if(serie.tags.device && serie.tags.if_index) // SNMP interface return serie.ext_label + " (" + new_label + ")"; else if(serie.tags.device && serie.tags.port) // Flow device return serie.tags.port + " (" + new_label + ")"; } else { if(serie.tags.protocol) return serie.tags.protocol; else if(serie.tags.category) return serie.tags.category; else if(serie.tags.profile) return serie.tags.profile; else if(data_label == "bytes") { if(schema.contains("volume")) return graph_i18n.traffic_volume; else return graph_i18n.traffic; } } if(schema_2_label[schema]) return capitaliseFirstLetter(schema_2_label[schema]); if(new_label) return capitaliseFirstLetter(new_label); // default return capitaliseFirstLetter(data_label); } // Value formatter function getValueFormatter(schema, metric_type, series, custom_formatter, stats) { if(series && series.length && series[0].label) { if(custom_formatter) { var formatters = []; if(typeof(custom_formatter) != "object") custom_formatter = [custom_formatter]; for(var i=0; i count) { console.error("points mismatch: serie '" + getSerieLabel(schema_name, series[i]) + "' has " + data.length + " points, expected " + count); rv = false; } else if(data.length < count) { /* upsample */ series[i].data = upsampleSerie(data, count); } } return rv; } function upsampleSerie(serie, num_points) { if(num_points <= serie.length) return serie; var res = []; var intervals = num_points / serie.length; function lerp(v0, v1, t) { return (1 - t) * v0 + t * v1; } for(var i=0; i>", v); res.push(v); } return res.slice(0, num_points); } // the stacked total serie function buildTotalSerie(data_series) { var series = []; for(var i=0; i 0.1) return res; } function buildTimeArray(start_time, end_time, step) { var arr = []; for(var t=start_time; t $.now()) $("#btn-jump-time-ahead").addClass("disabled"); else $("#btn-jump-time-ahead").removeClass("disabled"); } function showQuerySlow() { $("#query-slow-alert").show(); } function hideQuerySlow() { $("#query-slow-alert").hide(); } function chart_data_sum(series) { return(series.reduce(function(acc, x) { return(acc + x.values.reduce( function(acc, pt) { return(acc + pt[1] || 0); }, 0) ) }, 0)); } // add a new updateStackedChart function function attachStackedChartCallback(chart, schema_name, chart_id, zoom_reset_id, params, step, metric_type, align_step, show_all_smooth, initial_range, ts_table_shown) { var pending_chart_request = null; var pending_table_request = null; var d3_sel = d3.select(chart_id); var $chart = $(chart_id); var $zoom_reset = $(zoom_reset_id); var $graph_zoom = $("#graph_zoom"); var max_interval = findActualStep(step, params.epoch_begin) * 8; var initial_interval = (params.epoch_end - params.epoch_begin); var is_max_zoom = (initial_interval <= max_interval); var url = http_prefix + "/lua/rest/get/timeseries/ts.lua"; var first_load = true; var first_time_loaded = true; var manual_trigger_extra_series = {}; // keeps track of series manually shown/hidden by the user var datetime_format = "dd/MM/yyyy hh:mm:ss"; var max_cmp_over_total_ratio = 3; // if the comparison serie max value is too big compared to the actual chart series, hide it var max_line_over_total_ratio = 10; // if the extra line series max value is too big compared to the actual chart series, hide them var query_timer = null; var seconds_before_query_slow = 6; var query_completed = 0; var query_was_aborted = false; chart.is_zoomed = ((current_zoom_level > 0) || has_initial_zoom()); //var spinner = $(""); var spinner = $(''); $chart.parent().css("position", "relative"); var chart_colors_full = [ "#69B87F", "#94CFA4", "#B3DEB6", "#E5F1A6", "#FFFCC6", "#FEDEA5", "#FFB97B", "#FF8D6D", "#E27B85" ]; var chart_colors_min = ["#7CC28F", "#FCD384", "#FD977B"]; /* The default number of y points */ var num_ticks_y1 = null; var num_ticks_y2 = null; var domain_y1 = null; var domain_y2 = null; var first_run = true; var update_chart_data = function(new_data) { /* reset chart data so that the next transition animation will be gracefull */ d3_sel.datum([]).call(chart); d3_sel.datum(new_data); /* This additional refresh is needed to determine the yticks * and domain, needed below. * NOTE: calling transition().duration(500) is important to properly refresh * the tooltip position. */ d3_sel.transition().duration(500).call(chart); if(first_run) { num_ticks_y1 = chart.yAxis1.ticks(); num_ticks_y2 = chart.yAxis2.ticks(); domain_y1 = chart.yDomain1(); domain_y2 = chart.yDomain2(); first_run = false; } if(metric_type === "gauge") { var cur_domain_y1 = chart.yAxis1.scale().domain(); var cur_domain_y2 = chart.yAxis2.scale().domain(); cur_domain_y1 = cur_domain_y1[1] - cur_domain_y1[0]; cur_domain_y2 = cur_domain_y2[1] - cur_domain_y2[0]; /* If there are not enough points available, reduce the number of * ticks to avoid repeated ticks with same integer value. * Other solutions (documented in https://stackoverflow.com/questions/21075245/nvd3-prevent-repeated-values-on-y-axis) * are not easily applicable in this case. * * NOTE: the problem should not occur when using ffloat */ if(chart.yAxis1.tickFormat() != ffloat) chart.yAxis1.ticks(Math.min(cur_domain_y1, num_ticks_y1)); if(chart.yAxis2.tickFormat() != ffloat) chart.yAxis2.ticks(Math.min(cur_domain_y2, num_ticks_y2)); } var y1_sum = chart_data_sum(new_data.filter(function(x) { return(x.yAxis == 1); })) var y2_sum = chart_data_sum(new_data.filter(function(x) { return(x.yAxis == 2); })) /* Fix negative ydomain values appearing when dataset is empty */ if(y1_sum == 0) chart.yDomain1([0, 1]); else chart.yDomain1(domain_y1); if(y2_sum == 0) chart.yDomain2([0, 1]); else chart.yDomain2(domain_y2); /* Refresh the chart */ d3_sel.call(chart); nv.utils.windowResize(chart.update); spinner.remove(); } function isLegendDisabled(key, default_val) { if(typeof localStorage !== "undefined") { var val = localStorage.getItem("chart_series.disabled." + key); if(val != null) return(val === "true"); } return default_val; } chart.legend.dispatch.on('legendClick', function(d,i) { manual_trigger_extra_series[d.legend_key] = true; if(typeof localStorage !== "undefined") localStorage.setItem("chart_series.disabled." + d.legend_key, (!d.disabled) ? true : false); }); chart.dispatch.on("zoom", function(e) { var cur_zoom = [params.epoch_begin, params.epoch_end]; var t_start = Math.floor(e.xDomain[0]); var t_end = Math.ceil(e.xDomain[1]); var old_zoomed = chart.is_zoomed; var is_user_zoom = (typeof e.is_user_zoom !== "undefined") ? e.is_user_zoom : true; chart.is_zoomed = true; if(chart.updateStackedChart(t_start, t_end, false, is_user_zoom)) { if(is_user_zoom || e.push_state) { //console.log("zoom IN!"); current_zoom_level += 1; var url = getHistoryParameters({epoch_begin: t_start, epoch_end: t_end}); history.pushState({zoom_level: current_zoom_level, range: [t_start, t_end]}, "", url); } chart.fixChartButtons(); } else chart.is_zoomed = old_zoomed; }); function updateZoom(zoom, is_user_zoom, force) { var t_start = zoom[0]; var t_end = zoom[1]; chart.updateStackedChart(t_start, t_end, false, is_user_zoom, null, force); chart.fixChartButtons(); } chart.zoom_in = function() { var cur_interval = params.epoch_end - params.epoch_begin; if(cur_interval > 60) { var delta = cur_interval/4; $("#period_begin").datetimepicker("date", new Date((params.epoch_begin + delta) * 1000)); $("#period_end").datetimepicker("date", new Date((params.epoch_end - delta) * 1000)); updateChartFromPickers(); } } chart.zoom_out = function() { var cur_interval = params.epoch_end - params.epoch_begin; //if(current_zoom_level) { // Zoom out from history //console.log("zoom OUT"); //history.back(); //} else { // Zoom out with fixed interval //var delta = zoom_out_value; var delta = cur_interval/2; //if((params.epoch_end + delta)*1000 <= $.now()) //delta /= 2; $("#period_begin").datetimepicker("date", new Date((params.epoch_begin - delta) * 1000)); $("#period_end").datetimepicker("date", new Date((params.epoch_end + delta) * 1000)); updateChartFromPickers(); //} } $chart.on('dblclick', function(event) { if($(event.target).hasClass("nv-legend-text")) // legend was double-clicked, keep the original behavior return; chart.zoom_out(); }); $zoom_reset.on("click", function() { if(current_zoom_level) { //console.log("zoom RESET"); history.go(-current_zoom_level); } }); window.addEventListener('popstate', function(e) { var zoom = initial_range; //console.log("popstate: ", e.state); if(e.state) { zoom = e.state.range; current_zoom_level = e.state.zoom_level; } else current_zoom_level = 0; updateZoom(zoom, true, true /* force */); }); chart.fixChartButtons = function() { if((current_zoom_level > 0) || has_initial_zoom()) { $graph_zoom.find(".btn-warning:not(.custom-zoom-btn)") .addClass("initial-zoom-sel") .removeClass("btn-warning"); $graph_zoom.find(".custom-zoom-btn").css("visibility", "visible"); var zoom_link = $graph_zoom.find(".custom-zoom-btn"); var link = zoom_link.val().replace(/&epoch_begin=.*/, ""); link += "&epoch_begin=" + params.epoch_begin + "&epoch_end=" + params.epoch_end; zoom_link.val(link); } else { $graph_zoom.find(".initial-zoom-sel") .addClass("btn-warning"); $graph_zoom.find(".custom-zoom-btn").css("visibility", "hidden"); chart.is_zoomed = false; } fixJumpButtons(params.epoch_begin, params.epoch_end); if(current_zoom_level > 0) $zoom_reset.show(); else $zoom_reset.hide(); } function checkQueryCompleted() { var flows_dt = $("#chart1-flows"); var wait_num_queries = (ts_table_shown && ($("#chart1-flows").css("display") !== "none")) ? 2 : 1; query_completed += 1; if(query_completed >= wait_num_queries) { if(query_timer) { clearInterval(query_timer); query_timer = null; } hideQuerySlow(); } } chart.queryWasAborted = function() { return query_was_aborted; } chart.abortQuery = function() { query_was_aborted = true; if(pending_chart_request) { pending_chart_request.abort(); chart.noData(i18n.query_was_aborted); update_chart_data([]); } if(pending_table_request) pending_table_request.abort(); if(query_timer) { clearInterval(query_timer); query_timer = null; } hideQuerySlow(); } chart.tableRequestCompleted = function() { checkQueryCompleted(); pending_table_request = null; } chart.getDataUrl = function() { var data_params = jQuery.extend({}, params); delete data_params.zoom; delete data_params.ts_compare; data_params.extended = 1; /* with extended timestamps */ return url + "?" + $.param(data_params, true); } var old_start, old_end, old_interval; /* Returns false if zoom update is rejected. */ chart.updateStackedChart = function (tstart, tend, no_spinner, is_user_zoom, on_load_callback, force_update) { if(tstart) params.epoch_begin = tstart; if(tend) params.epoch_end = tend; var cur_interval = (params.epoch_end - params.epoch_begin); var actual_step = findActualStep(step, params.epoch_begin); max_interval = actual_step * 6; /* host traffic 30 min */ if(cur_interval < max_interval) { if((is_max_zoom && (cur_interval < old_interval)) && !force_update) { old_interval = cur_interval; return false; } if(!force_update) { /* Ensure that a minimal number of points is available */ var epoch = params.epoch_begin + (params.epoch_end - params.epoch_begin) / 2; var new_end = Math.floor(epoch + max_interval / 2); if(new_end * 1000 >= Date.now()) { /* Only expand on the left side of the interval */ params.epoch_begin = params.epoch_end - max_interval; } else { params.epoch_begin = Math.floor(epoch - max_interval / 2); params.epoch_end = Math.floor(epoch + max_interval / 2); } is_max_zoom = true; chart.zoomType(null); // disable zoom } } else if (cur_interval > max_interval) { is_max_zoom = false; chart.zoomType('x'); // enable zoom } old_interval = cur_interval; if(!first_load || has_initial_zoom() || force_update) align_step = null; fixTimeRange(chart, params, align_step, actual_step); if(first_load) initial_range = [params.epoch_begin, params.epoch_end]; if((old_start == params.epoch_begin) && (old_end == params.epoch_end) && (!force_update)) return false; old_start = params.epoch_begin; old_end = params.epoch_end; if(pending_table_request) pending_table_request.abort(); if(pending_chart_request) pending_chart_request.abort(); else if(!no_spinner) spinner.appendTo($chart.parent()); // Update datetime selection $("#period_begin").datetimepicker("date", new Date(params.epoch_begin * 1000)); $("#period_end").datetimepicker("date", new Date(Math.min(params.epoch_end * 1000, $.now()))); if(query_timer) clearInterval(query_timer); query_timer = setInterval(showQuerySlow, seconds_before_query_slow * 1000); query_completed = 0; query_was_aborted = false; chart.noData(i18n.no_data_available); hideQuerySlow(); var req_params = $.extend({}, params); // skip past period comparison if a custom interval is selected if(!canCompareBackwards(req_params.epoch_begin, req_params.epoch_end)) delete req_params.ts_compare; // Load data via ajax pending_chart_request = $.get(url, req_params, function(data) { if(data && data.error) chart.noData(data.error); if(!data || !data.series || !data.series.length || !checkSeriesConsinstency(schema_name, data.count, data.series)) { update_chart_data([]); return; } // Fix x axis var tick_step = Math.ceil(chart.tick_step / data.step) * data.step; chart.xAxis.tickValues(buildTimeArray(data.start, data.start + data.count * data.step, tick_step)); chart.xAxis.tickFormat(function(d) { return d3.time.format(chart.x_fmt)(new Date(d*1000)) }); // Adapt data var res = []; var series = data.series; var total_serie; var color_i = 0; var chart_colors = (series.length <= chart_colors_min.length) ? chart_colors_min : chart_colors_full; for(var j=0; j Other */ var other_serie = buildOtherSerie(total_serie, visual_total); if(other_serie) { res.push({ key: graph_i18n.other, yAxis: 1, values: arrayToNvSerie(other_serie, data.start, data.step), type: "area", color: chart_colors[color_i++], legend_key: "other", disabled: isLegendDisabled("other", false), }); has_full_data = true; } } else { total_serie = visual_total; has_full_data = !schema_name.startsWith("top:"); } var past_serie = null; if(data.additional_series) { for(var key in data.additional_series) { if(key == "total") { // handle manually as "other" above continue; } var serie_data = upsampleSerie(data.additional_series[key], data.count); var ratio_over_total = d3.max(serie_data) / d3.max(visual_total); var values = arrayToNvSerie(serie_data, data.start, data.step); var is_disabled = isLegendDisabled(key, false); past_serie = serie_data; // TODO: more reliable way to determine past serie /* Hide comparison serie at first load if it's too high */ if((first_time_loaded || !manual_trigger_extra_series[key]) && (ratio_over_total > max_cmp_over_total_ratio)) is_disabled = true; res.push({ key: capitaliseFirstLetter(key), yAxis: 1, values: values, type: "line", classed: "line-dashed line-animated", color: "#7E91A0", legend_key: key, disabled: is_disabled, }); } } /* Extra horizontal series */ if(visualization && visualization.extra_series) { for(var i=0; i max_line_over_total_ratio)) is_disabled = true; res.push({ key: serie.label, yAxis: serie.axis || 1, values: arrayToNvSerie(upsampleSerie([serie.value], data.count), data.start, data.step), type: serie.type || "line", color: serie.color || "red", classed: serie.class, legend_key: serie.label, disabled: is_disabled, }); } } if(!data.no_trend && has_full_data && (total_serie.length >= 3)) { // Smoothed serie /* num_smoothed_points determines the window size to use while computing rolling functions */ var num_smoothed_points = Math.min(Math.max(Math.floor(total_serie.length / 5), 3), 12); var smooth_functions = { trend: [graph_i18n.trend, "#62ADF6", smooth, num_smoothed_points], ema: ["EMA", "#F96BFF", exponentialMovingAverageArray, {periods: num_smoothed_points}], sma: ["SMA", "#A900FF", simpleMovingAverageArray, {periods: num_smoothed_points}], rsi: ["RSI cur vs past", "#00FF5D", relativeStrengthIndexArray, {periods: num_smoothed_points}], } function add_smoothed_serie(fn_to_use) { var options = smooth_functions[fn_to_use]; var smoothed; if(fn_to_use == "rsi") { if(!past_serie) return; var delta_serie = []; for(var i=0; i 0) { var aligned; if((fn_to_use != "ema") && (fn_to_use != "sma") && (fn_to_use != "rsi")) { var scale = d3.max(total_serie) / max_val; var scaled = $.map(smoothed, function(x) { return x * scale; }); aligned = upsampleSerie(scaled, data.count); } else { var remaining = (data.count - smoothed.length); var to_fill = remaining < num_smoothed_points ? remaining : num_smoothed_points; /* Fill the initial buffering space */ for(var i=0; i 0) } stats_table.show(); var enabled_series = res.filter(function(d) { return(d.disabled !== true); }); if(second_axis_series.length > 0 || enabled_series.length == 0) { // Enable all the series for(var i=0; i 0) { // Don't allow series toggle by disabling legend clicks chart.legend.updateState(false); } update_chart_data(res); first_time_loaded = false; if(data.source_aggregation) $("#data-aggr-dropdown > button > span:first").html(data.source_aggregation); }).fail(function(xhr, status, error) { if (xhr.statusText =='abort') { return; } console.error("Error while retrieving the timeseries data [" + status + "]: " + error); chart.noData(error); update_chart_data([]); }).always(function(data, status, xhr) { checkQueryCompleted(); pending_chart_request = null; }); if(first_load) { first_load = false; /* Wait for page load because datatable is not instantiated yet right now */ $(function() { var flows_dt = $("#chart1-flows").data("datatable"); if(flows_dt) pending_table_request = flows_dt.pendingRequest; }); } else { var flows_dt = $("#chart1-flows"); /* Reload datatable */ if(ts_table_shown) { /* note: flows_dt.data("datatable") will change after this call */ updateGraphsTableView(null, params); if($("#chart1-flows").css("display") !== "none") pending_table_request = flows_dt.data("datatable").pendingRequest; } } if(typeof on_load_callback === "function") on_load_callback(chart); return true; } } var graph_old_view = null; var graph_old_has_nindex = null; var graph_old_nindex_query = null; function tsQueryToTags(ts_query) { return ts_query.split(","). reduce(function(params, value) { var pos = value.indexOf(":"); if(pos != -1) { var k = value.slice(0, pos); var v = value.slice(pos+1); params[k] = v; } return params; }, {}); } /* Hide or show the timeseries table items based on the current time range */ function recheckGraphTableEntries() { var table_view = graph_table_views; var tdiff = (graph_params.epoch_end - graph_params.epoch_begin); var reset_selection = false; $("#chart1-flows").show(); $("#graphs-table-selector").show(); for(view_id in table_view) { var view = table_view[view_id]; var elem = $("#" + view.html_id); if(tdiff <= view.min_step) { if(graph_old_view.id === view_id) reset_selection = true; elem.hide(); } else elem.show(); } /* Hide/show the headers */ var items_ul = $("#graphs-table-active-view").closest(".btn-group").find("ul:first"); items_ul.find("li.dropdown-header").each(function(idx,e) { var next_item = $(e).nextAll("li").filter(function(idx,e) { return(($(e).css("display") !== "none") || (!$(e).attr("data-view-id"))); }).first(); var divider = $(e).nextAll(".divider").first(); if(!next_item.attr("data-view-id")) { $(e).hide(); divider.hide(); } else { $(e).show(); divider.show(); } }); if(reset_selection) { /* Select the first available view */ var first_view = items_ul.find("li[data-view-id]").filter(function(idx,e) { return($(e).css("display") !== "none"); }).first(); if(first_view.length) setActiveGraphsTableView(first_view.attr("data-view-id")); else { $("#chart1-flows").hide(); $("#graphs-table-selector").hide(); } return false; } return true; } function updateGraphsTableView(view, graph_params, has_nindex, nindex_query, per_page) { if(view) graph_old_view = view; if(!recheckGraphTableEntries(graph_params)) { /* handled by setActiveGraphsTableView */ return; } if(view) { graph_old_has_nindex = has_nindex; graph_old_nindex_query = nindex_query; } else { view = graph_old_view; has_nindex = graph_old_has_nindex; nindex_query = graph_old_nindex_query; } var graph_table = $("#chart1-flows"); nindex_query = nindex_query + "&begin_time_clause=" + graph_params.epoch_begin + "&end_time_clause=" + graph_params.epoch_end; nindex_query = nindex_query + "&timezone=" + (new Date().getTimezoneOffset() * 60 * -1); var nindex_buttons = ""; var params_obj = tsQueryToTags(graph_params.ts_query); // TODO localize /* Hide IP version selector when a host is selected */ if(!params_obj.host) { nindex_buttons += '
'; } nindex_buttons += '
'; if(view.columns) { var url = http_prefix + (view.nindex_view ? "/lua/pro/get_nindex_flows.lua" : "/lua/pro/get_ts_table.lua"); var columns = view.columns.map(function(col) { return { title: col[1], field: col[0], css: { textAlign: col[2], width: col[3],// }, hidden: col[4] ? true : false, }; }); columns.push({ title: i18n.actions, field: "drilldown", css: {width: "1%", "white-space": "nowrap", "text-align": "center"}, }); var old_dt = graph_table.data("datatable"); if(old_dt && old_dt.pendingRequest) old_dt.pendingRequest.abort(); /* Force reinstantiation */ graph_table.removeData('datatable'); graph_table.html(""); graph_table.datatable({ title: "", url: url, perPage: per_page, noResultsMessage: function() { if(ts_chart.queryWasAborted()) return i18n.query_was_aborted; else return i18n.no_results_found; }, post: function() { var params = $.extend({}, graph_params); delete params.ts_compare; delete params.initial_point; params.limit = 1; // TODO make specific query // TODO change topk // TODO disable statistics params.detail_view = view.id; params.timezone = (new Date().getTimezoneOffset() * 60 * -1); return params; }, loadingYOffset: 40, columns: columns, buttons: view.nindex_view ? [nindex_buttons, ] : [], tableCallback: function() { var data = this.resultset; ts_chart.tableRequestCompleted(); if(!data) { // error return; } /* The user changed page */ if(data.currentPage > 1) graph_table.data("has_interaction", true); var stats_div = $("#chart1-flows-stats"); var has_drilldown = (data && data.data.some(function(row) { return row.drilldown; })); /* Remove the drilldown column if no drilldown is available */ if(!has_drilldown) $("table td:last-child, th:last-child", graph_table).remove(); if(data && data.stats && data.stats.loading_time) { $("#flows-load-time").html(data.stats.loading_time); $("#flows-processed-records").html(data.stats.num_records_processed); stats_div.show(); } else stats_div.hide(); }, rowCallback: function(row, row_data) { if((typeof row_data.tags === "object") && ( (params_obj.category && (row_data.tags.category === params_obj.category)) || (params_obj.protocol && (row_data.tags.protocol === params_obj.protocol)) )) { /* Highlight the row */ row.addClass("info"); } return row; } }); } }