7 'require tools.widgets as widgets';
9 function count_changes(section_id) {
10 var changes = L.ui.changes.changes, n = 0;
12 if (!L.isObject(changes))
15 if (Array.isArray(changes.wireless))
16 for (var i = 0; i < changes.wireless.length; i++)
17 n += (changes.wireless[i][1] == section_id);
22 function render_radio_badge(radioDev) {
23 return E('span', { 'class': 'ifacebadge' }, [
24 E('img', { 'src': L.resource('icons/wifi%s.png').format(radioDev.isUp() ? '' : '_disabled') }),
30 function render_signal_badge(signalPercent, signalValue, noiseValue, wrap) {
33 if (signalPercent < 0)
34 icon = L.resource('icons/signal-none.png');
35 else if (signalPercent == 0)
36 icon = L.resource('icons/signal-0.png');
37 else if (signalPercent < 25)
38 icon = L.resource('icons/signal-0-25.png');
39 else if (signalPercent < 50)
40 icon = L.resource('icons/signal-25-50.png');
41 else if (signalPercent < 75)
42 icon = L.resource('icons/signal-50-75.png');
44 icon = L.resource('icons/signal-75-100.png');
46 if (signalValue != null && signalValue != 0) {
47 title = '%s %d %s'.format(_('Signal'), signalValue, _('dBm'));
49 if (noiseValue != null && noiseValue != 0)
50 title += ' / %s: %d %s'.format(_('Noise'), noiseValue, _('dBm'));
53 title = _('No signal');
56 return E('div', { 'class': wrap ? 'center' : 'ifacebadge', 'title': title },
57 [ E('img', { 'src': icon }), wrap ? E('br') : ' ', '%d%%'.format(Math.max(signalPercent, 0)) ]);
60 function render_network_badge(radioNet) {
61 return render_signal_badge(radioNet.isUp() ? radioNet.getSignalPercent() : -1, radioNet.getSignal(), radioNet.getNoise());
64 function render_radio_status(radioDev, wifiNets) {
65 var name = radioDev.getI18n().replace(/ Wireless Controller .+$/, ''),
66 node = E('div', [ E('big', {}, E('strong', {}, name)), E('div') ]),
67 channel, frequency, bitrate;
69 for (var i = 0; i < wifiNets.length; i++) {
70 channel = channel || wifiNets[i].getChannel();
71 frequency = frequency || wifiNets[i].getFrequency();
72 bitrate = bitrate || wifiNets[i].getBitRate();
76 L.itemlist(node.lastElementChild, [
77 _('Channel'), '%s (%s %s)'.format(channel || '?', frequency || '?', _('GHz')),
78 _('Bitrate'), '%s %s'.format(bitrate || '?', _('Mbit/s'))
81 node.lastElementChild.appendChild(E('em', _('Device is not active')));
86 function render_network_status(radioNet) {
87 var mode = radioNet.getActiveMode(),
88 bssid = radioNet.getActiveBSSID(),
89 channel = radioNet.getChannel(),
90 disabled = (radioNet.get('disabled') == '1'),
91 is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled),
92 changecount = count_changes(radioNet.getName()),
96 status_text = E('a', {
98 click: L.bind(L.ui.changes.displayChanges, L.ui.changes)
99 }, _('Interface has %d pending changes').format(changecount));
101 status_text = E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated'));
103 return L.itemlist(E('div'), [
104 _('SSID'), radioNet.getSSID() || '?',
106 _('BSSID'), (!changecount && is_assoc) ? bssid : null,
107 _('Encryption'), (!changecount && is_assoc) ? radioNet.getActiveEncryption() || _('None') : null,
109 ], [ ' | ', E('br') ]);
112 function render_modal_status(node, radioNet) {
113 var mode = radioNet.getActiveMode(),
114 noise = radioNet.getNoise(),
115 bssid = radioNet.getActiveBSSID(),
116 channel = radioNet.getChannel(),
117 disabled = (radioNet.get('disabled') == '1'),
118 is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled);
121 node = E('span', { 'class': 'ifacebadge large', 'data-network': radioNet.getName() }, [ E('small'), E('span') ]);
123 L.dom.content(node.firstElementChild, render_signal_badge(disabled ? -1 : radioNet.getSignalPercent(), radioNet.getSignal(), noise, true));
125 L.itemlist(node.lastElementChild, [
127 _('SSID'), radioNet.getSSID() || '?',
128 _('BSSID'), is_assoc ? bssid : null,
129 _('Encryption'), is_assoc ? radioNet.getActiveEncryption() || _('None') : null,
130 _('Channel'), is_assoc ? '%d (%.3f %s)'.format(radioNet.getChannel(), radioNet.getFrequency() || 0, _('GHz')) : null,
131 _('Tx-Power'), is_assoc ? '%d %s'.format(radioNet.getTXPower(), _('dBm')) : null,
132 _('Signal'), is_assoc ? '%d %s'.format(radioNet.getSignal(), _('dBm')) : null,
133 _('Noise'), (is_assoc && noise != null) ? '%d %s'.format(noise, _('dBm')) : null,
134 _('Bitrate'), is_assoc ? '%.1f %s'.format(radioNet.getBitRate() || 0, _('Mbit/s')) : null,
135 _('Country'), is_assoc ? radioNet.getCountryCode() : null
136 ], [ ' | ', E('br'), E('br'), E('br'), E('br'), E('br'), ' | ', E('br'), ' | ' ]);
139 L.dom.append(node.lastElementChild, E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated')));
144 function format_wifirate(rate) {
145 var s = '%.1f Mbit/s, %dMHz'.format(rate.rate / 1000, rate.mhz);
147 if (rate.ht || rate.vht) {
148 if (rate.vht) s += ', VHT-MCS %d'.format(rate.mcs);
149 if (rate.nss) s += ', VHT-NSS %d'.format(rate.nss);
150 if (rate.ht) s += ', MCS %s'.format(rate.mcs);
151 if (rate.short_gi) s += ', Short GI';
157 function radio_restart(id, ev) {
158 var row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id)),
159 dsc = row.querySelector('[data-name="_stat"] > div'),
160 btn = row.querySelector('.cbi-section-actions button');
163 btn.classList.add('spinning');
166 dsc.setAttribute('restart', '');
167 L.dom.content(dsc, E('em', _('Device is restarting…')));
170 function network_updown(id, map, ev) {
171 var radio = uci.get('wireless', id, 'device'),
172 disabled = (uci.get('wireless', id, 'disabled') == '1') ||
173 (uci.get('wireless', radio, 'disabled') == '1');
176 uci.unset('wireless', id, 'disabled');
177 uci.unset('wireless', radio, 'disabled');
180 uci.set('wireless', id, 'disabled', '1');
182 var all_networks_disabled = true,
183 wifi_ifaces = uci.sections('wireless', 'wifi-iface');
185 for (var i = 0; i < wifi_ifaces.length; i++) {
186 if (wifi_ifaces[i].device == radio && wifi_ifaces[i].disabled != '1') {
187 all_networks_disabled = false;
192 if (all_networks_disabled)
193 uci.set('wireless', radio, 'disabled', '1');
196 return map.save().then(function() {
201 function next_free_sid(offset) {
202 var sid = 'wifinet' + offset;
204 while (uci.get('wireless', sid))
205 sid = 'wifinet' + (++offset);
210 var CBIWifiFrequencyValue = form.Value.extend({
211 callFrequencyList: rpc.declare({
214 params: [ 'device' ],
215 expect: { results: [] }
218 load: function(section_id) {
220 network.getWifiDevice(section_id),
221 this.callFrequencyList(section_id)
222 ]).then(L.bind(function(data) {
224 '11g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
225 '11a': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : []
228 for (var i = 0; Array.isArray(data[1]) && i < data[1].length; i++)
229 this.channels[(data[1][i].mhz > 2484) ? '11a' : '11g'].push(
231 '%d (%d Mhz)'.format(data[1][i].channel, data[1][i].mhz),
232 !data[1][i].restricted
235 var hwmodelist = L.toArray(data[0] ? data[0].getHWModes() : null)
236 .reduce(function(o, v) { o[v] = true; return o }, {});
240 'n', 'N', hwmodelist.n,
241 'ac', 'AC', hwmodelist.ac
244 var htmodelist = L.toArray(data[0] ? data[0].getHTModes() : null)
245 .reduce(function(o, v) { o[v] = true; return o }, {});
248 '': [ '', '-', true ],
250 'HT20', '20 MHz', htmodelist.HT20,
251 'HT40', '40 MHz', htmodelist.HT40
254 'VHT20', '20 MHz', htmodelist.VHT20,
255 'VHT40', '40 MHz', htmodelist.VHT40,
256 'VHT80', '80 MHz', htmodelist.VHT80,
257 'VHT160', '160 MHz', htmodelist.VHT160
263 '11g', '2.4 GHz', this.channels['11g'].length > 3,
264 '11a', '5 GHz', this.channels['11a'].length > 3
267 '11g', '2.4 GHz', this.channels['11g'].length > 3,
268 '11a', '5 GHz', this.channels['11a'].length > 3
277 setValues: function(sel, vals) {
279 sel.vals.selected = sel.selectedIndex;
281 while (sel.options[0])
284 for (var i = 0; vals && i < vals.length; i += 3)
286 sel.add(E('option', { value: vals[i+0] }, [ vals[i+1] ]));
288 if (!isNaN(vals.selected))
289 sel.selectedIndex = vals.selected;
291 sel.parentNode.style.display = (sel.options.length <= 1) ? 'none' : '';
295 toggleWifiMode: function(elem) {
296 this.toggleWifiHTMode(elem);
297 this.toggleWifiBand(elem);
300 toggleWifiHTMode: function(elem) {
301 var mode = elem.querySelector('.mode');
302 var bwdt = elem.querySelector('.htmode');
304 this.setValues(bwdt, this.htmodes[mode.value]);
307 toggleWifiBand: function(elem) {
308 var mode = elem.querySelector('.mode');
309 var band = elem.querySelector('.band');
311 this.setValues(band, this.bands[mode.value]);
312 this.toggleWifiChannel(elem);
315 toggleWifiChannel: function(elem) {
316 var band = elem.querySelector('.band');
317 var chan = elem.querySelector('.channel');
319 this.setValues(chan, this.channels[band.value]);
322 setInitialValues: function(section_id, elem) {
323 var mode = elem.querySelector('.mode'),
324 band = elem.querySelector('.band'),
325 chan = elem.querySelector('.channel'),
326 bwdt = elem.querySelector('.htmode'),
327 htval = uci.get('wireless', section_id, 'htmode'),
328 hwval = uci.get('wireless', section_id, 'hwmode'),
329 chval = uci.get('wireless', section_id, 'channel');
331 this.setValues(mode, this.modes);
333 if (/VHT20|VHT40|VHT80|VHT160/.test(htval))
335 else if (/HT20|HT40/.test(htval))
340 this.toggleWifiMode(elem);
347 this.toggleWifiBand(elem);
355 renderWidget: function(section_id, option_index, cfgvalue) {
358 L.dom.content(elem, [
359 E('label', { 'style': 'float:left; margin-right:3px' }, [
363 'style': 'width:auto',
364 'change': L.bind(this.toggleWifiMode, this, elem)
367 E('label', { 'style': 'float:left; margin-right:3px' }, [
371 'style': 'width:auto',
372 'change': L.bind(this.toggleWifiBand, this, elem)
375 E('label', { 'style': 'float:left; margin-right:3px' }, [
376 _('Channel'), E('br'),
379 'style': 'width:auto'
382 E('label', { 'style': 'float:left; margin-right:3px' }, [
386 'style': 'width:auto'
389 E('br', { 'style': 'clear:left' })
392 return this.setInitialValues(section_id, elem);
395 cfgvalue: function(section_id) {
397 uci.get('wireless', section_id, 'htmode'),
398 uci.get('wireless', section_id, 'hwmode'),
399 uci.get('wireless', section_id, 'channel')
403 formvalue: function(section_id) {
404 var node = this.map.findElement('data-field', this.cbid(section_id));
407 node.querySelector('.htmode').value,
408 node.querySelector('.band').value,
409 node.querySelector('.channel').value
413 write: function(section_id, value) {
414 uci.set('wireless', section_id, 'htmode', value[0] || null);
415 uci.set('wireless', section_id, 'hwmode', value[1]);
416 uci.set('wireless', section_id, 'channel', value[2]);
420 var CBIWifiTxPowerValue = form.ListValue.extend({
421 callTxPowerList: rpc.declare({
423 method: 'txpowerlist',
424 params: [ 'device' ],
425 expect: { results: [] }
428 load: function(section_id) {
429 return this.callTxPowerList(section_id).then(L.bind(function(pwrlist) {
430 this.powerval = this.wifiNetwork ? this.wifiNetwork.getTXPower() : null;
431 this.poweroff = this.wifiNetwork ? this.wifiNetwork.getTXPowerOffset() : null;
433 this.value('', _('driver default'));
435 for (var i = 0; i < pwrlist.length; i++)
436 this.value(pwrlist[i].dbm, '%d dBm (%d mW)'.format(pwrlist[i].dbm, pwrlist[i].mw));
438 return form.ListValue.prototype.load.apply(this, [section_id]);
442 renderWidget: function(section_id, option_index, cfgvalue) {
443 var widget = form.ListValue.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
444 widget.firstElementChild.style.width = 'auto';
446 L.dom.append(widget, E('span', [
447 ' - ', _('Current power'), ': ',
448 E('span', [ this.powerval != null ? '%d dBm'.format(this.powerval) : E('em', _('unknown')) ]),
449 this.poweroff ? ' + %d dB offset = %s dBm'.format(this.poweroff, this.powerval != null ? this.powerval + this.poweroff : '?') : ''
456 var CBIWifiCountryValue = form.Value.extend({
457 callCountryList: rpc.declare({
459 method: 'countrylist',
460 params: [ 'device' ],
461 expect: { results: [] }
464 load: function(section_id) {
465 return this.callCountryList(section_id).then(L.bind(function(countrylist) {
466 if (Array.isArray(countrylist) && countrylist.length > 0) {
467 this.value('', _('driver default'));
469 for (var i = 0; i < countrylist.length; i++)
470 this.value(countrylist[i].iso3166, '%s - %s'.format(countrylist[i].iso3166, countrylist[i].country));
473 return form.Value.prototype.load.apply(this, [section_id]);
477 validate: function(section_id, formvalue) {
478 if (formvalue != null && formvalue != '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue))
479 return _('Use ISO/IEC 3166 alpha2 country codes.');
484 renderWidget: function(section_id, option_index, cfgvalue) {
485 var typeClass = this.keylist.length ? form.ListValue : form.Value;
486 return typeClass.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
490 return L.view.extend({
491 poll_status: function(map, data) {
492 var rows = map.querySelectorAll('.cbi-section-table-row[data-sid]');
494 for (var i = 0; i < rows.length; i++) {
495 var section_id = rows[i].getAttribute('data-sid'),
496 radioDev = data[1].filter(function(d) { return d.getName() == section_id })[0],
497 radioNet = data[2].filter(function(n) { return n.getName() == section_id })[0],
498 badge = rows[i].querySelector('[data-name="_badge"] > div'),
499 stat = rows[i].querySelector('[data-name="_stat"]'),
500 btns = rows[i].querySelectorAll('.cbi-section-actions button'),
501 busy = btns[0].classList.contains('spinning') || btns[1].classList.contains('spinning') || btns[2].classList.contains('spinning');
504 L.dom.content(badge, render_radio_badge(radioDev));
505 L.dom.content(stat, render_radio_status(radioDev, data[2].filter(function(n) { return n.getWifiDeviceName() == radioDev.getName() })));
508 L.dom.content(badge, render_network_badge(radioNet));
509 L.dom.content(stat, render_network_status(radioNet));
512 if (stat.hasAttribute('restart'))
513 L.dom.content(stat, E('em', _('Device is restarting…')));
515 btns[0].disabled = busy;
516 btns[1].disabled = busy;
517 btns[2].disabled = busy;
520 var table = document.querySelector('wifi_assoclist_table'),
524 for (var i = 0; i < data[3].length; i++) {
525 var bss = data[3][i],
526 name = hosts.getHostnameByMACAddr(bss.mac),
527 ipv4 = hosts.getIPAddrByMACAddr(bss.mac),
528 ipv6 = hosts.getIP6AddrByMACAddr(bss.mac);
531 E('span', { 'class': 'ifacebadge' }, [
533 'src': L.resource('icons/wifi%s.png').format(bss.network.isUp() ? '' : '_disabled'),
534 'title': bss.radio.getI18n()
536 ' %s '.format(bss.network.getShortName()),
537 E('small', '(%s)'.format(bss.network.getIfname()))
540 name ? '%s (%s)'.format(name, ipv4 || ipv6 || '?') : ipv4 || ipv6 || '?',
541 render_signal_badge(Math.min((bss.signal + 110) / 70 * 100, 100), bss.signal, bss.noise),
543 E('span', format_wifirate(bss.rx)),
545 E('span', format_wifirate(bss.tx))
550 cbi_update_table('#wifi_assoclist_table', trows, E('em', _('No information available')));
552 var stat = document.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large');
555 render_modal_status(stat, data[2].filter(function(n) { return n.getName() == stat.getAttribute('data-network') })[0]);
557 return network.flushCache();
567 checkAnonymousSections: function() {
568 var wifiIfaces = uci.sections('wireless', 'wifi-iface');
570 for (var i = 0; i < wifiIfaces.length; i++)
571 if (wifiIfaces[i]['.anonymous'])
577 callUciRename: rpc.declare({
580 params: [ 'config', 'section', 'name' ]
584 if (this.checkAnonymousSections())
585 return this.renderMigration();
587 return this.renderOverview();
590 handleMigration: function(ev) {
591 var wifiIfaces = uci.sections('wireless', 'wifi-iface'),
595 for (var i = 0; i < wifiIfaces.length; i++) {
596 if (!wifiIfaces[i]['.anonymous'])
599 var new_name = next_free_sid(id_offset);
601 tasks.push(this.callUciRename('wireless', wifiIfaces[i]['.name'], new_name));
602 id_offset = +new_name.substring(7) + 1;
605 return Promise.all(tasks)
606 .then(L.bind(L.ui.changes.init, L.ui.changes))
607 .then(L.bind(L.ui.changes.apply, L.ui.changes));
610 renderMigration: function() {
611 L.ui.showModal(_('Wireless configuration migration'), [
612 E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')),
613 E('p', _('Upon pressing "Continue", anonymous "wifi-iface" sections will be assigned with a name in the form <em>wifinet#</em> and the network will be restarted to apply the updated configuration.')),
614 E('div', { 'class': 'right' },
616 'class': 'btn cbi-button-action important',
617 'click': L.ui.createHandlerFn(this, 'handleMigration')
622 renderOverview: function() {
625 m = new form.Map('wireless');
629 s = m.section(form.GridSection, 'wifi-device', _('Wireless Overview'));
633 s.load = function() {
634 return network.getWifiDevices().then(L.bind(function(radios) {
635 this.radios = radios.sort(function(a, b) {
636 return a.getName() > b.getName();
641 for (var i = 0; i < radios.length; i++)
642 tasks.push(radios[i].getWifiNetworks());
644 return Promise.all(tasks);
645 }, this)).then(L.bind(function(data) {
648 for (var i = 0; i < data.length; i++)
649 this.wifis.push.apply(this.wifis, data[i]);
653 s.cfgsections = function() {
656 for (var i = 0; i < this.radios.length; i++) {
657 rv.push(this.radios[i].getName());
659 for (var j = 0; j < this.wifis.length; j++)
660 if (this.wifis[j].getWifiDeviceName() == this.radios[i].getName())
661 rv.push(this.wifis[j].getName());
667 s.modaltitle = function(section_id) {
668 var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id})[0];
669 return radioNet ? radioNet.getI18n() : _('Edit wireless network');
672 s.lookupRadioOrNetwork = function(section_id) {
673 var radioDev = this.radios.filter(function(r) { return r.getName() == section_id })[0];
677 var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id })[0];
684 s.renderRowActions = function(section_id) {
685 var inst = this.lookupRadioOrNetwork(section_id), btns;
687 if (inst.getWifiNetworks) {
690 'class': 'cbi-button cbi-button-neutral',
691 'title': _('Restart radio interface'),
692 'click': L.ui.createHandlerFn(this, radio_restart, section_id)
695 'class': 'cbi-button cbi-button-action important',
696 'title': _('Find and join network'),
697 'click': L.ui.createHandlerFn(this, 'handleScan', inst)
700 'class': 'cbi-button cbi-button-add',
701 'title': _('Provide new network'),
702 'click': L.ui.createHandlerFn(this, 'handleAdd', inst)
707 var isDisabled = (inst.get('disabled') == '1');
711 'class': 'cbi-button cbi-button-neutral enable-disable',
712 'title': isDisabled ? _('Enable this network') : _('Disable this network'),
713 'click': L.ui.createHandlerFn(this, network_updown, section_id, this.map)
714 }, isDisabled ? _('Enable') : _('Disable')),
716 'class': 'cbi-button cbi-button-action important',
717 'title': _('Edit this network'),
718 'click': L.ui.createHandlerFn(this, 'renderMoreOptionsModal', section_id)
721 'class': 'cbi-button cbi-button-negative remove',
722 'title': _('Delete this network'),
723 'click': L.ui.createHandlerFn(this, 'handleRemove', section_id)
728 return E('div', { 'class': 'td middle cbi-section-actions' }, E('div', btns));
731 s.addModalOptions = function(s) {
732 return network.getWifiNetwork(s.section).then(function(radioNet) {
733 var hwtype = uci.get('wireless', radioNet.getWifiDeviceName(), 'type');
736 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
740 ss.tab('general', _('General Setup'));
741 ss.tab('advanced', _('Advanced Settings'));
743 var isDisabled = (radioNet.get('disabled') == '1');
745 o = ss.taboption('general', form.DummyValue, '_wifistat_modal', _('Status'));
746 o.cfgvalue = L.bind(function(radioNet) {
747 return render_modal_status(null, radioNet);
749 o.write = function() {};
751 o = ss.taboption('general', form.Button, '_toggle', isDisabled ? _('Wireless network is disabled') : _('Wireless network is enabled'));
752 o.inputstyle = isDisabled ? 'apply' : 'reset';
753 o.inputtitle = isDisabled ? _('Enable') : _('Disable');
754 o.onclick = L.ui.createHandlerFn(s, network_updown, s.section, s.map);
756 o = ss.taboption('general', CBIWifiFrequencyValue, '_freq', '<br />' + _('Operating frequency'));
757 o.ucisection = s.section;
759 if (hwtype == 'mac80211') {
760 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.'));
761 o.wifiNetwork = radioNet;
763 o = ss.taboption('advanced', CBIWifiCountryValue, 'country', _('Country Code'));
764 o.wifiNetwork = radioNet;
766 o = ss.taboption('advanced', form.Flag, 'legacy_rates', _('Allow legacy 802.11b rates'));
767 o.default = o.enabled;
769 o = ss.taboption('advanced', form.Value, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
770 o.datatype = 'range(0,114750)';
771 o.placeholder = 'auto';
773 o = ss.taboption('advanced', form.Value, 'frag', _('Fragmentation Threshold'));
774 o.datatype = 'min(256)';
775 o.placeholder = _('off');
777 o = ss.taboption('advanced', form.Value, 'rts', _('RTS/CTS Threshold'));
778 o.datatype = 'uinteger';
779 o.placeholder = _('off');
781 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!'));
784 o = ss.taboption('advanced', form.Value, 'beacon_int', _('Beacon Interval'));
785 o.datatype = 'range(15,65535)';
791 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getName(), 'wifi-iface', _('Interface Configuration'));
795 ss.tab('general', _('General Setup'));
796 ss.tab('encryption', _('Wireless Security'));
797 ss.tab('macfilter', _('MAC-Filter'));
798 ss.tab('advanced', _('Advanced Settings'));
800 o = ss.taboption('general', form.ListValue, 'mode', _('Mode'));
801 o.value('ap', _('Access Point'));
802 o.value('sta', _('Client'));
803 o.value('adhoc', _('Ad-Hoc'));
805 o = ss.taboption('general', form.Value, 'mesh_id', _('Mesh Id'));
806 o.depends('mode', 'mesh');
808 o = ss.taboption('advanced', form.Flag, 'mesh_fwding', _('Forward mesh peer traffic'));
811 o.depends('mode', 'mesh');
813 o = ss.taboption('advanced', form.Value, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
816 o.datatype = 'range(-255,1)';
817 o.depends('mode', 'mesh');
819 o = ss.taboption('general', form.Value, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
820 o.datatype = 'maxlength(32)';
821 o.depends('mode', 'ap');
822 o.depends('mode', 'sta');
823 o.depends('mode', 'adhoc');
824 o.depends('mode', 'ahdemo');
825 o.depends('mode', 'monitor');
826 o.depends('mode', 'ap-wds');
827 o.depends('mode', 'sta-wds');
828 o.depends('mode', 'wds');
830 o = ss.taboption('general', form.Value, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
831 o.datatype = 'macaddr';
833 o = ss.taboption('general', widgets.NetworkSelect, 'network', _('Network'), _('Choose the network(s) you want to attach to this wireless interface or fill out the <em>create</em> field to define a new network.'));
837 o.write = function(section_id, value) {
838 return network.getDevice(section_id).then(L.bind(function(dev) {
839 var old_networks = dev.getNetworks().reduce(function(o, v) { o[v.getName()] = v; return o }, {}),
841 values = L.toArray(value),
844 for (var i = 0; i < values.length; i++) {
845 new_networks[values[i]] = true;
847 if (old_networks[values[i]])
850 tasks.push(network.getNetwork(values[i]).then(L.bind(function(name, net) {
851 return net || network.addNetwork(name, { proto: 'none' });
852 }, this, values[i])).then(L.bind(function(dev, net) {
855 net.set('type', 'bridge');
861 for (var name in old_networks)
862 if (!new_networks[name])
863 tasks.push(network.getNetwork(name).then(L.bind(function(dev, net) {
865 net.deleteDevice(dev);
868 return Promise.all(tasks);
872 if (hwtype == 'mac80211') {
873 var mode = ss.children[0],
874 bssid = ss.children[5],
877 mode.value('mesh', '802.11s');
878 mode.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
879 mode.value('monitor', _('Monitor'));
881 bssid.depends('mode', 'adhoc');
882 bssid.depends('mode', 'sta');
883 bssid.depends('mode', 'sta-wds');
885 o = ss.taboption('macfilter', form.ListValue, 'macfilter', _('MAC-Address Filter'));
886 o.depends('mode', 'ap');
887 o.depends('mode', 'ap-wds');
888 o.value('', _('disable'));
889 o.value('allow', _('Allow listed only'));
890 o.value('deny', _('Allow all except listed'));
892 o = ss.taboption('macfilter', form.DynamicList, 'maclist', _('MAC-List'));
893 o.datatype = 'macaddr';
894 o.depends('macfilter', 'allow');
895 o.depends('macfilter', 'deny');
896 o.load = function(section_id) {
897 return network.getHostHints().then(L.bind(function(hints) {
898 hints.getMACHints().map(L.bind(function(hint) {
899 this.value(hint[0], hint[1] ? '%s (%s)'.format(hint[0], hint[1]) : hint[0]);
902 return form.DynamicList.prototype.load.apply(this, [section_id]);
906 mode.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
907 mode.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
909 mode.write = function(section_id, value) {
912 uci.set('wireless', section_id, 'mode', 'ap');
913 uci.set('wireless', section_id, 'wds', '1');
917 uci.set('wireless', section_id, 'mode', 'sta');
918 uci.set('wireless', section_id, 'wds', '1');
922 uci.set('wireless', section_id, 'mode', value);
923 uci.unset('wireless', section_id, 'wds');
928 mode.cfgvalue = function(section_id) {
929 var mode = uci.get('wireless', section_id, 'mode'),
930 wds = uci.get('wireless', section_id, 'wds');
932 if (mode == 'ap' && wds)
934 else if (mode == 'sta' && wds)
940 o = ss.taboption('general', form.Flag, 'hidden', _('Hide <abbr title="Extended Service Set Identifier">ESSID</abbr>'));
941 o.depends('mode', 'ap');
942 o.depends('mode', 'ap-wds');
944 o = ss.taboption('general', form.Flag, 'wmm', _('WMM Mode'));
945 o.depends('mode', 'ap');
946 o.depends('mode', 'ap-wds');
947 o.default = o.enabled;
949 o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
950 o.depends('mode', 'ap');
951 o.depends('mode', 'ap-wds');
953 o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name'));
955 o.placeholder = radioNet.getIfname();
956 if (/^radio\d+\.network/.test(o.placeholder))
959 o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble'));
960 o.default = o.enabled;
962 o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
965 o.datatype = 'range(1,255)';
967 o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
970 o.datatype = 'uinteger';
972 o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling'));
974 o.datatype = 'uinteger';
976 o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('sec'));
979 o.datatype = 'uinteger';
981 o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval'));
983 o.placeholder = 65535;
984 o.datatype = 'uinteger';
986 o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
987 o.default = o.enabled;
991 encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption'));
992 o.depends('mode', 'ap');
993 o.depends('mode', 'sta');
994 o.depends('mode', 'adhoc');
995 o.depends('mode', 'ahdemo');
996 o.depends('mode', 'ap-wds');
997 o.depends('mode', 'sta-wds');
998 o.depends('mode', 'mesh');
1000 o.cfgvalue = function(section_id) {
1001 var v = String(uci.get('wireless', section_id, 'encryption'));
1004 else if (v.match(/\+/))
1005 return v.replace(/\+.+$/, '');
1009 o.write = function(section_id, value) {
1010 var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id),
1011 co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id);
1013 if (value == 'wpa' || value == 'wpa2')
1014 uci.unset('wireless', section_id, 'key');
1016 if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp'))
1019 uci.set('wireless', section_id, 'encryption', e);
1022 o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher'));
1023 o.depends('encryption', 'wpa');
1024 o.depends('encryption', 'wpa2');
1025 o.depends('encryption', 'psk');
1026 o.depends('encryption', 'psk2');
1027 o.depends('encryption', 'wpa-mixed');
1028 o.depends('encryption', 'psk-mixed');
1029 o.value('auto', _('auto'));
1030 o.value('ccmp', _('Force CCMP (AES)'));
1031 o.value('tkip', _('Force TKIP'));
1032 o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1033 o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write;
1035 o.cfgvalue = function(section_id) {
1036 var v = String(uci.get('wireless', section_id, 'encryption'));
1037 if (v.match(/\+/)) {
1038 v = v.replace(/^[^+]+\+/, '');
1041 else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip')
1048 encr.value('none', _('No Encryption'));
1049 encr.value('wep-open', _('WEP Open System'));
1050 encr.value('wep-shared', _('WEP Shared Key'));
1052 if (hwtype == 'mac80211') {
1053 var has_supplicant = L.hasSystemFeature('wpasupplicant'),
1054 has_hostapd = L.hasSystemFeature('hostapd');
1056 // Probe EAP support
1057 var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'),
1058 has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap');
1060 // Probe SAE support
1061 var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'),
1062 has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae');
1064 // Probe OWE support
1065 var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
1066 has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
1069 if (has_hostapd || has_supplicant) {
1070 encr.value('psk', 'WPA-PSK');
1071 encr.value('psk2', 'WPA2-PSK');
1072 encr.value('psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode');
1075 encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1078 if (has_ap_sae || has_sta_sae) {
1079 encr.value('sae', 'WPA3-SAE');
1080 encr.value('sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode');
1083 if (has_ap_eap || has_sta_eap) {
1084 encr.value('wpa', 'WPA-EAP');
1085 encr.value('wpa2', 'WPA2-EAP');
1088 if (has_ap_owe || has_sta_owe) {
1089 encr.value('owe', 'OWE');
1092 encr.crypto_support = {
1096 'psk': has_hostapd || _('Requires hostapd'),
1097 'psk2': has_hostapd || _('Requires hostapd'),
1098 'psk-mixed': has_hostapd || _('Requires hostapd'),
1099 'sae': has_ap_sae || _('Requires hostapd with SAE support'),
1100 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
1101 'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
1102 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
1103 'owe': has_ap_owe || _('Requires hostapd with OWE support')
1108 'psk': has_supplicant || _('Requires wpa-supplicant'),
1109 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1110 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1111 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1112 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1113 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1114 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1115 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
1120 'psk': has_supplicant || _('Requires wpa-supplicant'),
1121 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1122 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1125 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support')
1137 encr.crypto_support['ap-wds'] = encr.crypto_support['ap'];
1138 encr.crypto_support['sta-wds'] = encr.crypto_support['sta'];
1140 encr.validate = function(section_id, value) {
1141 var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
1142 modeval = modeopt.formvalue(section_id),
1143 modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)],
1144 enctitle = this.vallist[this.keylist.indexOf(value)];
1146 if (value == 'none')
1149 if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value))
1150 return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle);
1152 return this.crypto_support[modeval][value];
1155 else if (hwtype == 'broadcom') {
1156 encr.value('psk', 'WPA-PSK');
1157 encr.value('psk2', 'WPA2-PSK');
1158 encr.value('psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode');
1162 o = ss.taboption('encryption', form.Value, 'auth_server', _('Radius-Authentication-Server'));
1163 o.depends({ mode: 'ap', encryption: 'wpa' });
1164 o.depends({ mode: 'ap', encryption: 'wpa2' });
1165 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1166 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1168 o.datatype = 'host(0)';
1170 o = ss.taboption('encryption', form.Value, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
1171 o.depends({ mode: 'ap', encryption: 'wpa' });
1172 o.depends({ mode: 'ap', encryption: 'wpa2' });
1173 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1174 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1176 o.datatype = 'port';
1178 o = ss.taboption('encryption', form.Value, 'auth_secret', _('Radius-Authentication-Secret'));
1179 o.depends({ mode: 'ap', encryption: 'wpa' });
1180 o.depends({ mode: 'ap', encryption: 'wpa2' });
1181 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1182 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1186 o = ss.taboption('encryption', form.Value, 'acct_server', _('Radius-Accounting-Server'));
1187 o.depends({ mode: 'ap', encryption: 'wpa' });
1188 o.depends({ mode: 'ap', encryption: 'wpa2' });
1189 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1190 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1192 o.datatype = 'host(0)';
1194 o = ss.taboption('encryption', form.Value, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
1195 o.depends({ mode: 'ap', encryption: 'wpa' });
1196 o.depends({ mode: 'ap', encryption: 'wpa2' });
1197 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1198 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1200 o.datatype = 'port';
1202 o = ss.taboption('encryption', form.Value, 'acct_secret', _('Radius-Accounting-Secret'));
1203 o.depends({ mode: 'ap', encryption: 'wpa' });
1204 o.depends({ mode: 'ap', encryption: 'wpa2' });
1205 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1206 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1210 o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client'));
1211 o.depends({ mode: 'ap', encryption: 'wpa' });
1212 o.depends({ mode: 'ap', encryption: 'wpa2' });
1213 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1214 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1216 o.datatype = 'host(0)';
1218 o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
1219 o.depends({ mode: 'ap', encryption: 'wpa' });
1220 o.depends({ mode: 'ap', encryption: 'wpa2' });
1221 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1222 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1224 o.datatype = 'port';
1226 o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret'));
1227 o.depends({ mode: 'ap', encryption: 'wpa' });
1228 o.depends({ mode: 'ap', encryption: 'wpa2' });
1229 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1230 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1235 o = ss.taboption('encryption', form.Value, '_wpa_key', _('Key'));
1236 o.depends('encryption', 'psk');
1237 o.depends('encryption', 'psk2');
1238 o.depends('encryption', 'psk+psk2');
1239 o.depends('encryption', 'psk-mixed');
1240 o.depends('encryption', 'sae');
1241 o.depends('encryption', 'sae-mixed');
1242 o.datatype = 'wpakey';
1246 o.cfgvalue = function(section_id) {
1247 var key = uci.get('wireless', section_id, 'key');
1248 return /^[1234]$/.test(key) ? null : key;
1251 o.write = function(section_id, value) {
1252 uci.set('wireless', section_id, 'key', value);
1253 uci.unset('wireless', section_id, 'key1');
1257 o = ss.taboption('encryption', form.ListValue, '_wep_key', _('Used Key Slot'));
1258 o.depends('encryption', 'wep-open');
1259 o.depends('encryption', 'wep-shared');
1260 o.value('1', _('Key #%d').format(1));
1261 o.value('2', _('Key #%d').format(2));
1262 o.value('3', _('Key #%d').format(3));
1263 o.value('4', _('Key #%d').format(4));
1265 o.cfgvalue = function(section_id) {
1266 var slot = +uci.get('wireless', section_id, 'key');
1267 return (slot >= 1 && slot <= 4) ? slot : 1;
1270 o.write = function(section_id, value) {
1271 uci.set('wireless', section_id, 'key', value);
1274 for (var slot = 1; slot <= 4; slot++) {
1275 o = ss.taboption('encryption', form.Value, 'key%d'.format(slot), _('Key #%d').format(slot));
1276 o.depends('encryption', 'wep-open');
1277 o.depends('encryption', 'wep-shared');
1278 o.datatype = 'wepkey';
1282 o.write = function(section_id, value) {
1283 if (value != null && (value.length == 5 || value.length == 13))
1284 value = 's:%s'.format(value);
1285 uci.set('wireless', section_id, this.option, value);
1290 if (hwtype == 'mac80211') {
1291 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1292 var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap');
1294 o = ss.taboption('encryption', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1295 o.depends({ mode: 'ap', encryption: 'wpa' });
1296 o.depends({ mode: 'ap', encryption: 'wpa2' });
1297 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1298 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1300 o.depends({ mode: 'ap', encryption: 'psk' });
1301 o.depends({ mode: 'ap', encryption: 'psk2' });
1302 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1303 o.depends({ mode: 'ap', encryption: 'sae' });
1304 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1305 o.depends({ mode: 'ap-wds', encryption: 'psk' });
1306 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1307 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1308 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1309 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1313 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.'));
1314 o.depends({ mode: 'ap', encryption: 'wpa' });
1315 o.depends({ mode: 'ap', encryption: 'wpa2' });
1316 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1317 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1318 o.depends({ ieee80211r: '1' });
1321 o = ss.taboption('encryption', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1322 o.depends({ ieee80211r: '1' });
1323 o.placeholder = '4f57';
1324 o.datatype = 'and(hexstring,length(4))';
1327 o = ss.taboption('encryption', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1328 o.depends({ ieee80211r: '1' });
1329 o.placeholder = '1000';
1330 o.datatype = 'range(1000,65535)';
1333 o = ss.taboption('encryption', form.ListValue, 'ft_over_ds', _('FT protocol'));
1334 o.depends({ ieee80211r: '1' });
1335 o.value('1', _('FT over DS'));
1336 o.value('0', _('FT over the Air'));
1339 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.'));
1340 o.depends({ ieee80211r: '1' });
1341 o.default = o.enabled;
1344 o = ss.taboption('encryption', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1345 o.depends({ ieee80211r: '1' });
1346 o.placeholder = '10000';
1347 o.datatype = 'uinteger';
1350 o = ss.taboption('encryption', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1351 o.depends({ ieee80211r: '1' });
1352 o.placeholder = '00004f577274';
1353 o.datatype = 'and(hexstring,length(12))';
1356 o = ss.taboption('encryption', form.Flag, 'pmk_r1_push', _('PMK R1 Push'));
1357 o.depends({ ieee80211r: '1' });
1358 o.placeholder = '0';
1361 o = ss.taboption('encryption', form.DynamicList, 'r0kh', _('External R0 Key Holder List'), _('List of R0KHs in the same Mobility Domain. <br />Format: MAC-address,NAS-Identifier,128-bit key as hex string. <br />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.'));
1362 o.depends({ ieee80211r: '1' });
1365 o = ss.taboption('encryption', form.DynamicList, 'r1kh', _('External R1 Key Holder List'), _ ('List of R1KHs in the same Mobility Domain. <br />Format: MAC-address,R1KH-ID as 6 octets with colons,128-bit key as hex string. <br />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.'));
1366 o.depends({ ieee80211r: '1' });
1368 // End of 802.11r options
1370 o = ss.taboption('encryption', form.ListValue, 'eap_type', _('EAP-Method'));
1371 o.value('tls', 'TLS');
1372 o.value('ttls', 'TTLS');
1373 o.value('peap', 'PEAP');
1374 o.value('fast', 'FAST');
1375 o.depends({ mode: 'sta', encryption: 'wpa' });
1376 o.depends({ mode: 'sta', encryption: 'wpa2' });
1377 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1378 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1380 o = ss.taboption('encryption', form.FileUpload, 'ca_cert', _('Path to CA-Certificate'));
1381 o.depends({ mode: 'sta', encryption: 'wpa' });
1382 o.depends({ mode: 'sta', encryption: 'wpa2' });
1383 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1384 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1386 o = ss.taboption('encryption', form.FileUpload, 'client_cert', _('Path to Client-Certificate'));
1387 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1388 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1389 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1390 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1392 o = ss.taboption('encryption', form.FileUpload, 'priv_key', _('Path to Private Key'));
1393 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1394 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1395 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1396 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1398 o = ss.taboption('encryption', form.Value, 'priv_key_pwd', _('Password of Private Key'));
1399 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1400 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1401 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1402 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1405 o = ss.taboption('encryption', form.ListValue, 'auth', _('Authentication'));
1406 o.value('PAP', 'PAP');
1407 o.value('CHAP', 'CHAP');
1408 o.value('MSCHAP', 'MSCHAP');
1409 o.value('MSCHAPV2', 'MSCHAPv2');
1412 o.value('EAP-MSCHAPV2');
1414 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1415 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1416 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1417 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1418 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1419 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1420 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1421 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1422 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1423 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1424 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1425 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1427 o.validate = function(section_id, value) {
1428 var eo = this.section.children.filter(function(o) { return o.option == 'eap_type' })[0],
1429 ev = eo.formvalue(section_id);
1431 if (ev != 'ttls' && (value == 'PAP' || value == 'CHAP' || value == 'MSCHAP' || value == 'MSCHAPV2'))
1432 return _('This authentication type is not applicable to the selected EAP method.');
1437 o = ss.taboption('encryption', form.FileUpload, 'ca_cert2', _('Path to inner CA-Certificate'));
1438 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1439 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1440 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1441 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1443 o = ss.taboption('encryption', form.FileUpload, 'client_cert2', _('Path to inner Client-Certificate'));
1444 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1445 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1446 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1447 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1449 o = ss.taboption('encryption', form.FileUpload, 'priv_key2', _('Path to inner Private Key'));
1450 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1451 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1452 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1453 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1455 o = ss.taboption('encryption', form.Value, 'priv_key2_pwd', _('Password of inner Private Key'));
1456 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1457 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1458 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1459 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1462 o = ss.taboption('encryption', form.Value, 'identity', _('Identity'));
1463 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1464 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1465 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1466 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1467 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1468 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1469 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1470 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1471 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1472 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1473 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1474 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1475 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1476 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1477 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1478 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1480 o = ss.taboption('encryption', form.Value, 'anonymous_identity', _('Anonymous Identity'));
1481 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1482 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1483 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1484 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1485 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1486 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1487 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1488 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1489 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1490 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1491 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1492 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1493 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1494 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1495 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1496 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1498 o = ss.taboption('encryption', form.Value, 'password', _('Password'));
1499 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1500 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1501 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1502 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1503 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1504 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1505 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1506 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1507 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1508 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1509 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1510 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1514 if (hwtype == 'mac80211') {
1515 // ieee802.11w options
1516 if (L.hasSystemFeature('hostapd', '11w')) {
1517 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 <br />(as of Jan 2019: ath9k, ath10k, mwlwifi and mt76)"));
1519 o.value('', _('Disabled (default)'));
1520 o.value('1', _('Optional'));
1521 o.value('2', _('Required'));
1522 o.depends({ mode: 'ap', encryption: 'wpa2' });
1523 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1524 o.depends({ mode: 'ap', encryption: 'psk2' });
1525 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1526 o.depends({ mode: 'ap', encryption: 'sae' });
1527 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1528 o.depends({ mode: 'ap', encryption: 'owe' });
1529 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1530 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1531 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1532 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1533 o.depends({ mode: 'ap-wds', encryption: 'owe' });
1534 o.depends({ mode: 'sta', encryption: 'wpa2' });
1535 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1536 o.depends({ mode: 'sta', encryption: 'psk2' });
1537 o.depends({ mode: 'sta', encryption: 'psk-mixed' });
1538 o.depends({ mode: 'sta', encryption: 'sae' });
1539 o.depends({ mode: 'sta', encryption: 'sae-mixed' });
1540 o.depends({ mode: 'sta', encryption: 'owe' });
1541 o.depends({ mode: 'sta-wds', encryption: 'psk2' });
1542 o.depends({ mode: 'sta-wds', encryption: 'psk-mixed' });
1543 o.depends({ mode: 'sta-wds', encryption: 'sae' });
1544 o.depends({ mode: 'sta-wds', encryption: 'sae-mixed' });
1545 o.depends({ mode: 'sta-wds', encryption: 'owe' });
1547 o = ss.taboption('encryption', form.Value, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1548 o.depends('ieee80211w', '1');
1549 o.depends('ieee80211w', '2');
1550 o.datatype = 'uinteger';
1551 o.placeholder = '1000';
1554 o = ss.taboption('encryption', form.Value, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1555 o.depends('ieee80211w', '1');
1556 o.depends('ieee80211w', '2');
1557 o.datatype = 'uinteger';
1558 o.placeholder = '201';
1562 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.'));
1563 o.depends({ mode: 'ap', encryption: 'wpa2' });
1564 o.depends({ mode: 'ap', encryption: 'psk2' });
1565 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1566 o.depends({ mode: 'ap', encryption: 'sae' });
1567 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1568 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1569 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1570 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1571 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1572 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1574 if (L.hasSystemFeature('hostapd', 'cli') && L.hasSystemFeature('wpasupplicant')) {
1575 o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK'))
1578 o.default = o.disabled;
1579 o.depends('encryption', 'psk');
1580 o.depends('encryption', 'psk2');
1581 o.depends('encryption', 'psk-mixed');
1588 s.handleRemove = function(section_id, ev) {
1589 document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id)).style.opacity = 0.5;
1590 return form.TypedSection.prototype.handleRemove.apply(this, [section_id, ev]);
1593 s.handleScan = function(radioDev, ev) {
1594 var table = E('div', { 'class': 'table' }, [
1595 E('div', { 'class': 'tr table-titles' }, [
1596 E('div', { 'class': 'th col-2 middle center' }, _('Signal')),
1597 E('div', { 'class': 'th col-4 middle left' }, _('SSID')),
1598 E('div', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1599 E('div', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1600 E('div', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1601 E('div', { 'class': 'th col-3 middle left' }, _('Encryption')),
1602 E('div', { 'class': 'th cbi-section-actions right' }, ' '),
1606 cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1608 var md = L.ui.showModal(_('Join Network: Wireless Scan'), [
1610 E('div', { 'class': 'right' },
1613 'click': L.bind(this.handleScanAbort, this)
1617 md.style.maxWidth = '90%';
1618 md.style.maxHeight = 'none';
1620 this.pollFn = L.bind(this.handleScanRefresh, this, radioDev, {}, table);
1622 L.Poll.add(this.pollFn);
1626 s.handleScanRefresh = function(radioDev, scanCache, table) {
1627 return radioDev.getScanList().then(L.bind(function(results) {
1630 for (var i = 0; i < results.length; i++)
1631 scanCache[results[i].bssid] = results[i];
1633 for (var k in scanCache)
1634 if (scanCache[k].stale)
1635 results.push(scanCache[k]);
1637 results.sort(function(a, b) {
1638 var diff = (b.quality - a.quality) || (a.channel - b.channel);
1643 if (a.ssid < b.ssid)
1645 else if (a.ssid > b.ssid)
1648 if (a.bssid < b.bssid)
1650 else if (a.bssid > b.bssid)
1654 for (var i = 0; i < results.length; i++) {
1655 var res = results[i],
1656 qv = res.quality || 0,
1657 qm = res.quality_max || 0,
1658 q = (qv > 0 && qm > 0) ? Math.floor((100 / qm) * qv) : 0,
1659 s = res.stale ? 'opacity:0.5' : '';
1662 E('span', { 'style': s }, render_signal_badge(q, res.signal, res.noise)),
1663 E('span', { 'style': s }, '%h'.format(res.ssid)),
1664 E('span', { 'style': s }, '%d'.format(res.channel)),
1665 E('span', { 'style': s }, '%h'.format(res.mode)),
1666 E('span', { 'style': s }, '%h'.format(res.bssid)),
1667 E('span', { 'style': s }, '%h'.format(network.formatWifiEncryption(res.encryption))),
1668 E('div', { 'class': 'right' }, E('button', {
1669 'class': 'cbi-button cbi-button-action important',
1670 'click': L.bind(this.handleJoin, this, radioDev, res)
1671 }, _('Join Network')))
1677 cbi_update_table(table, rows);
1681 s.handleScanAbort = function(ev) {
1682 var md = L.dom.parent(ev.target, 'div[aria-modal="true"]');
1684 md.style.maxWidth = '';
1685 md.style.maxHeight = '';
1689 L.Poll.remove(this.pollFn);
1694 s.handleJoinConfirm = function(radioDev, bss, form, ev) {
1695 var nameopt = L.toArray(form.lookupOption('name', '_new_'))[0],
1696 passopt = L.toArray(form.lookupOption('password', '_new_'))[0],
1697 zoneopt = L.toArray(form.lookupOption('zone', '_new_'))[0],
1698 replopt = L.toArray(form.lookupOption('replace', '_new_'))[0],
1699 nameval = (nameopt && nameopt.isValid('_new_')) ? nameopt.formvalue('_new_') : null,
1700 passval = (passopt && passopt.isValid('_new_')) ? passopt.formvalue('_new_') : null,
1701 zoneval = zoneopt ? zoneopt.formvalue('_new_') : null,
1702 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1703 is_wep = (enc && Array.isArray(enc.wep)),
1704 is_psk = (enc && Array.isArray(enc.wpa) && Array.isArray(enc.authentication) && enc.authentication[0] == 'psk');
1706 if (nameval == null || (passopt && passval == null))
1709 var section_id = null;
1711 return this.map.save(function() {
1712 if (replopt.formvalue('_new_') == '1') {
1713 var sections = uci.sections('wireless', 'wifi-iface');
1715 for (var i = 0; i < sections.length; i++)
1716 if (sections[i].device == radioDev.getName())
1717 uci.remove('wireless', sections[i]['.name']);
1720 section_id = next_free_sid(uci.sections('wifi-iface').length);
1722 uci.add('wireless', 'wifi-iface', section_id);
1723 uci.set('wireless', section_id, 'device', radioDev.getName());
1724 uci.set('wireless', section_id, 'mode', (bss.mode == 'Ad-Hoc') ? 'adhoc' : 'sta');
1725 uci.set('wireless', section_id, 'network', nameval);
1727 if (bss.ssid != null)
1728 uci.set('wireless', section_id, 'ssid', bss.ssid);
1729 else if (bss.bssid != null)
1730 uci.set('wireless', section_id, 'bssid', bss.bssid);
1733 for (var i = enc.wpa.length - 1; i >= 0; i--) {
1734 if (enc.wpa[i] == 2) {
1735 uci.set('wireless', section_id, 'encryption', 'psk2');
1738 else if (enc.wpa[i] == 1) {
1739 uci.set('wireless', section_id, 'encryption', 'psk');
1744 uci.set('wireless', section_id, 'key', passval);
1747 uci.set('wireless', section_id, 'encryption', 'wep-open');
1748 uci.set('wireless', section_id, 'key', '1');
1749 uci.set('wireless', section_id, 'key1', passval);
1752 var zonePromise = zoneval
1753 ? firewall.getZone(zoneval).then(function(zone) { return zone || firewall.addZone(zoneval) })
1754 : Promise.resolve();
1756 return zonePromise.then(function(zone) {
1757 return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) {
1758 firewall.deleteNetwork(net.getName());
1761 zone.addNetwork(net.getName());
1764 }).then(L.bind(function() {
1765 return this.renderMoreOptionsModal(section_id);
1769 s.handleJoin = function(radioDev, bss, ev) {
1770 this.handleScanAbort(ev);
1772 var m2 = new form.Map('wireless'),
1773 s2 = m2.section(form.NamedSection, '_new_'),
1774 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1775 is_wep = (enc && Array.isArray(enc.wep)),
1776 is_psk = (enc && Array.isArray(enc.wpa) && Array.isArray(enc.authentication) && enc.authentication[0] == 'psk'),
1777 replace, passphrase, name, zone;
1779 s2.render = function() {
1780 return Promise.all([
1782 this.renderUCISection('_new_')
1783 ]).then(this.renderContents.bind(this));
1786 replace = s2.option(form.Flag, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
1788 name = s2.option(form.Value, 'name', _('Name of the new network'), _('The allowed characters are: <code>A-Z</code>, <code>a-z</code>, <code>0-9</code> and <code>_</code>'));
1789 name.datatype = 'uciname';
1790 name.default = 'wwan';
1791 name.rmempty = false;
1792 name.validate = function(section_id, value) {
1793 if (uci.get('network', value))
1794 return _('The network name is already used');
1799 for (var i = 2; uci.get('network', name.default); i++)
1800 name.default = 'wwan%d'.format(i);
1802 if (is_wep || is_psk) {
1803 passphrase = s2.option(form.Value, 'password', is_wep ? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
1804 passphrase.datatype = is_wep ? 'wepkey' : 'wpakey';
1805 passphrase.password = true;
1806 passphrase.rmempty = false;
1809 zone = s2.option(widgets.ZoneSelect, 'zone', _('Create / Assign firewall-zone'), _('Choose the firewall zone you want to assign to this interface. Select <em>unspecified</em> to remove the interface from the associated zone or fill out the <em>create</em> field to define a new zone and attach the interface to it.'));
1810 zone.default = 'wan';
1812 return m2.render().then(L.bind(function(nodes) {
1813 L.ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [
1815 E('div', { 'class': 'right' }, [
1818 'click': L.ui.hideModal
1819 }, _('Cancel')), ' ',
1821 'class': 'cbi-button cbi-button-positive important',
1822 'click': L.ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2)
1825 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase || name).cbid('_new_'))).focus();
1829 s.handleAdd = function(radioDev, ev) {
1830 var section_id = next_free_sid(uci.sections('wireless', 'wifi-iface').length);
1832 uci.unset('wireless', radioDev.getName(), 'disabled');
1834 uci.add('wireless', 'wifi-iface', section_id);
1835 uci.set('wireless', section_id, 'device', radioDev.getName());
1836 uci.set('wireless', section_id, 'mode', 'ap');
1837 uci.set('wireless', section_id, 'ssid', 'OpenWrt');
1838 uci.set('wireless', section_id, 'encryption', 'none');
1840 this.addedSection = section_id;
1841 return this.renderMoreOptionsModal(section_id);
1844 o = s.option(form.DummyValue, '_badge');
1845 o.modalonly = false;
1846 o.textvalue = function(section_id) {
1847 var inst = this.section.lookupRadioOrNetwork(section_id),
1848 node = E('div', { 'class': 'center' });
1850 if (inst.getWifiNetworks)
1851 node.appendChild(render_radio_badge(inst));
1853 node.appendChild(render_network_badge(inst));
1858 o = s.option(form.DummyValue, '_stat');
1859 o.modalonly = false;
1860 o.textvalue = function(section_id) {
1861 var inst = this.section.lookupRadioOrNetwork(section_id);
1863 if (inst.getWifiNetworks)
1864 return render_radio_status(inst, this.section.wifis.filter(function(e) {
1865 return (e.getWifiDeviceName() == inst.getName());
1868 return render_network_status(inst);
1871 return m.render().then(L.bind(function(m, nodes) {
1872 L.Poll.add(L.bind(function() {
1873 var section_ids = m.children[0].cfgsections(),
1874 tasks = [ network.getHostHints(), network.getWifiDevices() ];
1876 for (var i = 0; i < section_ids.length; i++) {
1877 var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])),
1878 dsc = row.querySelector('[data-name="_stat"] > div'),
1879 btns = row.querySelectorAll('.cbi-section-actions button');
1881 if (dsc.getAttribute('restart') == '') {
1882 dsc.setAttribute('restart', '1');
1883 tasks.push(L.Request.post(
1884 L.url('admin/network/wireless_reconnect', section_ids[i]),
1885 'token=' + L.env.token,
1886 { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
1887 ).catch(function() {}));
1889 else if (dsc.getAttribute('restart') == '1') {
1890 dsc.removeAttribute('restart');
1891 btns[0].classList.remove('spinning');
1892 btns[0].disabled = false;
1896 return Promise.all(tasks)
1897 .then(L.bind(function(hosts_radios) {
1900 for (var i = 0; i < hosts_radios[1].length; i++)
1901 tasks.push(hosts_radios[1][i].getWifiNetworks());
1903 return Promise.all(tasks).then(function(data) {
1904 hosts_radios[2] = [];
1906 for (var i = 0; i < data.length; i++)
1907 hosts_radios[2].push.apply(hosts_radios[2], data[i]);
1909 return hosts_radios;
1912 .then(L.bind(function(hosts_radios_wifis) {
1915 for (var i = 0; i < hosts_radios_wifis[2].length; i++)
1916 tasks.push(hosts_radios_wifis[2][i].getAssocList());
1918 return Promise.all(tasks).then(function(data) {
1919 hosts_radios_wifis[3] = [];
1921 for (var i = 0; i < data.length; i++) {
1922 var wifiNetwork = hosts_radios_wifis[2][i],
1923 radioDev = hosts_radios_wifis[1].filter(function(d) { return d.getName() == wifiNetwork.getWifiDeviceName() })[0];
1925 for (var j = 0; j < data[i].length; j++)
1926 hosts_radios_wifis[3].push(Object.assign({ radio: radioDev, network: wifiNetwork }, data[i][j]));
1929 return hosts_radios_wifis;
1932 .then(L.bind(this.poll_status, this, nodes));
1935 var table = E('div', { 'class': 'table', 'id': 'wifi_assoclist_table' }, [
1936 E('div', { 'class': 'tr table-titles' }, [
1937 E('div', { 'class': 'th nowrap' }, _('Network')),
1938 E('div', { 'class': 'th hide-xs' }, _('MAC-Address')),
1939 E('div', { 'class': 'th nowrap' }, _('Host')),
1940 E('div', { 'class': 'th nowrap' }, _('Signal / Noise')),
1941 E('div', { 'class': 'th nowrap' }, _('RX Rate / TX Rate'))
1945 cbi_update_table(table, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
1947 return E([ nodes, E('h3', _('Associated Stations')), table ]);