luci-base, luci-mod-status: convert realtime stats to client side views
authorJo-Philipp Wich <jo@mein.io>
Sat, 2 Nov 2019 21:55:59 +0000 (22:55 +0100)
committerJo-Philipp Wich <jo@mein.io>
Sun, 3 Nov 2019 16:56:58 +0000 (17:56 +0100)
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json
modules/luci-mod-status/htdocs/luci-static/resources/view/status/bandwidth.js [new file with mode: 0644]
modules/luci-mod-status/htdocs/luci-static/resources/view/status/connections.js [new file with mode: 0644]
modules/luci-mod-status/htdocs/luci-static/resources/view/status/load.js [new file with mode: 0644]
modules/luci-mod-status/htdocs/luci-static/resources/view/status/wireless.js [new file with mode: 0644]
modules/luci-mod-status/luasrc/controller/admin/status.lua
modules/luci-mod-status/luasrc/view/admin_status/bandwidth.htm [deleted file]
modules/luci-mod-status/luasrc/view/admin_status/connections.htm [deleted file]
modules/luci-mod-status/luasrc/view/admin_status/load.htm [deleted file]
modules/luci-mod-status/luasrc/view/admin_status/wireless.htm [deleted file]

index a09c6b424503dab320d5a70429d118343cb80487..ea19f32d49d3cb78077c8d6083eac534178b85e2 100644 (file)
                        "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 (file)
index 0000000..0565490
--- /dev/null
@@ -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 (file)
index 0000000..96dee1d
--- /dev/null
@@ -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 (file)
index 0000000..a1ed434
--- /dev/null
@@ -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 (file)
index 0000000..45dd54a
--- /dev/null
@@ -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
+});
index d09cb6e2f743c5def946e60c9abbead25031e32e..e888ccf097437075438e919a3daf3d16ee32eff7 100644 (file)
@@ -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 (file)
index 5cc661a..0000000
+++ /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 (file)
index 37debcd..0000000
+++ /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 (file)
index d31d340..0000000
+++ /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 (file)
index 5ac2eb4..0000000
+++ /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%>