'use strict'; 'require view'; 'require dom'; 'require poll'; 'require fs'; 'require ui'; 'require rpc'; 'require uci'; 'require form'; 'require network'; 'require firewall'; 'require tools.widgets as widgets'; var isReadonlyView = !L.hasViewPermission(); function count_changes(section_id) { var changes = ui.changes.changes, n = 0; if (!L.isObject(changes)) return n; if (Array.isArray(changes.wireless)) for (var i = 0; i < changes.wireless.length; i++) n += (changes.wireless[i][1] == section_id); return n; } function render_radio_badge(radioDev) { return E('span', { 'class': 'ifacebadge' }, [ E('img', { 'src': L.resource('icons/wifi%s.png').format(radioDev.isUp() ? '' : '_disabled') }), ' ', radioDev.getName() ]); } function render_signal_badge(signalPercent, signalValue, noiseValue, wrap, mode) { 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'); if (signalValue != null && signalValue != 0) { if (noiseValue != null && noiseValue != 0) { value = '%d/%d\xa0%s'.format(signalValue, noiseValue, _('dBm')); title = '%s: %d %s / %s: %d %s / %s %d'.format( _('Signal'), signalValue, _('dBm'), _('Noise'), noiseValue, _('dBm'), _('SNR'), signalValue - noiseValue); } else { value = '%d\xa0%s'.format(signalValue, _('dBm')); title = '%s: %d %s'.format(_('Signal'), signalValue, _('dBm')); } } else if (signalPercent > -1) { switch (mode) { case 'ap': title = _('No client associated'); break; case 'sta': case 'adhoc': case 'mesh': title = _('Not associated'); break; default: title = _('No RX signal'); } if (noiseValue != null && noiseValue != 0) { value = '---/%d\x0a%s'.format(noiseValue, _('dBm')); title = '%s / %s: %d %s'.format(title, _('Noise'), noiseValue, _('dBm')); } else { value = '---\xa0%s'.format(_('dBm')); } } else { value = E('em', {}, E('small', {}, [ _('disabled') ])); title = _('Interface is disabled'); } return E('div', { 'class': wrap ? 'center' : 'ifacebadge', 'title': title, 'data-signal': signalValue, 'data-noise': noiseValue }, [ E('img', { 'src': icon }), E('span', {}, [ wrap ? E('br') : ' ', value ]) ]); } function render_network_badge(radioNet) { return render_signal_badge( radioNet.isUp() ? radioNet.getSignalPercent() : -1, radioNet.getSignal(), radioNet.getNoise(), false, radioNet.getMode()); } function render_radio_status(radioDev, wifiNets) { var name = radioDev.getI18n().replace(/ Wireless Controller .+$/, ''), node = E('div', [ E('big', {}, E('strong', {}, name)), E('div') ]), channel, frequency, bitrate; for (var i = 0; i < wifiNets.length; i++) { channel = channel || wifiNets[i].getChannel(); frequency = frequency || wifiNets[i].getFrequency(); bitrate = bitrate || wifiNets[i].getBitRate(); } if (radioDev.isUp()) L.itemlist(node.lastElementChild, [ _('Channel'), '%s (%s %s)'.format(channel || '?', frequency || '?', _('GHz')), _('Bitrate'), '%s %s'.format(bitrate || '?', _('Mbit/s')) ], ' | '); else node.lastElementChild.appendChild(E('em', _('Device is not active'))); return node; } function render_network_status(radioNet) { var mode = radioNet.getActiveMode(), bssid = radioNet.getActiveBSSID(), channel = radioNet.getChannel(), 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(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'), [ 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, null, status_text ], [ ' | ', E('br') ]); } function render_modal_status(node, radioNet) { var mode = radioNet.getActiveMode(), noise = radioNet.getNoise(), bssid = radioNet.getActiveBSSID(), channel = radioNet.getChannel(), disabled = (radioNet.get('disabled') == '1'), is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled); if (node == null) node = E('span', { 'class': 'ifacebadge large', 'data-network': radioNet.getName() }, [ E('small'), E('span') ]); dom.content(node.firstElementChild, render_signal_badge( disabled ? -1 : radioNet.getSignalPercent(), radioNet.getSignal(), noise, true, radioNet.getMode())); L.itemlist(node.lastElementChild, [ _('Mode'), mode, _('SSID'), radioNet.getSSID() || '?', _('BSSID'), is_assoc ? bssid : null, _('Encryption'), is_assoc ? radioNet.getActiveEncryption() || _('None') : null, _('Channel'), is_assoc ? '%d (%.3f %s)'.format(radioNet.getChannel(), radioNet.getFrequency() || 0, _('GHz')) : null, _('Tx-Power'), is_assoc ? '%d %s'.format(radioNet.getTXPower(), _('dBm')) : null, _('Signal'), is_assoc ? '%d %s'.format(radioNet.getSignal(), _('dBm')) : null, _('Noise'), (is_assoc && noise != null) ? '%d %s'.format(noise, _('dBm')) : null, _('Bitrate'), is_assoc ? '%.1f %s'.format(radioNet.getBitRate() || 0, _('Mbit/s')) : null, _('Country'), is_assoc ? radioNet.getCountryCode() : null ], [ ' | ', E('br'), E('br'), E('br'), E('br'), E('br'), ' | ', E('br'), ' | ' ]); if (!is_assoc) dom.append(node.lastElementChild, E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated'))); return node; } function format_wifirate(rate) { var s = '%.1f\xa0%s, %d\xa0%s'.format(rate.rate / 1000, _('Mbit/s'), rate.mhz, _('MHz')), ht = rate.ht, vht = rate.vht, mhz = rate.mhz, nss = rate.nss, mcs = rate.mcs, sgi = rate.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; } function radio_restart(id, ev) { var row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id)), dsc = row.querySelector('[data-name="_stat"] > div'), btn = row.querySelector('.cbi-section-actions button'); btn.blur(); btn.classList.add('spinning'); btn.disabled = true; dsc.setAttribute('restart', ''); dom.content(dsc, E('em', _('Device is restarting…'))); } function network_updown(id, map, ev) { var radio = uci.get('wireless', id, 'device'), disabled = (uci.get('wireless', id, 'disabled') == '1') || (uci.get('wireless', radio, 'disabled') == '1'); if (disabled) { uci.unset('wireless', id, 'disabled'); uci.unset('wireless', radio, 'disabled'); } else { uci.set('wireless', id, 'disabled', '1'); var all_networks_disabled = true, wifi_ifaces = uci.sections('wireless', 'wifi-iface'); for (var i = 0; i < wifi_ifaces.length; i++) { if (wifi_ifaces[i].device == radio && wifi_ifaces[i].disabled != '1') { all_networks_disabled = false; break; } } if (all_networks_disabled) uci.set('wireless', radio, 'disabled', '1'); } return map.save().then(function() { ui.changes.apply() }); } function next_free_sid(offset) { var sid = 'wifinet' + offset; while (uci.get('wireless', sid)) sid = 'wifinet' + (++offset); return sid; } function add_dependency_permutations(o, deps) { var res = null; for (var key in deps) { if (!deps.hasOwnProperty(key) || !Array.isArray(deps[key])) continue; var list = deps[key], tmp = []; for (var j = 0; j < list.length; j++) { for (var k = 0; k < (res ? res.length : 1); k++) { var item = (res ? Object.assign({}, res[k]) : {}); item[key] = list[j]; tmp.push(item); } } res = tmp; } for (var i = 0; i < (res ? res.length : 0); i++) o.depends(res[i]); } var CBIWifiFrequencyValue = form.Value.extend({ callFrequencyList: rpc.declare({ object: 'iwinfo', method: 'freqlist', params: [ 'device' ], expect: { results: [] } }), load: function(section_id) { return Promise.all([ network.getWifiDevice(section_id), this.callFrequencyList(section_id) ]).then(L.bind(function(data) { this.channels = { '11g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [], '11a': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [] }; 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), !data[1][i].restricted ); var hwmodelist = L.toArray(data[0] ? data[0].getHWModes() : null) .reduce(function(o, v) { o[v] = true; return o }, {}); this.modes = [ '', 'Legacy', true, 'n', 'N', hwmodelist.n, 'ac', 'AC', hwmodelist.ac ]; var htmodelist = L.toArray(data[0] ? data[0].getHTModes() : null) .reduce(function(o, v) { o[v] = true; return o }, {}); this.htmodes = { '': [ '', '-', true ], 'n': [ 'HT20', '20 MHz', htmodelist.HT20, 'HT40', '40 MHz', htmodelist.HT40 ], 'ac': [ 'VHT20', '20 MHz', htmodelist.VHT20, 'VHT40', '40 MHz', htmodelist.VHT40, 'VHT80', '80 MHz', htmodelist.VHT80, 'VHT160', '160 MHz', htmodelist.VHT160 ] }; this.bands = { '': [ '11g', '2.4 GHz', this.channels['11g'].length > 3, '11a', '5 GHz', this.channels['11a'].length > 3 ], 'n': [ '11g', '2.4 GHz', this.channels['11g'].length > 3, '11a', '5 GHz', this.channels['11a'].length > 3 ], 'ac': [ '11a', '5 GHz', true ] }; }, this)); }, setValues: function(sel, vals) { if (sel.vals) sel.vals.selected = sel.selectedIndex; while (sel.options[0]) sel.remove(0); for (var i = 0; vals && i < vals.length; i += 3) if (vals[i+2]) sel.add(E('option', { value: vals[i+0] }, [ vals[i+1] ])); if (vals && !isNaN(vals.selected)) sel.selectedIndex = vals.selected; sel.parentNode.style.display = (sel.options.length <= 1) ? 'none' : ''; sel.vals = vals; }, toggleWifiMode: function(elem) { this.toggleWifiHTMode(elem); this.toggleWifiBand(elem); }, toggleWifiHTMode: function(elem) { var mode = elem.querySelector('.mode'); var bwdt = elem.querySelector('.htmode'); this.setValues(bwdt, this.htmodes[mode.value]); }, toggleWifiBand: function(elem) { var mode = elem.querySelector('.mode'); var band = elem.querySelector('.band'); this.setValues(band, this.bands[mode.value]); this.toggleWifiChannel(elem); }, toggleWifiChannel: function(elem) { var band = elem.querySelector('.band'); var chan = elem.querySelector('.channel'); this.setValues(chan, this.channels[band.value]); }, setInitialValues: function(section_id, elem) { var mode = elem.querySelector('.mode'), band = elem.querySelector('.band'), chan = elem.querySelector('.channel'), bwdt = elem.querySelector('.htmode'), htval = uci.get('wireless', section_id, 'htmode'), hwval = uci.get('wireless', section_id, 'hwmode'), chval = uci.get('wireless', section_id, 'channel'); this.setValues(mode, this.modes); if (/VHT20|VHT40|VHT80|VHT160/.test(htval)) mode.value = 'ac'; else if (/HT20|HT40/.test(htval)) mode.value = 'n'; else mode.value = ''; this.toggleWifiMode(elem); if (/a/.test(hwval)) band.value = '11a'; else band.value = '11g'; this.toggleWifiBand(elem); bwdt.value = htval; chan.value = chval; return elem; }, renderWidget: function(section_id, option_index, cfgvalue) { var elem = E('div'); dom.content(elem, [ E('label', { 'style': 'float:left; margin-right:3px' }, [ _('Mode'), E('br'), E('select', { 'class': 'mode', 'style': 'width:auto', 'change': L.bind(this.toggleWifiMode, this, elem), 'disabled': (this.disabled != null) ? this.disabled : this.map.readonly }) ]), E('label', { 'style': 'float:left; margin-right:3px' }, [ _('Band'), E('br'), E('select', { 'class': 'band', 'style': 'width:auto', 'change': L.bind(this.toggleWifiBand, this, elem), 'disabled': (this.disabled != null) ? this.disabled : this.map.readonly }) ]), E('label', { 'style': 'float:left; margin-right:3px' }, [ _('Channel'), E('br'), E('select', { 'class': 'channel', 'style': 'width:auto', 'disabled': (this.disabled != null) ? this.disabled : this.map.readonly }) ]), E('label', { 'style': 'float:left; margin-right:3px' }, [ _('Width'), E('br'), E('select', { 'class': 'htmode', 'style': 'width:auto', 'disabled': (this.disabled != null) ? this.disabled : this.map.readonly }) ]), E('br', { 'style': 'clear:left' }) ]); return this.setInitialValues(section_id, elem); }, cfgvalue: function(section_id) { return [ uci.get('wireless', section_id, 'htmode'), uci.get('wireless', section_id, 'hwmode'), uci.get('wireless', section_id, 'channel') ]; }, formvalue: function(section_id) { var node = this.map.findElement('data-field', this.cbid(section_id)); return [ node.querySelector('.htmode').value, node.querySelector('.band').value, node.querySelector('.channel').value ]; }, write: function(section_id, value) { uci.set('wireless', section_id, 'htmode', value[0] || null); uci.set('wireless', section_id, 'hwmode', value[1]); uci.set('wireless', section_id, 'channel', value[2]); } }); var CBIWifiTxPowerValue = form.ListValue.extend({ callTxPowerList: rpc.declare({ object: 'iwinfo', method: 'txpowerlist', params: [ 'device' ], expect: { results: [] } }), load: function(section_id) { return this.callTxPowerList(section_id).then(L.bind(function(pwrlist) { this.powerval = this.wifiNetwork ? this.wifiNetwork.getTXPower() : null; this.poweroff = this.wifiNetwork ? this.wifiNetwork.getTXPowerOffset() : null; this.value('', _('driver default')); for (var i = 0; i < pwrlist.length; i++) this.value(pwrlist[i].dbm, '%d dBm (%d mW)'.format(pwrlist[i].dbm, pwrlist[i].mw)); return form.ListValue.prototype.load.apply(this, [section_id]); }, this)); }, renderWidget: function(section_id, option_index, cfgvalue) { var widget = form.ListValue.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]); widget.firstElementChild.style.width = 'auto'; dom.append(widget, E('span', [ ' - ', _('Current power'), ': ', E('span', [ this.powerval != null ? '%d dBm'.format(this.powerval) : E('em', _('unknown')) ]), this.poweroff ? ' + %d dB offset = %s dBm'.format(this.poweroff, this.powerval != null ? this.powerval + this.poweroff : '?') : '' ])); return widget; } }); var CBIWifiCountryValue = form.Value.extend({ callCountryList: rpc.declare({ object: 'iwinfo', method: 'countrylist', params: [ 'device' ], expect: { results: [] } }), load: function(section_id) { return this.callCountryList(section_id).then(L.bind(function(countrylist) { if (Array.isArray(countrylist) && countrylist.length > 0) { this.value('', _('driver default')); for (var i = 0; i < countrylist.length; i++) this.value(countrylist[i].iso3166, '%s - %s'.format(countrylist[i].iso3166, countrylist[i].country)); } return form.Value.prototype.load.apply(this, [section_id]); }, this)); }, validate: function(section_id, formvalue) { if (formvalue != null && formvalue != '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue)) return _('Use ISO/IEC 3166 alpha2 country codes.'); return true; }, renderWidget: function(section_id, option_index, cfgvalue) { var typeClass = (this.keylist && this.keylist.length) ? form.ListValue : form.Value; return typeClass.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]); } }); return view.extend({ poll_status: function(map, data) { var rows = map.querySelectorAll('.cbi-section-table-row[data-sid]'); for (var i = 0; i < rows.length; i++) { var section_id = rows[i].getAttribute('data-sid'), radioDev = data[1].filter(function(d) { return d.getName() == section_id })[0], radioNet = data[2].filter(function(n) { return n.getName() == section_id })[0], badge = rows[i].querySelector('[data-name="_badge"] > div'), stat = rows[i].querySelector('[data-name="_stat"]'), btns = rows[i].querySelectorAll('.cbi-section-actions button'), busy = btns[0].classList.contains('spinning') || btns[1].classList.contains('spinning') || btns[2].classList.contains('spinning'); if (radioDev) { dom.content(badge, render_radio_badge(radioDev)); dom.content(stat, render_radio_status(radioDev, data[2].filter(function(n) { return n.getWifiDeviceName() == radioDev.getName() }))); } else { dom.content(badge, render_network_badge(radioNet)); dom.content(stat, render_network_status(radioNet)); } if (stat.hasAttribute('restart')) dom.content(stat, E('em', _('Device is restarting…'))); btns[0].disabled = isReadonlyView || busy; btns[1].disabled = (isReadonlyView && radioDev) || busy; btns[2].disabled = isReadonlyView || busy; } var table = document.querySelector('#wifi_assoclist_table'), hosts = data[0], trows = []; for (var i = 0; i < data[3].length; i++) { var bss = data[3][i], name = hosts.getHostnameByMACAddr(bss.mac), ipv4 = hosts.getIPAddrByMACAddr(bss.mac), ipv6 = hosts.getIP6AddrByMACAddr(bss.mac); 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', 'data-ifname': bss.network.getIfname(), 'data-ssid': bss.network.getSSID() }, [ E('img', { 'src': L.resource('icons/wifi%s.png').format(bss.network.isUp() ? '' : '_disabled'), 'title': bss.radio.getI18n() }), E('span', [ ' %s '.format(bss.network.getShortName()), E('small', '(%s)'.format(bss.network.getIfname())) ]) ]), bss.mac, 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 cbi-section-actions'})); row.push(E('button', { 'class': 'cbi-button cbi-button-remove', 'click': L.bind(function(net, mac, ev) { 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), 'disabled': isReadonlyView || null }, [ _('Disconnect') ])); } else { row.push('-'); } trows.push(row); } cbi_update_table(table, trows, E('em', _('No information available'))); var stat = document.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large'); if (stat) render_modal_status(stat, data[2].filter(function(n) { return n.getName() == stat.getAttribute('data-network') })[0]); return network.flushCache(); }, load: function() { return Promise.all([ uci.changes(), uci.load('wireless') ]); }, checkAnonymousSections: function() { var wifiIfaces = uci.sections('wireless', 'wifi-iface'); for (var i = 0; i < wifiIfaces.length; i++) if (wifiIfaces[i]['.anonymous']) return true; return false; }, callUciRename: rpc.declare({ object: 'uci', method: 'rename', params: [ 'config', 'section', 'name' ] }), render: function() { if (this.checkAnonymousSections()) return this.renderMigration(); else return this.renderOverview(); }, handleMigration: function(ev) { var wifiIfaces = uci.sections('wireless', 'wifi-iface'), id_offset = 0, tasks = []; for (var i = 0; i < wifiIfaces.length; i++) { if (!wifiIfaces[i]['.anonymous']) continue; var new_name = next_free_sid(id_offset); tasks.push(this.callUciRename('wireless', wifiIfaces[i]['.name'], new_name)); id_offset = +new_name.substring(7) + 1; } return Promise.all(tasks) .then(L.bind(ui.changes.init, ui.changes)) .then(L.bind(ui.changes.apply, ui.changes)); }, renderMigration: function() { 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': ui.createHandlerFn(this, 'handleMigration') }, _('Continue'))) ]); }, renderOverview: function() { var m, s, o; m = new form.Map('wireless'); m.chain('network'); m.chain('firewall'); s = m.section(form.GridSection, 'wifi-device', _('Wireless Overview')); s.anonymous = true; s.addremove = false; s.load = function() { return network.getWifiDevices().then(L.bind(function(radios) { this.radios = radios.sort(function(a, b) { return a.getName() > b.getName(); }); var tasks = []; for (var i = 0; i < radios.length; i++) tasks.push(radios[i].getWifiNetworks()); return Promise.all(tasks); }, this)).then(L.bind(function(data) { this.wifis = []; for (var i = 0; i < data.length; i++) this.wifis.push.apply(this.wifis, data[i]); }, this)); }; s.cfgsections = function() { var rv = []; for (var i = 0; i < this.radios.length; i++) { rv.push(this.radios[i].getName()); for (var j = 0; j < this.wifis.length; j++) if (this.wifis[j].getWifiDeviceName() == this.radios[i].getName()) rv.push(this.wifis[j].getName()); } return rv; }; s.modaltitle = function(section_id) { var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id})[0]; return radioNet ? radioNet.getI18n() : _('Edit wireless network'); }; s.lookupRadioOrNetwork = function(section_id) { var radioDev = this.radios.filter(function(r) { return r.getName() == section_id })[0]; if (radioDev) return radioDev; var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id })[0]; if (radioNet) return radioNet; return null; }; s.renderRowActions = function(section_id) { var inst = this.lookupRadioOrNetwork(section_id), btns; if (inst.getWifiNetworks) { btns = [ E('button', { 'class': 'cbi-button cbi-button-neutral', 'title': _('Restart radio interface'), 'click': ui.createHandlerFn(this, radio_restart, section_id) }, _('Restart')), E('button', { 'class': 'cbi-button cbi-button-action important', 'title': _('Find and join network'), 'click': ui.createHandlerFn(this, 'handleScan', inst) }, _('Scan')), E('button', { 'class': 'cbi-button cbi-button-add', 'title': _('Provide new network'), 'click': ui.createHandlerFn(this, 'handleAdd', inst) }, _('Add')) ]; } else { 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': 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': ui.createHandlerFn(this, 'renderMoreOptionsModal', section_id) }, _('Edit')), E('button', { 'class': 'cbi-button cbi-button-negative remove', 'title': _('Delete this network'), 'click': ui.createHandlerFn(this, 'handleRemove', section_id) }, _('Remove')) ]; } return E('div', { 'class': 'td middle cbi-section-actions' }, E('div', btns)); }; s.addModalOptions = function(s) { return network.getWifiNetwork(s.section).then(function(radioNet) { var hwtype = uci.get('wireless', radioNet.getWifiDeviceName(), 'type'); var o, ss; o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getWifiDeviceName(), 'wifi-device', _('Device Configuration')); o.modalonly = true; ss = o.subsection; ss.tab('general', _('General Setup')); ss.tab('advanced', _('Advanced Settings')); 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) { return render_modal_status(null, radioNet); }, this, radioNet); o.write = function() {}; 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 = ui.createHandlerFn(s, network_updown, s.section, s.map); o = ss.taboption('general', CBIWifiFrequencyValue, '_freq', '
' + _('Operating frequency')); o.ucisection = s.section; if (hwtype == 'mac80211') { o = ss.taboption('general', CBIWifiTxPowerValue, 'txpower', _('Maximum transmit power'), _('Specifies the maximum transmit power the wireless radio may use. Depending on regulatory requirements and wireless usage, the actual transmit power may be reduced by the driver.')); o.wifiNetwork = radioNet; o = ss.taboption('advanced', CBIWifiCountryValue, 'country', _('Country Code')); o.wifiNetwork = radioNet; o = ss.taboption('advanced', form.Flag, 'legacy_rates', _('Allow legacy 802.11b rates')); o.default = o.enabled; o = ss.taboption('advanced', form.Value, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.')); o.datatype = 'range(0,114750)'; o.placeholder = 'auto'; o = ss.taboption('advanced', form.Value, 'frag', _('Fragmentation Threshold')); o.datatype = 'min(256)'; o.placeholder = _('off'); o = ss.taboption('advanced', form.Value, 'rts', _('RTS/CTS Threshold')); o.datatype = 'uinteger'; o.placeholder = _('off'); o = ss.taboption('advanced', form.Flag, 'noscan', _('Force 40MHz mode'), _('Always use 40MHz channels even if the secondary channel overlaps. Using this option does not comply with IEEE 802.11n-2009!')); o.rmempty = true; o = ss.taboption('advanced', form.Value, 'beacon_int', _('Beacon Interval')); o.datatype = 'range(15,65535)'; o.placeholder = 100; o.rmempty = true; } o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getName(), 'wifi-iface', _('Interface Configuration')); o.modalonly = true; ss = o.subsection; ss.tab('general', _('General Setup')); ss.tab('encryption', _('Wireless Security')); ss.tab('macfilter', _('MAC-Filter')); ss.tab('advanced', _('Advanced Settings')); o = ss.taboption('general', form.ListValue, 'mode', _('Mode')); o.value('ap', _('Access Point')); o.value('sta', _('Client')); o.value('adhoc', _('Ad-Hoc')); o = ss.taboption('general', form.Value, 'mesh_id', _('Mesh Id')); o.depends('mode', 'mesh'); o = ss.taboption('advanced', form.Flag, 'mesh_fwding', _('Forward mesh peer traffic')); o.rmempty = false; o.default = '1'; o.depends('mode', 'mesh'); o = ss.taboption('advanced', form.Value, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default')); o.rmempty = false; o.default = '0'; o.datatype = 'range(-255,1)'; o.depends('mode', 'mesh'); o = ss.taboption('general', form.Value, 'ssid', _('ESSID')); o.datatype = 'maxlength(32)'; o.depends('mode', 'ap'); o.depends('mode', 'sta'); o.depends('mode', 'adhoc'); o.depends('mode', 'ahdemo'); o.depends('mode', 'monitor'); o.depends('mode', 'ap-wds'); o.depends('mode', 'sta-wds'); o.depends('mode', 'wds'); o = ss.taboption('general', form.Value, 'bssid', _('BSSID')); o.datatype = 'macaddr'; o = ss.taboption('general', widgets.NetworkSelect, 'network', _('Network'), _('Choose the network(s) you want to attach to this wireless interface or fill out the custom field to define a new network.')); o.rmempty = true; o.multiple = true; o.novirtual = true; o.write = function(section_id, value) { return network.getDevice(section_id).then(L.bind(function(dev) { var old_networks = dev.getNetworks().reduce(function(o, v) { o[v.getName()] = v; return o }, {}), new_networks = {}, values = L.toArray(value), tasks = []; for (var i = 0; i < values.length; i++) { new_networks[values[i]] = true; if (old_networks[values[i]]) continue; tasks.push(network.getNetwork(values[i]).then(L.bind(function(name, net) { return net || network.addNetwork(name, { proto: 'none' }); }, this, values[i])).then(L.bind(function(dev, net) { if (net) { if (!net.isEmpty()) net.set('type', 'bridge'); net.addDevice(dev); } }, this, dev))); } for (var name in old_networks) if (!new_networks[name]) tasks.push(network.getNetwork(name).then(L.bind(function(dev, net) { if (net) net.deleteDevice(dev); }, this, dev))); return Promise.all(tasks); }, this)); }; if (hwtype == 'mac80211') { var mode = ss.children[0], bssid = ss.children[5], encr; mode.value('mesh', '802.11s'); mode.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)')); mode.value('monitor', _('Monitor')); bssid.depends('mode', 'adhoc'); bssid.depends('mode', 'sta'); bssid.depends('mode', 'sta-wds'); o = ss.taboption('macfilter', form.ListValue, 'macfilter', _('MAC-Address Filter')); o.depends('mode', 'ap'); o.depends('mode', 'ap-wds'); o.value('', _('disable')); o.value('allow', _('Allow listed only')); o.value('deny', _('Allow all except listed')); o = ss.taboption('macfilter', form.DynamicList, 'maclist', _('MAC-List')); o.datatype = 'macaddr'; o.depends('macfilter', 'allow'); o.depends('macfilter', 'deny'); 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'))); mode.write = function(section_id, value) { switch (value) { case 'ap-wds': uci.set('wireless', section_id, 'mode', 'ap'); uci.set('wireless', section_id, 'wds', '1'); break; case 'sta-wds': uci.set('wireless', section_id, 'mode', 'sta'); uci.set('wireless', section_id, 'wds', '1'); break; default: uci.set('wireless', section_id, 'mode', value); uci.unset('wireless', section_id, 'wds'); break; } }; mode.cfgvalue = function(section_id) { var mode = uci.get('wireless', section_id, 'mode'), wds = uci.get('wireless', section_id, 'wds'); if (mode == 'ap' && wds) return 'ap-wds'; else if (mode == 'sta' && wds) return 'sta-wds'; return mode; }; o = ss.taboption('general', form.Flag, 'hidden', _('Hide ESSID')); o.depends('mode', 'ap'); o.depends('mode', 'ap-wds'); o = ss.taboption('general', form.Flag, 'wmm', _('WMM Mode')); o.depends('mode', 'ap'); o.depends('mode', 'ap-wds'); o.default = o.enabled; o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication')); o.depends('mode', 'ap'); o.depends('mode', 'ap-wds'); o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name')); o.optional = true; o.placeholder = radioNet.getIfname(); if (/^radio\d+\.network/.test(o.placeholder)) o.placeholder = ''; o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble')); o.default = o.enabled; o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval')); o.optional = true; o.placeholder = 2; o.datatype = 'range(1,255)'; o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec')); o.optional = true; o.placeholder = 600; o.datatype = 'uinteger'; o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling')); o.optional = true; o.datatype = 'uinteger'; o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('sec')); o.optional = true; o.placeholder = 300; o.datatype = 'uinteger'; o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval')); o.optional = true; o.placeholder = 65535; o.datatype = 'uinteger'; o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition')); o.default = o.enabled; } encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption')); o.depends('mode', 'ap'); o.depends('mode', 'sta'); o.depends('mode', 'adhoc'); o.depends('mode', 'ahdemo'); o.depends('mode', 'ap-wds'); o.depends('mode', 'sta-wds'); o.depends('mode', 'mesh'); o.cfgvalue = function(section_id) { var v = String(uci.get('wireless', section_id, 'encryption')); if (v == 'wep') return 'wep-open'; else if (v.match(/\+/)) return v.replace(/\+.+$/, ''); return v; }; o.write = function(section_id, value) { var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id), co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id); if (value == 'wpa' || value == 'wpa2' || value == 'wpa3' || value == 'wpa3-mixed') uci.unset('wireless', section_id, 'key'); if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp')) e += '+' + c; uci.set('wireless', section_id, 'encryption', e); }; o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher')); o.depends('encryption', 'wpa'); o.depends('encryption', 'wpa2'); o.depends('encryption', 'wpa3'); o.depends('encryption', 'wpa3-mixed'); o.depends('encryption', 'psk'); o.depends('encryption', 'psk2'); o.depends('encryption', 'wpa-mixed'); o.depends('encryption', 'psk-mixed'); o.value('auto', _('auto')); o.value('ccmp', _('Force CCMP (AES)')); o.value('tkip', _('Force TKIP')); o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)')); o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write; o.cfgvalue = function(section_id) { var v = String(uci.get('wireless', section_id, 'encryption')); if (v.match(/\+/)) { v = v.replace(/^[^+]+\+/, ''); if (v == 'aes') v = 'ccmp'; else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip') v = 'tkip+ccmp'; } return v; }; var crypto_modes = []; if (hwtype == 'mac80211') { var has_supplicant = L.hasSystemFeature('wpasupplicant'), has_hostapd = L.hasSystemFeature('hostapd'); // Probe EAP support var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'), has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap'); // Probe SAE support var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'), has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae'); // Probe OWE support var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'), has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe'); // Probe Suite-B support var has_ap_eap192 = L.hasSystemFeature('hostapd', 'suiteb192'), has_sta_eap192 = L.hasSystemFeature('wpasupplicant', 'suiteb192'); if (has_hostapd || has_supplicant) { crypto_modes.push(['psk2', 'WPA2-PSK', 35]); 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) { 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) { if (has_ap_eap192 || has_sta_eap192) { crypto_modes.push(['wpa3', 'WPA3-EAP', 33]); crypto_modes.push(['wpa3-mixed', 'WPA2-EAP/WPA3-EAP Mixed Mode', 32]); } crypto_modes.push(['wpa2', 'WPA2-EAP', 34]); crypto_modes.push(['wpa', 'WPA-EAP', 20]); } if (has_ap_owe || has_sta_owe) { crypto_modes.push(['owe', 'OWE', 1]); } encr.crypto_support = { 'ap': { 'wep-open': true, 'wep-shared': true, 'psk': has_hostapd || _('Requires hostapd'), 'psk2': has_hostapd || _('Requires hostapd'), 'psk-mixed': has_hostapd || _('Requires hostapd'), 'sae': has_ap_sae || _('Requires hostapd with SAE support'), 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'), 'wpa': has_ap_eap || _('Requires hostapd with EAP support'), 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'), 'wpa3': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'), 'wpa3-mixed': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'), 'owe': has_ap_owe || _('Requires hostapd with OWE support') }, 'sta': { 'wep-open': true, 'wep-shared': true, 'psk': has_supplicant || _('Requires wpa-supplicant'), 'psk2': has_supplicant || _('Requires wpa-supplicant'), 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'), 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'), 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'), 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'), 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'), 'wpa3': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'), 'wpa3-mixed': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'), 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support') }, 'adhoc': { 'wep-open': true, 'wep-shared': true, 'psk': has_supplicant || _('Requires wpa-supplicant'), 'psk2': has_supplicant || _('Requires wpa-supplicant'), 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'), }, 'mesh': { 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support') }, 'ahdemo': { 'wep-open': true, 'wep-shared': true }, 'wds': { 'wep-open': true, 'wep-shared': true } }; encr.crypto_support['ap-wds'] = encr.crypto_support['ap']; encr.crypto_support['sta-wds'] = encr.crypto_support['sta']; encr.validate = function(section_id, value) { var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0], modeval = modeopt.formvalue(section_id), modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)], enctitle = this.vallist[this.keylist.indexOf(value)]; if (value == 'none') return true; if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value)) return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle); return this.crypto_support[modeval][value]; }; } else if (hwtype == 'broadcom') { 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)); } o = ss.taboption('encryption', form.Value, 'auth_server', _('Radius-Authentication-Server')); add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); o.rmempty = true; o.datatype = 'host(0)'; o = ss.taboption('encryption', form.Value, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812)); add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); o.rmempty = true; o.datatype = 'port'; o = ss.taboption('encryption', form.Value, 'auth_secret', _('Radius-Authentication-Secret')); add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); o.rmempty = true; o.password = true; o = ss.taboption('encryption', form.Value, 'acct_server', _('Radius-Accounting-Server')); add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); o.rmempty = true; o.datatype = 'host(0)'; o = ss.taboption('encryption', form.Value, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813)); add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); o.rmempty = true; o.datatype = 'port'; o = ss.taboption('encryption', form.Value, 'acct_secret', _('Radius-Accounting-Secret')); add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); o.rmempty = true; o.password = true; o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client')); add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); o.rmempty = true; o.datatype = 'host(0)'; o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Default %d').format(3799)); add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); o.rmempty = true; o.datatype = 'port'; o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret')); add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); o.rmempty = true; o.password = true; o = ss.taboption('encryption', form.Value, '_wpa_key', _('Key')); o.depends('encryption', 'psk'); o.depends('encryption', 'psk2'); o.depends('encryption', 'psk+psk2'); o.depends('encryption', 'psk-mixed'); o.depends('encryption', 'sae'); o.depends('encryption', 'sae-mixed'); o.datatype = 'wpakey'; o.rmempty = true; o.password = true; o.cfgvalue = function(section_id) { var key = uci.get('wireless', section_id, 'key'); return /^[1234]$/.test(key) ? null : key; }; 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'); }; o = ss.taboption('encryption', form.ListValue, '_wep_key', _('Used Key Slot')); o.depends('encryption', 'wep-open'); o.depends('encryption', 'wep-shared'); o.value('1', _('Key #%d').format(1)); o.value('2', _('Key #%d').format(2)); o.value('3', _('Key #%d').format(3)); o.value('4', _('Key #%d').format(4)); o.cfgvalue = function(section_id) { var slot = +uci.get('wireless', section_id, 'key'); return (slot >= 1 && slot <= 4) ? String(slot) : ''; }; o.write = function(section_id, value) { uci.set('wireless', section_id, 'key', value); }; for (var slot = 1; slot <= 4; slot++) { o = ss.taboption('encryption', form.Value, 'key%d'.format(slot), _('Key #%d').format(slot)); o.depends('encryption', 'wep-open'); o.depends('encryption', 'wep-shared'); o.datatype = 'wepkey'; o.rmempty = true; o.password = true; o.write = function(section_id, value) { if (value != null && (value.length == 5 || value.length == 13)) value = 's:%s'.format(value); uci.set('wireless', section_id, this.option, value); }; } if (hwtype == 'mac80211') { // Probe 802.11r support (and EAP support as a proxy for Openwrt) var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap'); o = ss.taboption('encryption', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain')); add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); if (has_80211r) add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] }); o.rmempty = true; o = ss.taboption('encryption', form.Value, 'nasid', _('NAS ID'), _('Used for two different purposes: RADIUS NAS ID and 802.11r R0KH-ID. Not needed with normal WPA(2)-PSK.')); add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); o.depends({ ieee80211r: '1' }); o.rmempty = true; o = ss.taboption('encryption', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID')); o.depends({ ieee80211r: '1' }); o.placeholder = '4f57'; o.datatype = 'and(hexstring,length(4))'; o.rmempty = true; o = ss.taboption('encryption', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]')); o.depends({ ieee80211r: '1' }); o.placeholder = '1000'; o.datatype = 'range(1000,65535)'; o.rmempty = true; o = ss.taboption('encryption', form.ListValue, 'ft_over_ds', _('FT protocol')); o.depends({ ieee80211r: '1' }); o.value('1', _('FT over DS')); o.value('0', _('FT over the Air')); o.rmempty = true; o = ss.taboption('encryption', form.Flag, 'ft_psk_generate_local', _('Generate PMK locally'), _('When using a PSK, the PMK can be automatically generated. When enabled, the R0/R1 key options below are not applied. Disable this to use the R0 and R1 key options.')); o.depends({ ieee80211r: '1' }); o.default = o.enabled; o.rmempty = false; o = ss.taboption('encryption', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes')); o.depends({ ieee80211r: '1' }); o.placeholder = '10000'; o.datatype = 'uinteger'; o.rmempty = true; o = ss.taboption('encryption', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons')); o.depends({ ieee80211r: '1' }); o.placeholder = '00004f577274'; o.datatype = 'and(hexstring,length(12))'; o.rmempty = true; o = ss.taboption('encryption', form.Flag, 'pmk_r1_push', _('PMK R1 Push')); o.depends({ ieee80211r: '1' }); o.placeholder = '0'; o.rmempty = true; o = ss.taboption('encryption', form.DynamicList, 'r0kh', _('External R0 Key Holder List'), _('List of R0KHs in the same Mobility Domain.
Format: MAC-address,NAS-Identifier,128-bit key as hex string.
This list is used to map R0KH-ID (NAS Identifier) to a destination MAC address when requesting PMK-R1 key from the R0KH that the STA used during the Initial Mobility Domain Association.')); o.depends({ ieee80211r: '1' }); o.rmempty = true; o = ss.taboption('encryption', form.DynamicList, 'r1kh', _('External R1 Key Holder List'), _ ('List of R1KHs in the same Mobility Domain.
Format: MAC-address,R1KH-ID as 6 octets with colons,128-bit key as hex string.
This list is used to map R1KH-ID to a destination MAC address when sending PMK-R1 key from the R0KH. This is also the list of authorized R1KHs in the MD that can request PMK-R1 keys.')); o.depends({ ieee80211r: '1' }); o.rmempty = true; // End of 802.11r options o = ss.taboption('encryption', form.ListValue, 'eap_type', _('EAP-Method')); o.value('tls', 'TLS'); o.value('ttls', 'TTLS'); o.value('peap', 'PEAP'); o.value('fast', 'FAST'); add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); o = ss.taboption('encryption', form.Flag, 'ca_cert_usesystem', _('Use system certificates'), _("Validate server certificate using built-in system CA bundle,
requires the \"ca-bundle\" package")); o.enabled = '1'; o.disabled = '0'; o.default = o.disabled; add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); o.validate = function(section_id, value) { if (value == '1' && !L.hasSystemFeature('cabundle')) { return _("This option cannot be used because the ca-bundle package is not installed."); } return true; }; o = ss.taboption('encryption', form.FileUpload, 'ca_cert', _('Path to CA-Certificate')); add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], ca_cert_usesystem: ['0'] }); o = ss.taboption('encryption', form.Value, 'subject_match', _('Certificate constraint (Subject)'), _("Certificate constraint substring - e.g. /CN=wifi.mycompany.com
See `logread -f` during handshake for actual values")); add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); o = ss.taboption('encryption', form.DynamicList, 'altsubject_match', _('Certificate constraint (SAN)'), _("Certificate constraint(s) via Subject Alternate Name values
(supported attributes: EMAIL, DNS, URI) - e.g. DNS:wifi.mycompany.com")); add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); o = ss.taboption('encryption', form.DynamicList, 'domain_match', _('Certificate constraint (Domain)'), _("Certificate constraint(s) against DNS SAN values (if available)
or Subject CN (exact match)")); add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); o = ss.taboption('encryption', form.DynamicList, 'domain_suffix_match', _('Certificate constraint (Wildcard)'), _("Certificate constraint(s) against DNS SAN values (if available)
or Subject CN (suffix match)")); add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); o = ss.taboption('encryption', form.FileUpload, 'client_cert', _('Path to Client-Certificate')); add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['tls'] }); o = ss.taboption('encryption', form.FileUpload, 'priv_key', _('Path to Private Key')); add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['tls'] }); o = ss.taboption('encryption', form.Value, 'priv_key_pwd', _('Password of Private Key')); add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['tls'] }); o.password = true; o = ss.taboption('encryption', form.ListValue, 'auth', _('Authentication')); o.value('PAP', 'PAP'); o.value('CHAP', 'CHAP'); o.value('MSCHAP', 'MSCHAP'); o.value('MSCHAPV2', 'MSCHAPv2'); o.value('EAP-GTC', 'EAP-GTC'); o.value('EAP-MD5', 'EAP-MD5'); o.value('EAP-MSCHAPV2', 'EAP-MSCHAPv2'); o.value('EAP-TLS', 'EAP-TLS'); add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'ttls'] }); o.validate = function(section_id, value) { var eo = this.section.children.filter(function(o) { return o.option == 'eap_type' })[0], ev = eo.formvalue(section_id); if (ev != 'ttls' && (value == 'PAP' || value == 'CHAP' || value == 'MSCHAP' || value == 'MSCHAPV2')) return _('This authentication type is not applicable to the selected EAP method.'); return true; }; o = ss.taboption('encryption', form.Flag, 'ca_cert2_usesystem', _('Use system certificates for inner-tunnel'), _("Validate server certificate using built-in system CA bundle,
requires the \"ca-bundle\" package")); o.enabled = '1'; o.disabled = '0'; o.default = o.disabled; add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] }); o.validate = function(section_id, value) { if (value == '1' && !L.hasSystemFeature('cabundle')) { return _("This option cannot be used because the ca-bundle package is not installed."); } return true; }; o = ss.taboption('encryption', form.FileUpload, 'ca_cert2', _('Path to inner CA-Certificate')); add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'], ca_cert2_usesystem: ['0'] }); o = ss.taboption('encryption', form.Value, 'subject_match2', _('Inner certificate constraint (Subject)'), _("Certificate constraint substring - e.g. /CN=wifi.mycompany.com
See `logread -f` during handshake for actual values")); add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] }); o = ss.taboption('encryption', form.DynamicList, 'altsubject_match2', _('Inner certificate constraint (SAN)'), _("Certificate constraint(s) via Subject Alternate Name values
(supported attributes: EMAIL, DNS, URI) - e.g. DNS:wifi.mycompany.com")); add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] }); o = ss.taboption('encryption', form.DynamicList, 'domain_match2', _('Inner certificate constraint (Domain)'), _("Certificate constraint(s) against DNS SAN values (if available)
or Subject CN (exact match)")); add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] }); o = ss.taboption('encryption', form.DynamicList, 'domain_suffix_match2', _('Inner certificate constraint (Wildcard)'), _("Certificate constraint(s) against DNS SAN values (if available)
or Subject CN (suffix match)")); add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] }); o = ss.taboption('encryption', form.FileUpload, 'client_cert2', _('Path to inner Client-Certificate')); add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] }); o = ss.taboption('encryption', form.FileUpload, 'priv_key2', _('Path to inner Private Key')); add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] }); o = ss.taboption('encryption', form.Value, 'priv_key2_pwd', _('Password of inner Private Key')); add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] }); o.password = true; o = ss.taboption('encryption', form.Value, 'identity', _('Identity')); add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'tls', 'ttls'] }); o = ss.taboption('encryption', form.Value, 'anonymous_identity', _('Anonymous Identity')); add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'tls', 'ttls'] }); o = ss.taboption('encryption', form.Value, 'password', _('Password')); add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'ttls'] }); o.password = true; if (hwtype == 'mac80211') { // 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.value('', _('Disabled')); o.value('1', _('Optional')); o.value('2', _('Required')); add_dependency_permutations(o, { mode: ['ap', 'ap-wds', 'sta', 'sta-wds'], encryption: ['owe', 'psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] }); o.defaults = { '2': [{ encryption: 'sae' }, { encryption: 'owe' }, { encryption: 'wpa3' }, { encryption: 'wpa3-mixed' }], '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'); o.depends('ieee80211w', '2'); o.datatype = 'uinteger'; o.placeholder = '1000'; o.rmempty = true; o = ss.taboption('encryption', form.Value, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout')); o.depends('ieee80211w', '1'); o.depends('ieee80211w', '2'); o.datatype = 'uinteger'; o.placeholder = '201'; o.rmempty = true; }; 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'] }); if (L.hasSystemFeature('hostapd', 'cli') && L.hasSystemFeature('wpasupplicant')) { 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'); } } } }); }; s.handleRemove = function(section_id, ev) { document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id)).style.opacity = 0.5; return form.TypedSection.prototype.handleRemove.apply(this, [section_id, ev]); }; s.handleScan = function(radioDev, ev) { var table = E('div', { 'class': 'table' }, [ E('div', { 'class': 'tr table-titles' }, [ E('div', { 'class': 'th col-2 middle center' }, _('Signal')), E('div', { 'class': 'th col-4 middle left' }, _('SSID')), E('div', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')), E('div', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')), E('div', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')), E('div', { 'class': 'th col-3 middle left' }, _('Encryption')), E('div', { 'class': 'th cbi-section-actions right' }, ' '), ]) ]); var stop = E('button', { 'class': 'btn', 'click': L.bind(this.handleScanStartStop, this), 'style': 'display:none', 'data-state': 'stop' }, _('Stop refresh')); cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...'))); var md = ui.showModal(_('Join Network: Wireless Scan'), [ table, E('div', { 'class': 'right' }, [ stop, ' ', E('button', { 'class': 'btn', 'click': L.bind(this.handleScanAbort, this) }, _('Dismiss')) ]) ]); md.style.maxWidth = '90%'; md.style.maxHeight = 'none'; this.pollFn = L.bind(this.handleScanRefresh, this, radioDev, {}, table, stop); poll.add(this.pollFn); poll.start(); }; s.handleScanRefresh = function(radioDev, scanCache, table, stop) { return radioDev.getScanList().then(L.bind(function(results) { var rows = []; for (var i = 0; i < results.length; i++) scanCache[results[i].bssid] = results[i]; for (var k in scanCache) if (scanCache[k].stale) results.push(scanCache[k]); results.sort(function(a, b) { var diff = (b.quality - a.quality) || (a.channel - b.channel); if (diff) return diff; 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' : ''; rows.push([ E('span', { 'style': s }, render_signal_badge(q, res.signal, res.noise)), E('span', { 'style': s }, (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.mode)), E('span', { 'style': s }, '%h'.format(res.bssid)), E('span', { 'style': s }, '%h'.format(network.formatWifiEncryption(res.encryption))), E('div', { 'class': 'right' }, E('button', { 'class': 'cbi-button cbi-button-action important', 'click': L.bind(this.handleJoin, this, radioDev, res) }, _('Join Network'))) ]); res.stale = true; } cbi_update_table(table, rows); stop.disabled = false; stop.style.display = ''; stop.classList.remove('spinning'); }, this)); }; s.handleScanStartStop = function(ev) { var btn = ev.currentTarget; if (btn.getAttribute('data-state') == 'stop') { poll.remove(this.pollFn); btn.firstChild.data = _('Start refresh'); btn.setAttribute('data-state', 'start'); } else { poll.add(this.pollFn); btn.firstChild.data = _('Stop refresh'); btn.setAttribute('data-state', 'stop'); btn.classList.add('spinning'); btn.disabled = true; } }; s.handleScanAbort = function(ev) { var md = dom.parent(ev.target, 'div[aria-modal="true"]'); if (md) { md.style.maxWidth = ''; md.style.maxHeight = ''; } ui.hideModal(); poll.remove(this.pollFn); this.pollFn = null; }; s.handleJoinConfirm = function(radioDev, bss, form, ev) { var nameopt = L.toArray(form.lookupOption('name', '_new_'))[0], passopt = L.toArray(form.lookupOption('password', '_new_'))[0], bssidopt = L.toArray(form.lookupOption('bssid', '_new_'))[0], zoneopt = L.toArray(form.lookupOption('zone', '_new_'))[0], replopt = L.toArray(form.lookupOption('replace', '_new_'))[0], nameval = (nameopt && nameopt.isValid('_new_')) ? nameopt.formvalue('_new_') : null, passval = (passopt && passopt.isValid('_new_')) ? passopt.formvalue('_new_') : null, bssidval = (bssidopt && bssidopt.isValid('_new_')) ? bssidopt.formvalue('_new_') : null, 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) && 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; var section_id = null; return this.map.save(function() { var wifi_sections = uci.sections('wireless', 'wifi-iface'); if (replopt.formvalue('_new_') == '1') { 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'); uci.unset('wireless', radioDev.getName(), 'disabled'); } section_id = next_free_sid(wifi_sections.length); uci.add('wireless', 'wifi-iface', section_id); uci.set('wireless', section_id, 'device', radioDev.getName()); uci.set('wireless', section_id, 'mode', (bss.mode == 'Ad-Hoc') ? 'adhoc' : 'sta'); uci.set('wireless', section_id, 'network', nameval); if (bss.ssid != null) { uci.set('wireless', section_id, 'ssid', bss.ssid); if (bssidval == '1') uci.set('wireless', section_id, 'bssid', bss.bssid); } else if (bss.bssid != null) { uci.set('wireless', section_id, 'bssid', bss.bssid); } 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'); break; } else if (enc.wpa[i] == 1) { uci.set('wireless', section_id, 'encryption', 'psk'); break; } } uci.set('wireless', section_id, 'key', passval); } else if (is_wep) { uci.set('wireless', section_id, 'encryption', 'wep-open'); uci.set('wireless', section_id, 'key', '1'); uci.set('wireless', section_id, 'key1', passval); } else { uci.set('wireless', section_id, 'encryption', 'none'); } 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()); }); }); }).then(L.bind(function() { return this.renderMoreOptionsModal(section_id); }, this)); }; s.handleJoin = function(radioDev, bss, ev) { this.handleScanAbort(ev); var m2 = new form.Map('wireless'), 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) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' || a == 'sae' })), replace, passphrase, name, bssid, zone; var nameUsed = function(name) { var s = uci.get('network', name); if (s != null && s['.type'] != 'interface') return true; var net = (s != null) ? network.instantiateNetwork(name) : null; return (net != null && !net.isEmpty()); }; s2.render = function() { return Promise.all([ {}, this.renderUCISection('_new_') ]).then(this.renderContents.bind(this)); }; replace = s2.option(form.Flag, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.')); name = s2.option(form.Value, 'name', _('Name of the new network'), _('The allowed characters are: A-Z, a-z, 0-9 and _')); name.datatype = 'uciname'; name.default = 'wwan'; name.rmempty = false; name.validate = function(section_id, value) { if (nameUsed(value)) return _('The network name is already used'); return true; }; for (var i = 2; nameUsed(name.default); i++) name.default = 'wwan%d'.format(i); if (is_wep || is_psk) { passphrase = s2.option(form.Value, 'password', is_wep ? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.')); passphrase.datatype = is_wep ? 'wepkey' : 'wpakey'; passphrase.password = true; passphrase.rmempty = false; } if (bss.ssid != null) { bssid = s2.option(form.Flag, 'bssid', _('Lock to BSSID'), _('Instead of joining any network with a matching SSID, only connect to the BSSID %h.').format(bss.bssid)); bssid.default = '0'; } zone = s2.option(widgets.ZoneSelect, 'zone', _('Create / Assign firewall-zone'), _('Choose the firewall zone you want to assign to this interface. Select unspecified to remove the interface from the associated zone or fill out the custom field to define a new zone and attach the interface to it.')); zone.default = 'wan'; return m2.render().then(L.bind(function(nodes) { ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [ nodes, E('div', { 'class': 'right' }, [ E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Cancel')), ' ', E('button', { 'class': 'cbi-button cbi-button-positive important', 'click': ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2) }, _('Submit')) ]) ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase || name).cbid('_new_'))).focus(); }, this)); }; s.handleAdd = function(radioDev, ev) { var section_id = next_free_sid(uci.sections('wireless', 'wifi-iface').length); uci.unset('wireless', radioDev.getName(), 'disabled'); uci.add('wireless', 'wifi-iface', section_id); uci.set('wireless', section_id, 'device', radioDev.getName()); uci.set('wireless', section_id, 'mode', 'ap'); uci.set('wireless', section_id, 'ssid', 'OpenWrt'); uci.set('wireless', section_id, 'encryption', 'none'); this.addedSection = section_id; return this.renderMoreOptionsModal(section_id); }; o = s.option(form.DummyValue, '_badge'); o.modalonly = false; o.textvalue = function(section_id) { var inst = this.section.lookupRadioOrNetwork(section_id), node = E('div', { 'class': 'center' }); if (inst.getWifiNetworks) node.appendChild(render_radio_badge(inst)); else node.appendChild(render_network_badge(inst)); return node; }; o = s.option(form.DummyValue, '_stat'); o.modalonly = false; o.textvalue = function(section_id) { var inst = this.section.lookupRadioOrNetwork(section_id); if (inst.getWifiNetworks) return render_radio_status(inst, this.section.wifis.filter(function(e) { return (e.getWifiDeviceName() == inst.getName()); })); else return render_network_status(inst); }; return m.render().then(L.bind(function(m, nodes) { poll.add(L.bind(function() { var section_ids = m.children[0].cfgsections(), tasks = [ network.getHostHints(), network.getWifiDevices() ]; for (var i = 0; i < section_ids.length; i++) { var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])), dsc = row.querySelector('[data-name="_stat"] > div'), btns = row.querySelectorAll('.cbi-section-actions button'); if (dsc.getAttribute('restart') == '') { dsc.setAttribute('restart', '1'); 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'); btns[0].classList.remove('spinning'); btns[0].disabled = false; } } return Promise.all(tasks) .then(L.bind(function(hosts_radios) { var tasks = []; for (var i = 0; i < hosts_radios[1].length; i++) tasks.push(hosts_radios[1][i].getWifiNetworks()); return Promise.all(tasks).then(function(data) { hosts_radios[2] = []; for (var i = 0; i < data.length; i++) hosts_radios[2].push.apply(hosts_radios[2], data[i]); return hosts_radios; }); }, network)) .then(L.bind(function(hosts_radios_wifis) { var tasks = []; for (var i = 0; i < hosts_radios_wifis[2].length; i++) tasks.push(hosts_radios_wifis[2][i].getAssocList()); return Promise.all(tasks).then(function(data) { hosts_radios_wifis[3] = []; for (var i = 0; i < data.length; i++) { var wifiNetwork = hosts_radios_wifis[2][i], radioDev = hosts_radios_wifis[1].filter(function(d) { return d.getName() == wifiNetwork.getWifiDeviceName() })[0]; for (var j = 0; j < data[i].length; j++) hosts_radios_wifis[3].push(Object.assign({ radio: radioDev, network: wifiNetwork }, data[i][j])); } return hosts_radios_wifis; }); }, network)) .then(L.bind(this.poll_status, this, nodes)); }, this), 5); var table = E('div', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [ E('div', { 'class': 'tr table-titles' }, [ E('div', { 'class': 'th nowrap' }, _('Network')), E('div', { 'class': 'th hide-xs' }, _('MAC-Address')), E('div', { 'class': 'th' }, _('Host')), E('div', { 'class': 'th' }, _('Signal / Noise')), E('div', { 'class': 'th' }, _('RX Rate / TX Rate')) ]) ]); cbi_update_table(table, [], E('em', { 'class': 'spinning' }, _('Collecting data...'))) return E([ nodes, E('h3', _('Associated Stations')), table ]); }, this, m)); } });