X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=modules%2Fluci-mod-network%2Fhtdocs%2Fluci-static%2Fresources%2Fview%2Fnetwork%2Fwireless.js;h=61838a23631c5d841656818427e32b431abf01f9;hb=c00d860981007a74d497f79640c446883bffe0ca;hp=b50008b5c8c3680d3adcb4b975f19aa03bc8838b;hpb=5e7e9b58cf68f88593a473cfea07e288b3a28f65;p=oweals%2Fluci.git diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js index b50008b5c..61838a236 100644 --- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js +++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js @@ -1,4 +1,6 @@ 'use strict'; +'require fs'; +'require ui'; 'require rpc'; 'require uci'; 'require form'; @@ -7,7 +9,7 @@ 'require tools.widgets as widgets'; function count_changes(section_id) { - var changes = L.ui.changes.changes, n = 0; + var changes = ui.changes.changes, n = 0; if (!L.isObject(changes)) return n; @@ -87,21 +89,22 @@ function render_network_status(radioNet) { var mode = radioNet.getActiveMode(), bssid = radioNet.getActiveBSSID(), channel = radioNet.getChannel(), - disabled = (radioNet.get('disabled') == '1'), + disabled = (radioNet.get('disabled') == '1' || uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == '1'), is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled), + is_mesh = (radioNet.getMode() == 'mesh'), changecount = count_changes(radioNet.getName()), status_text = null; if (changecount) status_text = E('a', { href: '#', - click: L.bind(L.ui.changes.displayChanges, L.ui.changes) + click: L.bind(ui.changes.displayChanges, ui.changes) }, _('Interface has %d pending changes').format(changecount)); else if (!is_assoc) status_text = E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated')); return L.itemlist(E('div'), [ - _('SSID'), radioNet.getSSID() || '?', + is_mesh ? _('Mesh ID') : _('SSID'), (is_mesh ? radioNet.getMeshID() : radioNet.getSSID()) || '?', _('Mode'), mode, _('BSSID'), (!changecount && is_assoc) ? bssid : null, _('Encryption'), (!changecount && is_assoc) ? radioNet.getActiveEncryption() || _('None') : null, @@ -194,7 +197,7 @@ function network_updown(id, map, ev) { } return map.save().then(function() { - L.ui.changes.apply() + ui.changes.apply() }); } @@ -225,7 +228,7 @@ var CBIWifiFrequencyValue = form.Value.extend({ '11a': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [] }; - for (var i = 0; Array.isArray(data[1]) && i < data[1].length; i++) + for (var i = 0; i < data[1].length; i++) this.channels[(data[1][i].mhz > 2484) ? '11a' : '11g'].push( data[1][i].channel, '%d (%d Mhz)'.format(data[1][i].channel, data[1][i].mhz), @@ -285,7 +288,7 @@ var CBIWifiFrequencyValue = form.Value.extend({ if (vals[i+2]) sel.add(E('option', { value: vals[i+0] }, [ vals[i+1] ])); - if (!isNaN(vals.selected)) + if (vals && !isNaN(vals.selected)) sel.selectedIndex = vals.selected; sel.parentNode.style.display = (sel.options.length <= 1) ? 'none' : ''; @@ -482,7 +485,7 @@ var CBIWifiCountryValue = form.Value.extend({ }, renderWidget: function(section_id, option_index, cfgvalue) { - var typeClass = this.keylist.length ? form.ListValue : form.Value; + var typeClass = (this.keylist && this.keylist.length) ? form.ListValue : form.Value; return typeClass.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]); } }); @@ -517,7 +520,7 @@ return L.view.extend({ btns[2].disabled = busy; } - var table = document.querySelector('wifi_assoclist_table'), + var table = document.querySelector('#wifi_assoclist_table'), hosts = data[0], trows = []; @@ -527,7 +530,16 @@ return L.view.extend({ ipv4 = hosts.getIPAddrByMACAddr(bss.mac), ipv6 = hosts.getIP6AddrByMACAddr(bss.mac); - trows.push([ + var hint; + + if (name && ipv4 && ipv6) + hint = '%s (%s, %s)'.format(name, ipv4, ipv6); + else if (name && (ipv4 || ipv6)) + hint = '%s (%s)'.format(name, ipv4 || ipv6); + else + hint = name || ipv4 || ipv6 || '?'; + + var row = [ E('span', { 'class': 'ifacebadge' }, [ E('img', { 'src': L.resource('icons/wifi%s.png').format(bss.network.isUp() ? '' : '_disabled'), @@ -537,17 +549,39 @@ return L.view.extend({ E('small', '(%s)'.format(bss.network.getIfname())) ]), bss.mac, - name ? '%s (%s)'.format(name, ipv4 || ipv6 || '?') : ipv4 || ipv6 || '?', + hint, render_signal_badge(Math.min((bss.signal + 110) / 70 * 100, 100), bss.signal, bss.noise), E('span', {}, [ E('span', format_wifirate(bss.rx)), E('br'), E('span', format_wifirate(bss.tx)) ]) - ]); + ]; + + if (bss.network.isClientDisconnectSupported()) { + if (table.firstElementChild.childNodes.length < 6) + table.firstElementChild.appendChild(E('div', { 'class': 'th nowrap right'}, [ _('Disconnect') ])); + + row.push(E('button', { + 'class': 'cbi-button cbi-button-remove', + 'click': L.bind(function(net, mac, ev) { + L.dom.parent(ev.currentTarget, '.tr').style.opacity = 0.5; + ev.currentTarget.classList.add('spinning'); + ev.currentTarget.disabled = true; + ev.currentTarget.blur(); + + net.disconnectClient(mac, true, 5, 60000); + }, this, bss.network, bss.mac) + }, [ _('Disconnect') ])); + } + else { + row.push('-'); + } + + trows.push(row); } - cbi_update_table('#wifi_assoclist_table', trows, E('em', _('No information available'))); + cbi_update_table(table, trows, E('em', _('No information available'))); var stat = document.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large'); @@ -603,18 +637,18 @@ return L.view.extend({ } return Promise.all(tasks) - .then(L.bind(L.ui.changes.init, L.ui.changes)) - .then(L.bind(L.ui.changes.apply, L.ui.changes)); + .then(L.bind(ui.changes.init, ui.changes)) + .then(L.bind(ui.changes.apply, ui.changes)); }, renderMigration: function() { - L.ui.showModal(_('Wireless configuration migration'), [ + ui.showModal(_('Wireless configuration migration'), [ E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')), E('p', _('Upon pressing "Continue", anonymous "wifi-iface" sections will be assigned with a name in the form wifinet# and the network will be restarted to apply the updated configuration.')), E('div', { 'class': 'right' }, E('button', { 'class': 'btn cbi-button-action important', - 'click': L.ui.createHandlerFn(this, 'handleMigration') + 'click': ui.createHandlerFn(this, 'handleMigration') }, _('Continue'))) ]); }, @@ -689,38 +723,39 @@ return L.view.extend({ E('button', { 'class': 'cbi-button cbi-button-neutral', 'title': _('Restart radio interface'), - 'click': L.ui.createHandlerFn(this, radio_restart, section_id) + 'click': ui.createHandlerFn(this, radio_restart, section_id) }, _('Restart')), E('button', { 'class': 'cbi-button cbi-button-action important', 'title': _('Find and join network'), - 'click': L.ui.createHandlerFn(this, 'handleScan', inst) + 'click': ui.createHandlerFn(this, 'handleScan', inst) }, _('Scan')), E('button', { 'class': 'cbi-button cbi-button-add', 'title': _('Provide new network'), - 'click': L.ui.createHandlerFn(this, 'handleAdd', inst) + 'click': ui.createHandlerFn(this, 'handleAdd', inst) }, _('Add')) ]; } else { - var isDisabled = (inst.get('disabled') == '1'); + var isDisabled = (inst.get('disabled') == '1' || + uci.get('wireless', inst.getWifiDeviceName(), 'disabled') == '1'); btns = [ E('button', { 'class': 'cbi-button cbi-button-neutral enable-disable', 'title': isDisabled ? _('Enable this network') : _('Disable this network'), - 'click': L.ui.createHandlerFn(this, network_updown, section_id, this.map) + 'click': ui.createHandlerFn(this, network_updown, section_id, this.map) }, isDisabled ? _('Enable') : _('Disable')), E('button', { 'class': 'cbi-button cbi-button-action important', 'title': _('Edit this network'), - 'click': L.ui.createHandlerFn(this, 'renderMoreOptionsModal', section_id) + 'click': ui.createHandlerFn(this, 'renderMoreOptionsModal', section_id) }, _('Edit')), E('button', { 'class': 'cbi-button cbi-button-negative remove', 'title': _('Delete this network'), - 'click': L.ui.createHandlerFn(this, 'handleRemove', section_id) + 'click': ui.createHandlerFn(this, 'handleRemove', section_id) }, _('Remove')) ]; } @@ -740,7 +775,8 @@ return L.view.extend({ ss.tab('general', _('General Setup')); ss.tab('advanced', _('Advanced Settings')); - var isDisabled = (radioNet.get('disabled') == '1'); + var isDisabled = (radioNet.get('disabled') == '1' || + uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == 1); o = ss.taboption('general', form.DummyValue, '_wifistat_modal', _('Status')); o.cfgvalue = L.bind(function(radioNet) { @@ -751,7 +787,7 @@ return L.view.extend({ o = ss.taboption('general', form.Button, '_toggle', isDisabled ? _('Wireless network is disabled') : _('Wireless network is enabled')); o.inputstyle = isDisabled ? 'apply' : 'reset'; o.inputtitle = isDisabled ? _('Enable') : _('Disable'); - o.onclick = L.ui.createHandlerFn(s, network_updown, s.section, s.map); + o.onclick = ui.createHandlerFn(s, network_updown, s.section, s.map); o = ss.taboption('general', CBIWifiFrequencyValue, '_freq', '
' + _('Operating frequency')); o.ucisection = s.section; @@ -893,7 +929,15 @@ return L.view.extend({ o.datatype = 'macaddr'; o.depends('macfilter', 'allow'); o.depends('macfilter', 'deny'); - //nt.mac_hints(function(mac, name) ml:value(mac, '%s (%s)' %{ mac, name }) end); + o.load = function(section_id) { + return network.getHostHints().then(L.bind(function(hints) { + hints.getMACHints().map(L.bind(function(hint) { + this.value(hint[0], hint[1] ? '%s (%s)'.format(hint[0], hint[1]) : hint[0]); + }, this)); + + return form.DynamicList.prototype.load.apply(this, [section_id]); + }, this)); + }; mode.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS'))); mode.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS'))); @@ -1037,9 +1081,7 @@ return L.view.extend({ }; - encr.value('none', _('No Encryption')); - encr.value('wep-open', _('WEP Open System')); - encr.value('wep-shared', _('WEP Shared Key')); + var crypto_modes = []; if (hwtype == 'mac80211') { var has_supplicant = L.hasSystemFeature('wpasupplicant'), @@ -1059,26 +1101,26 @@ return L.view.extend({ if (has_hostapd || has_supplicant) { - encr.value('psk', 'WPA-PSK'); - encr.value('psk2', 'WPA2-PSK'); - encr.value('psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode'); + crypto_modes.push(['psk2', 'WPA2-PSK', 33]); + crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]); + crypto_modes.push(['psk', 'WPA-PSK', 21]); } else { encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.'); } if (has_ap_sae || has_sta_sae) { - encr.value('sae', 'WPA3-SAE'); - encr.value('sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode'); + crypto_modes.push(['sae', 'WPA3-SAE', 31]); + crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]); } if (has_ap_eap || has_sta_eap) { - encr.value('wpa', 'WPA-EAP'); - encr.value('wpa2', 'WPA2-EAP'); + crypto_modes.push(['wpa2', 'WPA2-EAP', 32]); + crypto_modes.push(['wpa', 'WPA-EAP', 20]); } if (has_ap_owe || has_sta_owe) { - encr.value('owe', 'OWE'); + crypto_modes.push(['owe', 'OWE', 1]); } encr.crypto_support = { @@ -1145,9 +1187,23 @@ return L.view.extend({ }; } else if (hwtype == 'broadcom') { - encr.value('psk', 'WPA-PSK'); - encr.value('psk2', 'WPA2-PSK'); - encr.value('psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode'); + crypto_modes.push(['psk2', 'WPA2-PSK', 33]); + crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]); + crypto_modes.push(['psk', 'WPA-PSK', 21]); + } + + crypto_modes.push(['wep-open', _('WEP Open System'), 11]); + crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]); + crypto_modes.push(['none', _('No Encryption'), 0]); + + crypto_modes.sort(function(a, b) { return b[2] - a[2] }); + + for (var i = 0; i < crypto_modes.length; i++) { + var security_level = (crypto_modes[i][2] >= 30) ? _('strong security') + : (crypto_modes[i][2] >= 20) ? _('medium security') + : (crypto_modes[i][2] >= 10) ? _('weak security') : _('open network'); + + encr.value(crypto_modes[i][0], '%s (%s)'.format(crypto_modes[i][1], security_level)); } @@ -1243,6 +1299,9 @@ return L.view.extend({ o.write = function(section_id, value) { uci.set('wireless', section_id, 'key', value); uci.unset('wireless', section_id, 'key1'); + uci.unset('wireless', section_id, 'key2'); + uci.unset('wireless', section_id, 'key3'); + uci.unset('wireless', section_id, 'key4'); }; @@ -1256,7 +1315,7 @@ return L.view.extend({ o.cfgvalue = function(section_id) { var slot = +uci.get('wireless', section_id, 'key'); - return (slot >= 1 && slot <= 4) ? slot : 1; + return (slot >= 1 && slot <= 4) ? String(slot) : ''; }; o.write = function(section_id, value) { @@ -1507,8 +1566,7 @@ return L.view.extend({ // ieee802.11w options if (L.hasSystemFeature('hostapd', '11w')) { o = ss.taboption('encryption', form.ListValue, 'ieee80211w', _('802.11w Management Frame Protection'), _("Requires the 'full' version of wpad/hostapd and support from the wifi driver
(as of Jan 2019: ath9k, ath10k, mwlwifi and mt76)")); - o.default = ''; - o.value('', _('Disabled (default)')); + o.value('', _('Disabled')); o.value('1', _('Optional')); o.value('2', _('Required')); o.depends({ mode: 'ap', encryption: 'wpa2' }); @@ -1535,6 +1593,11 @@ return L.view.extend({ o.depends({ mode: 'sta-wds', encryption: 'sae' }); o.depends({ mode: 'sta-wds', encryption: 'sae-mixed' }); o.depends({ mode: 'sta-wds', encryption: 'owe' }); + o.defaults = { + '2': [{ encryption: 'sae' }, { encryption: 'owe' }], + '1': [{ encryption: 'sae-mixed'}], + '': [] + }; o = ss.taboption('encryption', form.Value, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout')); o.depends('ieee80211w', '1'); @@ -1564,13 +1627,15 @@ return L.view.extend({ o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' }); if (L.hasSystemFeature('hostapd', 'cli') && L.hasSystemFeature('wpasupplicant')) { - o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK')) + o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE')) o.enabled = '1'; o.disabled = '0'; o.default = o.disabled; o.depends('encryption', 'psk'); o.depends('encryption', 'psk2'); o.depends('encryption', 'psk-mixed'); + o.depends('encryption', 'sae'); + o.depends('encryption', 'sae-mixed'); } } } @@ -1597,7 +1662,7 @@ return L.view.extend({ cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...'))); - var md = L.ui.showModal(_('Join Network: Wireless Scan'), [ + var md = ui.showModal(_('Join Network: Wireless Scan'), [ table, E('div', { 'class': 'right' }, E('button', { @@ -1677,7 +1742,7 @@ return L.view.extend({ md.style.maxHeight = ''; } - L.ui.hideModal(); + ui.hideModal(); L.Poll.remove(this.pollFn); this.pollFn = null; @@ -1693,7 +1758,8 @@ return L.view.extend({ zoneval = zoneopt ? zoneopt.formvalue('_new_') : null, enc = L.isObject(bss.encryption) ? bss.encryption : null, is_wep = (enc && Array.isArray(enc.wep)), - is_psk = (enc && Array.isArray(enc.wpa) && Array.isArray(enc.authentication) && enc.authentication[0] == 'psk'); + is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' })), + is_sae = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'sae' })); if (nameval == null || (passopt && passval == null)) return; @@ -1701,15 +1767,23 @@ return L.view.extend({ var section_id = null; return this.map.save(function() { + var wifi_sections = uci.sections('wireless', 'wifi-iface'); + if (replopt.formvalue('_new_') == '1') { - var sections = uci.sections('wireless', 'wifi-iface'); + for (var i = 0; i < wifi_sections.length; i++) + if (wifi_sections[i].device == radioDev.getName()) + uci.remove('wireless', wifi_sections[i]['.name']); + } + + if (uci.get('wireless', radioDev.getName(), 'disabled') == '1') { + for (var i = 0; i < wifi_sections.length; i++) + if (wifi_sections[i].device == radioDev.getName()) + uci.set('wireless', wifi_sections[i]['.name'], 'disabled', '1'); - for (var i = 0; i < sections.length; i++) - if (sections[i].device == radioDev.getName()) - uci.remove('wireless', sections[i]['.name']); + uci.unset('wireless', radioDev.getName(), 'disabled'); } - section_id = next_free_sid(uci.sections('wifi-iface').length); + section_id = next_free_sid(wifi_sections.length); uci.add('wireless', 'wifi-iface', section_id); uci.set('wireless', section_id, 'device', radioDev.getName()); @@ -1721,7 +1795,11 @@ return L.view.extend({ else if (bss.bssid != null) uci.set('wireless', section_id, 'bssid', bss.bssid); - if (is_psk) { + if (is_sae) { + uci.set('wireless', section_id, 'encryption', 'sae'); + uci.set('wireless', section_id, 'key', passval); + } + else if (is_psk) { for (var i = enc.wpa.length - 1; i >= 0; i--) { if (enc.wpa[i] == 2) { uci.set('wireless', section_id, 'encryption', 'psk2'); @@ -1741,14 +1819,14 @@ return L.view.extend({ uci.set('wireless', section_id, 'key1', passval); } - var zonePromise = zoneval - ? firewall.getZone(zoneval).then(function(zone) { return zone || firewall.addZone(zoneval) }) - : Promise.resolve(); + return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) { + firewall.deleteNetwork(net.getName()); - return zonePromise.then(function(zone) { - return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) { - firewall.deleteNetwork(net.getName()); + var zonePromise = zoneval + ? firewall.getZone(zoneval).then(function(zone) { return zone || firewall.addZone(zoneval) }) + : Promise.resolve(); + return zonePromise.then(function(zone) { if (zone) zone.addNetwork(net.getName()); }); @@ -1765,7 +1843,7 @@ return L.view.extend({ s2 = m2.section(form.NamedSection, '_new_'), enc = L.isObject(bss.encryption) ? bss.encryption : null, is_wep = (enc && Array.isArray(enc.wep)), - is_psk = (enc && Array.isArray(enc.wpa) && Array.isArray(enc.authentication) && enc.authentication[0] == 'psk'), + is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' || a == 'sae' })), replace, passphrase, name, zone; s2.render = function() { @@ -1802,16 +1880,16 @@ return L.view.extend({ zone.default = 'wan'; return m2.render().then(L.bind(function(nodes) { - L.ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [ + ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [ nodes, E('div', { 'class': 'right' }, [ E('button', { 'class': 'btn', - 'click': L.ui.hideModal + 'click': ui.hideModal }, _('Cancel')), ' ', E('button', { 'class': 'cbi-button cbi-button-positive important', - 'click': L.ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2) + 'click': ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2) }, _('Submit')) ]) ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase || name).cbid('_new_'))).focus(); @@ -1872,11 +1950,9 @@ return L.view.extend({ if (dsc.getAttribute('restart') == '') { dsc.setAttribute('restart', '1'); - tasks.push(L.Request.post( - L.url('admin/network/wireless_reconnect', section_ids[i]), - 'token=' + L.env.token, - { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } } - ).catch(function() {})); + tasks.push(fs.exec('/sbin/wifi', ['up', section_ids[i]]).catch(function(e) { + ui.addNotification(null, E('p', e.message)); + })); } else if (dsc.getAttribute('restart') == '1') { dsc.removeAttribute('restart'); @@ -1928,7 +2004,7 @@ return L.view.extend({ E('div', { 'class': 'tr table-titles' }, [ E('div', { 'class': 'th nowrap' }, _('Network')), E('div', { 'class': 'th hide-xs' }, _('MAC-Address')), - E('div', { 'class': 'th nowrap' }, _('Host')), + E('div', { 'class': 'th' }, _('Host')), E('div', { 'class': 'th nowrap' }, _('Signal / Noise')), E('div', { 'class': 'th nowrap' }, _('RX Rate / TX Rate')) ])