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' || uci.get('wireless', radioNet.getWifiDeviceName(), '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' ||
708 uci.get('wireless', inst.getWifiDeviceName(), 'disabled') == '1');
712 'class': 'cbi-button cbi-button-neutral enable-disable',
713 'title': isDisabled ? _('Enable this network') : _('Disable this network'),
714 'click': L.ui.createHandlerFn(this, network_updown, section_id, this.map)
715 }, isDisabled ? _('Enable') : _('Disable')),
717 'class': 'cbi-button cbi-button-action important',
718 'title': _('Edit this network'),
719 'click': L.ui.createHandlerFn(this, 'renderMoreOptionsModal', section_id)
722 'class': 'cbi-button cbi-button-negative remove',
723 'title': _('Delete this network'),
724 'click': L.ui.createHandlerFn(this, 'handleRemove', section_id)
729 return E('div', { 'class': 'td middle cbi-section-actions' }, E('div', btns));
732 s.addModalOptions = function(s) {
733 return network.getWifiNetwork(s.section).then(function(radioNet) {
734 var hwtype = uci.get('wireless', radioNet.getWifiDeviceName(), 'type');
737 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
741 ss.tab('general', _('General Setup'));
742 ss.tab('advanced', _('Advanced Settings'));
744 var isDisabled = (radioNet.get('disabled') == '1');
746 o = ss.taboption('general', form.DummyValue, '_wifistat_modal', _('Status'));
747 o.cfgvalue = L.bind(function(radioNet) {
748 return render_modal_status(null, radioNet);
750 o.write = function() {};
752 o = ss.taboption('general', form.Button, '_toggle', isDisabled ? _('Wireless network is disabled') : _('Wireless network is enabled'));
753 o.inputstyle = isDisabled ? 'apply' : 'reset';
754 o.inputtitle = isDisabled ? _('Enable') : _('Disable');
755 o.onclick = L.ui.createHandlerFn(s, network_updown, s.section, s.map);
757 o = ss.taboption('general', CBIWifiFrequencyValue, '_freq', '<br />' + _('Operating frequency'));
758 o.ucisection = s.section;
760 if (hwtype == 'mac80211') {
761 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.'));
762 o.wifiNetwork = radioNet;
764 o = ss.taboption('advanced', CBIWifiCountryValue, 'country', _('Country Code'));
765 o.wifiNetwork = radioNet;
767 o = ss.taboption('advanced', form.Flag, 'legacy_rates', _('Allow legacy 802.11b rates'));
768 o.default = o.enabled;
770 o = ss.taboption('advanced', form.Value, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
771 o.datatype = 'range(0,114750)';
772 o.placeholder = 'auto';
774 o = ss.taboption('advanced', form.Value, 'frag', _('Fragmentation Threshold'));
775 o.datatype = 'min(256)';
776 o.placeholder = _('off');
778 o = ss.taboption('advanced', form.Value, 'rts', _('RTS/CTS Threshold'));
779 o.datatype = 'uinteger';
780 o.placeholder = _('off');
782 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!'));
785 o = ss.taboption('advanced', form.Value, 'beacon_int', _('Beacon Interval'));
786 o.datatype = 'range(15,65535)';
792 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getName(), 'wifi-iface', _('Interface Configuration'));
796 ss.tab('general', _('General Setup'));
797 ss.tab('encryption', _('Wireless Security'));
798 ss.tab('macfilter', _('MAC-Filter'));
799 ss.tab('advanced', _('Advanced Settings'));
801 o = ss.taboption('general', form.ListValue, 'mode', _('Mode'));
802 o.value('ap', _('Access Point'));
803 o.value('sta', _('Client'));
804 o.value('adhoc', _('Ad-Hoc'));
806 o = ss.taboption('general', form.Value, 'mesh_id', _('Mesh Id'));
807 o.depends('mode', 'mesh');
809 o = ss.taboption('advanced', form.Flag, 'mesh_fwding', _('Forward mesh peer traffic'));
812 o.depends('mode', 'mesh');
814 o = ss.taboption('advanced', form.Value, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
817 o.datatype = 'range(-255,1)';
818 o.depends('mode', 'mesh');
820 o = ss.taboption('general', form.Value, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
821 o.datatype = 'maxlength(32)';
822 o.depends('mode', 'ap');
823 o.depends('mode', 'sta');
824 o.depends('mode', 'adhoc');
825 o.depends('mode', 'ahdemo');
826 o.depends('mode', 'monitor');
827 o.depends('mode', 'ap-wds');
828 o.depends('mode', 'sta-wds');
829 o.depends('mode', 'wds');
831 o = ss.taboption('general', form.Value, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
832 o.datatype = 'macaddr';
834 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.'));
838 o.write = function(section_id, value) {
839 return network.getDevice(section_id).then(L.bind(function(dev) {
840 var old_networks = dev.getNetworks().reduce(function(o, v) { o[v.getName()] = v; return o }, {}),
842 values = L.toArray(value),
845 for (var i = 0; i < values.length; i++) {
846 new_networks[values[i]] = true;
848 if (old_networks[values[i]])
851 tasks.push(network.getNetwork(values[i]).then(L.bind(function(name, net) {
852 return net || network.addNetwork(name, { proto: 'none' });
853 }, this, values[i])).then(L.bind(function(dev, net) {
856 net.set('type', 'bridge');
862 for (var name in old_networks)
863 if (!new_networks[name])
864 tasks.push(network.getNetwork(name).then(L.bind(function(dev, net) {
866 net.deleteDevice(dev);
869 return Promise.all(tasks);
873 if (hwtype == 'mac80211') {
874 var mode = ss.children[0],
875 bssid = ss.children[5],
878 mode.value('mesh', '802.11s');
879 mode.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
880 mode.value('monitor', _('Monitor'));
882 bssid.depends('mode', 'adhoc');
883 bssid.depends('mode', 'sta');
884 bssid.depends('mode', 'sta-wds');
886 o = ss.taboption('macfilter', form.ListValue, 'macfilter', _('MAC-Address Filter'));
887 o.depends('mode', 'ap');
888 o.depends('mode', 'ap-wds');
889 o.value('', _('disable'));
890 o.value('allow', _('Allow listed only'));
891 o.value('deny', _('Allow all except listed'));
893 o = ss.taboption('macfilter', form.DynamicList, 'maclist', _('MAC-List'));
894 o.datatype = 'macaddr';
895 o.depends('macfilter', 'allow');
896 o.depends('macfilter', 'deny');
897 o.load = function(section_id) {
898 return network.getHostHints().then(L.bind(function(hints) {
899 hints.getMACHints().map(L.bind(function(hint) {
900 this.value(hint[0], hint[1] ? '%s (%s)'.format(hint[0], hint[1]) : hint[0]);
903 return form.DynamicList.prototype.load.apply(this, [section_id]);
907 mode.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
908 mode.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
910 mode.write = function(section_id, value) {
913 uci.set('wireless', section_id, 'mode', 'ap');
914 uci.set('wireless', section_id, 'wds', '1');
918 uci.set('wireless', section_id, 'mode', 'sta');
919 uci.set('wireless', section_id, 'wds', '1');
923 uci.set('wireless', section_id, 'mode', value);
924 uci.unset('wireless', section_id, 'wds');
929 mode.cfgvalue = function(section_id) {
930 var mode = uci.get('wireless', section_id, 'mode'),
931 wds = uci.get('wireless', section_id, 'wds');
933 if (mode == 'ap' && wds)
935 else if (mode == 'sta' && wds)
941 o = ss.taboption('general', form.Flag, 'hidden', _('Hide <abbr title="Extended Service Set Identifier">ESSID</abbr>'));
942 o.depends('mode', 'ap');
943 o.depends('mode', 'ap-wds');
945 o = ss.taboption('general', form.Flag, 'wmm', _('WMM Mode'));
946 o.depends('mode', 'ap');
947 o.depends('mode', 'ap-wds');
948 o.default = o.enabled;
950 o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
951 o.depends('mode', 'ap');
952 o.depends('mode', 'ap-wds');
954 o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name'));
956 o.placeholder = radioNet.getIfname();
957 if (/^radio\d+\.network/.test(o.placeholder))
960 o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble'));
961 o.default = o.enabled;
963 o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
966 o.datatype = 'range(1,255)';
968 o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
971 o.datatype = 'uinteger';
973 o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling'));
975 o.datatype = 'uinteger';
977 o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('sec'));
980 o.datatype = 'uinteger';
982 o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval'));
984 o.placeholder = 65535;
985 o.datatype = 'uinteger';
987 o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
988 o.default = o.enabled;
992 encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption'));
993 o.depends('mode', 'ap');
994 o.depends('mode', 'sta');
995 o.depends('mode', 'adhoc');
996 o.depends('mode', 'ahdemo');
997 o.depends('mode', 'ap-wds');
998 o.depends('mode', 'sta-wds');
999 o.depends('mode', 'mesh');
1001 o.cfgvalue = function(section_id) {
1002 var v = String(uci.get('wireless', section_id, 'encryption'));
1005 else if (v.match(/\+/))
1006 return v.replace(/\+.+$/, '');
1010 o.write = function(section_id, value) {
1011 var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id),
1012 co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id);
1014 if (value == 'wpa' || value == 'wpa2')
1015 uci.unset('wireless', section_id, 'key');
1017 if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp'))
1020 uci.set('wireless', section_id, 'encryption', e);
1023 o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher'));
1024 o.depends('encryption', 'wpa');
1025 o.depends('encryption', 'wpa2');
1026 o.depends('encryption', 'psk');
1027 o.depends('encryption', 'psk2');
1028 o.depends('encryption', 'wpa-mixed');
1029 o.depends('encryption', 'psk-mixed');
1030 o.value('auto', _('auto'));
1031 o.value('ccmp', _('Force CCMP (AES)'));
1032 o.value('tkip', _('Force TKIP'));
1033 o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1034 o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write;
1036 o.cfgvalue = function(section_id) {
1037 var v = String(uci.get('wireless', section_id, 'encryption'));
1038 if (v.match(/\+/)) {
1039 v = v.replace(/^[^+]+\+/, '');
1042 else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip')
1049 encr.value('none', _('No Encryption'));
1050 encr.value('wep-open', _('WEP Open System'));
1051 encr.value('wep-shared', _('WEP Shared Key'));
1053 if (hwtype == 'mac80211') {
1054 var has_supplicant = L.hasSystemFeature('wpasupplicant'),
1055 has_hostapd = L.hasSystemFeature('hostapd');
1057 // Probe EAP support
1058 var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'),
1059 has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap');
1061 // Probe SAE support
1062 var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'),
1063 has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae');
1065 // Probe OWE support
1066 var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
1067 has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
1070 if (has_hostapd || has_supplicant) {
1071 encr.value('psk', 'WPA-PSK');
1072 encr.value('psk2', 'WPA2-PSK');
1073 encr.value('psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode');
1076 encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1079 if (has_ap_sae || has_sta_sae) {
1080 encr.value('sae', 'WPA3-SAE');
1081 encr.value('sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode');
1084 if (has_ap_eap || has_sta_eap) {
1085 encr.value('wpa', 'WPA-EAP');
1086 encr.value('wpa2', 'WPA2-EAP');
1089 if (has_ap_owe || has_sta_owe) {
1090 encr.value('owe', 'OWE');
1093 encr.crypto_support = {
1097 'psk': has_hostapd || _('Requires hostapd'),
1098 'psk2': has_hostapd || _('Requires hostapd'),
1099 'psk-mixed': has_hostapd || _('Requires hostapd'),
1100 'sae': has_ap_sae || _('Requires hostapd with SAE support'),
1101 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
1102 'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
1103 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
1104 'owe': has_ap_owe || _('Requires hostapd with OWE support')
1109 'psk': has_supplicant || _('Requires wpa-supplicant'),
1110 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1111 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1112 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1113 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1114 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1115 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1116 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
1121 'psk': has_supplicant || _('Requires wpa-supplicant'),
1122 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1123 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1126 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support')
1138 encr.crypto_support['ap-wds'] = encr.crypto_support['ap'];
1139 encr.crypto_support['sta-wds'] = encr.crypto_support['sta'];
1141 encr.validate = function(section_id, value) {
1142 var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
1143 modeval = modeopt.formvalue(section_id),
1144 modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)],
1145 enctitle = this.vallist[this.keylist.indexOf(value)];
1147 if (value == 'none')
1150 if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value))
1151 return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle);
1153 return this.crypto_support[modeval][value];
1156 else if (hwtype == 'broadcom') {
1157 encr.value('psk', 'WPA-PSK');
1158 encr.value('psk2', 'WPA2-PSK');
1159 encr.value('psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode');
1163 o = ss.taboption('encryption', form.Value, 'auth_server', _('Radius-Authentication-Server'));
1164 o.depends({ mode: 'ap', encryption: 'wpa' });
1165 o.depends({ mode: 'ap', encryption: 'wpa2' });
1166 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1167 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1169 o.datatype = 'host(0)';
1171 o = ss.taboption('encryption', form.Value, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
1172 o.depends({ mode: 'ap', encryption: 'wpa' });
1173 o.depends({ mode: 'ap', encryption: 'wpa2' });
1174 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1175 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1177 o.datatype = 'port';
1179 o = ss.taboption('encryption', form.Value, 'auth_secret', _('Radius-Authentication-Secret'));
1180 o.depends({ mode: 'ap', encryption: 'wpa' });
1181 o.depends({ mode: 'ap', encryption: 'wpa2' });
1182 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1183 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1187 o = ss.taboption('encryption', form.Value, 'acct_server', _('Radius-Accounting-Server'));
1188 o.depends({ mode: 'ap', encryption: 'wpa' });
1189 o.depends({ mode: 'ap', encryption: 'wpa2' });
1190 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1191 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1193 o.datatype = 'host(0)';
1195 o = ss.taboption('encryption', form.Value, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
1196 o.depends({ mode: 'ap', encryption: 'wpa' });
1197 o.depends({ mode: 'ap', encryption: 'wpa2' });
1198 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1199 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1201 o.datatype = 'port';
1203 o = ss.taboption('encryption', form.Value, 'acct_secret', _('Radius-Accounting-Secret'));
1204 o.depends({ mode: 'ap', encryption: 'wpa' });
1205 o.depends({ mode: 'ap', encryption: 'wpa2' });
1206 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1207 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1211 o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client'));
1212 o.depends({ mode: 'ap', encryption: 'wpa' });
1213 o.depends({ mode: 'ap', encryption: 'wpa2' });
1214 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1215 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1217 o.datatype = 'host(0)';
1219 o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
1220 o.depends({ mode: 'ap', encryption: 'wpa' });
1221 o.depends({ mode: 'ap', encryption: 'wpa2' });
1222 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1223 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1225 o.datatype = 'port';
1227 o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret'));
1228 o.depends({ mode: 'ap', encryption: 'wpa' });
1229 o.depends({ mode: 'ap', encryption: 'wpa2' });
1230 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1231 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1236 o = ss.taboption('encryption', form.Value, '_wpa_key', _('Key'));
1237 o.depends('encryption', 'psk');
1238 o.depends('encryption', 'psk2');
1239 o.depends('encryption', 'psk+psk2');
1240 o.depends('encryption', 'psk-mixed');
1241 o.depends('encryption', 'sae');
1242 o.depends('encryption', 'sae-mixed');
1243 o.datatype = 'wpakey';
1247 o.cfgvalue = function(section_id) {
1248 var key = uci.get('wireless', section_id, 'key');
1249 return /^[1234]$/.test(key) ? null : key;
1252 o.write = function(section_id, value) {
1253 uci.set('wireless', section_id, 'key', value);
1254 uci.unset('wireless', section_id, 'key1');
1258 o = ss.taboption('encryption', form.ListValue, '_wep_key', _('Used Key Slot'));
1259 o.depends('encryption', 'wep-open');
1260 o.depends('encryption', 'wep-shared');
1261 o.value('1', _('Key #%d').format(1));
1262 o.value('2', _('Key #%d').format(2));
1263 o.value('3', _('Key #%d').format(3));
1264 o.value('4', _('Key #%d').format(4));
1266 o.cfgvalue = function(section_id) {
1267 var slot = +uci.get('wireless', section_id, 'key');
1268 return String((slot >= 1 && slot <= 4) ? slot : 1);
1271 o.write = function(section_id, value) {
1272 uci.set('wireless', section_id, 'key', value);
1275 for (var slot = 1; slot <= 4; slot++) {
1276 o = ss.taboption('encryption', form.Value, 'key%d'.format(slot), _('Key #%d').format(slot));
1277 o.depends('encryption', 'wep-open');
1278 o.depends('encryption', 'wep-shared');
1279 o.datatype = 'wepkey';
1283 o.write = function(section_id, value) {
1284 if (value != null && (value.length == 5 || value.length == 13))
1285 value = 's:%s'.format(value);
1286 uci.set('wireless', section_id, this.option, value);
1291 if (hwtype == 'mac80211') {
1292 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1293 var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap');
1295 o = ss.taboption('encryption', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1296 o.depends({ mode: 'ap', encryption: 'wpa' });
1297 o.depends({ mode: 'ap', encryption: 'wpa2' });
1298 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1299 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1301 o.depends({ mode: 'ap', encryption: 'psk' });
1302 o.depends({ mode: 'ap', encryption: 'psk2' });
1303 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1304 o.depends({ mode: 'ap', encryption: 'sae' });
1305 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1306 o.depends({ mode: 'ap-wds', encryption: 'psk' });
1307 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1308 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1309 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1310 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1314 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.'));
1315 o.depends({ mode: 'ap', encryption: 'wpa' });
1316 o.depends({ mode: 'ap', encryption: 'wpa2' });
1317 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1318 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1319 o.depends({ ieee80211r: '1' });
1322 o = ss.taboption('encryption', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1323 o.depends({ ieee80211r: '1' });
1324 o.placeholder = '4f57';
1325 o.datatype = 'and(hexstring,length(4))';
1328 o = ss.taboption('encryption', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1329 o.depends({ ieee80211r: '1' });
1330 o.placeholder = '1000';
1331 o.datatype = 'range(1000,65535)';
1334 o = ss.taboption('encryption', form.ListValue, 'ft_over_ds', _('FT protocol'));
1335 o.depends({ ieee80211r: '1' });
1336 o.value('1', _('FT over DS'));
1337 o.value('0', _('FT over the Air'));
1340 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.'));
1341 o.depends({ ieee80211r: '1' });
1342 o.default = o.enabled;
1345 o = ss.taboption('encryption', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1346 o.depends({ ieee80211r: '1' });
1347 o.placeholder = '10000';
1348 o.datatype = 'uinteger';
1351 o = ss.taboption('encryption', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1352 o.depends({ ieee80211r: '1' });
1353 o.placeholder = '00004f577274';
1354 o.datatype = 'and(hexstring,length(12))';
1357 o = ss.taboption('encryption', form.Flag, 'pmk_r1_push', _('PMK R1 Push'));
1358 o.depends({ ieee80211r: '1' });
1359 o.placeholder = '0';
1362 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.'));
1363 o.depends({ ieee80211r: '1' });
1366 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.'));
1367 o.depends({ ieee80211r: '1' });
1369 // End of 802.11r options
1371 o = ss.taboption('encryption', form.ListValue, 'eap_type', _('EAP-Method'));
1372 o.value('tls', 'TLS');
1373 o.value('ttls', 'TTLS');
1374 o.value('peap', 'PEAP');
1375 o.value('fast', 'FAST');
1376 o.depends({ mode: 'sta', encryption: 'wpa' });
1377 o.depends({ mode: 'sta', encryption: 'wpa2' });
1378 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1379 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1381 o = ss.taboption('encryption', form.FileUpload, 'ca_cert', _('Path to CA-Certificate'));
1382 o.depends({ mode: 'sta', encryption: 'wpa' });
1383 o.depends({ mode: 'sta', encryption: 'wpa2' });
1384 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1385 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1387 o = ss.taboption('encryption', form.FileUpload, 'client_cert', _('Path to Client-Certificate'));
1388 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1389 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1390 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1391 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1393 o = ss.taboption('encryption', form.FileUpload, 'priv_key', _('Path to Private Key'));
1394 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1395 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1396 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1397 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1399 o = ss.taboption('encryption', form.Value, 'priv_key_pwd', _('Password of Private Key'));
1400 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1401 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1402 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1403 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1406 o = ss.taboption('encryption', form.ListValue, 'auth', _('Authentication'));
1407 o.value('PAP', 'PAP');
1408 o.value('CHAP', 'CHAP');
1409 o.value('MSCHAP', 'MSCHAP');
1410 o.value('MSCHAPV2', 'MSCHAPv2');
1413 o.value('EAP-MSCHAPV2');
1415 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1416 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1417 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1418 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1419 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1420 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1421 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1422 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1423 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1424 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1425 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1426 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1428 o.validate = function(section_id, value) {
1429 var eo = this.section.children.filter(function(o) { return o.option == 'eap_type' })[0],
1430 ev = eo.formvalue(section_id);
1432 if (ev != 'ttls' && (value == 'PAP' || value == 'CHAP' || value == 'MSCHAP' || value == 'MSCHAPV2'))
1433 return _('This authentication type is not applicable to the selected EAP method.');
1438 o = ss.taboption('encryption', form.FileUpload, 'ca_cert2', _('Path to inner CA-Certificate'));
1439 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1440 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1441 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1442 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1444 o = ss.taboption('encryption', form.FileUpload, 'client_cert2', _('Path to inner Client-Certificate'));
1445 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1446 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1447 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1448 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1450 o = ss.taboption('encryption', form.FileUpload, 'priv_key2', _('Path to inner Private Key'));
1451 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1452 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1453 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1454 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1456 o = ss.taboption('encryption', form.Value, 'priv_key2_pwd', _('Password of inner Private Key'));
1457 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1458 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1459 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1460 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1463 o = ss.taboption('encryption', form.Value, 'identity', _('Identity'));
1464 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1465 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1466 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1467 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1468 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1469 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1470 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1471 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1472 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1473 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1474 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1475 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1476 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1477 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1478 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1479 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1481 o = ss.taboption('encryption', form.Value, 'anonymous_identity', _('Anonymous Identity'));
1482 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1483 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1484 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1485 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1486 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1487 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1488 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1489 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1490 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1491 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1492 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1493 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1494 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1495 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1496 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1497 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1499 o = ss.taboption('encryption', form.Value, 'password', _('Password'));
1500 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1501 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1502 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1503 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1504 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1505 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1506 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1507 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1508 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1509 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1510 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1511 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1515 if (hwtype == 'mac80211') {
1516 // ieee802.11w options
1517 if (L.hasSystemFeature('hostapd', '11w')) {
1518 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'));
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 '2': [{ encryption: 'sae' }, { encryption: 'owe' }],
1548 '1': [{ encryption: 'sae-mixed'}],
1552 o = ss.taboption('encryption', form.Value, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1553 o.depends('ieee80211w', '1');
1554 o.depends('ieee80211w', '2');
1555 o.datatype = 'uinteger';
1556 o.placeholder = '1000';
1559 o = ss.taboption('encryption', form.Value, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1560 o.depends('ieee80211w', '1');
1561 o.depends('ieee80211w', '2');
1562 o.datatype = 'uinteger';
1563 o.placeholder = '201';
1567 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.'));
1568 o.depends({ mode: 'ap', encryption: 'wpa2' });
1569 o.depends({ mode: 'ap', encryption: 'psk2' });
1570 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1571 o.depends({ mode: 'ap', encryption: 'sae' });
1572 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1573 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1574 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1575 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1576 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1577 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1579 if (L.hasSystemFeature('hostapd', 'cli') && L.hasSystemFeature('wpasupplicant')) {
1580 o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK'))
1583 o.default = o.disabled;
1584 o.depends('encryption', 'psk');
1585 o.depends('encryption', 'psk2');
1586 o.depends('encryption', 'psk-mixed');
1593 s.handleRemove = function(section_id, ev) {
1594 document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id)).style.opacity = 0.5;
1595 return form.TypedSection.prototype.handleRemove.apply(this, [section_id, ev]);
1598 s.handleScan = function(radioDev, ev) {
1599 var table = E('div', { 'class': 'table' }, [
1600 E('div', { 'class': 'tr table-titles' }, [
1601 E('div', { 'class': 'th col-2 middle center' }, _('Signal')),
1602 E('div', { 'class': 'th col-4 middle left' }, _('SSID')),
1603 E('div', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1604 E('div', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1605 E('div', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1606 E('div', { 'class': 'th col-3 middle left' }, _('Encryption')),
1607 E('div', { 'class': 'th cbi-section-actions right' }, ' '),
1611 cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1613 var md = L.ui.showModal(_('Join Network: Wireless Scan'), [
1615 E('div', { 'class': 'right' },
1618 'click': L.bind(this.handleScanAbort, this)
1622 md.style.maxWidth = '90%';
1623 md.style.maxHeight = 'none';
1625 this.pollFn = L.bind(this.handleScanRefresh, this, radioDev, {}, table);
1627 L.Poll.add(this.pollFn);
1631 s.handleScanRefresh = function(radioDev, scanCache, table) {
1632 return radioDev.getScanList().then(L.bind(function(results) {
1635 for (var i = 0; i < results.length; i++)
1636 scanCache[results[i].bssid] = results[i];
1638 for (var k in scanCache)
1639 if (scanCache[k].stale)
1640 results.push(scanCache[k]);
1642 results.sort(function(a, b) {
1643 var diff = (b.quality - a.quality) || (a.channel - b.channel);
1648 if (a.ssid < b.ssid)
1650 else if (a.ssid > b.ssid)
1653 if (a.bssid < b.bssid)
1655 else if (a.bssid > b.bssid)
1659 for (var i = 0; i < results.length; i++) {
1660 var res = results[i],
1661 qv = res.quality || 0,
1662 qm = res.quality_max || 0,
1663 q = (qv > 0 && qm > 0) ? Math.floor((100 / qm) * qv) : 0,
1664 s = res.stale ? 'opacity:0.5' : '';
1667 E('span', { 'style': s }, render_signal_badge(q, res.signal, res.noise)),
1668 E('span', { 'style': s }, '%h'.format(res.ssid)),
1669 E('span', { 'style': s }, '%d'.format(res.channel)),
1670 E('span', { 'style': s }, '%h'.format(res.mode)),
1671 E('span', { 'style': s }, '%h'.format(res.bssid)),
1672 E('span', { 'style': s }, '%h'.format(network.formatWifiEncryption(res.encryption))),
1673 E('div', { 'class': 'right' }, E('button', {
1674 'class': 'cbi-button cbi-button-action important',
1675 'click': L.bind(this.handleJoin, this, radioDev, res)
1676 }, _('Join Network')))
1682 cbi_update_table(table, rows);
1686 s.handleScanAbort = function(ev) {
1687 var md = L.dom.parent(ev.target, 'div[aria-modal="true"]');
1689 md.style.maxWidth = '';
1690 md.style.maxHeight = '';
1694 L.Poll.remove(this.pollFn);
1699 s.handleJoinConfirm = function(radioDev, bss, form, ev) {
1700 var nameopt = L.toArray(form.lookupOption('name', '_new_'))[0],
1701 passopt = L.toArray(form.lookupOption('password', '_new_'))[0],
1702 zoneopt = L.toArray(form.lookupOption('zone', '_new_'))[0],
1703 replopt = L.toArray(form.lookupOption('replace', '_new_'))[0],
1704 nameval = (nameopt && nameopt.isValid('_new_')) ? nameopt.formvalue('_new_') : null,
1705 passval = (passopt && passopt.isValid('_new_')) ? passopt.formvalue('_new_') : null,
1706 zoneval = zoneopt ? zoneopt.formvalue('_new_') : null,
1707 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1708 is_wep = (enc && Array.isArray(enc.wep)),
1709 is_psk = (enc && Array.isArray(enc.wpa) && Array.isArray(enc.authentication) && enc.authentication[0] == 'psk');
1711 if (nameval == null || (passopt && passval == null))
1714 var section_id = null;
1716 return this.map.save(function() {
1717 if (replopt.formvalue('_new_') == '1') {
1718 var sections = uci.sections('wireless', 'wifi-iface');
1720 for (var i = 0; i < sections.length; i++)
1721 if (sections[i].device == radioDev.getName())
1722 uci.remove('wireless', sections[i]['.name']);
1725 section_id = next_free_sid(uci.sections('wifi-iface').length);
1727 uci.add('wireless', 'wifi-iface', section_id);
1728 uci.set('wireless', section_id, 'device', radioDev.getName());
1729 uci.set('wireless', section_id, 'mode', (bss.mode == 'Ad-Hoc') ? 'adhoc' : 'sta');
1730 uci.set('wireless', section_id, 'network', nameval);
1732 if (bss.ssid != null)
1733 uci.set('wireless', section_id, 'ssid', bss.ssid);
1734 else if (bss.bssid != null)
1735 uci.set('wireless', section_id, 'bssid', bss.bssid);
1738 for (var i = enc.wpa.length - 1; i >= 0; i--) {
1739 if (enc.wpa[i] == 2) {
1740 uci.set('wireless', section_id, 'encryption', 'psk2');
1743 else if (enc.wpa[i] == 1) {
1744 uci.set('wireless', section_id, 'encryption', 'psk');
1749 uci.set('wireless', section_id, 'key', passval);
1752 uci.set('wireless', section_id, 'encryption', 'wep-open');
1753 uci.set('wireless', section_id, 'key', '1');
1754 uci.set('wireless', section_id, 'key1', passval);
1757 var zonePromise = zoneval
1758 ? firewall.getZone(zoneval).then(function(zone) { return zone || firewall.addZone(zoneval) })
1759 : Promise.resolve();
1761 return zonePromise.then(function(zone) {
1762 return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) {
1763 firewall.deleteNetwork(net.getName());
1766 zone.addNetwork(net.getName());
1769 }).then(L.bind(function() {
1770 return this.renderMoreOptionsModal(section_id);
1774 s.handleJoin = function(radioDev, bss, ev) {
1775 this.handleScanAbort(ev);
1777 var m2 = new form.Map('wireless'),
1778 s2 = m2.section(form.NamedSection, '_new_'),
1779 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1780 is_wep = (enc && Array.isArray(enc.wep)),
1781 is_psk = (enc && Array.isArray(enc.wpa) && Array.isArray(enc.authentication) && enc.authentication[0] == 'psk'),
1782 replace, passphrase, name, zone;
1784 s2.render = function() {
1785 return Promise.all([
1787 this.renderUCISection('_new_')
1788 ]).then(this.renderContents.bind(this));
1791 replace = s2.option(form.Flag, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
1793 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>'));
1794 name.datatype = 'uciname';
1795 name.default = 'wwan';
1796 name.rmempty = false;
1797 name.validate = function(section_id, value) {
1798 if (uci.get('network', value))
1799 return _('The network name is already used');
1804 for (var i = 2; uci.get('network', name.default); i++)
1805 name.default = 'wwan%d'.format(i);
1807 if (is_wep || is_psk) {
1808 passphrase = s2.option(form.Value, 'password', is_wep ? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
1809 passphrase.datatype = is_wep ? 'wepkey' : 'wpakey';
1810 passphrase.password = true;
1811 passphrase.rmempty = false;
1814 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.'));
1815 zone.default = 'wan';
1817 return m2.render().then(L.bind(function(nodes) {
1818 L.ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [
1820 E('div', { 'class': 'right' }, [
1823 'click': L.ui.hideModal
1824 }, _('Cancel')), ' ',
1826 'class': 'cbi-button cbi-button-positive important',
1827 'click': L.ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2)
1830 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase || name).cbid('_new_'))).focus();
1834 s.handleAdd = function(radioDev, ev) {
1835 var section_id = next_free_sid(uci.sections('wireless', 'wifi-iface').length);
1837 uci.unset('wireless', radioDev.getName(), 'disabled');
1839 uci.add('wireless', 'wifi-iface', section_id);
1840 uci.set('wireless', section_id, 'device', radioDev.getName());
1841 uci.set('wireless', section_id, 'mode', 'ap');
1842 uci.set('wireless', section_id, 'ssid', 'OpenWrt');
1843 uci.set('wireless', section_id, 'encryption', 'none');
1845 this.addedSection = section_id;
1846 return this.renderMoreOptionsModal(section_id);
1849 o = s.option(form.DummyValue, '_badge');
1850 o.modalonly = false;
1851 o.textvalue = function(section_id) {
1852 var inst = this.section.lookupRadioOrNetwork(section_id),
1853 node = E('div', { 'class': 'center' });
1855 if (inst.getWifiNetworks)
1856 node.appendChild(render_radio_badge(inst));
1858 node.appendChild(render_network_badge(inst));
1863 o = s.option(form.DummyValue, '_stat');
1864 o.modalonly = false;
1865 o.textvalue = function(section_id) {
1866 var inst = this.section.lookupRadioOrNetwork(section_id);
1868 if (inst.getWifiNetworks)
1869 return render_radio_status(inst, this.section.wifis.filter(function(e) {
1870 return (e.getWifiDeviceName() == inst.getName());
1873 return render_network_status(inst);
1876 return m.render().then(L.bind(function(m, nodes) {
1877 L.Poll.add(L.bind(function() {
1878 var section_ids = m.children[0].cfgsections(),
1879 tasks = [ network.getHostHints(), network.getWifiDevices() ];
1881 for (var i = 0; i < section_ids.length; i++) {
1882 var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])),
1883 dsc = row.querySelector('[data-name="_stat"] > div'),
1884 btns = row.querySelectorAll('.cbi-section-actions button');
1886 if (dsc.getAttribute('restart') == '') {
1887 dsc.setAttribute('restart', '1');
1888 tasks.push(L.Request.post(
1889 L.url('admin/network/wireless_reconnect', section_ids[i]),
1890 'token=' + L.env.token,
1891 { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
1892 ).catch(function() {}));
1894 else if (dsc.getAttribute('restart') == '1') {
1895 dsc.removeAttribute('restart');
1896 btns[0].classList.remove('spinning');
1897 btns[0].disabled = false;
1901 return Promise.all(tasks)
1902 .then(L.bind(function(hosts_radios) {
1905 for (var i = 0; i < hosts_radios[1].length; i++)
1906 tasks.push(hosts_radios[1][i].getWifiNetworks());
1908 return Promise.all(tasks).then(function(data) {
1909 hosts_radios[2] = [];
1911 for (var i = 0; i < data.length; i++)
1912 hosts_radios[2].push.apply(hosts_radios[2], data[i]);
1914 return hosts_radios;
1917 .then(L.bind(function(hosts_radios_wifis) {
1920 for (var i = 0; i < hosts_radios_wifis[2].length; i++)
1921 tasks.push(hosts_radios_wifis[2][i].getAssocList());
1923 return Promise.all(tasks).then(function(data) {
1924 hosts_radios_wifis[3] = [];
1926 for (var i = 0; i < data.length; i++) {
1927 var wifiNetwork = hosts_radios_wifis[2][i],
1928 radioDev = hosts_radios_wifis[1].filter(function(d) { return d.getName() == wifiNetwork.getWifiDeviceName() })[0];
1930 for (var j = 0; j < data[i].length; j++)
1931 hosts_radios_wifis[3].push(Object.assign({ radio: radioDev, network: wifiNetwork }, data[i][j]));
1934 return hosts_radios_wifis;
1937 .then(L.bind(this.poll_status, this, nodes));
1940 var table = E('div', { 'class': 'table', 'id': 'wifi_assoclist_table' }, [
1941 E('div', { 'class': 'tr table-titles' }, [
1942 E('div', { 'class': 'th nowrap' }, _('Network')),
1943 E('div', { 'class': 'th hide-xs' }, _('MAC-Address')),
1944 E('div', { 'class': 'th nowrap' }, _('Host')),
1945 E('div', { 'class': 'th nowrap' }, _('Signal / Noise')),
1946 E('div', { 'class': 'th nowrap' }, _('RX Rate / TX Rate'))
1950 cbi_update_table(table, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
1952 return E([ nodes, E('h3', _('Associated Stations')), table ]);