Merge pull request #4508 from Ansuel/wpscontrol
luci-mod-status: add WPS control for wifi info
This commit is contained in:
commit
40acd95726
4 changed files with 166 additions and 94 deletions
|
@ -219,7 +219,7 @@ local methods = {
|
||||||
rv.cabundle = fs.access("/etc/ssl/certs/ca-certificates.crt")
|
rv.cabundle = fs.access("/etc/ssl/certs/ca-certificates.crt")
|
||||||
rv.relayd = fs.access("/usr/sbin/relayd")
|
rv.relayd = fs.access("/usr/sbin/relayd")
|
||||||
|
|
||||||
local wifi_features = { "eap", "11n", "11ac", "11r", "11w", "acs", "sae", "owe", "suiteb192", "wep" }
|
local wifi_features = { "eap", "11n", "11ac", "11r", "11w", "acs", "sae", "owe", "suiteb192", "wep", "wps }
|
||||||
|
|
||||||
if fs.access("/usr/sbin/hostapd") then
|
if fs.access("/usr/sbin/hostapd") then
|
||||||
rv.hostapd = { cli = fs.access("/usr/sbin/hostapd_cli") }
|
rv.hostapd = { cli = fs.access("/usr/sbin/hostapd_cli") }
|
||||||
|
|
|
@ -1634,7 +1634,7 @@ return view.extend({
|
||||||
o = ss.taboption('encryption', form.Flag, 'wpa_disable_eapol_key_retries', _('Enable key reinstallation (KRACK) countermeasures'), _('Complicates key reinstallation attacks on the client side by disabling retransmission of EAPOL-Key frames that are used to install keys. This workaround might cause interoperability issues and reduced robustness of key negotiation especially in environments with heavy traffic load.'));
|
o = ss.taboption('encryption', form.Flag, 'wpa_disable_eapol_key_retries', _('Enable key reinstallation (KRACK) countermeasures'), _('Complicates key reinstallation attacks on the client side by disabling retransmission of EAPOL-Key frames that are used to install keys. This workaround might cause interoperability issues and reduced robustness of key negotiation especially in environments with heavy traffic load.'));
|
||||||
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
|
add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
|
||||||
|
|
||||||
if (L.hasSystemFeature('hostapd', 'cli') && L.hasSystemFeature('wpasupplicant')) {
|
if (L.hasSystemFeature('hostapd', 'wps') && L.hasSystemFeature('wpasupplicant')) {
|
||||||
o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
|
o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
|
||||||
o.enabled = '1';
|
o.enabled = '1';
|
||||||
o.disabled = '0';
|
o.disabled = '0';
|
||||||
|
|
|
@ -2,16 +2,83 @@
|
||||||
'require baseclass';
|
'require baseclass';
|
||||||
'require dom';
|
'require dom';
|
||||||
'require network';
|
'require network';
|
||||||
|
'require uci';
|
||||||
|
'require fs';
|
||||||
'require rpc';
|
'require rpc';
|
||||||
|
|
||||||
var callSessionAccess = rpc.declare({
|
return baseclass.extend({
|
||||||
|
title: _('Wireless'),
|
||||||
|
|
||||||
|
WPSTranslateTbl: {
|
||||||
|
Disabled: _('Disabled'),
|
||||||
|
Active: _('Active'),
|
||||||
|
'Timed-out': _('Timed-out'),
|
||||||
|
Overlap: _('Overlap'),
|
||||||
|
Unknown: _('Unknown')
|
||||||
|
},
|
||||||
|
|
||||||
|
callSessionAccess: rpc.declare({
|
||||||
object: 'session',
|
object: 'session',
|
||||||
method: 'access',
|
method: 'access',
|
||||||
params: [ 'scope', 'object', 'function' ],
|
params: [ 'scope', 'object', 'function' ],
|
||||||
expect: { 'access': false }
|
expect: { 'access': false }
|
||||||
});
|
}),
|
||||||
|
|
||||||
function renderbox(radio, networks) {
|
wifirate: function(rt) {
|
||||||
|
var s = '%.1f\xa0%s, %d\xa0%s'.format(rt.rate / 1000, _('Mbit/s'), rt.mhz, _('MHz')),
|
||||||
|
ht = rt.ht, vht = rt.vht,
|
||||||
|
mhz = rt.mhz, nss = rt.nss,
|
||||||
|
mcs = rt.mcs, sgi = rt.short_gi;
|
||||||
|
|
||||||
|
if (ht || vht) {
|
||||||
|
if (vht) s += ', VHT-MCS\xa0%d'.format(mcs);
|
||||||
|
if (nss) s += ', VHT-NSS\xa0%d'.format(nss);
|
||||||
|
if (ht) s += ', MCS\xa0%s'.format(mcs);
|
||||||
|
if (sgi) s += ', ' + _('Short GI').replace(/ /g, '\xa0');
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleDelClient: function(wifinet, mac, ev) {
|
||||||
|
dom.parent(ev.currentTarget, '.tr').style.opacity = 0.5;
|
||||||
|
ev.currentTarget.classList.add('spinning');
|
||||||
|
ev.currentTarget.disabled = true;
|
||||||
|
ev.currentTarget.blur();
|
||||||
|
|
||||||
|
wifinet.disconnectClient(mac, true, 5, 60000);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleGetWPSStatus: function(wifinet) {
|
||||||
|
return rpc.declare({
|
||||||
|
object: 'hostapd.%s'.format(wifinet),
|
||||||
|
method: 'wps_status',
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
|
||||||
|
handleCallWPS: function(wifinet, ev) {
|
||||||
|
ev.currentTarget.classList.add('spinning');
|
||||||
|
ev.currentTarget.disabled = true;
|
||||||
|
ev.currentTarget.blur();
|
||||||
|
|
||||||
|
return rpc.declare({
|
||||||
|
object: 'hostapd.%s'.format(wifinet),
|
||||||
|
method: 'wps_start',
|
||||||
|
})();
|
||||||
|
},
|
||||||
|
|
||||||
|
handleCancelWPS: function(wifinet, ev) {
|
||||||
|
ev.currentTarget.classList.add('spinning');
|
||||||
|
ev.currentTarget.disabled = true;
|
||||||
|
ev.currentTarget.blur();
|
||||||
|
|
||||||
|
return rpc.declare({
|
||||||
|
object: 'hostapd.%s'.format(wifinet),
|
||||||
|
method: 'wps_cancel',
|
||||||
|
})();
|
||||||
|
},
|
||||||
|
|
||||||
|
renderbox: function(radio, networks) {
|
||||||
var chan = null,
|
var chan = null,
|
||||||
freq = null,
|
freq = null,
|
||||||
rate = null,
|
rate = null,
|
||||||
|
@ -36,6 +103,22 @@ function renderbox(radio, networks) {
|
||||||
else
|
else
|
||||||
icon = L.resource('icons/signal-75-100.png');
|
icon = L.resource('icons/signal-75-100.png');
|
||||||
|
|
||||||
|
var WPS_button;
|
||||||
|
|
||||||
|
if (this.isWPSEnabled[net.sid]) {
|
||||||
|
if (net.wps_status == 'Active') {
|
||||||
|
WPS_button = E('button', {
|
||||||
|
'class' : 'cbi-button cbi-button-remove',
|
||||||
|
'click': L.bind(this.handleCancelWPS, this, net.getIfname()),
|
||||||
|
}, [ _('Stop WPS') ])
|
||||||
|
} else {
|
||||||
|
WPS_button = E('button', {
|
||||||
|
'class' : 'cbi-button cbi-button-apply',
|
||||||
|
'click': L.bind(this.handleCallWPS, this, net.getIfname()),
|
||||||
|
}, [ _('Start WPS') ])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var badge = renderBadge(
|
var badge = renderBadge(
|
||||||
icon,
|
icon,
|
||||||
'%s: %d dBm / %s: %d%%'.format(_('Signal'), net.getSignal(), _('Quality'), quality),
|
'%s: %d dBm / %s: %d%%'.format(_('Signal'), net.getSignal(), _('Quality'), quality),
|
||||||
|
@ -44,7 +127,10 @@ function renderbox(radio, networks) {
|
||||||
_('BSSID'), is_assoc ? (net.getActiveBSSID() || '-') : null,
|
_('BSSID'), is_assoc ? (net.getActiveBSSID() || '-') : null,
|
||||||
_('Encryption'), is_assoc ? net.getActiveEncryption() : null,
|
_('Encryption'), is_assoc ? net.getActiveEncryption() : null,
|
||||||
_('Associations'), is_assoc ? (net.assoclist.length || '-') : null,
|
_('Associations'), is_assoc ? (net.assoclist.length || '-') : null,
|
||||||
null, is_assoc ? null : E('em', net.isDisabled() ? _('Wireless is disabled') : _('Wireless is not associated')));
|
null, is_assoc ? null : E('em', net.isDisabled() ? _('Wireless is disabled') : _('Wireless is not associated')),
|
||||||
|
_('WPS status'), this.WPSTranslateTbl[net.wps_status],
|
||||||
|
'', WPS_button
|
||||||
|
);
|
||||||
|
|
||||||
badges.push(badge);
|
badges.push(badge);
|
||||||
|
|
||||||
|
@ -60,60 +146,46 @@ function renderbox(radio, networks) {
|
||||||
L.itemlist(E('span'), [
|
L.itemlist(E('span'), [
|
||||||
_('Type'), radio.getI18n().replace(/^Generic | Wireless Controller .+$/g, ''),
|
_('Type'), radio.getI18n().replace(/^Generic | Wireless Controller .+$/g, ''),
|
||||||
_('Channel'), chan ? '%d (%.3f %s)'.format(chan, freq, _('GHz')) : '-',
|
_('Channel'), chan ? '%d (%.3f %s)'.format(chan, freq, _('GHz')) : '-',
|
||||||
_('Bitrate'), rate ? '%d %s'.format(rate, _('Mbit/s')) : '-'
|
_('Bitrate'), rate ? '%d %s'.format(rate, _('Mbit/s')) : '-',
|
||||||
]),
|
]),
|
||||||
E('div', {}, badges)
|
E('div', {}, badges)
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
}
|
|
||||||
|
|
||||||
function wifirate(rt) {
|
|
||||||
var s = '%.1f\xa0%s, %d\xa0%s'.format(rt.rate / 1000, _('Mbit/s'), rt.mhz, _('MHz')),
|
|
||||||
ht = rt.ht, vht = rt.vht,
|
|
||||||
mhz = rt.mhz, nss = rt.nss,
|
|
||||||
mcs = rt.mcs, sgi = rt.short_gi;
|
|
||||||
|
|
||||||
if (ht || vht) {
|
|
||||||
if (vht) s += ', VHT-MCS\xa0%d'.format(mcs);
|
|
||||||
if (nss) s += ', VHT-NSS\xa0%d'.format(nss);
|
|
||||||
if (ht) s += ', MCS\xa0%s'.format(mcs);
|
|
||||||
if (sgi) s += ', ' + _('Short GI').replace(/ /g, '\xa0');
|
|
||||||
}
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
return baseclass.extend({
|
|
||||||
title: _('Wireless'),
|
|
||||||
|
|
||||||
handleDelClient: function(wifinet, mac, ev) {
|
|
||||||
dom.parent(ev.currentTarget, '.tr').style.opacity = 0.5;
|
|
||||||
ev.currentTarget.classList.add('spinning');
|
|
||||||
ev.currentTarget.disabled = true;
|
|
||||||
ev.currentTarget.blur();
|
|
||||||
|
|
||||||
wifinet.disconnectClient(mac, true, 5, 60000);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isWPSEnabled: {},
|
||||||
|
|
||||||
load: function() {
|
load: function() {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
network.getWifiDevices(),
|
network.getWifiDevices(),
|
||||||
network.getWifiNetworks(),
|
network.getWifiNetworks(),
|
||||||
network.getHostHints(),
|
network.getHostHints(),
|
||||||
callSessionAccess('access-group', 'luci-mod-status-index-wifi', 'read'),
|
this.callSessionAccess('access-group', 'luci-mod-status-index-wifi', 'read'),
|
||||||
callSessionAccess('access-group', 'luci-mod-status-index-wifi', 'write')
|
this.callSessionAccess('access-group', 'luci-mod-status-index-wifi', 'write'),
|
||||||
]).then(function(radios_networks_hints) {
|
uci.load('wireless')
|
||||||
var tasks = [];
|
]).then(L.bind(function(data) {
|
||||||
|
var tasks = [],
|
||||||
|
radios_networks_hints = data[1],
|
||||||
|
hasWPS = L.hasSystemFeature('hostapd', 'wps');
|
||||||
|
|
||||||
for (var i = 0; i < radios_networks_hints[1].length; i++)
|
for (var i = 0; i < radios_networks_hints.length; i++) {
|
||||||
tasks.push(L.resolveDefault(radios_networks_hints[1][i].getAssocList(), []).then(L.bind(function(net, list) {
|
tasks.push(L.resolveDefault(radios_networks_hints[i].getAssocList(), []).then(L.bind(function(net, list) {
|
||||||
net.assoclist = list.sort(function(a, b) { return a.mac > b.mac });
|
net.assoclist = list.sort(function(a, b) { return a.mac > b.mac });
|
||||||
}, this, radios_networks_hints[1][i])));
|
}, this, radios_networks_hints[i])));
|
||||||
|
|
||||||
|
if (hasWPS && uci.get('wireless', radios_networks_hints[i].sid, 'wps_pushbutton') == '1') {
|
||||||
|
this.isWPSEnabled[radios_networks_hints[i].sid] = true;
|
||||||
|
tasks.push(L.resolveDefault(this.handleGetWPSStatus(radios_networks_hints[i].getIfname()), null)
|
||||||
|
.then(L.bind(function(net, data) {
|
||||||
|
net.wps_status = data ? data.pbc_status : _('No Data');
|
||||||
|
}, this, radios_networks_hints[i])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.all(tasks).then(function() {
|
return Promise.all(tasks).then(function() {
|
||||||
return radios_networks_hints;
|
return data;
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
}, this));
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function(data) {
|
render: function(data) {
|
||||||
|
@ -127,7 +199,7 @@ return baseclass.extend({
|
||||||
var table = E('div', { 'class': 'network-status-table' });
|
var table = E('div', { 'class': 'network-status-table' });
|
||||||
|
|
||||||
for (var i = 0; i < radios.sort(function(a, b) { a.getName() > b.getName() }).length; i++)
|
for (var i = 0; i < radios.sort(function(a, b) { a.getName() > b.getName() }).length; i++)
|
||||||
table.appendChild(renderbox(radios[i],
|
table.appendChild(this.renderbox(radios[i],
|
||||||
networks.filter(function(net) { return net.getWifiDeviceName() == radios[i].getName() })));
|
networks.filter(function(net) { return net.getWifiDeviceName() == radios[i].getName() })));
|
||||||
|
|
||||||
if (!table.lastElementChild)
|
if (!table.lastElementChild)
|
||||||
|
@ -215,9 +287,9 @@ return baseclass.extend({
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
E('span', {}, [
|
E('span', {}, [
|
||||||
E('span', wifirate(bss.rx)),
|
E('span', this.wifirate(bss.rx)),
|
||||||
E('br'),
|
E('br'),
|
||||||
E('span', wifirate(bss.tx))
|
E('span', this.wifirate(bss.tx))
|
||||||
])
|
])
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -125,7 +125,7 @@
|
||||||
},
|
},
|
||||||
"write": {
|
"write": {
|
||||||
"ubus": {
|
"ubus": {
|
||||||
"hostapd.*": [ "del_client" ]
|
"hostapd.*": [ "del_client", "wps_start", "wps_cancel", "wps_status" ]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue