diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Diagnostics/Api/TrafficController.php b/src/opnsense/mvc/app/controllers/OPNsense/Diagnostics/Api/TrafficController.php index 7fdd444b417..c084c3bc456 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Diagnostics/Api/TrafficController.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/Diagnostics/Api/TrafficController.php @@ -71,9 +71,11 @@ public function TopAction($interfaces) if (count($iflist) > 0) { $data = (new Backend())->configdpRun('interface show top', [implode(",", $iflist)]); $data = json_decode($data, true); - foreach ($data as $if => $content) { - if (isset($ifmap[$if])) { - $response[$ifmap[$if]] = $content; + if (is_array($data)) { + foreach ($data as $if => $content) { + if (isset($ifmap[$if])) { + $response[$ifmap[$if]] = $content; + } } } } diff --git a/src/opnsense/mvc/app/views/OPNsense/Diagnostics/traffic.volt b/src/opnsense/mvc/app/views/OPNsense/Diagnostics/traffic.volt index 6e32dfd6f67..56e93d23b80 100644 --- a/src/opnsense/mvc/app/views/OPNsense/Diagnostics/traffic.volt +++ b/src/opnsense/mvc/app/views/OPNsense/Diagnostics/traffic.volt @@ -227,6 +227,59 @@ POSSIBILITY OF SUCH DAMAGE. return new Chart(ctx, config); } + /** + * iftop (top talkers) update + */ + function updateTopTable(data) { + let target = $("#rxTopTable > tbody"); + let update_stamp = Math.trunc(Date.now() / 1000.0); + Object.keys(data).forEach(function(intf) { + let intf_label = $("#interfaces > option[value="+intf+"]").data('content'); + ['in', 'out'].forEach(function(dir) { + for (var i=0; i < data[intf][dir].length ; i++) { + let item = data[intf][dir][i]; + let tr = target.find("tr[data-address='"+item.address+"']"); + if (tr.length === 0) { + tr = $(""); + tr.attr("data-address", item.address); + tr.append($("").html(intf_label)); + tr.append($("").text(item.address)); + tr.append($("").text("0b")); + tr.append($("").text("0b")); + tr.append($("").text("0b")); + tr.append($("").text("0b")); + target.append(tr); + } + tr.attr('data-bps_'+dir, item.rate_bits); + tr.attr('data-last_seen', update_stamp); + if (parseInt(tr.data('bps_max_'+dir)) < item.rate_bits) { + tr.attr('data-bps_max_'+dir, item.rate_bits); + tr.find('td.bps_max_'+dir).text(item.rate); + } + tr.find('td.bps_'+dir).text(item.rate); + } + }); + }); + let ttl = 120; // keep visible for ttl seconds + target.find('tr').each(function(){ + if (parseInt($(this).data('last_seen')) < (update_stamp - ttl)) { + $(this).remove(); + } else if (parseInt($(this).data('last_seen')) != update_stamp) { + // reset measurements not in this set + $(this).attr('data-bps_in', 0); + $(this).attr('data-bps_out', 0); + $(this).find('td.bps_in').text("0b"); + $(this).find('td.bps_out').text("0b"); + } + }); + // sort by current top consumer + target.find('tr').sort(function(a, b) { + let a_total = +$(a).data('bps_in') + $(a).data('bps_out'); + let b_total = +$(b).data('bps_in') + $(b).data('bps_out'); + return b_total - a_total; + }).appendTo(target); + } + /** * startup, fetch initial interface stats and create graphs */ @@ -334,6 +387,7 @@ POSSIBILITY OF SUCH DAMAGE. if (status == 'success') { $( document ).trigger( "updateTrafficTopCharts", [ data ] ); top_traffic_poller(); + updateTopTable(data); } else { setTimeout(top_traffic_poller, 2000); } @@ -346,7 +400,6 @@ POSSIBILITY OF SUCH DAMAGE. window.localStorage.setItem("api.diagnostics.traffic.interface", $(this).val()); } }); - }); @@ -357,46 +410,67 @@ POSSIBILITY OF SUCH DAMAGE. } -
-
+ +
+
-
-
-
- -   -
-
-
-
- -
-
-
-
- -
-
+
+
-
-
-
+
+
+
-
-
-
- -
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
-
-
- -
+
+
+
+
+
+
+ + + + + + + + + + + + + +
{{ lang._('Address') }}{{ lang._('In (bps)') }}{{ lang._('Out (bps)') }}{{ lang._('In max(bps)') }}{{ lang._('Out max(bps)') }}
+
+
diff --git a/src/opnsense/scripts/interfaces/traffic_top.py b/src/opnsense/scripts/interfaces/traffic_top.py index 78364fd6428..2a23f1c23f4 100755 --- a/src/opnsense/scripts/interfaces/traffic_top.py +++ b/src/opnsense/scripts/interfaces/traffic_top.py @@ -61,6 +61,18 @@ def local_addresses(): result.append(ip) return result +def convert_bformat(value): + value = value.lower() + if value.endswith('kb'): + return decimal.Decimal(value[:-2]) * 1000 + elif value.endswith('mb'): + return decimal.Decimal(value[:-2]) * 1000000 + elif value.endswith('gb'): + return decimal.Decimal(value[:-2]) * 1000000000 + elif value.endswith('b') and value[:-1].isdigit(): + return decimal.Decimal(value[:-1]) + return 0 + if __name__ == '__main__': result = dict() parser = argparse.ArgumentParser() @@ -86,16 +98,14 @@ def local_addresses(): parts = line.split() if parts[0].find('.') == -1 and parts[0].find(':') == -1: parts.pop(0) - rate_bits = 0 - if parts[2].endswith('Kb'): - rate_bits = decimal.Decimal(parts[2][:-2]) * 1000 - elif parts[2].endswith('Mb'): - rate_bits = decimal.Decimal(parts[2][:-2]) * 1000000 - elif parts[2].endswith('Gb'): - rate_bits = decimal.Decimal(parts[2][:-2]) * 1000000000 - elif parts[2].endswith('b') and parts[2][:-1].isdigit(): - rate_bits = decimal.Decimal(parts[2][:-1]) - item = {'address': parts[0], 'rate': parts[2], 'rate_bits': rate_bits, 'tags': []} + item = { + 'address': parts[0], + 'rate': parts[2], + 'rate_bits': int(convert_bformat(parts[2])), + 'cumulative': parts[5], + 'cumulative_bytes': int(convert_bformat(parts[5])), + 'tags': [] + } # attach tags (type of address) try: ip = IPAddress(parts[0])