luci/modules/luci-mod-status/htdocs/luci-static/resources/view/status/channel_analysis.js
Christian Marangi 54f5b171bc
luci-mod-status: fix channel_analysis ReferenceError: diff is not defined
In refactoring the code there was an error and the sort function wasn't
correctly reworked with diff not correctly dropped.

Drop that and correctly sort the WiFi ap if the channel is different.

Fixes: 75dcb09754 ("luci-mod-status: improve channel_analysis page")
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
2022-12-23 19:19:26 +01:00

432 lines
13 KiB
JavaScript

'use strict';
'require view';
'require poll';
'require request';
'require network';
'require ui';
'require rpc';
'require tools.prng as random';
return view.extend({
callFrequencyList : rpc.declare({
object: 'iwinfo',
method: 'freqlist',
params: [ 'device' ],
expect: { results: [] }
}),
callInfo : rpc.declare({
object: 'iwinfo',
method: 'info',
params: [ 'device' ],
expect: { }
}),
render_signal_badge: function(signalPercent, signalValue) {
var icon, title, value;
if (signalPercent < 0)
icon = L.resource('icons/signal-none.png');
else if (signalPercent == 0)
icon = L.resource('icons/signal-0.png');
else if (signalPercent < 25)
icon = L.resource('icons/signal-0-25.png');
else if (signalPercent < 50)
icon = L.resource('icons/signal-25-50.png');
else if (signalPercent < 75)
icon = L.resource('icons/signal-50-75.png');
else
icon = L.resource('icons/signal-75-100.png');
value = '%d\xa0%s'.format(signalValue, _('dBm'));
title = '%s: %d %s'.format(_('Signal'), signalValue, _('dBm'));
return E('div', {
'class': 'ifacebadge',
'title': title,
'data-signal': signalValue
}, [
E('img', { 'src': icon }),
value
]);
},
add_wifi_to_graph: function(chan_analysis, res, scanCache, channels, channel_width) {
var offset_tbl = chan_analysis.offset_tbl,
height = chan_analysis.graph.offsetHeight - 2,
step = chan_analysis.col_width,
height_diff = (height-(height-(res.signal*-4)));
if (scanCache[res.bssid].color == null)
scanCache[res.bssid].color = random.derive_color(res.bssid);
if (scanCache[res.bssid].graph == null)
scanCache[res.bssid].graph = [];
channels.forEach(function(channel) {
var chan_offset = offset_tbl[channel],
points = [
(chan_offset-(step*channel_width))+','+height,
(chan_offset-(step*(channel_width-1)))+','+height_diff,
(chan_offset+(step*(channel_width-1)))+','+height_diff,
(chan_offset+(step*(channel_width)))+','+height
];
if (scanCache[res.bssid].graph[i] == null) {
var group = document.createElementNS('http://www.w3.org/2000/svg', 'g'),
line = document.createElementNS('http://www.w3.org/2000/svg', 'polyline'),
text = document.createElementNS('http://www.w3.org/2000/svg', 'text'),
color = scanCache[res.bssid].color;
line.setAttribute('style', 'fill:'+color+'4f'+';stroke:'+color+';stroke-width:0.5');
text.setAttribute('style', 'fill:'+color+';font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000');
text.appendChild(document.createTextNode(res.ssid || res.bssid));
group.appendChild(line)
group.appendChild(text)
chan_analysis.graph.firstElementChild.appendChild(group);
scanCache[res.bssid].graph[i] = { group : group, line : line, text : text };
}
scanCache[res.bssid].graph[i].text.setAttribute('x', chan_offset-step);
scanCache[res.bssid].graph[i].text.setAttribute('y', height_diff - 2);
scanCache[res.bssid].graph[i].line.setAttribute('points', points);
scanCache[res.bssid].graph[i].group.style.zIndex = res.signal*-1;
scanCache[res.bssid].graph[i].group.style.opacity = res.stale ? '0.5' : null;
})
},
create_channel_graph: function(chan_analysis, freq_tbl, freq) {
var is5GHz = freq == '5GHz',
columns = is5GHz ? freq_tbl.length * 4 : freq_tbl.length + 3,
chan_graph = chan_analysis.graph,
G = chan_graph.firstElementChild,
step = (chan_graph.offsetWidth - 2) / columns,
curr_offset = step;
function createGraphHLine(graph, pos) {
var elem = document.createElementNS('http://www.w3.org/2000/svg', 'line');
elem.setAttribute('x1', pos);
elem.setAttribute('y1', 0);
elem.setAttribute('x2', pos);
elem.setAttribute('y2', '100%');
elem.setAttribute('style', 'stroke:black;stroke-width:0.1');
graph.appendChild(elem);
}
function createGraphText(graph, pos, text) {
var elem = document.createElementNS('http://www.w3.org/2000/svg', 'text');
elem.setAttribute('y', 15);
elem.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000');
elem.setAttribute('x', pos + 5);
elem.appendChild(document.createTextNode(text));
graph.appendChild(elem);
}
chan_analysis.col_width = step;
createGraphHLine(G,curr_offset);
for (var i=0; i< freq_tbl.length;i++) {
var channel = freq_tbl[i]
chan_analysis.offset_tbl[channel] = curr_offset+step;
createGraphHLine(G,curr_offset+step);
createGraphText(G,curr_offset+step, channel);
curr_offset += step;
if (is5GHz && freq_tbl[i+1]) {
var next_channel = freq_tbl[i+1];
/* Check if we are transitioning to another 5Ghz band range */
if ((next_channel - channel) == 4) {
for (var j=1; j < 4; j++) {
chan_analysis.offset_tbl[channel+j] = curr_offset+step;
createGraphHLine(G,curr_offset+step);
curr_offset += step;
}
} else {
chan_analysis.offset_tbl[channel+1] = curr_offset+step;
createGraphHLine(G,curr_offset+step);
curr_offset += step;
chan_analysis.offset_tbl[next_channel-2] = curr_offset+step;
createGraphHLine(G,curr_offset+step);
curr_offset += step;
chan_analysis.offset_tbl[next_channel-1] = curr_offset+step;
createGraphHLine(G,curr_offset+step);
curr_offset += step;
}
}
}
createGraphHLine(G,curr_offset+step);
chan_analysis.tab.addEventListener('cbi-tab-active', L.bind(function(ev) {
this.active_tab = ev.detail.tab;
if (!this.radios[this.active_tab].loadedOnce)
poll.start();
}, this));
},
handleScanRefresh: function() {
if (!this.active_tab)
return;
var radio = this.radios[this.active_tab];
return Promise.all([
radio.dev.getScanList(),
this.callInfo(radio.dev.getName())
]).then(L.bind(function(data) {
var results = data[0],
local_wifi = data[1],
table = radio.table,
chan_analysis = radio.graph,
scanCache = radio.scanCache;
var rows = [];
for (var i = 0; i < results.length; i++) {
if (scanCache[results[i].bssid] == null)
scanCache[results[i].bssid] = {};
scanCache[results[i].bssid].data = results[i];
}
if (scanCache[local_wifi.bssid] == null)
scanCache[local_wifi.bssid] = {};
scanCache[local_wifi.bssid].data = local_wifi;
if (chan_analysis.offset_tbl[local_wifi.channel] != null && local_wifi.center_chan1) {
var center_channels = [local_wifi.center_chan1],
chan_width_text = local_wifi.htmode.replace(/(V)*HT/,''),
chan_width = parseInt(chan_width_text)/10;
if (local_wifi.center_chan2) {
center_channels.push(local_wifi.center_chan2);
chan_width = 8;
}
local_wifi.signal = -10;
local_wifi.ssid = 'Local Interface';
this.add_wifi_to_graph(chan_analysis, local_wifi, scanCache, center_channels, chan_width);
rows.push([
this.render_signal_badge(q, local_wifi.signal),
[
E('span', { 'style': 'color:'+scanCache[local_wifi.bssid].color }, '⬤ '),
local_wifi.ssid
],
'%d'.format(local_wifi.channel),
'%h MHz'.format(chan_width_text),
'%h'.format(local_wifi.mode),
'%h'.format(local_wifi.bssid)
]);
}
for (var k in scanCache)
if (scanCache[k].stale)
results.push(scanCache[k].data);
results.sort(function(a, b) {
if (a.channel - b.channel)
return 1;
if (a.ssid < b.ssid)
return -1;
else if (a.ssid > b.ssid)
return 1;
if (a.bssid < b.bssid)
return -1;
else if (a.bssid > b.bssid)
return 1;
});
for (var i = 0; i < results.length; i++) {
var res = results[i],
qv = res.quality || 0,
qm = res.quality_max || 0,
q = (qv > 0 && qm > 0) ? Math.floor((100 / qm) * qv) : 0,
s = res.stale ? 'opacity:0.5' : '',
center_channels = [res.channel],
chan_width = 2;
/* Skip WiFi not supported by the current band */
if (chan_analysis.offset_tbl[res.channel] == null)
continue;
res.channel_width = "20 MHz";
if (res.ht_operation != null)
if (res.ht_operation.channel_width == 2040) { /* 40 MHz Channel Enabled */
if (res.ht_operation.secondary_channel_offset == "below") {
res.channel_width = "40 MHz";
chan_width = 4; /* 40 MHz Channel Used */
center_channels[0] -= 2;
} else if (res.ht_operation.secondary_channel_offset == "above") {
res.channel_width = "40 MHz";
chan_width = 4; /* 40 MHz Channel Used */
center_channels[0] += 2;
} else {
res.channel_width = "20 MHz (40 MHz Intolerant)";
}
}
if (res.vht_operation != null) {
center_channels[0] = res.vht_operation.center_freq_1;
if (res.vht_operation.channel_width == 80) {
chan_width = 8;
res.channel_width = "80 MHz";
} else if (res.vht_operation.channel_width == 8080) {
res.channel_width = "80+80 MHz";
chan_width = 8;
center_channels.push(res.vht_operation.center_freq_2);
} else if (res.vht_operation.channel_width == 160) {
res.channel_width = "160 MHz";
chan_width = 16;
}
}
this.add_wifi_to_graph(chan_analysis, res, scanCache, center_channels, chan_width);
rows.push([
E('span', { 'style': s }, this.render_signal_badge(q, res.signal)),
E('span', { 'style': s }, [
E('span', { 'style': 'color:'+scanCache[results[i].bssid].color }, '⬤ '),
(res.ssid != null) ? '%h'.format(res.ssid) : E('em', _('hidden'))
]),
E('span', { 'style': s }, '%d'.format(res.channel)),
E('span', { 'style': s }, '%h'.format(res.channel_width)),
E('span', { 'style': s }, '%h'.format(res.mode)),
E('span', { 'style': s }, '%h'.format(res.bssid))
]);
res.stale = true;
}
cbi_update_table(table, rows);
if (!radio.loadedOnce) {
radio.loadedOnce = true;
poll.stop();
}
}, this))
},
radios : {},
loadSVG : function(src) {
return request.get(src).then(function(response) {
if (!response.ok)
throw new Error(response.statusText);
return E('div', {
'id': 'channel_graph',
'style': 'width:100%;height:400px;border:1px solid #000;background:#fff'
}, E(response.text()));
});
},
load: function() {
return Promise.all([
this.loadSVG(L.resource('svg/channel_analysis.svg')),
network.getWifiDevices().then(L.bind(function(data) {
var tasks = [], ret = [];
for (var i = 0; i < data.length; i++) {
ret[data[i].getName()] = { dev : data[i] };
tasks.push(this.callFrequencyList(data[i].getName())
.then(L.bind(function(radio, data) {
ret[radio.getName()].freq = data;
}, this, data[i])));
}
return Promise.all(tasks).then(function() { return ret; })
}, this))
]);
},
render: function(data) {
var svg = data[0],
wifiDevs = data[1];
var h2 = E('div', {'class' : 'cbi-title-section'}, [
E('h2', {'class': 'cbi-title-field'}, [ _('Channel Analysis') ]),
E('div', {'class': 'cbi-title-buttons' }, [
E('button', {
'class': 'cbi-button cbi-button-edit',
'click': ui.createHandlerFn(this, 'handleScanRefresh')
}, [ _('Refresh Channels') ])])
]);
var tabs = E('div', {}, E('div'));
for (var ifname in wifiDevs) {
var freq_tbl = {
['2.4GHz'] : [],
['5GHz'] : [],
};
/* Split FrequencyList in Bands */
wifiDevs[ifname].freq.forEach(function(freq) {
if (freq.mhz >= 5000) {
freq_tbl['5GHz'].push(freq.channel);
} else {
freq_tbl['2.4GHz'].push(freq.channel);
}
});
for (var freq in freq_tbl) {
if (freq_tbl[freq].length == 0)
continue;
var csvg = svg.cloneNode(true),
table = E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th col-2 middle center' }, _('Signal')),
E('th', { 'class': 'th col-4 middle left' }, _('SSID')),
E('th', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
E('th', { 'class': 'th col-3 middle left' }, _('Channel Width')),
E('th', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
E('th', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID'))
])
]),
tab = E('div', { 'data-tab': ifname+freq, 'data-tab-title': ifname+' ('+freq+')' },
[E('br'),csvg,E('br'),table,E('br')]),
graph_data = {
graph: csvg,
offset_tbl: {},
col_width: 0,
tab: tab,
};
this.radios[ifname+freq] = {
dev: wifiDevs[ifname].dev,
graph: graph_data,
table: table,
scanCache: {},
loadedOnce: false,
};
cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
tabs.firstElementChild.appendChild(tab)
requestAnimationFrame(L.bind(this.create_channel_graph, this, graph_data, freq_tbl[freq], freq));
}
}
ui.tabs.initTabGroup(tabs.firstElementChild.childNodes);
this.pollFn = L.bind(this.handleScanRefresh, this);
poll.add(this.pollFn);
return E('div', {}, [h2, tabs]);
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});