ntopng/http_src/utilities/ntop-utils.js
GabrieleDeri 85fa6d1b1a
Updated host pool pages to vuejs, missing policy page TODO (#9469)
* Updated host pool pages to vuejs, missing policy page TODO

* Removed print
2025-08-02 16:24:29 +02:00

1517 lines
50 KiB
JavaScript

// 2014-20 - ntop.org
Date.prototype.format = function (format) { //author: meizz
var o = {
"M+": this.getMonth() + 1, //month
"d+": this.getDate(), //day
"h+": this.getHours(), //hour
"m+": this.getMinutes(), //minute
"s+": this.getSeconds(), //second
"q+": Math.floor((this.getMonth() + 3) / 3), //quarter
"S": this.getMilliseconds() //millisecond
}
if (/(y+)/.test(format)) format = format.replace(RegExp.$1,
(this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o) if (new RegExp("(" + k + ")").test(format))
format = format.replace(RegExp.$1,
RegExp.$1.length == 1 ? o[k] :
("00" + o[k]).substr(("" + o[k]).length));
return format;
}
// Extended disable function
jQuery.fn.extend({
disable: function (state) {
return this.each(function () {
var $this = $(this);
if ($this.is('input, button, textarea, select'))
this.disabled = state;
else
$this.toggleClass('disabled', state);
});
}
});
const NTOPNG_MIN_VISUAL_VALUE = 0.005;
const REGEXES = {
ipv4: String.raw`^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$`,
ipv6: String.raw`^((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$|^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*)$`,
domainName: String.raw`^([a-zA-Z]([a-zA-Z]|[0-9])?\.[a-zA-Z]{2,13}|[a-zA-Z0-9]([\-_.a-zA-Z0-9]{1,61}[a-zA-Z0-9])?\.[a-zA-Z]{2,13}|[a-zA-Z0-9]([\-_.a-zA-Z0-9]{1,61}[a-zA-Z0-9])?\.[a-zA-Z]{2,30}\.[a-zA-Z]{2,3})$`,
port: String.raw`^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$`,
latency: String.raw`^([0-9]*[.])?[0-9]+$`,
url: String.raw`^(https?\:\/\/[^\/\s]+(\/.*)?)$`,
emailUrl: String.raw`^smtps?:\/\/[\-a-zA-Z0-9:.]{1,256}$`,
macAddress: String.raw`^([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$`,
hostname: String.raw`^(?!\s*$)[a-zA-Z0-9._: \-\/]{1,250}|^[a-zA-Z0-9._: \-\/]{1,250}@[0-9]{0,5}`,
username: String.raw`^[a-zA-Z0-9._@!-?]{3,30}$`,
singleword: String.raw`^(?=[a-zA-Z0-9._:\-]{3,253}$)(?!.*[_.:\-]{2})[^_.:\-].*[^_.:\-]$`,
multiword: String.raw`^([a-zA-Z0-9._:\-\s]{3,253})$`,
email: String.raw`^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[a-zA-Z]{2,})$`,
emailCommaList: String.raw`^((?:[a-zA-Z0-9.!#$%&'*+\-\/=?^_\`\|~]+@[a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*)|([a-zA-Z\d.!#$%&'*+\-\/=?^_\`\|~]{1,128}))(?:,((?:[a-zA-Z0-9.!#$%&'*+\-\/=?^_\`\|~]+@[a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*)|([a-zA-Z\d.!#$%&'*+\-\/=?^_\`\|~]{1,128})))*$`,
https: String.raw`^https?:\/\/.+$`,
token: String.raw`^[0-9a-f]{32}`,
score: String.raw`^[0-9]{1,5}`,
telegram_channel: String.raw`^[0-9\-]{1,15}`,
password: String.raw`^[\w\/$!\/()=?^*@_-]{5,31}$`,
tls_certificate: String.raw`^[^=,]+=[^=,]+(,\s[^=,]+=[^=,]+)*$`,
domain_name_not_strict: String.raw`^[a-zA-Z0-9\-_~]+((\.[a-zA-Z0-9\-_~]+)+)$`,
non_quoted_text: String.raw`^[a-zA-Z0-9.-_]+$`,
};
export default class NtopUtils {
/* Show an overlay to hide loading */
static toggleOverlays(time = 500) {
$(`.overlay`).toggle(time);
}
static showOverlays(time = 500) {
$(`.overlay`).fadeIn(time);
}
static hideOverlays(time = 500) {
$(`.overlay`).fadeOut(time);
}
static get REGEXES() {
return REGEXES;
}
static getIPv4RegexWithCIDR() {
const length = REGEXES.ipv4.length;
return `${REGEXES.ipv4.substring(0, length - 1)}(\\/?)(\\b([0-9]|[12][0-9]|3[0-2])?\\b)$`;
}
static getIPv6RegexWithCIDR() {
const length = REGEXES.ipv6.length;
return `${REGEXES.ipv6.substring(0, length - 1)}(\\/?)\\b([0-9]|[1-9][0-9]|1[01][0-9]|12[0-8])?\\b$`;
}
/**
* Resolve a hostname by doing a DNS Resolve.
* @param {string} hostname The hostname to resolve
*/
static async resolveDNS(hostname = "ntop.org") {
// resolve the hostname by doing a fetch request to the backend
try {
const request = await fetch(`${http_prefix}/lua/rest/v2/get/dns/resolve.lua?hostname=${hostname}`);
const response = await request.json();
return response;
}
catch (err) {
// prints out the error if the request fails
console.error(`Something went wrong when resolving hostname: ${err}`)
}
// if the request has failed return a placeholder response
// indicating the failure
return { rc: -1, rc_str: "FAILED_HTTP_REQUEST" };
}
/**
* Replace the inputs which contain the [data-pattern] attribute
* with the [pattern] attribute.
*/
static initDataPatterns() {
// for each input with the data-pattern attribute
// substitute the data-pattern with the right regexes
$(`input[data-pattern]`).each(function () {
// if the pattern is empty then print a warn inside the console
const dataPattern = $(this).data('pattern');
if (!dataPattern) {
console.warn(`An empty data-pattern on an input was found!`, this);
return;
}
// build the regexp pattern for the input
const pattern = dataPattern.split('|').map(p => REGEXES[p].toString()).join('|');
// load the pattern
$(this).attr('pattern', pattern);
// remove the data-pattern from the input
$(this).removeAttr('data-pattern');
});
}
static is_good_ipv4(ipv4) {
return new RegExp(REGEXES.ipv4).test(ipv4);
}
static is_good_ipv6(ipv6) {
return new RegExp(REGEXES.ipv6).test(ipv6);
}
static is_mac_address(mac) {
return new RegExp(REGEXES.macAddress).test(mac);
}
static isNumeric(value) {
return /^\d+$/.test(value);
}
static is_network_mask(what, optional_mask) {
var elems = what.split("/");
var mask = null;
var ip_addr;
if (elems.length != 2) {
if (!optional_mask)
return null;
else
ip_addr = what;
} else {
ip_addr = elems[0];
if (!NtopUtils.isNumeric(elems[1]))
return null;
mask = parseInt(elems[1]);
if (mask < 0)
return null;
}
if (NtopUtils.is_good_ipv4(ip_addr)) {
if (mask === null)
mask = 32;
else if (mask > 32)
return null;
return {
type: "ipv4",
address: ip_addr,
mask: mask
};
} else if (NtopUtils.is_good_ipv6(elems[0])) {
if (mask === null)
mask = 128;
else if (mask > 128)
return (false);
return {
type: "ipv6",
address: ip_addr,
mask: mask
};
}
return null;
}
static fbits(bits) {
const sizes = ['bps', 'Kbps', 'Mbps', 'Gbps', 'Tbps'];
if (typeof (bits) === "undefined")
return "-";
if (bits == 0) return '0';
if ((bits > 0) && (bits < NTOPNG_MIN_VISUAL_VALUE)) return ('< ' + NTOPNG_MIN_VISUAL_VALUE + ' bps');
var bits_log1000 = Math.log(bits) / Math.log(1000)
var i = parseInt(Math.floor(bits_log1000));
if (i < 0 || isNaN(i)) {
i = 0;
} else if (i >= sizes.length) { // prevents overflows
return "> " + sizes[sizes.length - 1]
}
if (i <= 1) {
return Math.round(bits / Math.pow(1000, i) * 100) / 100 + ' ' + sizes[i]
}
else {
var ret = parseFloat(bits / Math.pow(1000, i)).toFixed(2)
if (ret % 1 == 0)
ret = Math.round(ret)
return ret + ' ' + sizes[i]
}
}
static export_rate(eps) {
if (typeof (eps) === "undefined")
return "-";
var sizes = ['exp/s', 'Kexp/s'];
if (eps == 0) return '0';
if ((eps > 0) && (eps < NTOPNG_MIN_VISUAL_VALUE)) return ('< ' + NTOPNG_MIN_VISUAL_VALUE + ' exps/s');
var res = NtopUtils.scaleValue(eps, sizes, 1000);
// Round to two decimal digits
return Math.round(res[0] * 100) / 100 + ' ' + res[1];
}
static exports_format(exports) {
if (typeof (exports) === "undefined")
return "-";
var exports_label = i18n_ext.exports.toLowerCase();
var sizes = [exports_label, 'K ' + exports_label];
if (exports == 0) return '0';
if ((exports > 0) && (exports < NTOPNG_MIN_VISUAL_VALUE)) return ('< ' + NTOPNG_MIN_VISUAL_VALUE + ' exps/s');
var res = NtopUtils.scaleValue(exports, sizes, 1000);
// Round to two decimal digits
return Math.round(res[0] * 100) / 100 + ' ' + res[1];
}
static fbits_from_bytes(bytes) {
if (typeof (bytes) === "undefined")
return "-";
return (NtopUtils.fbits(bytes * 8));
}
static fpackets(pps) {
if (typeof (pps) === "undefined")
return "-";
var sizes = ['pps', 'Kpps', 'Mpps', 'Gpps', 'Tpps'];
if (pps == 0) return '0';
if ((pps > 0) && (pps < NTOPNG_MIN_VISUAL_VALUE)) return ('< ' + NTOPNG_MIN_VISUAL_VALUE + ' pps');
var res = NtopUtils.scaleValue(pps, sizes, 1000);
// Round to two decimal digits
return Math.round(res[0] * 100) / 100 + ' ' + res[1];
}
static fpoints(pps) {
if (typeof (pps) === "undefined")
return "-";
var sizes = ['pt/s', 'Kpt/s', 'Mpt/s', 'Gpt/s', 'Tpt/s'];
if (pps == 0) return '0';
if ((pps > 0) && (pps < NTOPNG_MIN_VISUAL_VALUE)) return ('< ' + NTOPNG_MIN_VISUAL_VALUE + ' pt/s');
var res = NtopUtils.scaleValue(pps, sizes, 1000);
// Round to two decimal digits
return Math.round(res[0] * 100) / 100 + ' ' + res[1];
}
static fflows(fps) {
if (typeof (fps) === "undefined")
return "-";
var sizes = ['fps', 'Kfps', 'Mfps', 'Gfps', 'Tfps'];
if (fps == 0) return '0';
if ((fps > 0) && (fps < NTOPNG_MIN_VISUAL_VALUE)) return ('< ' + NTOPNG_MIN_VISUAL_VALUE + ' fps');
var res = NtopUtils.scaleValue(fps, sizes, 1000);
// Round to two decimal digits
return Math.round(res[0] * 100) / 100 + ' ' + res[1];
}
static fmsgs(mps) {
if (typeof (mps) === "undefined")
return "-";
var sizes = ['msg/s', 'Kmsg/s', 'Msg/s', 'Gmsg/s', 'Tmsg/s'];
if (mps == 0) return '0';
if ((mps > 0) && (mps < NTOPNG_MIN_VISUAL_VALUE)) return ('< ' + NTOPNG_MIN_VISUAL_VALUE + ' mps');
var res = NtopUtils.scaleValue(mps, sizes, 1000);
// Round to two decimal digits
return Math.round(res[0] * 100) / 100 + ' ' + res[1];
}
static fmillis(ms) {
if (ms === undefined) return '-';
const sizes = ['ms'];
const res = NtopUtils.scaleValue(ms, sizes, 1000);
return Math.round(res[0] * 100) / 100 + ' ' + res[1];
}
static fnone(val) {
if (val === undefined) return '-';
return Math.round(val * 100) / 100;
}
static falerts(aps) {
if (typeof (aps) === "undefined")
return "-";
// Round to two decimal digits
return Math.round(aps * 100) / 100 + ' alerts/s';
}
static fint(value) {
if (typeof (value) === "undefined")
return "-";
var x = Math.round(value);
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
static ffloat(value) {
if (typeof (value) === "undefined")
return "-";
var x = Math.round(value * 100) / 100.;
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
static fpercent(value) {
if (typeof (value) === "undefined")
return "-";
return Math.round(value * 100) / 100 + " %";
}
static percentage(value, total) {
if (total > 0) {
var pctg = Math.round((value * 10000) / total)
if (pctg > 0) {
/* Two decimals */
return (" [ " + (pctg / 100) + " % ] ")
}
}
return ("")
}
static fdate(when) {
var epoch = when * 1000;
var d = new Date(epoch);
return (d);
}
static capitaliseFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
static get_trend(actual, before) {
if ((actual === undefined) || (before === undefined) || (actual == before)) {
return ("<i class=\"fas fa-minus\"></i>");
} else if (actual > before) {
return ("<i class=\"fas fa-arrow-up\"></i>");
} else {
return ("<i class=\"fas fa-arrow-down\"></i>");
}
}
static abbreviateString(str, len) {
if (!str)
return "";
if (str.length < len)
return str;
return str.substring(0, len) + "...";
}
static toFixed2(num) {
if (!num) return "";
return num.toFixed(2);
}
// Convert bytes to human readable format
static bytesToSize(bytes) {
if (typeof (bytes) === "undefined")
return "-";
var precision = 2;
var kilobyte = 1024;
var megabyte = kilobyte * 1024;
var gigabyte = megabyte * 1024;
var terabyte = gigabyte * 1024;
if ((bytes >= 0) && (bytes < kilobyte))
if (bytes != 0)
return parseFloat(bytes.toFixed(precision)) + " Bytes";
else
return parseFloat(bytes) + " Bytes";
else if ((bytes >= kilobyte) && (bytes < megabyte))
return parseFloat((bytes / kilobyte).toFixed(precision)) + ' KB';
else if ((bytes >= megabyte) && (bytes < gigabyte))
return parseFloat((bytes / megabyte).toFixed(precision)) + ' MB';
else if ((bytes >= gigabyte) && (bytes < terabyte))
return parseFloat((bytes / gigabyte).toFixed(precision)) + ' GB';
else if (bytes >= terabyte)
return parseFloat((bytes / terabyte).toFixed(precision)) + ' TB';
else
return parseFloat(bytes.toFixed(precision)) + ' Bytes';
}
static drawTrend(current, last, withColor) {
if (current == last) {
return ("<i class=\"fas fa-minus\"></i>");
} else if (current > last) {
return ("<i class=\"fas fa-arrow-up\"" + withColor + "></i>");
} else {
return ("<i class=\"fas fa-arrow-down\"></i>");
}
}
static toggleAllTabs(enabled) {
if (enabled === true)
$("#historical-tabs-container").find("li").removeClass("disabled").find("a").attr("data-toggle", "tab");
else
$("#historical-tabs-container").find("li").addClass("disabled").find("a").removeAttr("data-toggle");
}
static disableAllDropdownsAndTabs() {
$("select").each(function () {
$(this).prop("disabled", true);
});
NtopUtils.toggleAllTabs(false)
}
static enableAllDropdownsAndTabs() {
$("select").each(function () {
$(this).prop("disabled", false);
});
NtopUtils.toggleAllTabs(true)
}
static capitalize(s) {
return s && s[0].toUpperCase() + s.slice(1);
}
static addCommas(nStr) {
nStr += '';
var x = nStr.split('.');
var x1 = x[0];
var x2 = x.length > 1 ? '.' + x[1] : '';
var rgx = /(\d+)(\d{3})/;
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + ',' + '$2');
}
return x1 + x2;
}
static scaleValue(val, sizes, scale, decimals) {
if (val == 0) return [0, sizes[0]];
let factor = decimals ? (10 * decimals) : 10;
var i = parseInt(Math.floor(Math.log(val) / Math.log(scale)));
if (i < 0 || isNaN(i)) {
i = 0;
} else if (i >= sizes.length) {
i = sizes.length - 1;
}
return [Math.round((val / Math.pow(scale, i)) * factor) / factor, sizes[i]];
}
static formatValue(val, decimals) {
var sizes = ['', 'K', 'M', 'G', 'T'];
if (val == 0) return '0';
if ((val > 0) && (val < NTOPNG_MIN_VISUAL_VALUE)) return ('< ' + NTOPNG_MIN_VISUAL_VALUE);
if (decimals == undefined) decimals = 0;
var res = NtopUtils.scaleValue(val, sizes, 1000, decimals);
return res[0] + res[1];
}
static formatPackets(n) {
return (NtopUtils.addCommas(n.toFixed(0)) + " Pkts");
}
static bytesToVolume(bytes) {
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes == 0) return '0 Bytes';
if ((bytes > 0) && (bytes < NTOPNG_MIN_VISUAL_VALUE)) return ('< ' + NTOPNG_MIN_VISUAL_VALUE + " Bytes");
var res = NtopUtils.scaleValue(bytes, sizes, 1024);
return parseFloat(res[0]) + " " + res[1];
};
static bytesToVolumeAndLabel(bytes) {
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes == 0) return '0 Bytes';
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
return [(bytes / Math.pow(1024, i)).toFixed(2), sizes[i]];
};
static bitsToSize(bits, factor) {
factor = factor || 1000;
var sizes = ['bps', 'Kbps', 'Mbps', 'Gbps', 'Tbps'];
if (bits == 0) return '0 bps';
if ((bits > 0) && (bits < NTOPNG_MIN_VISUAL_VALUE)) return ('< ' + NTOPNG_MIN_VISUAL_VALUE + " bps");
var res = NtopUtils.scaleValue(bits, sizes, factor);
return res[0].toFixed(2) + " " + res[1];
};
static bitsToSize_no_comma(bits, factor) {
factor = factor || 1000;
var sizes = ['bps', 'Kbps', 'Mbps', 'Gbps', 'Tbps'];
if (bits == 0) return '0 bps';
if ((bits > 0) && (bits < NTOPNG_MIN_VISUAL_VALUE)) return ('< ' + NTOPNG_MIN_VISUAL_VALUE + " bps");
var res = NtopUtils.scaleValue(bits, sizes, factor);
return res[0] + " " + res[1];
};
static secondsToTime(seconds) {
if (seconds < 1) {
return ("< 1 sec")
}
let days = Math.floor(seconds / 86400)
let hours = Math.floor((seconds / 3600) - (days * 24))
let minutes = Math.floor((seconds / 60) - (days * 1440) - (hours * 60))
let sec = seconds % 60
let msg = "", msg_array = []
if (days > 0) {
let years = Math.floor(days / 365)
if (years > 0) {
days = days % 365
msg = years + " year"
if (years > 1) {
msg += "s"
}
msg_array.push(msg)
msg = ""
}
msg = days + " day"
if (days > 1) { msg += "s" }
msg_array.push(msg)
msg = ""
}
if (hours > 0) {
if (hours < 10) { msg = "0" }
msg += hours + ":";
}
if (minutes < 10) { msg += "0" }
msg += minutes + ":";
if (sec < 10) { msg += "0" }
msg += sec;
msg_array.push(msg)
return msg_array.join(", ")
}
static msecToTime(msec) {
if (msec >= 1000) {
return NtopUtils.secondsToTime(msec / 1000);
} else {
var x = Math.round(msec * 1000) / 1000.;
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + " ms";
}
}
static epoch2Seen(epoch) {
/* 08/01/13 15:12:37 [18 min, 13 sec ago] */
var d = new Date(epoch * 1000);
var tdiff = Math.floor(((new Date()).getTime() / 1000) - epoch);
return (d.format("dd/MM/yyyy hh:mm:ss") + " [" + NtopUtils.secondsToTime(tdiff) + " ago]");
}
/* ticks for graph x axis */
static graphGetXAxisTicksFormat(diff_epoch) {
var tickFormat;
if (diff_epoch <= 86400) {
tickFormat = "%H:%M:%S";
} else if (diff_epoch <= 2 * 86400) {
tickFormat = "%b %e, %H:%M:%S";
} else {
tickFormat = "%b %e";
}
return (tickFormat);
}
static paramsExtend(defaults, override) {
return $.extend({}, defaults, override);
}
static paramsToForm(form, params) {
form = $(form);
for (var k in params) {
if (params.hasOwnProperty(k)) {
var input = $('<input type="hidden" name="' + k + '" value="' + params[k] + '">');
input.appendTo(form);
}
}
return form;
}
/*
* This function creates a javascript object where each k->v pair of the input object
* translates into two pairs in the output object: a key_[i]->k and a val_[i]->v, where
* i is an incremental index.
*
* The output object can then be serialized to an URL. This conversion is required for
* handling special characters: since ntopng strips special characters in _GET keys,
* _GET values must be used.
*
* This function performs the inverse conversion of lua paramsPairsDecode.
*
*/
static paramsPairsEncode(params) {
var i = 0;
var res = {};
for (var k in params) {
res["key_" + i] = k;
res["val_" + i] = params[k];
i = i + 1;
}
return res;
}
static hostkey2hostInfo(host_key) {
var info;
host_key = host_key.replace(/____/g, ":");
host_key = host_key.replace(/___/g, "/");
host_key = host_key.replace(/__/g, ".");
info = host_key.split("@");
return (info);
}
static handle_tab_state(nav_object, default_tab) {
$('a', nav_object).click(function (e) {
e.preventDefault();
});
// store the currently selected tab in the hash value
$(" > li > a", nav_object).on("shown.bs.tab", function (e) {
var id = $(e.target).attr("href").substr(1);
if (history.replaceState) {
// this will prevent the 'jump' to the hash
history.replaceState(null, null, "#" + id);
} else {
// fallback
window.location.hash = id;
}
});
// on load of the page: switch to the currently selected tab
var hash = window.location.hash;
if (!hash) hash = "#" + default_tab;
$('a[href="' + hash + '"]', nav_object).tab('show');
}
static _add_find_host_link(form, name, data) {
$('<input>').attr({
type: 'hidden',
id: name,
name: name,
value: data,
}).appendTo(form);
}
/* Used while searching hosts a and macs with typeahead */
static makeFindHostBeforeSubmitCallback(http_prefix) {
return function (form, data) {
if (data.context && data.context == "historical") {
form.attr("action", http_prefix + "/lua/pro/db_search.lua");
if (data.type == "ip") {
NtopUtils._add_find_host_link(form, "ip", data.ip);
} else if (data.type == "mac") {
NtopUtils._add_find_host_link(form, "mac", data.mac);
} else if (data.type == "community_id") {
NtopUtils._add_find_host_link(form, "community_id", data.community_id);
} else if (data.type == "ja3_client") {
NtopUtils._add_find_host_link(form, "ja3_client", data.ja3_client);
} else if (data.type == "ja3_server") {
NtopUtils._add_find_host_link(form, "ja3_server", data.ja3_server);
} else /* "hostname" */ {
NtopUtils._add_find_host_link(form, "name", data.hostname ? data.hostname : data.name);
}
} else {
if (data.type == "mac") {
form.attr("action", http_prefix + "/lua/mac_details.lua");
} else if (data.type == "network") {
form.attr("action", http_prefix + "/lua/hosts_stats.lua");
NtopUtils._add_find_host_link(form, "network", data.network);
} else if (data.type == "snmp") {
form.attr("action", http_prefix + "/lua/pro/enterprise/snmp_interface_details.lua");
NtopUtils._add_find_host_link(form, "snmp_port_idx", data.snmp_port_idx);
} else if (data.type == "snmp_device") {
form.attr("action", http_prefix + "/lua/pro/enterprise/snmp_device_details.lua");
} else if (data.type == "asn") {
form.attr("action", http_prefix + "/lua/hosts_stats.lua");
NtopUtils._add_find_host_link(form, "asn", data.asn);
} else {
form.attr("action", http_prefix + "/lua/host_details.lua");
NtopUtils._add_find_host_link(form, "mode", "restore");
}
}
return true;
}
}
static tstampToDateString(html_tag, format, tdiff) {
tdiff = tdiff || 0;
var timestamp = parseInt(html_tag.html()) + tdiff;
var localized = d3.time.format(format)(new Date(timestamp * 1000));
html_tag.html(localized).removeClass("hidden");
return localized;
}
static noHtml(s) {
return s.replace(/<[^>]+>/g, '');
}
static cleanCustomHostUrl(host) {
/* Remove starting http(s). */
return host
.replace(/^http:\/\//gi, '')
.replace(/^https:\/\//gi, '')
/* Remove starting www. */
.replace(/^www\./gi, '')
/* Remove non-allowed characters */
.replace(/[^0-9a-zA-Z\.:\/_-]/gi, '');
}
/* https://stackoverflow.com/questions/2090551/parse-query-string-in-javascript */
static parseQuery(queryString) {
var query = {};
var pairs = (queryString[0] === '?' ? queryString.substr(1) : queryString).split('&');
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i].split('=');
query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || '');
}
return query;
}
static getHistoryParameters(params) {
var cur_params = NtopUtils.parseQuery(window.location.search);
var new_params = $.extend(cur_params, params);
var new_query = "?" + $.param(new_params, true);
var baseUrl = [location.protocol, '//', location.host, location.pathname].join('');
return baseUrl + new_query;
}
// return true if the status code is different from 200
static check_status_code(status_code, status_text, $error_label) {
const is_different = status_code != 200;
if (is_different && $error_label != null) {
let message = i18n_ext.request_failed_message;
if (status_code && status_text) {
message += `: ${status_code} - ${status_text}`;
}
$error_label.text(message).show();
}
else if (is_different && $error_label == null) {
alert(`${i18n_ext.request_failed_message}: ${status_code} - ${status_text}`);
}
return is_different;
}
// To be used in conjunction with httpdocs/templates/config_list_components/import_modal.html
static importModalHelper(params) {
if (!params.loadConfigXHR) { throw ("importModalHelper:: Missing 'loadConfigXHR' param"); }
$(`input#import-input`).on('change', function () {
$(`#btn-confirm-import`).removeAttr("disabled");
});
$(`#import-modal`).on('hidden.bs.modal', function () {
$(`#import-input`).val('');
$("#import-error").hide().removeClass('text-warning').addClass('invalid-feedback');
$(`#btn-confirm-import`).attr("disabled", "disabled");
});
$("#import-modal").on("submit", "form", function (e) {
e.preventDefault();
const $button = $('#btn-confirm-import');
$button.attr("disabled", "");
// read configuration file
const file = $('#import-input')[0].files[0];
if (!file) {
$("#import-error").text(`${i18n_ext.no_file}`).show();
$button.removeAttr("disabled");
return;
}
const reader = new FileReader();
reader.readAsText(file, "UTF-8");
reader.onload = function (responseJSON) {
// Client-side configuration file format check
let jsonConfiguration = null
try {
jsonConfiguration = JSON.parse(reader.result);
} catch (e) {
ToastUtils.showToast({
id: 'import-error-toast',
level: 'error',
title: i18n("error"),
body: e,
delay: 3000
});
return;
}
if (!jsonConfiguration) {
$("#import-error").text(i18n_ext.rest_consts[responseJSON.rc_str] || 'Not Implemented Yet').show();
$button.removeAttr("disabled");
return;
}
// Submit configuration file
params.loadConfigXHR(reader.result)
.done((response, status, xhr) => {
if (response.rc < 0) {
$("#import-error").text(response.rc_str).show();
return;
}
// if the operation was successful call the successCallback
if (params.successCallback) {
params.successCallback(response);
}
const key = $(`input[name='configuration']:checked`).val();
const body = (key == 'all')
? i18n("manage_configurations.messages.import_all_success")
: i18n("manage_configurations.messages.import_success");
// show a success alert message
ToastUtils.showToast({
id: 'import-configuration-alert',
level: 'success',
title: i18n("success"),
body: body,
delay: 2000
});
$("#import-modal").modal('hide');
})
.fail(({ responseJSON }) => {
const PARTIAL_IMPORT_RC = -28;
if (params.failureCallback) {
params.failureCallback(responseJSON);
}
if (responseJSON && responseJSON.rc > 0) return;
if (responseJSON.rc == PARTIAL_IMPORT_RC)
$(`#import-error`).removeClass('invalid-feedback').addClass('text-warning');
$("#import-error").text(i18n_ext.rest_consts[responseJSON.rc_str] || i18n_ext.FAILED_HTTP_REQUEST).show();
})
.always(() => {
$button.removeAttr("disabled");
});
};
});
}
static serializeFormArray(serializedArray) {
const serialized = {};
serializedArray.forEach((obj) => {
/* if the object is an array */
if (obj.name.includes('[]')) {
return;
}
else {
// clean the string
if (typeof obj.value === "string") {
obj.value = obj.value.trim();
}
serialized[obj.name] = obj.value;
}
});
return serialized;
}
static cleanForm(formSelector) {
/* remove validation fields and tracks */
$(formSelector).find('input,select,textarea').each(function (i, input) {
$(this).removeClass(`is-valid`).removeClass(`is-invalid`);
});
/* reset all the values */
$(formSelector)[0].reset();
}
/**
* Make a fetch call with a timeout option
*/
static fetchWithTimeout(uri, options = {}, time = 5000) {
const controller = new AbortController()
const config = { ...options, signal: controller.signal }
return fetch(uri, config)
.then((response) => {
if (!response.ok) {
throw new Error(`${response.status}: ${response.statusText}`)
}
return response
})
.catch((error) => {
if (error.name === 'AbortError') {
throw new Error('Response timed out')
}
})
}
static setPref(action, csrf, success, failure) {
if (action == undefined) {
console.warn("An action key must be defined to set a preference!");
return;
}
const empty = () => { };
const request = $.post(`${http_prefix}/lua/update_prefs.lua`, { action: action, csrf: csrf });
request.done(success || empty);
request.fail(failure || empty);
}
/**
* Glue strings contained in array separated by a comma.
* @param {array} array The array of strings. I.e. ["Hello", "World"]
* @param {number} limit How many words the string contains
*
* @return {string} A string built by array's elements. i.e: "Hello, World"
*/
static arrayToListString(array, limit) {
if (array == undefined) return "";
if (array.length > limit) {
return array.slice(0, limit).join(", ") + ` ${i18n_ext.and_x_more.replace('$num', array.length - limit)}`;
}
return array.slice(0, limit).join(", ");
}
static buildURL(location, params = {}, hasReferer = false, refererParams = {}) {
const url = new URL(location, window.location);
for (const [name, value] of Object.entries(params)) {
if (value || value === 0)
url.searchParams.set(name, value);
continue;
}
if (hasReferer) {
const refUrl = new URL(window.location.href);
for (const [name, value] of Object.entries(refererParams)) {
if (!value) continue;
refUrl.searchParams.set(name, value);
}
url.searchParams.set('referer', refUrl.toString());
}
return url.toString();
}
static getEditPoolLink(href, poolId) {
const url = new URL(href, window.location);
url.searchParams.set('pool_id', poolId);
return url.toString();
}
static getPoolLink(poolType, poolId = 0) {
return `${http_prefix}/lua/rest/v2/get/${poolType}/pools.lua?pool=${poolId}`;
}
static async getPool(poolType, id = 0) {
if (poolType === null) throw 'A pool type must be defined!';
try {
const request = await fetch(NtopUtils.getPoolLink(poolType, id));
const pool = await request.json();
if (pool.rc < 0) {
return [false, {}];
}
return [true, pool.rsp[0]];
}
catch (err) {
return [false, {}];
}
}
/**
* Save the scale of element inside the local storage
* @param {object} $element
* @param {object} scale
*/
static saveElementScale($element, scale = { width: 0, height: 0 }) {
const key = NtopUtils.generateScaleElementKey($element);
localStorage.setItem(key, JSON.stringify(scale));
}
static generateScaleElementKey($element) {
let identificator;
const page = location.pathname;
const elementId = $element.attr('id');
if (elementId !== "") {
identificator = elementId;
}
else {
const className = $element.attr('class');
identificator = className;
}
const key = `${identificator}-${page}-scale`;
return key;
}
/**
* Load the old scale value ofx element from the local storage
* @param {object} $element
*/
static loadElementScale($element) {
const key = NtopUtils.generateScaleElementKey($element);
const currentValue = localStorage.getItem(key);
if (currentValue == null) return undefined;
return JSON.parse(currentValue);
}
static fillFieldIfValid($field, value) {
if (value === undefined) {
$field.val('');
}
else {
$field.val(value);
}
}
static copyToClipboard(text, item) {
const el = document.createElement('textarea');
el.value = text;
el.setAttribute('readonly', '');
el.style.position = 'absolute';
el.style.left = '-9999px';
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
$(item).attr("title", "Copied!").tooltip("dispose").tooltip().tooltip("show");
$(item).removeAttr("data-bs-original-title")
$(item).attr("title", text)
}
static stripTags(html) {
let t = document.createElement("div");
t.innerHTML = html;
return t.textContent || t.innerText || "";
}
static shortenLabel(label, len, last_char) {
let shortened_label = label
if (label.length > len + 5) {
if (last_char) {
let last_index = label.lastIndexOf(last_char)
const requested_label = label.slice(last_index)
if (len > last_index)
len = last_index
shortened_label = label.slice(0, len) + "... " + requested_label
} else {
shortened_label = label.slice(0, len) + "...";
}
}
return shortened_label
}
static sortAlphabetically(a, b) {
const nameA = a.label?.toUpperCase(); // ignore upper and lowercase
const nameB = b.label?.toUpperCase(); // ignore upper and lowercase
if (nameA < nameB) { return -1; }
if (nameA > nameB) { return 1; }
return 0;
}
/* This function, given a name and a value, return a string
* formatted in the following way:
* name [value]
* If max_name_len is different from 0, then it's going to cut the name string
* to max_name_len
*/
static formatNameValue(name, value, max_name_len) {
let label = name;
if (name != value) {
if (max_name_len && typeof (max_name_len) == 'number')
label = this.shortenLabel(label, max_name_len, '.');
label = `${label} [${value}]`
}
return label
}
/* This function, remove from a string the VLAN 0
* name@0 -> name
*/
static removeVlan(name) {
let label = name
const vlan_index = label.lastIndexOf('@');
if (vlan_index != -1) {
const vlan = label.slice(vlan_index + 1);
if (vlan == 0) {
label = label.slice(0, vlan_index);
}
}
return label
}
/* Format an object with label and value from a column row */
static formatGenericObj(obj, row) {
let label = obj.label ? obj.label : obj.value;
let key = obj.value;
return label;
}
/* Format a country from a column object */
static formatCountry(obj, row) {
let country_code = obj.value;
let label = obj.label ? obj.label : obj.value;
return `${label} <img src="/dist/images/blank.gif" class="flag flag-${country_code.toLowerCase()}">`;
}
/* Format an host from a column object */
static formatHost(obj, row, is_client) {
let label = "";
if (!obj) {
return label;
}
/* Link */
let host_key = obj.ip;
if (row.vlan_id && row.vlan_id.value)
host_key = host_key + '@' + row.vlan_id.value;
/* Label */
label = obj.label ? obj.label : obj.value;
if (row.vlan_id && row.vlan_id.label)
label += `@${row.vlan_id.label}`;
const url = NtopUtils.buildURL(`${http_prefix}/lua/host_details.lua`, { host: host_key });
label = `<a href="${url}">${label}</a>`;
/* Country */
let country_obj = is_client ? row.cli_country : row.srv_country;
if (!country_obj && row.country) country_obj = row.country;
if (country_obj && country_obj.value)
label += ` <img src="${http_prefix}/dist/images/blank.gif" class="flag flag-${country_obj.value.toLowerCase()}" title="${country_obj.title}"></a>`;
return label;
}
/* Format a network from a column object */
static formatNetwork(obj, row) {
let label = "";
if (!obj) {
return label;
}
/* Link */
let network_key = obj.value;
/* Label */
label = obj.label ? obj.label : obj.value;
if (row.vlan_id && row.vlan_id.label)
label += `@${row.vlan_id.label}`;
const url = NtopUtils.buildURL(`${http_prefix}/lua/hosts_stats.lua`, { network: network_key });
label = `<a href="${url}">${label}</a>`;
return label;
}
/* This function converts a mac address to a string*/
static convertMACAddress(a) {
return a.toLowerCase().replace(/[^a-f0-9]/g, '');
}
/* This function converts an ip to a number equale to the ip but without . or :: in case of ipv6
* this is needed in case of ordering
*/
static convertIPAddress(a) {
var i, item;
var m, n, t;
var x, xa;
if (!a) {
return 0;
}
a = a.replace(/<[\s\S]*?>/g, "");
//IPv4:Port
t = a.split(":");
if (t.length == 2) {
m = t[0].split(".");
}
else {
m = a.split(".");
}
n = a.split(":");
x = "";
xa = "";
if (m.length == 4) {
// IPV4
for (i = 0; i < m.length; i++) {
item = m[i];
if (item.length == 1) {
x += "00" + item;
}
else if (item.length == 2) {
x += "0" + item;
}
else {
x += item;
}
}
}
else if (n.length > 0) {
// IPV6
var count = 0;
for (i = 0; i < n.length; i++) {
item = n[i];
if (i > 0) {
xa += ":";
}
if (item.length === 0) {
count += 0;
}
else if (item.length == 1) {
xa += "000" + item;
count += 4;
}
else if (item.length == 2) {
xa += "00" + item;
count += 4;
}
else if (item.length == 3) {
xa += "0" + item;
count += 4;
}
else {
xa += item;
count += 4;
}
}
// Padding the ::
n = xa.split(":");
var paddDone = 0;
for (i = 0; i < n.length; i++) {
item = n[i];
if (item.length === 0 && paddDone === 0) {
for (var padding = 0; padding < (32 - count); padding++) {
x += "0";
paddDone = 1;
}
}
else {
x += item;
}
}
}
return x;
}
/* Format an AS from a column object */
static formatASN(obj, row) {
let label = "";
if (!obj) {
return label;
}
/* Link */
let asn_key = obj.value;
/* Label */
label = obj.label ? obj.label : obj.value;
const url = NtopUtils.buildURL(`${http_prefix}/lua/hosts_stats.lua`, { asn: asn_key });
label = `<a href="${url}">${label}</a>`;
return label;
}
static createProgressBar(percentage) {
return `<div class="d-flex flex-row align-items-center">
<div class="col-9 progress">
<div class="progress-bar bg-warning" aria-valuenow="${percentage}" aria-valuemin="0" aria-valuemax="100" style="width: ${percentage}%;">
</div>
</div>
<div class="col">&nbsp;${percentage} %</div>
</div>`
}
static createBreakdown(percentage_1, percentage_2, label_1, label_2) {
if (percentage_1 == 0 && percentage_2 == 0) return ''
return `<div class="d-flex flex-row align-items-center">
<div class="col-12 progress">
<div class="progress-bar bg-warning" aria-valuenow="${percentage_1}" aria-valuemin="0" aria-valuemax="100" style="width: ${percentage_1}%;">${label_1}</div>
<div class="progress-bar bg-success" aria-valuenow="${percentage_2}" aria-valuemin="0" aria-valuemax="100" style="width: ${percentage_2}%;">${label_2}</div>
</div>
</div>`
}
/* Return the number of rows available in a table */
static getNumTableRows() {
return [10, 20, 50, 100];
}
static formatApexChartLabelFromXandName({ series, seriesIndex, dataPointIndex, w }) {
const serie = w.config.series[seriesIndex]["data"][dataPointIndex];
const name = serie["name"]
const y_value = serie["y"];
const host_name = serie["meta"]["label"];
const x_axis_title = w.config.xaxis.title.text;
const y_axis_title = w.config.yaxis[0].title.text;
return (`
<div class='apexcharts-theme-light apexcharts-active' id='test'>
<div class='apexcharts-tooltip-title' style='font-family: Helvetica, Arial, sans-serif; font-size: 12px;'>
${host_name}
</div>
<div class='apexcharts-tooltip-series-group apexcharts-active d-block'>
<div class='apexcharts-tooltip-text text-left'>
<b>${x_axis_title}</b>: ${name}
</div>
<div class='apexcharts-tooltip-text text-left'>
<b>${y_axis_title}</b>: ${y_value}
</div>
</div>
</div>
`)
}
static apexChartJumpToAlerts(event, chartContext, config) {
const { seriesIndex, dataPointIndex } = config;
const { series } = config.config;
if (seriesIndex === -1) return;
if (series === undefined) return;
const serie = series[seriesIndex];
const base_url = serie.base_url || series[0]['base_url']
const default_url = serie.start_url || series[0]['start_url']
if (base_url != null && default_url != null) {
const search = serie.data[dataPointIndex].meta.url_query;
location.href = `${base_url}?${default_url}${search}`;
}
}
static apexChartJumpToHostDetails(event, chartContext, config) {
const { seriesIndex, dataPointIndex } = config;
const { series } = config.config;
if (seriesIndex === -1) return;
if (series === undefined) return;
const serie = series[seriesIndex];
const base_url = serie.base_url || series[0]['base_url']
if (base_url != null) {
const url = `${base_url}?${serie.data[dataPointIndex].meta.url_query}`;
ntopng_url_manager.go_to_url(url);
}
}
static formatApexChartLabelFromXandY({ series, seriesIndex, dataPointIndex, w }) {
const serie = w.config.series[seriesIndex]["data"][dataPointIndex];
const x_value = serie["x"];
const y_value = serie["y"];
const host_name = serie["meta"]["label"];
const x_axis_title = w.config.xaxis.title.text;
const y_axis_title = w.config.yaxis[0].title.text;
return (`
<div class='apexcharts-theme-light apexcharts-active' id='test'>
<div class='apexcharts-tooltip-title' style='font-family: Helvetica, Arial, sans-serif; font-size: 12px;'>
${host_name}
</div>
<div class='apexcharts-tooltip-series-group apexcharts-active d-block'>
<div class='apexcharts-tooltip-text text-left'>
<b>${x_axis_title}</b>: ${x_value}
</div>
<div class='apexcharts-tooltip-text text-left'>
<b>${y_axis_title}</b>: ${y_value}
</div>
</div>
</div>
`)
}
static loadBSTooltips() {
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
})
}
static disposeBSTooltips() {
document.querySelectorAll('.tooltip').forEach(tooltip => {
const tooltipInstance = bootstrap.Tooltip.getInstance(tooltip);
if (tooltipInstance) {
tooltipInstance.dispose();
}
tooltip.remove();
});
}
static reloadBSTooltips() {
this.disposeBSTooltips();
this.loadBSTooltips();
}
}
$(function () {
// if there are inputs with 'pattern' data attribute
// then initialize them
NtopUtils.initDataPatterns();
});