diff options
author | Jo-Philipp Wich <jo@mein.io> | 2019-11-02 22:55:59 +0100 |
---|---|---|
committer | Jo-Philipp Wich <jo@mein.io> | 2019-11-03 17:56:58 +0100 |
commit | cfb5af89e108960c33d12cbd6dd76e9aa4e19bee (patch) | |
tree | eff25a43f550318944e8c018b4129f36bc7da8bd | |
parent | a2f43983b63b2e65aba7dd7f7f2fa22306c12e21 (diff) |
luci-base, luci-mod-status: convert realtime stats to client side views
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
10 files changed, 1369 insertions, 1477 deletions
diff --git a/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json b/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json index a09c6b424..ea19f32d4 100644 --- a/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json +++ b/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json @@ -43,9 +43,10 @@ "ubus": { "file": [ "list", "read", "stat" ], "iwinfo": [ "assoclist", "freqlist", "txpowerlist", "countrylist" ], - "luci": [ "getDUIDHints", "getInitList", "getLocaltime", "getTimezones", "getLEDs", "getUSBDevices", "getSwconfigFeatures", "getSwconfigPortState", "getBlockDevices", "getMountPoints" ], + "luci": [ "getConntrackList", "getDUIDHints", "getInitList", "getLocaltime", "getRealtimeStats", "getTimezones", "getLEDs", "getUSBDevices", "getSwconfigFeatures", "getSwconfigPortState", "getBlockDevices", "getMountPoints" ], "luci-rpc": [ "getBoardJSON", "getDHCPLeases", "getDSLStatus", "getHostHints", "getNetworkDevices", "getWirelessDevices" ], "network.interface": [ "dump" ], + "network.rrdns": [ "lookup" ], "network": [ "get_proto_handlers" ], "system": [ "board", "info", "validate_firmware_image" ], "uci": [ "changes", "get" ] diff --git a/modules/luci-mod-status/htdocs/luci-static/resources/view/status/bandwidth.js b/modules/luci-mod-status/htdocs/luci-static/resources/view/status/bandwidth.js new file mode 100644 index 000000000..0565490b9 --- /dev/null +++ b/modules/luci-mod-status/htdocs/luci-static/resources/view/status/bandwidth.js @@ -0,0 +1,311 @@ +'use strict'; +'require rpc'; +'require network'; + +var callLuciRealtimeStats = rpc.declare({ + object: 'luci', + method: 'getRealtimeStats', + params: [ 'mode', 'device' ], + expect: { result: [] } +}); + +var graphPolls = [], + pollInterval = 3; + +Math.log2 = Math.log2 || function(x) { return Math.log(x) * Math.LOG2E; }; + +function rate(n, br) { + n = (n || 0).toFixed(2); + return [ '%1024.2mbit/s'.format(n * 8), br ? E('br') : ' ', '(%1024.2mB/s)'.format(n) ] +} + +return L.view.extend({ + load: function() { + return Promise.all([ + this.loadSVG(L.resource('bandwidth.svg')), + network.getDevices() + ]); + }, + + updateGraph: function(ifname, svg, lines, cb) { + var G = svg.firstElementChild; + + var view = document.querySelector('#view'); + + var width = view.offsetWidth - 2; + var height = 300 - 2; + var step = 5; + + var data_wanted = Math.floor(width / step); + + var data_values = [], + line_elements = []; + + for (var i = 0; i < lines.length; i++) + if (lines[i] != null) + data_values.push([]); + + var info = { + line_current: [], + line_average: [], + line_peak: [] + }; + + /* prefill datasets */ + for (var i = 0; i < data_values.length; i++) + for (var j = 0; j < data_wanted; j++) + data_values[i][j] = 0; + + /* plot horizontal time interval lines */ + for (var i = width % (step * 60); i < width; i += step * 60) { + var line = document.createElementNS('http://www.w3.org/2000/svg', 'line'); + line.setAttribute('x1', i); + line.setAttribute('y1', 0); + line.setAttribute('x2', i); + line.setAttribute('y2', '100%'); + line.setAttribute('style', 'stroke:black;stroke-width:0.1'); + + var text = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + text.setAttribute('x', i + 5); + text.setAttribute('y', 15); + text.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000'); + text.appendChild(document.createTextNode(Math.round((width - i) / step / 60) + 'm')); + + G.appendChild(line); + G.appendChild(text); + } + + info.interval = pollInterval; + info.timeframe = data_wanted / 60; + + graphPolls.push({ + ifname: ifname, + svg: svg, + lines: lines, + cb: cb, + info: info, + width: width, + height: height, + step: step, + values: data_values, + timestamp: 0, + fill: 1 + }); + }, + + pollData: function() { + L.Poll.add(L.bind(function() { + var tasks = []; + + for (var i = 0; i < graphPolls.length; i++) { + var ctx = graphPolls[i]; + tasks.push(L.resolveDefault(callLuciRealtimeStats('interface', ctx.ifname), [])); + } + + return Promise.all(tasks).then(L.bind(function(datasets) { + for (var gi = 0; gi < graphPolls.length; gi++) { + var ctx = graphPolls[gi], + data = datasets[gi], + values = ctx.values, + lines = ctx.lines, + info = ctx.info; + + var data_scale = 0; + var data_wanted = Math.floor(ctx.width / ctx.step); + var last_timestamp = NaN; + + for (var i = 0, di = 0; di < lines.length; di++) { + if (lines[di] == null) + continue; + + var multiply = (lines[di].multiply != null) ? lines[di].multiply : 1, + offset = (lines[di].offset != null) ? lines[di].offset : 0; + + for (var j = ctx.timestamp ? 0 : 1; j < data.length; j++) { + /* skip overlapping entries */ + if (data[j][0] <= ctx.timestamp) + continue; + + if (i == 0) { + ctx.fill++; + last_timestamp = data[j][0]; + } + + if (lines[di].counter) { + /* normalize difference against time interval */ + if (j > 0) { + var time_delta = data[j][0] - data[j - 1][0]; + if (time_delta) { + info.line_current[i] = (data[j][di + 1] * multiply - data[j - 1][di + 1] * multiply) / time_delta; + info.line_current[i] -= Math.min(info.line_current[i], offset); + values[i].push(info.line_current[i]); + } + } + } + else { + info.line_current[i] = data[j][di + 1] * multiply; + info.line_current[i] -= Math.min(info.line_current[i], offset); + values[i].push(info.line_current[i]); + } + } + + i++; + } + + /* cut off outdated entries */ + ctx.fill = Math.min(ctx.fill, data_wanted); + + for (var i = 0; i < values.length; i++) { + var len = values[i].length; + values[i] = values[i].slice(len - data_wanted, len); + + /* find peaks, averages */ + info.line_peak[i] = NaN; + info.line_average[i] = 0; + + for (var j = 0; j < values[i].length; j++) { + info.line_peak[i] = isNaN(info.line_peak[i]) ? values[i][j] : Math.max(info.line_peak[i], values[i][j]); + info.line_average[i] += values[i][j]; + } + + info.line_average[i] = info.line_average[i] / ctx.fill; + } + + info.peak = Math.max.apply(Math, info.line_peak); + + /* remember current timestamp, calculate horizontal scale */ + if (!isNaN(last_timestamp)) + ctx.timestamp = last_timestamp; + + var size = Math.floor(Math.log2(info.peak)), + div = Math.pow(2, size - (size % 10)), + mult = info.peak / div, + mult = (mult < 5) ? 2 : ((mult < 50) ? 10 : ((mult < 500) ? 100 : 1000)); + + info.peak = info.peak + (mult * div) - (info.peak % (mult * div)); + + data_scale = ctx.height / info.peak; + + /* plot data */ + for (var i = 0, di = 0; di < lines.length; di++) { + if (lines[di] == null) + continue; + + var el = ctx.svg.firstElementChild.getElementById(lines[di].line), + pt = '0,' + ctx.height, + y = 0; + + if (!el) + continue; + + for (var j = 0; j < values[i].length; j++) { + var x = j * ctx.step; + + y = ctx.height - Math.floor(values[i][j] * data_scale); + //y -= Math.floor(y % (1 / data_scale)); + + pt += ' ' + x + ',' + y; + } + + pt += ' ' + ctx.width + ',' + y + ' ' + ctx.width + ',' + ctx.height; + + el.setAttribute('points', pt); + + i++; + } + + info.label_25 = 0.25 * info.peak; + info.label_50 = 0.50 * info.peak; + info.label_75 = 0.75 * info.peak; + + if (typeof(ctx.cb) == 'function') + ctx.cb(ctx.svg, info); + } + }, this)); + }, this), pollInterval); + }, + + loadSVG: function(src) { + return L.Request.get(src).then(function(response) { + if (!response.ok) + throw new Error(response.statusText); + + return E('div', { + 'style': 'width:100%;height:300px;border:1px solid #000;background:#fff' + }, response.text()); + }); + }, + + render: function(data) { + var svg = data[0], + devs = data[1]; + + var v = E('div', {}, E('div')); + + for (var i = 0; i < devs.length; i++) { + var ifname = devs[i].getName(); + + if (!ifname) + continue; + + var csvg = svg.cloneNode(true); + + v.firstElementChild.appendChild(E('div', { 'data-tab': ifname, 'data-tab-title': ifname }, [ + csvg, + E('div', { 'class': 'right' }, E('small', { 'id': 'scale' }, '-')), + E('br'), + + E('div', { 'class': 'table', 'style': 'width:100%;table-layout:fixed' }, [ + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid blue' }, [ _('Inbound:') ])), + E('div', { 'class': 'td', 'id': 'rx_bw_cur' }, rate(0, true)), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])), + E('div', { 'class': 'td', 'id': 'rx_bw_avg' }, rate(0, true)), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])), + E('div', { 'class': 'td', 'id': 'rx_bw_peak' }, rate(0, true)) + ]), + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid green' }, [ _('Outbound:') ])), + E('div', { 'class': 'td', 'id': 'tx_bw_cur' }, rate(0, true)), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])), + E('div', { 'class': 'td', 'id': 'tx_bw_avg' }, rate(0, true)), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])), + E('div', { 'class': 'td', 'id': 'tx_bw_peak' }, rate(0, true)) + ]) + ]) + ])); + + this.updateGraph(ifname, csvg, [ { line: 'rx', counter: true }, null, { line: 'tx', counter: true } ], function(svg, info) { + var G = svg.firstElementChild, tab = svg.parentNode; + + G.getElementById('label_25').firstChild.data = rate(info.label_25).join(''); + G.getElementById('label_50').firstChild.data = rate(info.label_50).join(''); + G.getElementById('label_75').firstChild.data = rate(info.label_75).join(''); + + tab.querySelector('#scale').firstChild.data = _('(%d minute window, %d second interval)').format(info.timeframe, info.interval); + + L.dom.content(tab.querySelector('#rx_bw_cur'), rate(info.line_current[0], true)); + L.dom.content(tab.querySelector('#rx_bw_avg'), rate(info.line_average[0], true)); + L.dom.content(tab.querySelector('#rx_bw_peak'), rate(info.line_peak[0], true)); + + L.dom.content(tab.querySelector('#tx_bw_cur'), rate(info.line_current[1], true)); + L.dom.content(tab.querySelector('#tx_bw_avg'), rate(info.line_average[1], true)); + L.dom.content(tab.querySelector('#tx_bw_peak'), rate(info.line_peak[1], true)); + }); + } + + L.ui.tabs.initTabGroup(v.firstElementChild.childNodes); + + this.pollData(); + + return v; + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/modules/luci-mod-status/htdocs/luci-static/resources/view/status/connections.js b/modules/luci-mod-status/htdocs/luci-static/resources/view/status/connections.js new file mode 100644 index 000000000..96dee1db7 --- /dev/null +++ b/modules/luci-mod-status/htdocs/luci-static/resources/view/status/connections.js @@ -0,0 +1,423 @@ +'use strict'; +'require rpc'; + +var callLuciRealtimeStats = rpc.declare({ + object: 'luci', + method: 'getRealtimeStats', + params: [ 'mode', 'device' ], + expect: { result: [] } +}); + +var callLuciConntrackList = rpc.declare({ + object: 'luci', + method: 'getConntrackList', + expect: { result: [] } +}); + +var callNetworkRrdnsLookup = rpc.declare({ + object: 'network.rrdns', + method: 'lookup', + params: [ 'addrs', 'timeout', 'limit' ], + expect: { '': {} } +}); + +var graphPolls = [], + pollInterval = 3, + dns_cache = {}, + enableLookups = false; + +var recheck_lookup_queue = {}; + +Math.log2 = Math.log2 || function(x) { return Math.log(x) * Math.LOG2E; }; + +return L.view.extend({ + load: function() { + return Promise.all([ + this.loadSVG(L.resource('connections.svg')) + ]); + }, + + updateGraph: function(svg, lines, cb) { + var G = svg.firstElementChild; + + var view = document.querySelector('#view'); + + var width = view.offsetWidth - 2; + var height = 300 - 2; + var step = 5; + + var data_wanted = Math.floor(width / step); + + var data_values = [], + line_elements = []; + + for (var i = 0; i < lines.length; i++) + if (lines[i] != null) + data_values.push([]); + + var info = { + line_current: [], + line_average: [], + line_peak: [] + }; + + /* prefill datasets */ + for (var i = 0; i < data_values.length; i++) + for (var j = 0; j < data_wanted; j++) + data_values[i][j] = 0; + + /* plot horizontal time interval lines */ + for (var i = width % (step * 60); i < width; i += step * 60) { + var line = document.createElementNS('http://www.w3.org/2000/svg', 'line'); + line.setAttribute('x1', i); + line.setAttribute('y1', 0); + line.setAttribute('x2', i); + line.setAttribute('y2', '100%'); + line.setAttribute('style', 'stroke:black;stroke-width:0.1'); + + var text = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + text.setAttribute('x', i + 5); + text.setAttribute('y', 15); + text.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000'); + text.appendChild(document.createTextNode(Math.round((width - i) / step / 60) + 'm')); + + G.appendChild(line); + G.appendChild(text); + } + + info.interval = pollInterval; + info.timeframe = data_wanted / 60; + + graphPolls.push({ + svg: svg, + lines: lines, + cb: cb, + info: info, + width: width, + height: height, + step: step, + values: data_values, + timestamp: 0, + fill: 1 + }); + }, + + updateConntrack: function(conn) { + var lookup_queue = [ ]; + var rows = []; + + conn.sort(function(a, b) { + return b.bytes - a.bytes; + }); + + for (var i = 0; i < conn.length; i++) + { + var c = conn[i]; + + if ((c.src == '127.0.0.1' && c.dst == '127.0.0.1') || + (c.src == '::1' && c.dst == '::1')) + continue; + + if (!dns_cache[c.src] && lookup_queue.indexOf(c.src) == -1) + lookup_queue.push(c.src); + + if (!dns_cache[c.dst] && lookup_queue.indexOf(c.dst) == -1) + lookup_queue.push(c.dst); + + var src = dns_cache[c.src] || (c.layer3 == 'ipv6' ? '[' + c.src + ']' : c.src); + var dst = dns_cache[c.dst] || (c.layer3 == 'ipv6' ? '[' + c.dst + ']' : c.dst); + + rows.push([ + c.layer3.toUpperCase(), + c.layer4.toUpperCase(), + c.hasOwnProperty('sport') ? (src + ':' + c.sport) : src, + c.hasOwnProperty('dport') ? (dst + ':' + c.dport) : dst, + '%1024.2mB (%d %s)'.format(c.bytes, c.packets, _('Pkts.')) + ]); + } + + cbi_update_table('#connections', rows, E('em', _('No information available'))); + + if (enableLookups && lookup_queue.length > 0) { + var reduced_lookup_queue = lookup_queue; + + if (lookup_queue.length > 100) + reduced_lookup_queue = lookup_queue.slice(0, 100); + + callNetworkRrdnsLookup(reduced_lookup_queue, 5000, 1000).then(function(replies) { + for (var index in reduced_lookup_queue) { + var address = reduced_lookup_queue[index]; + + if (!address) + continue; + + if (replies[address]) { + dns_cache[address] = replies[address]; + lookup_queue.splice(reduced_lookup_queue.indexOf(address), 1); + continue; + } + + if (recheck_lookup_queue[address] > 2) { + dns_cache[address] = (address.match(/:/)) ? '[' + address + ']' : address; + lookup_queue.splice(index, 1); + } + else { + recheck_lookup_queue[address] = (recheck_lookup_queue[address] || 0) + 1; + } + } + + var btn = document.querySelector('.btn.toggle-lookups'); + if (btn) { + btn.firstChild.data = enableLookups ? _('Disable DNS lookups') : _('Enable DNS lookups'); + btn.classList.remove('spinning'); + btn.disabled = false; + } + }); + } + }, + + pollData: function() { + L.Poll.add(L.bind(function() { + var tasks = [ + L.resolveDefault(callLuciConntrackList(), []) + ]; + + for (var i = 0; i < graphPolls.length; i++) { + var ctx = graphPolls[i]; + tasks.push(L.resolveDefault(callLuciRealtimeStats('conntrack'), [])); + } + + return Promise.all(tasks).then(L.bind(function(datasets) { + this.updateConntrack(datasets[0]); + + for (var gi = 0; gi < graphPolls.length; gi++) { + var ctx = graphPolls[gi], + data = datasets[gi + 1], + values = ctx.values, + lines = ctx.lines, + info = ctx.info; + + var data_scale = 0; + var data_wanted = Math.floor(ctx.width / ctx.step); + var last_timestamp = NaN; + + for (var i = 0, di = 0; di < lines.length; di++) { + if (lines[di] == null) + continue; + + var multiply = (lines[di].multiply != null) ? lines[di].multiply : 1, + offset = (lines[di].offset != null) ? lines[di].offset : 0; + + for (var j = ctx.timestamp ? 0 : 1; j < data.length; j++) { + /* skip overlapping entries */ + if (data[j][0] <= ctx.timestamp) + continue; + + if (i == 0) { + ctx.fill++; + last_timestamp = data[j][0]; + } + + info.line_current[i] = data[j][di + 1] * multiply; + info.line_current[i] -= Math.min(info.line_current[i], offset); + values[i].push(info.line_current[i]); + } + + i++; + } + + /* cut off outdated entries */ + ctx.fill = Math.min(ctx.fill, data_wanted); + + for (var i = 0; i < values.length; i++) { + var len = values[i].length; + values[i] = values[i].slice(len - data_wanted, len); + + /* find peaks, averages */ + info.line_peak[i] = NaN; + info.line_average[i] = 0; + + for (var j = 0; j < values[i].length; j++) { + info.line_peak[i] = isNaN(info.line_peak[i]) ? values[i][j] : Math.max(info.line_peak[i], values[i][j]); + info.line_average[i] += values[i][j]; + } + + info.line_average[i] = info.line_average[i] / ctx.fill; + } + + info.peak = Math.max.apply(Math, info.line_peak); + + /* remember current timestamp, calculate horizontal scale */ + if (!isNaN(last_timestamp)) + ctx.timestamp = last_timestamp; + + var size = Math.floor(Math.log2(info.peak)), + div = Math.pow(2, size - (size % 10)), + mult = info.peak / div, + mult = (mult < 5) ? 2 : ((mult < 50) ? 10 : ((mult < 500) ? 100 : 1000)); + + info.peak = info.peak + (mult * div) - (info.peak % (mult * div)); + + data_scale = ctx.height / info.peak; + + /* plot data */ + for (var i = 0, di = 0; di < lines.length; di++) { + if (lines[di] == null) + continue; + + var el = ctx.svg.firstElementChild.getElementById(lines[di].line), + pt = '0,' + ctx.height, + y = 0; + + if (!el) + continue; + + for (var j = 0; j < values[i].length; j++) { + var x = j * ctx.step; + + y = ctx.height - Math.floor(values[i][j] * data_scale); + //y -= Math.floor(y % (1 / data_scale)); + + pt += ' ' + x + ',' + y; + } + + pt += ' ' + ctx.width + ',' + y + ' ' + ctx.width + ',' + ctx.height; + + el.setAttribute('points', pt); + + i++; + } + + info.label_25 = 0.25 * info.peak; + info.label_50 = 0.50 * info.peak; + info.label_75 = 0.75 * info.peak; + + if (typeof(ctx.cb) == 'function') + ctx.cb(ctx.svg, info); + } + }, this)); + }, this), pollInterval); + }, + + loadSVG: function(src) { + return L.Request.get(src).then(function(response) { + if (!response.ok) + throw new Error(response.statusText); + + return E('div', { + 'style': 'width:100%;height:300px;border:1px solid #000;background:#fff' + }, response.text()); + }); + }, + + render: function(data) { + var svg = data[0]; + + var v = E([], [ + svg, + E('div', { 'class': 'right' }, E('small', { 'id': 'scale' }, '-')), + E('br'), + + E('div', { 'class': 'table', 'style': 'width:100%;table-layout:fixed' }, [ + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid blue' }, [ _('UDP:') ])), + E('div', { 'class': 'td', 'id': 'lb_udp_cur' }, [ '0' ]), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])), + E('div', { 'class': 'td', 'id': 'lb_udp_avg' }, [ '0' ]), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])), + E('div', { 'class': 'td', 'id': 'lb_udp_peak' }, [ '0' ]) + ]), + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid green' }, [ _('TCP:') ])), + E('div', { 'class': 'td', 'id': 'lb_tcp_cur' }, [ '0' ]), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])), + E('div', { 'class': 'td', 'id': 'lb_tcp_avg' }, [ '0' ]), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])), + E('div', { 'class': 'td', 'id': 'lb_tcp_peak' }, [ '0' ]) + ]), + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid red' }, [ _('Other:') ])), + E('div', { 'class': 'td', 'id': 'lb_otr_cur' }, [ '0' ]), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])), + E('div', { 'class': 'td', 'id': 'lb_otr_avg' }, [ '0' ]), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])), + E('div', { 'class': 'td', 'id': 'lb_otr_peak' }, [ '0' ]) + ]) + ]), + + E('div', { 'class': 'right' }, [ + E('button', { + 'class': 'btn toggle-lookups', + 'click': function(ev) { + if (!enableLookups) { + ev.currentTarget.classList.add('spinning'); + ev.currentTarget.disabled = true; + enableLookups = true; + } + else { + ev.currentTarget.firstChild.data = _('Enable DNS lookups'); + enableLookups = false; + } + + this.blur(); + } + }, [ enableLookups ? _('Disable DNS lookups') : _('Enable DNS lookups') ]) + ]), + + E('br'), + + E('div', { 'class': 'cbi-section-node' }, [ + E('div', { 'class': 'table', 'id': 'connections' }, [ + E('div', { 'class': 'tr table-titles' }, [ + E('div', { 'class': 'th col-2 hide-xs' }, [ _('Network') ]), + E('div', { 'class': 'th col-2' }, [ _('Protocol') ]), + E('div', { 'class': 'th col-7' }, [ _('Source') ]), + E('div', { 'class': 'th col-7' }, [ _('Destination') ]), + E('div', { 'class': 'th col-4' }, [ _('Transfer') ]) + ]), + E('div', { 'class': 'tr placeholder' }, [ + E('div', { 'class': 'td' }, [ + E('em', {}, [ _('Collecting data...') ]) + ]) + ]) + ]) + ]) + ]); + + this.updateGraph(svg, [ { line: 'udp' }, { line: 'tcp' }, { line: 'other' } ], function(svg, info) { + var G = svg.firstElementChild, tab = svg.parentNode; + + G.getElementById('label_25').firstChild.data = '%d'.format(info.label_25); + G.getElementById('label_50').firstChild.data = '%d'.format(info.label_50); + G.getElementById('label_75').firstChild.data = '%d'.format(info.label_75); + + tab.querySelector('#scale').firstChild.data = _('(%d minute window, %d second interval)').format(info.timeframe, info.interval); + + tab.querySelector('#lb_udp_cur').firstChild.data = '%d'.format(info.line_current[0]); + tab.querySelector('#lb_udp_avg').firstChild.data = '%d'.format(info.line_average[0]); + tab.querySelector('#lb_udp_peak').firstChild.data = '%d'.format(info.line_peak[0]); + + tab.querySelector('#lb_tcp_cur').firstChild.data = '%d'.format(info.line_current[1]); + tab.querySelector('#lb_tcp_avg').firstChild.data = '%d'.format(info.line_average[1]); + tab.querySelector('#lb_tcp_peak').firstChild.data = '%d'.format(info.line_peak[1]); + + tab.querySelector('#lb_otr_cur').firstChild.data = '%d'.format(info.line_current[2]); + tab.querySelector('#lb_otr_avg').firstChild.data = '%d'.format(info.line_average[2]); + tab.querySelector('#lb_otr_peak').firstChild.data = '%d'.format(info.line_peak[2]); + }); + + this.pollData(); + + return v; + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/modules/luci-mod-status/htdocs/luci-static/resources/view/status/load.js b/modules/luci-mod-status/htdocs/luci-static/resources/view/status/load.js new file mode 100644 index 000000000..a1ed43478 --- /dev/null +++ b/modules/luci-mod-status/htdocs/luci-static/resources/view/status/load.js @@ -0,0 +1,290 @@ +'use strict'; +'require rpc'; + +var callLuciRealtimeStats = rpc.declare({ + object: 'luci', + method: 'getRealtimeStats', + params: [ 'mode', 'device' ], + expect: { result: [] } +}); + +var graphPolls = [], + pollInterval = 3; + +Math.log2 = Math.log2 || function(x) { return Math.log(x) * Math.LOG2E; }; + +return L.view.extend({ + load: function() { + return Promise.all([ + this.loadSVG(L.resource('load.svg')) + ]); + }, + + updateGraph: function(svg, lines, cb) { + var G = svg.firstElementChild; + + var view = document.querySelector('#view'); + + var width = view.offsetWidth - 2; + var height = 300 - 2; + var step = 5; + + var data_wanted = Math.floor(width / step); + + var data_values = [], + line_elements = []; + + for (var i = 0; i < lines.length; i++) + if (lines[i] != null) + data_values.push([]); + + var info = { + line_current: [], + line_average: [], + line_peak: [] + }; + + /* prefill datasets */ + for (var i = 0; i < data_values.length; i++) + for (var j = 0; j < data_wanted; j++) + data_values[i][j] = 0; + + /* plot horizontal time interval lines */ + for (var i = width % (step * 60); i < width; i += step * 60) { + var line = document.createElementNS('http://www.w3.org/2000/svg', 'line'); + line.setAttribute('x1', i); + line.setAttribute('y1', 0); + line.setAttribute('x2', i); + line.setAttribute('y2', '100%'); + line.setAttribute('style', 'stroke:black;stroke-width:0.1'); + + var text = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + text.setAttribute('x', i + 5); + text.setAttribute('y', 15); + text.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000'); + text.appendChild(document.createTextNode(Math.round((width - i) / step / 60) + 'm')); + + G.appendChild(line); + G.appendChild(text); + } + + info.interval = pollInterval; + info.timeframe = data_wanted / 60; + + graphPolls.push({ + svg: svg, + lines: lines, + cb: cb, + info: info, + width: width, + height: height, + step: step, + values: data_values, + timestamp: 0, + fill: 1 + }); + }, + + pollData: function() { + L.Poll.add(L.bind(function() { + var tasks = []; + + for (var i = 0; i < graphPolls.length; i++) { + var ctx = graphPolls[i]; + tasks.push(L.resolveDefault(callLuciRealtimeStats('load'), [])); + } + + return Promise.all(tasks).then(L.bind(function(datasets) { + for (var gi = 0; gi < graphPolls.length; gi++) { + var ctx = graphPolls[gi], + data = datasets[gi], + values = ctx.values, + lines = ctx.lines, + info = ctx.info; + + var data_scale = 0; + var data_wanted = Math.floor(ctx.width / ctx.step); + var last_timestamp = NaN; + + for (var i = 0, di = 0; di < lines.length; di++) { + if (lines[di] == null) + continue; + + var multiply = (lines[di].multiply != null) ? lines[di].multiply : 1, + offset = (lines[di].offset != null) ? lines[di].offset : 0; + + for (var j = ctx.timestamp ? 0 : 1; j < data.length; j++) { + /* skip overlapping entries */ + if (data[j][0] <= ctx.timestamp) + continue; + + if (i == 0) { + ctx.fill++; + last_timestamp = data[j][0]; + } + + info.line_current[i] = data[j][di + 1] * multiply; + info.line_current[i] -= Math.min(info.line_current[i], offset); + values[i].push(info.line_current[i]); + } + + i++; + } + + /* cut off outdated entries */ + ctx.fill = Math.min(ctx.fill, data_wanted); + + for (var i = 0; i < values.length; i++) { + var len = values[i].length; + values[i] = values[i].slice(len - data_wanted, len); + + /* find peaks, averages */ + info.line_peak[i] = NaN; + info.line_average[i] = 0; + + for (var j = 0; j < values[i].length; j++) { + info.line_peak[i] = isNaN(info.line_peak[i]) ? values[i][j] : Math.max(info.line_peak[i], values[i][j]); + info.line_average[i] += values[i][j]; + } + + info.line_average[i] = info.line_average[i] / ctx.fill; + } + + info.peak = Math.max.apply(Math, info.line_peak); + + /* remember current timestamp, calculate horizontal scale */ + if (!isNaN(last_timestamp)) + ctx.timestamp = last_timestamp; + + var size = Math.floor(Math.log2(info.peak)), + div = Math.pow(2, size - (size % 10)), + mult = info.peak / div, + mult = (mult < 5) ? 2 : ((mult < 50) ? 10 : ((mult < 500) ? 100 : 1000)); + + info.peak = info.peak + (mult * div) - (info.peak % (mult * div)); + + data_scale = ctx.height / info.peak; + + /* plot data */ + for (var i = 0, di = 0; di < lines.length; di++) { + if (lines[di] == null) + continue; + + var el = ctx.svg.firstElementChild.getElementById(lines[di].line), + pt = '0,' + ctx.height, + y = 0; + + if (!el) + continue; + + for (var j = 0; j < values[i].length; j++) { + var x = j * ctx.step; + + y = ctx.height - Math.floor(values[i][j] * data_scale); + //y -= Math.floor(y % (1 / data_scale)); + + pt += ' ' + x + ',' + y; + } + + pt += ' ' + ctx.width + ',' + y + ' ' + ctx.width + ',' + ctx.height; + + el.setAttribute('points', pt); + + i++; + } + + info.label_25 = 0.25 * info.peak; + info.label_50 = 0.50 * info.peak; + info.label_75 = 0.75 * info.peak; + + if (typeof(ctx.cb) == 'function') + ctx.cb(ctx.svg, info); + } + }, this)); + }, this), pollInterval); + }, + + loadSVG: function(src) { + return L.Request.get(src).then(function(response) { + if (!response.ok) + throw new Error(response.statusText); + + return E('div', { + 'style': 'width:100%;height:300px;border:1px solid #000;background:#fff' + }, response.text()); + }); + }, + + render: function(data) { + var svg = data[0]; + + var v = E([], [ + svg, + E('div', { 'class': 'right' }, E('small', { 'id': 'scale' }, '-')), + E('br'), + + E('div', { 'class': 'table', 'style': 'width:100%;table-layout:fixed' }, [ + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid #f00' }, [ _('1 Minute Load:') ])), + E('div', { 'class': 'td', 'id': 'lb_load01_cur' }, [ '0.00' ]), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])), + E('div', { 'class': 'td', 'id': 'lb_load01_avg' }, [ '0.00' ]), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])), + E('div', { 'class': 'td', 'id': 'lb_load01_peak' }, [ '0.00' ]) + ]), + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid #f60' }, [ _('5 Minute Load:') ])), + E('div', { 'class': 'td', 'id': 'lb_load05_cur' }, [ '0.00' ]), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])), + E('div', { 'class': 'td', 'id': 'lb_load05_avg' }, [ '0.00' ]), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])), + E('div', { 'class': 'td', 'id': 'lb_load05_peak' }, [ '0.00' ]) + ]), + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid #fa0' }, [ _('15 Minute Load:') ])), + E('div', { 'class': 'td', 'id': 'lb_load15_cur' }, [ '0.00' ]), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])), + E('div', { 'class': 'td', 'id': 'lb_load15_avg' }, [ '0.00' ]), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])), + E('div', { 'class': 'td', 'id': 'lb_load15_peak' }, [ '0.00' ]) + ]) + ]) + ]); + + this.updateGraph(svg, [ { line: 'load01' }, { line: 'load05' }, { line: 'load15' } ], function(svg, info) { + var G = svg.firstElementChild, tab = svg.parentNode; + + G.getElementById('label_25').firstChild.data = '%.2f'.format(info.label_25 / 100); + G.getElementById('label_50').firstChild.data = '%.2f'.format(info.label_50 / 100); + G.getElementById('label_75').firstChild.data = '%.2f'.format(info.label_75 / 100); + + tab.querySelector('#scale').firstChild.data = _('(%d minute window, %d second interval)').format(info.timeframe, info.interval); + + tab.querySelector('#lb_load01_cur').firstChild.data = '%.2f'.format(info.line_current[0] / 100); + tab.querySelector('#lb_load01_avg').firstChild.data = '%.2f'.format(info.line_average[0] / 100); + tab.querySelector('#lb_load01_peak').firstChild.data = '%.2f'.format(info.line_peak[0] / 100); + + tab.querySelector('#lb_load05_cur').firstChild.data = '%.2f'.format(info.line_current[1] / 100); + tab.querySelector('#lb_load05_avg').firstChild.data = '%.2f'.format(info.line_average[1] / 100); + tab.querySelector('#lb_load05_peak').firstChild.data = '%.2f'.format(info.line_peak[1] / 100); + + tab.querySelector('#lb_load15_cur').firstChild.data = '%.2f'.format(info.line_current[2] / 100); + tab.querySelector('#lb_load15_avg').firstChild.data = '%.2f'.format(info.line_average[2] / 100); + tab.querySelector('#lb_load15_peak').firstChild.data = '%.2f'.format(info.line_peak[2] / 100); + }); + + this.pollData(); + + return v; + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/modules/luci-mod-status/htdocs/luci-static/resources/view/status/wireless.js b/modules/luci-mod-status/htdocs/luci-static/resources/view/status/wireless.js new file mode 100644 index 000000000..45dd54a88 --- /dev/null +++ b/modules/luci-mod-status/htdocs/luci-static/resources/view/status/wireless.js @@ -0,0 +1,339 @@ +'use strict'; +'require rpc'; +'require network'; + +var callLuciRealtimeStats = rpc.declare({ + object: 'luci', + method: 'getRealtimeStats', + params: [ 'mode', 'device' ], + expect: { result: [] } +}); + +var graphPolls = [], + pollInterval = 3; + +Math.log2 = Math.log2 || function(x) { return Math.log(x) * Math.LOG2E; }; + +return L.view.extend({ + load: function() { + return Promise.all([ + this.loadSVG(L.resource('wireless.svg')), + this.loadSVG(L.resource('wifirate.svg')), + network.getWifiDevices().then(function(radios) { + var tasks = [], all_networks = []; + + for (var i = 0; i < radios.length; i++) + tasks.push(radios[i].getWifiNetworks().then(function(networks) { + all_networks.push.apply(all_networks, networks); + })); + + return Promise.all(tasks).then(function() { + return all_networks; + }); + }) + ]); + }, + + updateGraph: function(ifname, svg, lines, cb) { + var G = svg.firstElementChild; + + var view = document.querySelector('#view'); + + var width = view.offsetWidth - 2; + var height = 300 - 2; + var step = 5; + + var data_wanted = Math.floor(width / step); + + var data_values = [], + line_elements = []; + + for (var i = 0; i < lines.length; i++) + if (lines[i] != null) + data_values.push([]); + + var info = { + line_current: [], + line_average: [], + line_peak: [] + }; + + /* prefill datasets */ + for (var i = 0; i < data_values.length; i++) + for (var j = 0; j < data_wanted; j++) + data_values[i][j] = 0; + + /* plot horizontal time interval lines */ + for (var i = width % (step * 60); i < width; i += step * 60) { + var line = document.createElementNS('http://www.w3.org/2000/svg', 'line'); + line.setAttribute('x1', i); + line.setAttribute('y1', 0); + line.setAttribute('x2', i); + line.setAttribute('y2', '100%'); + line.setAttribute('style', 'stroke:black;stroke-width:0.1'); + + var text = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + text.setAttribute('x', i + 5); + text.setAttribute('y', 15); + text.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000'); + text.appendChild(document.createTextNode(Math.round((width - i) / step / 60) + 'm')); + + G.appendChild(line); + G.appendChild(text); + } + + info.interval = pollInterval; + info.timeframe = data_wanted / 60; + + graphPolls.push({ + ifname: ifname, + svg: svg, + lines: lines, + cb: cb, + info: info, + width: width, + height: height, + step: step, + values: data_values, + timestamp: 0, + fill: 1 + }); + }, + + pollData: function() { + L.Poll.add(L.bind(function() { + var tasks = []; + + for (var i = 0; i < graphPolls.length; i++) { + var ctx = graphPolls[i]; + tasks.push(L.resolveDefault(callLuciRealtimeStats('wireless', ctx.ifname), [])); + } + + return Promise.all(tasks).then(L.bind(function(datasets) { + for (var gi = 0; gi < graphPolls.length; gi++) { + var ctx = graphPolls[gi], + data = datasets[gi], + values = ctx.values, + lines = ctx.lines, + info = ctx.info; + + var data_scale = 0; + var data_wanted = Math.floor(ctx.width / ctx.step); + var last_timestamp = NaN; + + for (var i = 0, di = 0; di < lines.length; di++) { + if (lines[di] == null) + continue; + + var multiply = (lines[di].multiply != null) ? lines[di].multiply : 1, + offset = (lines[di].offset != null) ? lines[di].offset : 0; + + for (var j = ctx.timestamp ? 0 : 1; j < data.length; j++) { + /* skip overlapping entries */ + if (data[j][0] <= ctx.timestamp) + continue; + + if (i == 0) { + ctx.fill++; + last_timestamp = data[j][0]; + } + + info.line_current[i] = data[j][di + 1] * multiply; + info.line_current[i] -= Math.min(info.line_current[i], offset); + values[i].push(info.line_current[i]); + } + + i++; + } + + /* cut off outdated entries */ + ctx.fill = Math.min(ctx.fill, data_wanted); + + for (var i = 0; i < values.length; i++) { + var len = values[i].length; + values[i] = values[i].slice(len - data_wanted, len); + + /* find peaks, averages */ + info.line_peak[i] = NaN; + info.line_average[i] = 0; + + for (var j = 0; j < values[i].length; j++) { + info.line_peak[i] = isNaN(info.line_peak[i]) ? values[i][j] : Math.max(info.line_peak[i], values[i][j]); + info.line_average[i] += values[i][j]; + } + + info.line_average[i] = info.line_average[i] / ctx.fill; + } + + info.peak = Math.max.apply(Math, info.line_peak); + + /* remember current timestamp, calculate horizontal scale */ + if (!isNaN(last_timestamp)) + ctx.timestamp = last_timestamp; + + var size = Math.floor(Math.log2(info.peak)), + div = Math.pow(2, size - (size % 10)), + mult = info.peak / div, + mult = (mult < 5) ? 2 : ((mult < 50) ? 10 : ((mult < 500) ? 100 : 1000)); + + info.peak = info.peak + (mult * div) - (info.peak % (mult * div)); + + data_scale = ctx.height / info.peak; + + /* plot data */ + for (var i = 0, di = 0; di < lines.length; di++) { + if (lines[di] == null) + continue; + + var el = ctx.svg.firstElementChild.getElementById(lines[di].line), + pt = '0,' + ctx.height, + y = 0; + + if (!el) + continue; + + for (var j = 0; j < values[i].length; j++) { + var x = j * ctx.step; + + y = ctx.height - Math.floor(values[i][j] * data_scale); + //y -= Math.floor(y % (1 / data_scale)); + + pt += ' ' + x + ',' + y; + } + + pt += ' ' + ctx.width + ',' + y + ' ' + ctx.width + ',' + ctx.height; + + el.setAttribute('points', pt); + + i++; + } + + info.label_25 = 0.25 * info.peak; + info.label_50 = 0.50 * info.peak; + info.label_75 = 0.75 * info.peak; + + if (typeof(ctx.cb) == 'function') + ctx.cb(ctx.svg, info); + } + }, this)); + }, this), pollInterval); + }, + + loadSVG: function(src) { + return L.Request.get(src).then(function(response) { + if (!response.ok) + throw new Error(response.statusText); + + return E('div', { + 'style': 'width:100%;height:300px;border:1px solid #000;background:#fff' + }, response.text()); + }); + }, + + render: function(data) { + var svg1 = data[0], + svg2 = data[1], + wifidevs = data[2]; + + var v = E('div', {}, E('div')); + + for (var i = 0; i < wifidevs.length; i++) { + var ifname = wifidevs[i].getIfname(); + + if (!ifname) + continue; + + var csvg1 = svg1.cloneNode(true), + csvg2 = svg2.cloneNode(true); + + v.firstElementChild.appendChild(E('div', { 'data-tab': ifname, 'data-tab-title': ifname }, [ + csvg1, + E('div', { 'class': 'right' }, E('small', { 'id': 'scale' }, '-')), + E('br'), + + E('div', { 'class': 'table', 'style': 'width:100%;table-layout:fixed' }, [ + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid blue' }, [ _('Signal:') ])), + E('div', { 'class': 'td', 'id': 'rssi_bw_cur' }, [ '0 ' + _('dBm') ]), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])), + E('div', { 'class': 'td', 'id': 'rssi_bw_avg' }, [ '0 ' + _('dBm') ]), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])), + E('div', { 'class': 'td', 'id': 'rssi_bw_peak' }, [ '0 ' + _('dBm') ]) + ]), + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid red' }, [ _('Noise:') ])), + E('div', { 'class': 'td', 'id': 'noise_bw_cur' }, [ '0 ' + _('dBm') ]), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])), + E('div', { 'class': 'td', 'id': 'noise_bw_avg' }, [ '0 ' + _('dBm') ]), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])), + E('div', { 'class': 'td', 'id': 'noise_bw_peak' }, [ '0 ' + _('dBm') ]) + ]) + ]), + E('br'), + + csvg2, + E('div', { 'class': 'right' }, E('small', { 'id': 'scale2' }, '-')), + E('br'), + + E('div', { 'class': 'table', 'style': 'width:100%;table-layout:fixed' }, [ + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid green' }, [ _('Phy Rate:') ])), + E('div', { 'class': 'td', 'id': 'rate_bw_cur' }, [ '0 MBit/s' ]), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])), + E('div', { 'class': 'td', 'id': 'rate_bw_avg' }, [ '0 MBit/s' ]), + + E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])), + E('div', { 'class': 'td', 'id': 'rate_bw_peak' }, [ '0 MBit/s' ]) + ]) + ]) + ])); + + this.updateGraph(ifname, csvg1, [ null, { line: 'rssi', offset: 155 }, { line: 'noise', offset: 155 } ], function(svg, info) { + var G = svg.firstElementChild, tab = svg.parentNode; + + G.getElementById('label_25').firstChild.data = '%d %s'.format(info.label_25 - 100, _('dBm')); + G.getElementById('label_50').firstChild.data = '%d %s'.format(info.label_50 - 100, _('dBm')); + G.getElementById('label_75').firstChild.data = '%d %s'.format(info.label_75 - 100, _('dBm')); + + tab.querySelector('#scale').firstChild.data = _('(%d minute window, %d second interval)').format(info.timeframe, info.interval); + + tab.querySelector('#rssi_bw_cur').firstChild.data = '%d %s'.format(info.line_current[0] - 100, _('dBm')); + tab.querySelector('#rssi_bw_avg').firstChild.data = '%d %s'.format(info.line_average[0] - 100, _('dBm')); + tab.querySelector('#rssi_bw_peak').firstChild.data = '%d %s'.format(info.line_peak[0] - 100, _('dBm')); + + tab.querySelector('#noise_bw_cur').firstChild.data = '%d %s'.format(info.line_current[1] - 100, _('dBm')); + tab.querySelector('#noise_bw_avg').firstChild.data = '%d %s'.format(info.line_average[1] - 100, _('dBm')); + tab.querySelector('#noise_bw_peak').firstChild.data = '%d %s'.format(info.line_peak[1] - 100, _('dBm')); + }); + + this.updateGraph(ifname, csvg2, [ { line: 'rate', multiply: 0.001 } ], function(svg, info) { + var G = svg.firstElementChild, tab = svg.parentNode; + + G.getElementById('label_25').firstChild.data = '%.2f %s'.format(info.label_25, _('MBit/s')); + G.getElementById('label_50').firstChild.data = '%.2f %s'.format(info.label_50, _('MBit/s')); + G.getElementById('label_75').firstChild.data = '%.2f %s'.format(info.label_75, _('MBit/s')); + + tab.querySelector('#scale2').firstChild.data = _('(%d minute window, %d second interval)').format(info.timeframe, info.interval); + + tab.querySelector('#rate_bw_cur').firstChild.data = '%d %s'.format(info.line_current[0], _('Mbit/s')); + tab.querySelector('#rate_bw_avg').firstChild.data = '%d %s'.format(info.line_average[0], _('Mbit/s')); + tab.querySelector('#rate_bw_peak').firstChild.data = '%d %s'.format(info.line_peak[0], _('Mbit/s')); + }); + } + + L.ui.tabs.initTabGroup(v.firstElementChild.childNodes); + + this.pollData(); + + return v; + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/modules/luci-mod-status/luasrc/controller/admin/status.lua b/modules/luci-mod-status/luasrc/controller/admin/status.lua index d09cb6e2f..e888ccf09 100644 --- a/modules/luci-mod-status/luasrc/controller/admin/status.lua +++ b/modules/luci-mod-status/luasrc/controller/admin/status.lua @@ -20,22 +20,10 @@ function index() entry({"admin", "status", "realtime"}, alias("admin", "status", "realtime", "load"), _("Realtime Graphs"), 7) - entry({"admin", "status", "realtime", "load"}, template("admin_status/load"), _("Load"), 1).leaf = true - entry({"admin", "status", "realtime", "load_status"}, call("action_load")).leaf = true - - entry({"admin", "status", "realtime", "bandwidth"}, template("admin_status/bandwidth"), _("Traffic"), 2).leaf = true - entry({"admin", "status", "realtime", "bandwidth_status"}, call("action_bandwidth")).leaf = true - - page = entry({"admin", "status", "realtime", "wireless"}, template("admin_status/wireless"), _("Wireless"), 3) - page.uci_depends = { wireless = true } - page.leaf = true - - page = entry({"admin", "status", "realtime", "wireless_status"}, call("action_wireless")) - page.uci_depends = { wireless = true } - page.leaf = true - - entry({"admin", "status", "realtime", "connections"}, template("admin_status/connections"), _("Connections"), 4).leaf = true - entry({"admin", "status", "realtime", "connections_status"}, call("action_connections")).leaf = true + entry({"admin", "status", "realtime", "load"}, view("status/load"), _("Load"), 1) + entry({"admin", "status", "realtime", "bandwidth"}, view("status/bandwidth"), _("Traffic"), 2) + entry({"admin", "status", "realtime", "wireless"}, view("status/wireless"), _("Wireless"), 3).uci_depends = { wireless = true } + entry({"admin", "status", "realtime", "connections"}, view("status/connections"), _("Connections"), 4) entry({"admin", "status", "nameinfo"}, call("action_nameinfo")).leaf = true end @@ -84,97 +72,3 @@ function action_iptables() luci.http.redirect(luci.dispatcher.build_url("admin/status/iptables")) end - -function action_bandwidth(iface) - luci.http.prepare_content("application/json") - - local bwc = io.popen("luci-bwc -i %s 2>/dev/null" - % luci.util.shellquote(iface)) - - if bwc then - luci.http.write("[") - - while true do - local ln = bwc:read("*l") - if not ln then break end - luci.http.write(ln) - end - - luci.http.write("]") - bwc:close() - end -end - -function action_wireless(iface) - luci.http.prepare_content("application/json") - - local bwc = io.popen("luci-bwc -r %s 2>/dev/null" - % luci.util.shellquote(iface)) - - if bwc then - luci.http.write("[") - - while true do - local ln = bwc:read("*l") - if not ln then break end - luci.http.write(ln) - end - - luci.http.write("]") - bwc:close() - end -end - -function action_load() - luci.http.prepare_content("application/json") - - local bwc = io.popen("luci-bwc -l 2>/dev/null") - if bwc then - luci.http.write("[") - - while true do - local ln = bwc:read("*l") - if not ln then break end - luci.http.write(ln) - end - - luci.http.write("]") - bwc:close() - end -end - -function action_connections() - local sys = require "luci.sys" - - luci.http.prepare_content("application/json") - - luci.http.write('{ "connections": ') - luci.http.write_json(sys.net.conntrack()) - - local bwc = io.popen("luci-bwc -c 2>/dev/null") - if bwc then - luci.http.write(', "statistics": [') - - while true do - local ln = bwc:read("*l") - if not ln then break end - luci.http.write(ln) - end - - luci.http.write("]") - bwc:close() - end - - luci.http.write(" }") -end - -function action_nameinfo(...) - local util = require "luci.util" - - luci.http.prepare_content("application/json") - luci.http.write_json(util.ubus("network.rrdns", "lookup", { - addrs = { ... }, - timeout = 5000, - limit = 1000 - }) or { }) -end diff --git a/modules/luci-mod-status/luasrc/view/admin_status/bandwidth.htm b/modules/luci-mod-status/luasrc/view/admin_status/bandwidth.htm deleted file mode 100644 index 5cc661ad1..000000000 --- a/modules/luci-mod-status/luasrc/view/admin_status/bandwidth.htm +++ /dev/null @@ -1,308 +0,0 @@ -<%# - Copyright 2010-2018 Jo-Philipp Wich <jo@mein.io> - Licensed to the public under the Apache License 2.0. --%> - -<%- - local ntm = require "luci.model.network".init() - - local dev - local devices = { } - for _, dev in luci.util.vspairs(luci.sys.net.devices()) do - if dev ~= "lo" and not ntm:ignore_interface(dev) then - devices[#devices+1] = dev - end - end - - local curdev = luci.http.formvalue("dev") or devices[1] --%> - -<%+header%> - -<script type="text/javascript">//<![CDATA[ - var bwxhr = new XHR(); - - var G; - var TIME = 0; - var RXB = 1; - var RXP = 2; - var TXB = 3; - var TXP = 4; - - var width = 760; - var height = 300; - var step = 5; - - var data_wanted = Math.floor(width / step); - var data_fill = 1; - var data_stamp = 0; - - var data_rx = [ ]; - var data_tx = [ ]; - - var line_rx; - var line_tx; - - var label_25; - var label_50; - var label_75; - - var label_rx_cur; - var label_rx_avg; - var label_rx_peak; - - var label_tx_cur; - var label_tx_avg; - var label_tx_peak; - - var label_scale; - - - Math.log2 = Math.log2 || function(x) { return Math.log(x) * Math.LOG2E; }; - - function bandwidth_label(bytes, br) - { - var uby = '<%:kB/s%>'; - var kby = (bytes / 1024); - - if (kby >= 1024) - { - uby = '<%:MB/s%>'; - kby = kby / 1024; - } - - var ubi = '<%:kbit/s%>'; - var kbi = (bytes * 8 / 1024); - - if (kbi >= 1024) - { - ubi = '<%:Mbit/s%>'; - kbi = kbi / 1024; - } - - return String.format("%f %s%s(%f %s)", - kbi.toFixed(2), ubi, - br ? '<br />' : ' ', - kby.toFixed(2), uby - ); - } - - /* wait for SVG */ - window.setTimeout( - function() { - var svg = document.getElementById('bwsvg'); - - try { - G = svg.getSVGDocument - ? svg.getSVGDocument() : svg.contentDocument; - } - catch(e) { - G = document.embeds['bwsvg'].getSVGDocument(); - } - - if (!G) - { - window.setTimeout(arguments.callee, 1000); - } - else - { - /* find sizes */ - width = svg.offsetWidth - 2; - height = svg.offsetHeight - 2; - data_wanted = Math.ceil(width / step); - - /* prefill datasets */ - for (var i = 0; i < data_wanted; i++) - { - data_rx[i] = 0; - data_tx[i] = 0; - } - - /* find svg elements */ - line_rx = G.getElementById('rx'); - line_tx = G.getElementById('tx'); - - label_25 = G.getElementById('label_25'); - label_50 = G.getElementById('label_50'); - label_75 = G.getElementById('label_75'); - - label_rx_cur = document.getElementById('rx_bw_cur'); - label_rx_avg = document.getElementById('rx_bw_avg'); - label_rx_peak = document.getElementById('rx_bw_peak'); - - label_tx_cur = document.getElementById('tx_bw_cur'); - label_tx_avg = document.getElementById('tx_bw_avg'); - label_tx_peak = document.getElementById('tx_bw_peak'); - - label_scale = document.getElementById('scale'); - - - /* plot horizontal time interval lines */ - for (var i = width % (step * 60); i < width; i += step * 60) - { - var line = G.createElementNS('http://www.w3.org/2000/svg', 'line'); - line.setAttribute('x1', i); - line.setAttribute('y1', 0); - line.setAttribute('x2', i); - line.setAttribute('y2', '100%'); - line.setAttribute('style', 'stroke:black;stroke-width:0.1'); - - var text = G.createElementNS('http://www.w3.org/2000/svg', 'text'); - text.setAttribute('x', i + 5); - text.setAttribute('y', 15); - text.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000'); - text.appendChild(G.createTextNode(Math.round((width - i) / step / 60) + 'm')); - - label_25.parentNode.appendChild(line); - label_25.parentNode.appendChild(text); - } - - label_scale.innerHTML = String.format('<%:(%d minute window, %d second interval)%>', data_wanted / 60, 3); - - /* render datasets, start update interval */ - XHR.poll(3, '<%=build_url("admin/status/realtime/bandwidth_status", curdev)%>', null, - function(x, data) - { - var data_max = 0; - var data_scale = 0; - - var data_rx_avg = 0; - var data_tx_avg = 0; - - var data_rx_peak = 0; - var data_tx_peak = 0; - - for (var i = data_stamp ? 0 : 1; i < data.length; i++) - { - /* skip overlapping entries */ - if (data[i][TIME] <= data_stamp) - continue; - - data_fill++; - - /* normalize difference against time interval */ - if (i > 0) - { - var time_delta = data[i][TIME] - data[i-1][TIME]; - if (time_delta) - { - data_rx.push((data[i][RXB] - data[i-1][RXB]) / time_delta); - data_tx.push((data[i][TXB] - data[i-1][TXB]) / time_delta); - } - } - } - - /* cut off outdated entries */ - data_rx = data_rx.slice(data_rx.length - data_wanted, data_rx.length); - data_tx = data_tx.slice(data_tx.length - data_wanted, data_tx.length); - data_fill = Math.min(data_fill, data_wanted); - - /* find peak */ - for (var i = 0; i < data_rx.length; i++) - { - data_max = Math.max(data_max, data_rx[i]); - data_max = Math.max(data_max, data_tx[i]); - - data_rx_peak = Math.max(data_rx_peak, data_rx[i]); - data_tx_peak = Math.max(data_tx_peak, data_tx[i]); - - data_rx_avg += data_rx[i]; - data_tx_avg += data_tx[i]; - } - - data_rx_avg = (data_rx_avg / data_fill); - data_tx_avg = (data_tx_avg / data_fill); - - var size = Math.floor(Math.log2(data_max)), - div = Math.pow(2, size - (size % 10)), - mult = data_max / div, - mult = (mult < 5) ? 2 : ((mult < 50) ? 10 : ((mult < 500) ? 100 : 1000)); - - data_max = data_max + (mult * div) - (data_max % (mult * div)); - - /* remember current timestamp, calculate horizontal scale */ - data_stamp = data[data.length-1][TIME]; - data_scale = height / data_max; - - /* plot data */ - var pt_rx = '0,' + height; - var pt_tx = '0,' + height; - - var y_rx = 0; - var y_tx = 0; - - for (var i = 0; i < data_rx.length; i++) - { - var x = i * step; - - y_rx = height - Math.floor(data_rx[i] * data_scale); - y_tx = height - Math.floor(data_tx[i] * data_scale); - - pt_rx += ' ' + x + ',' + y_rx; - pt_tx += ' ' + x + ',' + y_tx; - } - - pt_rx += ' ' + width + ',' + y_rx + ' ' + width + ',' + height; - pt_tx += ' ' + width + ',' + y_tx + ' ' + width + ',' + height; - - - line_rx.setAttribute('points', pt_rx); - line_tx.setAttribute('points', pt_tx); - - label_25.firstChild.data = bandwidth_label(0.25 * data_max); - label_50.firstChild.data = bandwidth_label(0.50 * data_max); - label_75.firstChild.data = bandwidth_label(0.75 * data_max); - - label_rx_cur.innerHTML = bandwidth_label(data_rx[data_rx.length-1], true); - label_tx_cur.innerHTML = bandwidth_label(data_tx[data_tx.length-1], true); - - label_rx_avg.innerHTML = bandwidth_label(data_rx_avg, true); - label_tx_avg.innerHTML = bandwidth_label(data_tx_avg, true); - - label_rx_peak.innerHTML = bandwidth_label(data_rx_peak, true); - label_tx_peak.innerHTML = bandwidth_label(data_tx_peak, true); - } - ); - - XHR.run(); - } - }, 1000 - ); -//]]></script> - -<h2 name="content"><%:Realtime Traffic%></h2> - -<ul class="cbi-tabmenu"> - <% for _, dev in ipairs(devices) do %> - <li class="cbi-tab<%= dev == curdev and "" or "-disabled" %>"><a href="?dev=<%=pcdata(dev)%>"><%=pcdata(dev)%></a></li> - <% end %> -</ul> - -<embed id="bwsvg" style="width:100%; height:300px; border:1px solid #000000; background-color:#FFFFFF" src="<%=resource%>/bandwidth.svg" /> -<div style="text-align:right"><small id="scale">-</small></div> -<br /> - -<div class="table" style="width:100%; table-layout:fixed" cellspacing="5"> - <div class="tr"> - <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid blue"><%:Inbound:%></strong></div> - <div class="td" id="rx_bw_cur">0 <%:kbit/s%><br />(0 <%:kB/s%>)</div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> - <div class="td" id="rx_bw_avg">0 <%:kbit/s%><br />(0 <%:kB/s%>)</div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> - <div class="td" id="rx_bw_peak">0 <%:kbit/s%><br />(0 <%:kB/s%>)</div> - </div> - <div class="tr"> - <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid green"><%:Outbound:%></strong></div> - <div class="td" id="tx_bw_cur">0 <%:kbit/s%><br />(0 <%:kB/s%>)</div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> - <div class="td" id="tx_bw_avg">0 <%:kbit/s%><br />(0 <%:kB/s%>)</div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> - <div class="td" id="tx_bw_peak">0 <%:kbit/s%><br />(0 <%:kB/s%>)</div> - </div> -</div> - -<%+footer%> diff --git a/modules/luci-mod-status/luasrc/view/admin_status/connections.htm b/modules/luci-mod-status/luasrc/view/admin_status/connections.htm deleted file mode 100644 index 37debcde6..000000000 --- a/modules/luci-mod-status/luasrc/view/admin_status/connections.htm +++ /dev/null @@ -1,405 +0,0 @@ -<%# - Copyright 2010-2018 Jo-Philipp Wich <jo@mein.io> - Licensed to the public under the Apache License 2.0. --%> - -<%+header%> - -<script type="text/javascript">//<![CDATA[ - var bwxhr = new XHR(); - - var G; - var TIME = 0; - var UDP = 1; - var TCP = 2; - var OTHER = 3; - - var width = 760; - var height = 300; - var step = 5; - - var data_wanted = Math.floor(width / step); - var data_fill = 1; - var data_stamp = 0; - - var data_udp = [ ]; - var data_tcp = [ ]; - var data_otr = [ ]; - - var line_udp; - var line_tcp; - - var label_25; - var label_50; - var label_75; - - var label_udp_cur; - var label_udp_avg; - var label_udp_peak; - - var label_tcp_cur; - var label_tcp_avg; - var label_tcp_peak; - - var label_otr_cur; - var label_otr_avg; - var label_otr_peak; - - var label_scale; - - var conn_table; - - var dns_cache = { }; - - - /* wait for SVG */ - window.setTimeout( - function() { - var svg = document.getElementById('bwsvg'); - - try { - G = svg.getSVGDocument - ? svg.getSVGDocument() : svg.contentDocument; - } - catch(e) { - G = document.embeds['bwsvg'].getSVGDocument(); - } - - if (!G) - { - window.setTimeout(arguments.callee, 1000); - } - else - { - /* find sizes */ - width = svg.offsetWidth - 2; - height = svg.offsetHeight - 2; - data_wanted = Math.ceil(width / step); - - /* prefill datasets */ - for (var i = 0; i < data_wanted; i++) - { - data_udp[i] = 0; - data_tcp[i] = 0; - data_otr[i] = 0; - } - - /* find svg elements */ - line_udp = G.getElementById('udp'); - line_tcp = G.getElementById('tcp'); - line_otr = G.getElementById('other'); - - label_25 = G.getElementById('label_25'); - label_50 = G.getElementById('label_50'); - label_75 = G.getElementById('label_75'); - - label_udp_cur = document.getElementById('lb_udp_cur'); - label_udp_avg = document.getElementById('lb_udp_avg'); - label_udp_peak = document.getElementById('lb_udp_peak'); - - label_tcp_cur = document.getElementById('lb_tcp_cur'); - label_tcp_avg = document.getElementById('lb_tcp_avg'); - label_tcp_peak = document.getElementById('lb_tcp_peak'); - - label_otr_cur = document.getElementById('lb_otr_cur'); - label_otr_avg = document.getElementById('lb_otr_avg'); - label_otr_peak = document.getElementById('lb_otr_peak'); - - label_scale = document.getElementById('scale'); - - conn_table = document.getElementById('connections'); - - - /* plot horizontal time interval lines */ - for (var i = width % (step * 60); i < width; i += step * 60) - { - var line = G.createElementNS('http://www.w3.org/2000/svg', 'line'); - line.setAttribute('x1', i); - line.setAttribute('y1', 0); - line.setAttribute('x2', i); - line.setAttribute('y2', '100%'); - line.setAttribute('style', 'stroke:black;stroke-width:0.1'); - - var text = G.createElementNS('http://www.w3.org/2000/svg', 'text'); - text.setAttribute('x', i + 5); - text.setAttribute('y', 15); - text.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000'); - text.appendChild(G.createTextNode(Math.round((width - i) / step / 60) + 'm')); - - label_25.parentNode.appendChild(line); - label_25.parentNode.appendChild(text); - } - - label_scale.innerHTML = String.format('<%:(%d minute window, %d second interval)%>', data_wanted / 60, 3); - - var recheck_lookup_queue = {}; - - /* render datasets, start update interval */ - XHR.poll(3, '<%=build_url("admin/status/realtime/connections_status")%>', null, - function(x, json) - { - - if (!json.connections) - return; - - var conn = json.connections; - - var lookup_queue = [ ]; - var rows = []; - - conn.sort(function(a, b) { - return b.bytes - a.bytes; - }); - - for (var i = 0; i < conn.length; i++) - { - var c = conn[i]; - - if ((c.src == '127.0.0.1' && c.dst == '127.0.0.1') || - (c.src == '::1' && c.dst == '::1')) - continue; - - if (!dns_cache[c.src] && lookup_queue.indexOf(c.src) == -1) - lookup_queue.push(c.src); - - if (!dns_cache[c.dst] && lookup_queue.indexOf(c.dst) == -1) - lookup_queue.push(c.dst); - - var src = dns_cache[c.src] || (c.layer3 == 'ipv6' ? '[' + c.src + ']' : c.src); - var dst = dns_cache[c.dst] || (c.layer3 == 'ipv6' ? '[' + c.dst + ']' : c.dst); - - rows.push([ - c.layer3.toUpperCase(), - c.layer4.toUpperCase(), - c.hasOwnProperty('sport') ? (src + ':' + c.sport) : src, - c.hasOwnProperty('dport') ? (dst + ':' + c.dport) : dst, - '%1024.2mB (%d <%:Pkts.%>)'.format(c.bytes, c.packets) - ]); - } - - cbi_update_table(conn_table, rows, '<em><%:No information available%></em>'); - - if (lookup_queue.length > 0) { - var reduced_lookup_queue = lookup_queue; - - if (lookup_queue.length > 100) - reduced_lookup_queue = lookup_queue.slice(0, 100); - - XHR.get('<%=build_url("admin/status/nameinfo")%>/' + reduced_lookup_queue.join('/'), null, - function(x, json) { - if (!json) - return; - - for (var index in reduced_lookup_queue) { - var address = reduced_lookup_queue[index]; - - if (!address) - continue; - - if (json[address]) { - dns_cache[address] = json[address]; - lookup_queue.splice(reduced_lookup_queue.indexOf(address),1); - continue; - } - - if(recheck_lookup_queue[address] > 2) { - dns_cache[address] = (address.match(/:/)) ? '[' + address + ']' : address; - lookup_queue.splice(index,1); - } else { - recheck_lookup_queue[address] != null ? recheck_lookup_queue[address]++ : recheck_lookup_queue[address] = 0; - } - } - } - ); - } - - - var data = json.statistics; - - var data_max = 0; - var data_scale = 0; - - var data_udp_avg = 0; - var data_tcp_avg = 0; - var data_otr_avg = 0; - - var data_udp_peak = 0; - var data_tcp_peak = 0; - var data_otr_peak = 0; - - for (var i = data_stamp ? 0 : 1; i < data.length; i++) - { - /* skip overlapping entries */ - if (data[i][TIME] <= data_stamp) - continue; - - data_fill++; - - data_udp.push(data[i][UDP]); - data_tcp.push(data[i][TCP]); - data_otr.push(data[i][OTHER]); - } - - /* cut off outdated entries */ - data_fill = Math.min(data_fill, data_wanted); - data_udp = data_udp.slice(data_udp.length - data_wanted, data_udp.length); - data_tcp = data_tcp.slice(data_tcp.length - data_wanted, data_tcp.length); - data_otr = data_otr.slice(data_otr.length - data_wanted, data_otr.length); - - /* find peak */ - for (var i = 0; i < data_udp.length; i++) - { - data_max = Math.max(data_max, data_udp[i]); - data_max = Math.max(data_max, data_tcp[i]); - data_max = Math.max(data_max, data_otr[i]); - - data_udp_peak = Math.max(data_udp_peak, data_udp[i]); - data_tcp_peak = Math.max(data_tcp_peak, data_tcp[i]); - data_otr_peak = Math.max(data_otr_peak, data_otr[i]); - - data_udp_avg += data_udp[i]; - data_tcp_avg += data_tcp[i]; - data_otr_avg += data_otr[i]; - } - - data_udp_avg = data_udp_avg / data_fill; - data_tcp_avg = data_tcp_avg / data_fill; - data_otr_avg = data_otr_avg / data_fill; - - /* remember current timestamp, calculate horizontal scale */ - data_stamp = data[data.length-1][TIME]; - data_scale = height / (data_max * 1.1); - - - /* plot data */ - var pt_udp = '0,' + height; - var pt_tcp = '0,' + height; - var pt_otr = '0,' + height; - - var y_udp = 0; - var y_tcp = 0; - var y_otr = 0; - - for (var i = 0; i < data_udp.length; i++) - { - var x = i * step; - - y_udp = height - Math.floor(data_udp[i] * data_scale); - y_tcp = height - Math.floor(data_tcp[i] * data_scale); - y_otr = height - Math.floor(data_otr[i] * data_scale); - - pt_udp += ' ' + x + ',' + y_udp; - pt_tcp += ' ' + x + ',' + y_tcp; - pt_otr += ' ' + x + ',' + y_otr; - } - - pt_udp += ' ' + width + ',' + y_udp + ' ' + width + ',' + height; - pt_tcp += ' ' + width + ',' + y_tcp + ' ' + width + ',' + height; - pt_otr += ' ' + width + ',' + y_otr + ' ' + width + ',' + height; - - - var order = [ - [ line_udp, data_udp[data_udp.length-1] ], - [ line_tcp, data_tcp[data_tcp.length-1] ], - [ line_otr, data_otr[data_otr.length-1] ] - ]; - - order.sort(function(a, b) { return b[1] - a[1] }); - - for (var i = 0; i < order.length; i++) - order[i][0].parentNode.appendChild(order[i][0]); - - - line_udp.setAttribute('points', pt_udp); - line_tcp.setAttribute('points', pt_tcp); - line_otr.setAttribute('points', pt_otr); - - label_25.firstChild.data = Math.floor(1.1 * 0.25 * data_max); - label_50.firstChild.data = Math.floor(1.1 * 0.50 * data_max); - label_75.firstChild.data = Math.floor(1.1 * 0.75 * data_max); - - label_udp_cur.innerHTML = Math.floor(data_udp[data_udp.length-1]); - label_tcp_cur.innerHTML = Math.floor(data_tcp[data_tcp.length-1]); - label_otr_cur.innerHTML = Math.floor(data_otr[data_otr.length-1]); - - label_udp_avg.innerHTML = Math.floor(data_udp_avg); - label_tcp_avg.innerHTML = Math.floor(data_tcp_avg); - label_otr_avg.innerHTML = Math.floor(data_otr_avg); - - label_udp_peak.innerHTML = Math.floor(data_udp_peak); - label_tcp_peak.innerHTML = Math.floor(data_tcp_peak); - label_otr_peak.innerHTML = Math.floor(data_otr_peak); - } - ); - - XHR.run(); - } - }, 1000 - ); -//]]></script> - -<h2 name="content"><%:Realtime Connections%></h2> - -<div class="cbi-map-descr"><%:This page gives an overview over currently active network connections.%></div> - -<fieldset class="cbi-section" id="cbi-table-table"> - <legend><%:Active Connections%></legend> - - <embed id="bwsvg" style="width:100%; height:300px; border:1px solid #000000; background-color:#FFFFFF" src="<%=resource%>/connections.svg" /> - <div style="text-align:right"><small id="scale">-</small></div> - <br /> - - <div class="table"> - <div class="tr"> - <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid blue"><%:UDP:%></strong></div> - <div class="td" id="lb_udp_cur">0</div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> - <div class="td" id="lb_udp_avg">0</div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> - <div class="td" id="lb_udp_peak">0</div> - </div> - <div class="tr"> - <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid green"><%:TCP:%></strong></div> - <div class="td" id="lb_tcp_cur">0</div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> - <div class="td" id="lb_tcp_avg">0</div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> - <div class="td" id="lb_tcp_peak">0</div> - </div> - <div class="tr"> - <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid red"><%:Other:%></strong></div> - <div class="td" id="lb_otr_cur">0</div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> - <div class="td" id="lb_otr_avg">0</div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> - <div class="td" id="lb_otr_peak">0</div> - </div> - </div> - <br /> - - <div class="cbi-section-node"> - <div class="table" id="connections"> - <div class="tr table-titles"> - <div class="th col-2 hide-xs"><%:Network%></div> - <div class="th col-2"><%:Protocol%></div> - <div class="th col-7"><%:Source%></div> - <div class="th col-7"><%:Destination%></div> - <div class="th col-4"><%:Transfer%></div> - </div> - - <div class="tr placeholder"> - <div class="td"> - <em><%:Collecting data...%></em> - </div> - </div> - </div> - </div> -</fieldset> - -<%+footer%> diff --git a/modules/luci-mod-status/luasrc/view/admin_status/load.htm b/modules/luci-mod-status/luasrc/view/admin_status/load.htm deleted file mode 100644 index d31d34062..000000000 --- a/modules/luci-mod-status/luasrc/view/admin_status/load.htm +++ /dev/null @@ -1,283 +0,0 @@ -<%# - Copyright 2010-2018 Jo-Philipp Wich <jo@mein.io> - Licensed to the public under the Apache License 2.0. --%> - -<%+header%> - -<script type="text/javascript">//<![CDATA[ - var bwxhr = new XHR(); - - var G; - var TIME = 0; - var L01 = 1; - var L05 = 2; - var L15 = 3; - - var width = 760; - var height = 300; - var step = 5; - - var data_wanted = Math.floor(width / step); - var data_fill = 1; - var data_stamp = 0; - - var data_01 = [ ]; - var data_05 = [ ]; - var data_15 = [ ]; - - var line_01; - var line_05; - var line_15; - - var label_25; - var label_050; - var label_75; - - var label_01_cur; - var label_01_avg; - var label_01_peak; - - var label_05_cur; - var label_05_avg; - var label_05_peak; - - var label_15_cur; - var label_15_avg; - var label_15_peak; - - var label_scale; - - - /* wait for SVG */ - window.setTimeout( - function() { - var svg = document.getElementById('bwsvg'); - - try { - G = svg.getSVGDocument - ? svg.getSVGDocument() : svg.contentDocument; - } - catch(e) { - G = document.embeds['bwsvg'].getSVGDocument(); - } - - if (!G) - { - window.setTimeout(arguments.callee, 1000); - } - else - { - /* find sizes */ - width = svg.offsetWidth - 2; - height = svg.offsetHeight - 2; - data_wanted = Math.ceil(width / step); - - /* prefill datasets */ - for (var i = 0; i < data_wanted; i++) - { - data_01[i] = 0; - data_05[i] = 0; - data_15[i] = 0; - } - - /* find svg elements */ - line_01 = G.getElementById('load01'); - line_05 = G.getElementById('load05'); - line_15 = G.getElementById('load15'); - - label_25 = G.getElementById('label_25'); - label_50 = G.getElementById('label_50'); - label_75 = G.getElementById('label_75'); - - label_01_cur = document.getElementById('lb_load01_cur'); - label_01_avg = document.getElementById('lb_load01_avg'); - label_01_peak = document.getElementById('lb_load01_peak'); - - label_05_cur = document.getElementById('lb_load05_cur'); - label_05_avg = document.getElementById('lb_load05_avg'); - label_05_peak = document.getElementById('lb_load05_peak'); - - label_15_cur = document.getElementById('lb_load15_cur'); - label_15_avg = document.getElementById('lb_load15_avg'); - label_15_peak = document.getElementById('lb_load15_peak'); - - label_scale = document.getElementById('scale'); - - - /* plot horizontal time interval lines */ - for (var i = width % (step * 60); i < width; i += step * 60) - { - var line = G.createElementNS('http://www.w3.org/2000/svg', 'line'); - line.setAttribute('x1', i); - line.setAttribute('y1', 0); - line.setAttribute('x2', i); - line.setAttribute('y2', '100%'); - line.setAttribute('style', 'stroke:black;stroke-width:0.1'); - - var text = G.createElementNS('http://www.w3.org/2000/svg', 'text'); - text.setAttribute('x', i + 5); - text.setAttribute('y', 15); - text.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000'); - text.appendChild(G.createTextNode(Math.round((width - i) / step / 60) + 'm')); - - label_25.parentNode.appendChild(line); - label_25.parentNode.appendChild(text); - } - - label_scale.innerHTML = String.format('<%:(%d minute window, %d second interval)%>', data_wanted / 60, 3); - - /* render datasets, start update interval */ - XHR.poll(3, '<%=build_url("admin/status/realtime/load_status")%>', null, - function(x, data) - { - var data_max = 0; - var data_scale = 0; - - var data_01_avg = 0; - var data_05_avg = 0; - var data_15_avg = 0; - - var data_01_peak = 0; - var data_05_peak = 0; - var data_15_peak = 0; - - for (var i = data_stamp ? 0 : 1; i < data.length; i++) - { - /* skip overlapping entries */ - if (data[i][TIME] <= data_stamp) - continue; - - data_fill++; - - data_01.push(data[i][L01]); - data_05.push(data[i][L05]); - data_15.push(data[i][L15]); - } - - /* cut off outdated entries */ - data_fill = Math.min(data_fill, data_wanted); - data_01 = data_01.slice(data_01.length - data_wanted, data_01.length); - data_05 = data_05.slice(data_05.length - data_wanted, data_05.length); - data_15 = data_15.slice(data_15.length - data_wanted, data_15.length); - - /* find peak */ - for (var i = 0; i < data_01.length; i++) - { - data_max = Math.max(data_max, data_01[i]); - data_max = Math.max(data_max, data_05[i]); - data_max = Math.max(data_max, data_15[i]); - - data_01_peak = Math.max(data_01_peak, data_01[i]); - data_05_peak = Math.max(data_05_peak, data_05[i]); - data_15_peak = Math.max(data_15_peak, data_15[i]); - - data_01_avg += data_01[i]; - data_05_avg += data_05[i]; - data_15_avg += data_15[i]; - } - - data_01_avg = data_01_avg / data_fill; - data_05_avg = data_05_avg / data_fill; - data_15_avg = data_15_avg / data_fill; - - /* remember current timestamp, calculate horizontal scale */ - data_stamp = data[data.length-1][TIME]; - data_scale = height / (data_max * 1.1); - - - /* plot data */ - var pt_01 = '0,' + height; - var pt_05 = '0,' + height; - var pt_15 = '0,' + height; - - var y_01 = 0; - var y_05 = 0; - var y_15 = 0; - - for (var i = 0; i < data_01.length; i++) - { - var x = i * step; - - y_01 = height - Math.floor(data_01[i] * data_scale); - y_05 = height - Math.floor(data_05[i] * data_scale); - y_15 = height - Math.floor(data_15[i] * data_scale); - - pt_01 += ' ' + x + ',' + y_01; - pt_05 += ' ' + x + ',' + y_05; - pt_15 += ' ' + x + ',' + y_15; - } - - pt_01 += ' ' + width + ',' + y_01 + ' ' + width + ',' + height; - pt_05 += ' ' + width + ',' + y_05 + ' ' + width + ',' + height; - pt_15 += ' ' + width + ',' + y_15 + ' ' + width + ',' + height; - - - line_01.setAttribute('points', pt_01); - line_05.setAttribute('points', pt_05); - line_15.setAttribute('points', pt_15); - - label_25.firstChild.data = (1.1 * 0.25 * data_max / 100).toFixed(2); - label_50.firstChild.data = (1.1 * 0.50 * data_max / 100).toFixed(2); - label_75.firstChild.data = (1.1 * 0.75 * data_max / 100).toFixed(2); - - label_01_cur.innerHTML = (data_01[data_01.length-1] / 100).toFixed(2); - label_05_cur.innerHTML = (data_05[data_05.length-1] / 100).toFixed(2); - label_15_cur.innerHTML = (data_15[data_15.length-1] / 100).toFixed(2); - - label_01_avg.innerHTML = (data_01_avg / 100).toFixed(2); - label_05_avg.innerHTML = (data_05_avg / 100).toFixed(2); - label_15_avg.innerHTML = (data_15_avg / 100).toFixed(2); - - label_01_peak.innerHTML = (data_01_peak / 100).toFixed(2); - label_05_peak.innerHTML = (data_05_peak / 100).toFixed(2); - label_15_peak.innerHTML = (data_15_peak / 100).toFixed(2); - } - ); - - XHR.run(); - } - }, 1000 - ); -//]]></script> - -<h2 name="content"><%:Realtime Load%></h2> - -<embed id="bwsvg" style="width:100%; height:300px; border:1px solid #000000; background-color:#FFFFFF" src="<%=resource%>/load.svg" /> -<div style="text-align:right"><small id="scale">-</small></div> -<br /> - -<div class="table" style="width:100%; table-layout:fixed" cellspacing="5"> - <div class="tr"> - <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid #ff0000; white-space:nowrap"><%:1 Minute Load:%></strong></div> - <div class="td" id="lb_load01_cur">0</div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> - <div class="td" id="lb_load01_avg">0</div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> - <div class="td" id="lb_load01_peak">0</div> - </div> - <div class="tr"> - <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid #ff6600; white-space:nowrap"><%:5 Minute Load:%></strong></div> - <div class="td" id="lb_load05_cur">0</div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> - <div class="td" id="lb_load05_avg">0</div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> - <div class="td" id="lb_load05_peak">0</div> - </div> - <div class="tr"> - <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid #ffaa00; white-space:nowrap"><%:15 Minute Load:%></strong></div> - <div class="td" id="lb_load15_cur">0</div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> - <div class="td" id="lb_load15_avg">0</div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> - <div class="td" id="lb_load15_peak">0</div> - </div> -</div> - -<%+footer%> diff --git a/modules/luci-mod-status/luasrc/view/admin_status/wireless.htm b/modules/luci-mod-status/luasrc/view/admin_status/wireless.htm deleted file mode 100644 index 5ac2eb462..000000000 --- a/modules/luci-mod-status/luasrc/view/admin_status/wireless.htm +++ /dev/null @@ -1,370 +0,0 @@ -<%# - Copyright 2011-2018 Jo-Philipp Wich <jo@mein.io> - Licensed to the public under the Apache License 2.0. --%> - -<%- - local ntm = require "luci.model.network".init() - - local dev - local devices = { } - for _, dev in luci.util.vspairs(luci.sys.net.devices()) do - if dev:match("^wlan%d") or dev:match("^ath%d") or dev:match("^wl%d") then - devices[#devices+1] = dev - end - end - - local curdev = luci.http.formvalue("dev") or devices[1] --%> - -<%+header%> - -<script type="text/javascript">//<![CDATA[ - var bwxhr = new XHR(); - - var G, G2; - var TIME = 0; - var RATE = 1; - var RSSI = 2; - var NOISE = 3; - - var width = 760; - var height = 300; - var step = 5; - - var data_wanted = Math.floor(width / step); - var data_fill = 1; - var data_stamp = 0; - - var data_rssi = [ ]; - var data_noise = [ ]; - var data_rate = [ ]; - - var line_rssi; - var line_noise; - var line_rate; - - var label_25, label_25_2; - var label_50, label_50_2; - var label_75, label_75_2; - - var label_rssi_cur; - var label_rssi_avg; - var label_rssi_peak; - - var label_noise_cur; - var label_noise_avg; - var label_noise_peak; - - var label_rate_cur; - var label_rate_avg; - var label_rate_peak; - - var label_scale; - var label_scale_2; - - - /* wait for SVG */ - window.setTimeout( - function() { - var svg = document.getElementById('iwsvg'); - var svg2 = document.getElementById('iwsvg2'); - - try { - G = svg.getSVGDocument - ? svg.getSVGDocument() : svg.contentDocument; - G2 = svg2.getSVGDocument - ? svg2.getSVGDocument() : svg2.contentDocument; - } - catch(e) { - G = document.embeds['iwsvg'].getSVGDocument(); - G2 = document.embeds['iwsvg2'].getSVGDocument(); - } - - if (!G || !G2) - { - window.setTimeout(arguments.callee, 1000); - } - else - { - /* find sizes */ - width = svg.offsetWidth - 2; - height = svg.offsetHeight - 2; - data_wanted = Math.ceil(width / step); - - /* prefill datasets */ - for (var i = 0; i < data_wanted; i++) - { - data_rssi[i] = 0; - data_noise[i] = 0; - data_rate[i] = 0; - } - - /* find svg elements */ - line_rssi = G.getElementById('rssi'); - line_noise = G.getElementById('noise'); - line_rate = G2.getElementById('rate'); - - label_25 = G.getElementById('label_25'); - label_50 = G.getElementById('label_50'); - label_75 = G.getElementById('label_75'); - label_25_2 = G2.getElementById('label_25'); - label_50_2 = G2.getElementById('label_50'); - label_75_2 = G2.getElementById('label_75'); - - label_rssi_cur = document.getElementById('rssi_bw_cur'); - label_rssi_avg = document.getElementById('rssi_bw_avg'); - label_rssi_peak = document.getElementById('rssi_bw_peak'); - - label_noise_cur = document.getElementById('noise_bw_cur'); - label_noise_avg = document.getElementById('noise_bw_avg'); - label_noise_peak = document.getElementById('noise_bw_peak'); - - label_rate_cur = document.getElementById('rate_bw_cur'); - label_rate_avg = document.getElementById('rate_bw_avg'); - label_rate_peak = document.getElementById('rate_bw_peak'); - - label_scale = document.getElementById('scale'); - label_scale_2 = document.getElementById('scale2'); - - - /* plot horizontal time interval lines */ - for (var i = width % (step * 60); i < width; i += step * 60) - { - var line = G.createElementNS('http://www.w3.org/2000/svg', 'line'); - line.setAttribute('x1', i); - line.setAttribute('y1', 0); - line.setAttribute('x2', i); - line.setAttribute('y2', '100%'); - line.setAttribute('style', 'stroke:black;stroke-width:0.1'); - - var text = G.createElementNS('http://www.w3.org/2000/svg', 'text'); - text.setAttribute('x', i + 5); - text.setAttribute('y', 15); - text.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000'); - text.appendChild(G.createTextNode(Math.round((width - i) / step / 60) + 'm')); - - label_25.parentNode.appendChild(line); - label_25.parentNode.appendChild(text); - - - var line2 = G2.createElementNS('http://www.w3.org/2000/svg', 'line'); - line2.setAttribute('x1', i); - line2.setAttribute('y1', 0); - line2.setAttribute('x2', i); - line2.setAttribute('y2', '100%'); - line2.setAttribute('style', 'stroke:black;stroke-width:0.1'); - - var text2 = G2.createElementNS('http://www.w3.org/2000/svg', 'text'); - text2.setAttribute('x', i + 5); - text2.setAttribute('y', 15); - text2.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000'); - text2.appendChild(G.createTextNode(Math.round((width - i) / step / 60) + 'm')); - - label_25_2.parentNode.appendChild(line2); - label_25_2.parentNode.appendChild(text2); - } - - label_scale.innerHTML = String.format('<%:(%d minute window, %d second interval)%>', data_wanted / 60, 3); - label_scale_2.innerHTML = String.format('<%:(%d minute window, %d second interval)%>', data_wanted / 60, 3); - - /* render datasets, start update interval */ - XHR.poll(3, '<%=build_url("admin/status/realtime/wireless_status", curdev)%>', null, - function(x, data) - { - var noise_floor = 255; - var rate_floor = 60000; - - for (var i = 0; i < data.length; i++) { - noise_floor = Math.min(noise_floor, data[i][NOISE]); - rate_floor = Math.min(rate_floor, data[i][RATE]); - } - - noise_floor -= 5; - - var data_max = 0; - var data_scale = 0; - var data_max_2 = 0; - var data_scale_2 = 0; - - var data_rssi_avg = 0; - var data_noise_avg = 0; - var data_rate_avg = 0; - - var data_rssi_peak = 0; - var data_noise_peak = 0; - var data_rate_peak = 0; - - for (var i = data_stamp ? 0 : 1; i < data.length; i++) - { - /* skip overlapping entries */ - if (data[i][TIME] <= data_stamp) - continue; - - data_fill++; - - data_rssi.push(data[i][RSSI] - noise_floor); - data_noise.push(data[i][NOISE] - noise_floor); - data_rate.push(Math.floor(data[i][RATE] / 1000)); - } - - /* cut off outdated entries */ - data_fill = Math.min(data_fill, data_wanted); - data_rssi = data_rssi.slice(data_rssi.length - data_wanted, data_rssi.length); - data_noise = data_noise.slice(data_noise.length - data_wanted, data_noise.length); - data_rate = data_rate.slice(data_rate.length - data_wanted, data_rate.length); - - /* find peak */ - for (var i = 0; i < data_rssi.length; i++) - { - data_max = Math.max(data_max, data_rssi[i]); - data_max_2 = Math.max(data_max_2, data_rate[i]); - - data_rssi_peak = Math.max(data_rssi_peak, data_rssi[i]); - data_noise_peak = Math.max(data_noise_peak, data_noise[i]); - data_rate_peak = Math.max(data_rate_peak, data_rate[i]); - - data_rssi_avg += data_rssi[i]; - data_noise_avg += data_noise[i]; - data_rate_avg += data_rate[i]; - } - - data_rssi_avg = data_rssi_avg / data_fill; - data_noise_avg = data_noise_avg / data_fill; - data_rate_avg = data_rate_avg / data_fill; - - /* remember current timestamp, calculate horizontal scale */ - data_stamp = data[data.length-1][TIME]; - data_scale = (height / (data_max * 1.1)).toFixed(1); - data_scale_2 = (height / (data_max_2 * 1.1)).toFixed(1); - - /* plot data */ - var pt_rssi = '0,' + height; - var pt_noise = '0,' + height; - var pt_rate = '0,' + height; - - var y_rssi = 0; - var y_noise = 0; - var y_rate = 0; - - for (var i = 0; i < data_rssi.length; i++) - { - var x = i * step; - - y_rssi = height - Math.floor(data_rssi[i] * data_scale); - y_noise = height - Math.floor(data_noise[i] * data_scale); - y_rate = height - Math.floor(data_rate[i] * data_scale_2); - - y_rssi -= Math.floor(y_rssi % (1/data_scale)); - y_noise -= Math.floor(y_noise % (1/data_scale)); - - pt_rssi += ' ' + x + ',' + y_rssi; - pt_noise += ' ' + x + ',' + y_noise; - pt_rate += ' ' + x + ',' + y_rate; - } - - pt_rssi += ' ' + width + ',' + y_rssi + ' ' + width + ',' + height; - pt_noise += ' ' + width + ',' + y_noise + ' ' + width + ',' + height; - pt_rate += ' ' + width + ',' + y_rate + ' ' + width + ',' + height; - - line_rssi.setAttribute('points', pt_rssi); - line_noise.setAttribute('points', pt_noise); - line_rate.setAttribute('points', pt_rate); - - function wireless_label(dbm, noise) - { - if (noise) - return String.format("%d <%:dBm%> (SNR %d <%:dB%>)", noise_floor + dbm - 255, dbm - noise); - else - return String.format("%d <%:dBm%>", noise_floor + dbm - 255); - } - - function rate_label(mbit) - { - return String.format("%d <%:Mbit/s%>", mbit); - } - - label_25.firstChild.data = wireless_label(1.1 * 0.25 * data_max); - label_50.firstChild.data = wireless_label(1.1 * 0.50 * data_max); - label_75.firstChild.data = wireless_label(1.1 * 0.75 * data_max); - - label_25_2.firstChild.data = rate_label(1.1 * 0.25 * data_max_2); - label_50_2.firstChild.data = rate_label(1.1 * 0.50 * data_max_2); - label_75_2.firstChild.data = rate_label(1.1 * 0.75 * data_max_2); - - label_rssi_cur.innerHTML = wireless_label(data_rssi[data_rssi.length-1], data_noise[data_noise.length-1]).nobr(); - label_noise_cur.innerHTML = wireless_label(data_noise[data_noise.length-1]).nobr(); - - label_rssi_avg.innerHTML = wireless_label(data_rssi_avg, data_noise_avg).nobr(); - label_noise_avg.innerHTML = wireless_label(data_noise_avg).nobr(); - - label_rssi_peak.innerHTML = wireless_label(data_rssi_peak, data_noise_peak).nobr(); - label_noise_peak.innerHTML = wireless_label(data_noise_peak).nobr(); - - label_rate_cur.innerHTML = rate_label(data_rate[data_rate.length-1]); - label_rate_avg.innerHTML = rate_label(data_rate_avg); - label_rate_peak.innerHTML = rate_label(data_rate_peak); - } - ); - - XHR.run(); - } - }, 1000 - ); -//]]></script> - -<h2 name="content"><%:Realtime Wireless%></h2> - -<ul class="cbi-tabmenu"> - <% for _, dev in ipairs(devices) do %> - <li class="cbi-tab<%= dev == curdev and "" or "-disabled" %>"><a href="?dev=<%=pcdata(dev)%>"><%=pcdata(dev)%></a></li> - <% end %> -</ul> - -<embed id="iwsvg" style="width:100%; height:300px; border:1px solid #000000; background-color:#FFFFFF" src="<%=resource%>/wireless.svg" /> -<div style="text-align:right"><small id="scale">-</small></div> -<br /> - -<div class="table" style="width:100%; table-layout:fixed" cellspacing="5"> - <div class="tr"> - <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid blue"><%:Signal:%></strong></div> - <div class="td" id="rssi_bw_cur">0 <%:dBm%></div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> - <div class="td" id="rssi_bw_avg">0 <%:dBm%></div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> - <div class="td" id="rssi_bw_peak">0 <%:dBm%></div> - </div> - <div class="tr"> - <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid red"><%:Noise:%></strong></div> - <div class="td" id="noise_bw_cur">0 <%:dBm%></div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> - <div class="td" id="noise_bw_avg">0 <%:dBm%></div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> - <div class="td" id="noise_bw_peak">0 <%:dBm%></div> - </div> -</div> - -<br /> - -<embed id="iwsvg2" style="width:100%; height:300px; border:1px solid #000000; background-color:#FFFFFF" src="<%=resource%>/wifirate.svg" /> -<div style="text-align:right"><small id="scale2">-</small></div> -<br /> - -<div class="table" style="width:100%; table-layout:fixed" cellspacing="5"> - <div class="tr"> - <div class="td" style="text-align:right; vertical-align:top"><strong style="border-bottom:2px solid green"><%:Phy Rate:%></strong></div> - <div class="td" id="rate_bw_cur">0 MBit/s</div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Average:%></strong></div> - <div class="td" id="rate_bw_avg">0 MBit/s</div> - - <div class="td" style="text-align:right; vertical-align:top"><strong><%:Peak:%></strong></div> - <div class="td" id="rate_bw_peak">0 MBit/s</div> - </div> -</div> - -<%+footer%> |