luci-base, luci-mod-status: convert realtime stats to client side views

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
This commit is contained in:
Jo-Philipp Wich 2019-11-02 22:55:59 +01:00
parent a2f43983b6
commit cfb5af89e1
10 changed files with 1369 additions and 1477 deletions

View file

@ -43,9 +43,10 @@
"ubus": {
"file": [ "list", "read", "stat" ],
"iwinfo": [ "assoclist", "freqlist", "txpowerlist", "countrylist" ],
"luci": [ "getDUIDHints", "getInitList", "getLocaltime", "getTimezones", "getLEDs", "getUSBDevices", "getSwconfigFeatures", "getSwconfigPortState", "getBlockDevices", "getMountPoints" ],
"luci": [ "getConntrackList", "getDUIDHints", "getInitList", "getLocaltime", "getRealtimeStats", "getTimezones", "getLEDs", "getUSBDevices", "getSwconfigFeatures", "getSwconfigPortState", "getBlockDevices", "getMountPoints" ],
"luci-rpc": [ "getBoardJSON", "getDHCPLeases", "getDSLStatus", "getHostHints", "getNetworkDevices", "getWirelessDevices" ],
"network.interface": [ "dump" ],
"network.rrdns": [ "lookup" ],
"network": [ "get_proto_handlers" ],
"system": [ "board", "info", "validate_firmware_image" ],
"uci": [ "changes", "get" ]

View file

@ -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
});

View file

@ -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
});

View file

@ -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
});

View file

@ -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
});

View 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

View file

@ -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%>

View file

@ -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%>

View file

@ -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%>

View file

@ -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%>