8 'require tools.widgets as widgets';
10 function count_changes(section_id) {
11 var changes = L.ui.changes.changes, n = 0;
13 if (!L.isObject(changes))
16 if (Array.isArray(changes.wireless))
17 for (var i = 0; i < changes.wireless.length; i++)
18 n += (changes.wireless[i][1] == section_id);
23 function render_radio_badge(radioDev) {
24 return E('span', { 'class': 'ifacebadge' }, [
25 E('img', { 'src': L.resource('icons/wifi%s.png').format(radioDev.isUp() ? '' : '_disabled') }),
31 function render_signal_badge(signalPercent, signalValue, noiseValue, wrap) {
34 if (signalPercent < 0)
35 icon = L.resource('icons/signal-none.png');
36 else if (signalPercent == 0)
37 icon = L.resource('icons/signal-0.png');
38 else if (signalPercent < 25)
39 icon = L.resource('icons/signal-0-25.png');
40 else if (signalPercent < 50)
41 icon = L.resource('icons/signal-25-50.png');
42 else if (signalPercent < 75)
43 icon = L.resource('icons/signal-50-75.png');
45 icon = L.resource('icons/signal-75-100.png');
47 if (signalValue != null && signalValue != 0) {
48 title = '%s %d %s'.format(_('Signal'), signalValue, _('dBm'));
50 if (noiseValue != null && noiseValue != 0)
51 title += ' / %s: %d %s'.format(_('Noise'), noiseValue, _('dBm'));
54 title = _('No signal');
57 return E('div', { 'class': wrap ? 'center' : 'ifacebadge', 'title': title },
58 [ E('img', { 'src': icon }), wrap ? E('br') : ' ', '%d%%'.format(Math.max(signalPercent, 0)) ]);
61 function render_network_badge(radioNet) {
62 return render_signal_badge(radioNet.isUp() ? radioNet.getSignalPercent() : -1, radioNet.getSignal(), radioNet.getNoise());
65 function render_radio_status(radioDev, wifiNets) {
66 var name = radioDev.getI18n().replace(/ Wireless Controller .+$/, ''),
67 node = E('div', [ E('big', {}, E('strong', {}, name)), E('div') ]),
68 channel, frequency, bitrate;
70 for (var i = 0; i < wifiNets.length; i++) {
71 channel = channel || wifiNets[i].getChannel();
72 frequency = frequency || wifiNets[i].getFrequency();
73 bitrate = bitrate || wifiNets[i].getBitRate();
77 L.itemlist(node.lastElementChild, [
78 _('Channel'), '%s (%s %s)'.format(channel || '?', frequency || '?', _('GHz')),
79 _('Bitrate'), '%s %s'.format(bitrate || '?', _('Mbit/s'))
82 node.lastElementChild.appendChild(E('em', _('Device is not active')));
87 function render_network_status(radioNet) {
88 var mode = radioNet.getActiveMode(),
89 bssid = radioNet.getActiveBSSID(),
90 channel = radioNet.getChannel(),
91 disabled = (radioNet.get('disabled') == '1' || uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == '1'),
92 is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled),
93 is_mesh = (radioNet.getMode() == 'mesh'),
94 changecount = count_changes(radioNet.getName()),
98 status_text = E('a', {
100 click: L.bind(L.ui.changes.displayChanges, L.ui.changes)
101 }, _('Interface has %d pending changes').format(changecount));
103 status_text = E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated'));
105 return L.itemlist(E('div'), [
106 is_mesh ? _('Mesh ID') : _('SSID'), (is_mesh ? radioNet.getMeshID() : radioNet.getSSID()) || '?',
108 _('BSSID'), (!changecount && is_assoc) ? bssid : null,
109 _('Encryption'), (!changecount && is_assoc) ? radioNet.getActiveEncryption() || _('None') : null,
111 ], [ ' | ', E('br') ]);
114 function render_modal_status(node, radioNet) {
115 var mode = radioNet.getActiveMode(),
116 noise = radioNet.getNoise(),
117 bssid = radioNet.getActiveBSSID(),
118 channel = radioNet.getChannel(),
119 disabled = (radioNet.get('disabled') == '1'),
120 is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled);
123 node = E('span', { 'class': 'ifacebadge large', 'data-network': radioNet.getName() }, [ E('small'), E('span') ]);
125 L.dom.content(node.firstElementChild, render_signal_badge(disabled ? -1 : radioNet.getSignalPercent(), radioNet.getSignal(), noise, true));
127 L.itemlist(node.lastElementChild, [
129 _('SSID'), radioNet.getSSID() || '?',
130 _('BSSID'), is_assoc ? bssid : null,
131 _('Encryption'), is_assoc ? radioNet.getActiveEncryption() || _('None') : null,
132 _('Channel'), is_assoc ? '%d (%.3f %s)'.format(radioNet.getChannel(), radioNet.getFrequency() || 0, _('GHz')) : null,
133 _('Tx-Power'), is_assoc ? '%d %s'.format(radioNet.getTXPower(), _('dBm')) : null,
134 _('Signal'), is_assoc ? '%d %s'.format(radioNet.getSignal(), _('dBm')) : null,
135 _('Noise'), (is_assoc && noise != null) ? '%d %s'.format(noise, _('dBm')) : null,
136 _('Bitrate'), is_assoc ? '%.1f %s'.format(radioNet.getBitRate() || 0, _('Mbit/s')) : null,
137 _('Country'), is_assoc ? radioNet.getCountryCode() : null
138 ], [ ' | ', E('br'), E('br'), E('br'), E('br'), E('br'), ' | ', E('br'), ' | ' ]);
141 L.dom.append(node.lastElementChild, E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated')));
146 function format_wifirate(rate) {
147 var s = '%.1f Mbit/s, %dMHz'.format(rate.rate / 1000, rate.mhz);
149 if (rate.ht || rate.vht) {
150 if (rate.vht) s += ', VHT-MCS %d'.format(rate.mcs);
151 if (rate.nss) s += ', VHT-NSS %d'.format(rate.nss);
152 if (rate.ht) s += ', MCS %s'.format(rate.mcs);
153 if (rate.short_gi) s += ', Short GI';
159 function radio_restart(id, ev) {
160 var row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id)),
161 dsc = row.querySelector('[data-name="_stat"] > div'),
162 btn = row.querySelector('.cbi-section-actions button');
165 btn.classList.add('spinning');
168 dsc.setAttribute('restart', '');
169 L.dom.content(dsc, E('em', _('Device is restarting…')));
172 function network_updown(id, map, ev) {
173 var radio = uci.get('wireless', id, 'device'),
174 disabled = (uci.get('wireless', id, 'disabled') == '1') ||
175 (uci.get('wireless', radio, 'disabled') == '1');
178 uci.unset('wireless', id, 'disabled');
179 uci.unset('wireless', radio, 'disabled');
182 uci.set('wireless', id, 'disabled', '1');
184 var all_networks_disabled = true,
185 wifi_ifaces = uci.sections('wireless', 'wifi-iface');
187 for (var i = 0; i < wifi_ifaces.length; i++) {
188 if (wifi_ifaces[i].device == radio && wifi_ifaces[i].disabled != '1') {
189 all_networks_disabled = false;
194 if (all_networks_disabled)
195 uci.set('wireless', radio, 'disabled', '1');
198 return map.save().then(function() {
203 function next_free_sid(offset) {
204 var sid = 'wifinet' + offset;
206 while (uci.get('wireless', sid))
207 sid = 'wifinet' + (++offset);
212 var CBIWifiFrequencyValue = form.Value.extend({
213 callFrequencyList: rpc.declare({
216 params: [ 'device' ],
217 expect: { results: [] }
220 load: function(section_id) {
222 network.getWifiDevice(section_id),
223 this.callFrequencyList(section_id)
224 ]).then(L.bind(function(data) {
226 '11g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
227 '11a': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : []
230 for (var i = 0; i < data[1].length; i++)
231 this.channels[(data[1][i].mhz > 2484) ? '11a' : '11g'].push(
233 '%d (%d Mhz)'.format(data[1][i].channel, data[1][i].mhz),
234 !data[1][i].restricted
237 var hwmodelist = L.toArray(data[0] ? data[0].getHWModes() : null)
238 .reduce(function(o, v) { o[v] = true; return o }, {});
242 'n', 'N', hwmodelist.n,
243 'ac', 'AC', hwmodelist.ac
246 var htmodelist = L.toArray(data[0] ? data[0].getHTModes() : null)
247 .reduce(function(o, v) { o[v] = true; return o }, {});
250 '': [ '', '-', true ],
252 'HT20', '20 MHz', htmodelist.HT20,
253 'HT40', '40 MHz', htmodelist.HT40
256 'VHT20', '20 MHz', htmodelist.VHT20,
257 'VHT40', '40 MHz', htmodelist.VHT40,
258 'VHT80', '80 MHz', htmodelist.VHT80,
259 'VHT160', '160 MHz', htmodelist.VHT160
265 '11g', '2.4 GHz', this.channels['11g'].length > 3,
266 '11a', '5 GHz', this.channels['11a'].length > 3
269 '11g', '2.4 GHz', this.channels['11g'].length > 3,
270 '11a', '5 GHz', this.channels['11a'].length > 3
279 setValues: function(sel, vals) {
281 sel.vals.selected = sel.selectedIndex;
283 while (sel.options[0])
286 for (var i = 0; vals && i < vals.length; i += 3)
288 sel.add(E('option', { value: vals[i+0] }, [ vals[i+1] ]));
290 if (vals && !isNaN(vals.selected))
291 sel.selectedIndex = vals.selected;
293 sel.parentNode.style.display = (sel.options.length <= 1) ? 'none' : '';
297 toggleWifiMode: function(elem) {
298 this.toggleWifiHTMode(elem);
299 this.toggleWifiBand(elem);
302 toggleWifiHTMode: function(elem) {
303 var mode = elem.querySelector('.mode');
304 var bwdt = elem.querySelector('.htmode');
306 this.setValues(bwdt, this.htmodes[mode.value]);
309 toggleWifiBand: function(elem) {
310 var mode = elem.querySelector('.mode');
311 var band = elem.querySelector('.band');
313 this.setValues(band, this.bands[mode.value]);
314 this.toggleWifiChannel(elem);
317 toggleWifiChannel: function(elem) {
318 var band = elem.querySelector('.band');
319 var chan = elem.querySelector('.channel');
321 this.setValues(chan, this.channels[band.value]);
324 setInitialValues: function(section_id, elem) {
325 var mode = elem.querySelector('.mode'),
326 band = elem.querySelector('.band'),
327 chan = elem.querySelector('.channel'),
328 bwdt = elem.querySelector('.htmode'),
329 htval = uci.get('wireless', section_id, 'htmode'),
330 hwval = uci.get('wireless', section_id, 'hwmode'),
331 chval = uci.get('wireless', section_id, 'channel');
333 this.setValues(mode, this.modes);
335 if (/VHT20|VHT40|VHT80|VHT160/.test(htval))
337 else if (/HT20|HT40/.test(htval))
342 this.toggleWifiMode(elem);
349 this.toggleWifiBand(elem);
357 renderWidget: function(section_id, option_index, cfgvalue) {
360 L.dom.content(elem, [
361 E('label', { 'style': 'float:left; margin-right:3px' }, [
365 'style': 'width:auto',
366 'change': L.bind(this.toggleWifiMode, this, elem)
369 E('label', { 'style': 'float:left; margin-right:3px' }, [
373 'style': 'width:auto',
374 'change': L.bind(this.toggleWifiBand, this, elem)
377 E('label', { 'style': 'float:left; margin-right:3px' }, [
378 _('Channel'), E('br'),
381 'style': 'width:auto'
384 E('label', { 'style': 'float:left; margin-right:3px' }, [
388 'style': 'width:auto'
391 E('br', { 'style': 'clear:left' })
394 return this.setInitialValues(section_id, elem);
397 cfgvalue: function(section_id) {
399 uci.get('wireless', section_id, 'htmode'),
400 uci.get('wireless', section_id, 'hwmode'),
401 uci.get('wireless', section_id, 'channel')
405 formvalue: function(section_id) {
406 var node = this.map.findElement('data-field', this.cbid(section_id));
409 node.querySelector('.htmode').value,
410 node.querySelector('.band').value,
411 node.querySelector('.channel').value
415 write: function(section_id, value) {
416 uci.set('wireless', section_id, 'htmode', value[0] || null);
417 uci.set('wireless', section_id, 'hwmode', value[1]);
418 uci.set('wireless', section_id, 'channel', value[2]);
422 var CBIWifiTxPowerValue = form.ListValue.extend({
423 callTxPowerList: rpc.declare({
425 method: 'txpowerlist',
426 params: [ 'device' ],
427 expect: { results: [] }
430 load: function(section_id) {
431 return this.callTxPowerList(section_id).then(L.bind(function(pwrlist) {
432 this.powerval = this.wifiNetwork ? this.wifiNetwork.getTXPower() : null;
433 this.poweroff = this.wifiNetwork ? this.wifiNetwork.getTXPowerOffset() : null;
435 this.value('', _('driver default'));
437 for (var i = 0; i < pwrlist.length; i++)
438 this.value(pwrlist[i].dbm, '%d dBm (%d mW)'.format(pwrlist[i].dbm, pwrlist[i].mw));
440 return form.ListValue.prototype.load.apply(this, [section_id]);
444 renderWidget: function(section_id, option_index, cfgvalue) {
445 var widget = form.ListValue.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
446 widget.firstElementChild.style.width = 'auto';
448 L.dom.append(widget, E('span', [
449 ' - ', _('Current power'), ': ',
450 E('span', [ this.powerval != null ? '%d dBm'.format(this.powerval) : E('em', _('unknown')) ]),
451 this.poweroff ? ' + %d dB offset = %s dBm'.format(this.poweroff, this.powerval != null ? this.powerval + this.poweroff : '?') : ''
458 var CBIWifiCountryValue = form.Value.extend({
459 callCountryList: rpc.declare({
461 method: 'countrylist',
462 params: [ 'device' ],
463 expect: { results: [] }
466 load: function(section_id) {
467 return this.callCountryList(section_id).then(L.bind(function(countrylist) {
468 if (Array.isArray(countrylist) && countrylist.length > 0) {
469 this.value('', _('driver default'));
471 for (var i = 0; i < countrylist.length; i++)
472 this.value(countrylist[i].iso3166, '%s - %s'.format(countrylist[i].iso3166, countrylist[i].country));
475 return form.Value.prototype.load.apply(this, [section_id]);
479 validate: function(section_id, formvalue) {
480 if (formvalue != null && formvalue != '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue))
481 return _('Use ISO/IEC 3166 alpha2 country codes.');
486 renderWidget: function(section_id, option_index, cfgvalue) {
487 var typeClass = (this.keylist && this.keylist.length) ? form.ListValue : form.Value;
488 return typeClass.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
492 return L.view.extend({
493 poll_status: function(map, data) {
494 var rows = map.querySelectorAll('.cbi-section-table-row[data-sid]');
496 for (var i = 0; i < rows.length; i++) {
497 var section_id = rows[i].getAttribute('data-sid'),
498 radioDev = data[1].filter(function(d) { return d.getName() == section_id })[0],
499 radioNet = data[2].filter(function(n) { return n.getName() == section_id })[0],
500 badge = rows[i].querySelector('[data-name="_badge"] > div'),
501 stat = rows[i].querySelector('[data-name="_stat"]'),
502 btns = rows[i].querySelectorAll('.cbi-section-actions button'),
503 busy = btns[0].classList.contains('spinning') || btns[1].classList.contains('spinning') || btns[2].classList.contains('spinning');
506 L.dom.content(badge, render_radio_badge(radioDev));
507 L.dom.content(stat, render_radio_status(radioDev, data[2].filter(function(n) { return n.getWifiDeviceName() == radioDev.getName() })));
510 L.dom.content(badge, render_network_badge(radioNet));
511 L.dom.content(stat, render_network_status(radioNet));
514 if (stat.hasAttribute('restart'))
515 L.dom.content(stat, E('em', _('Device is restarting…')));
517 btns[0].disabled = busy;
518 btns[1].disabled = busy;
519 btns[2].disabled = busy;
522 var table = document.querySelector('wifi_assoclist_table'),
526 for (var i = 0; i < data[3].length; i++) {
527 var bss = data[3][i],
528 name = hosts.getHostnameByMACAddr(bss.mac),
529 ipv4 = hosts.getIPAddrByMACAddr(bss.mac),
530 ipv6 = hosts.getIP6AddrByMACAddr(bss.mac);
533 E('span', { 'class': 'ifacebadge' }, [
535 'src': L.resource('icons/wifi%s.png').format(bss.network.isUp() ? '' : '_disabled'),
536 'title': bss.radio.getI18n()
538 ' %s '.format(bss.network.getShortName()),
539 E('small', '(%s)'.format(bss.network.getIfname()))
542 name ? '%s (%s)'.format(name, ipv4 || ipv6 || '?') : ipv4 || ipv6 || '?',
543 render_signal_badge(Math.min((bss.signal + 110) / 70 * 100, 100), bss.signal, bss.noise),
545 E('span', format_wifirate(bss.rx)),
547 E('span', format_wifirate(bss.tx))
552 cbi_update_table('#wifi_assoclist_table', trows, E('em', _('No information available')));
554 var stat = document.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large');
557 render_modal_status(stat, data[2].filter(function(n) { return n.getName() == stat.getAttribute('data-network') })[0]);
559 return network.flushCache();
569 checkAnonymousSections: function() {
570 var wifiIfaces = uci.sections('wireless', 'wifi-iface');
572 for (var i = 0; i < wifiIfaces.length; i++)
573 if (wifiIfaces[i]['.anonymous'])
579 callUciRename: rpc.declare({
582 params: [ 'config', 'section', 'name' ]
586 if (this.checkAnonymousSections())
587 return this.renderMigration();
589 return this.renderOverview();
592 handleMigration: function(ev) {
593 var wifiIfaces = uci.sections('wireless', 'wifi-iface'),
597 for (var i = 0; i < wifiIfaces.length; i++) {
598 if (!wifiIfaces[i]['.anonymous'])
601 var new_name = next_free_sid(id_offset);
603 tasks.push(this.callUciRename('wireless', wifiIfaces[i]['.name'], new_name));
604 id_offset = +new_name.substring(7) + 1;
607 return Promise.all(tasks)
608 .then(L.bind(L.ui.changes.init, L.ui.changes))
609 .then(L.bind(L.ui.changes.apply, L.ui.changes));
612 renderMigration: function() {
613 L.ui.showModal(_('Wireless configuration migration'), [
614 E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')),
615 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.')),
616 E('div', { 'class': 'right' },
618 'class': 'btn cbi-button-action important',
619 'click': L.ui.createHandlerFn(this, 'handleMigration')
624 renderOverview: function() {
627 m = new form.Map('wireless');
631 s = m.section(form.GridSection, 'wifi-device', _('Wireless Overview'));
635 s.load = function() {
636 return network.getWifiDevices().then(L.bind(function(radios) {
637 this.radios = radios.sort(function(a, b) {
638 return a.getName() > b.getName();
643 for (var i = 0; i < radios.length; i++)
644 tasks.push(radios[i].getWifiNetworks());
646 return Promise.all(tasks);
647 }, this)).then(L.bind(function(data) {
650 for (var i = 0; i < data.length; i++)
651 this.wifis.push.apply(this.wifis, data[i]);
655 s.cfgsections = function() {
658 for (var i = 0; i < this.radios.length; i++) {
659 rv.push(this.radios[i].getName());
661 for (var j = 0; j < this.wifis.length; j++)
662 if (this.wifis[j].getWifiDeviceName() == this.radios[i].getName())
663 rv.push(this.wifis[j].getName());
669 s.modaltitle = function(section_id) {
670 var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id})[0];
671 return radioNet ? radioNet.getI18n() : _('Edit wireless network');
674 s.lookupRadioOrNetwork = function(section_id) {
675 var radioDev = this.radios.filter(function(r) { return r.getName() == section_id })[0];
679 var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id })[0];
686 s.renderRowActions = function(section_id) {
687 var inst = this.lookupRadioOrNetwork(section_id), btns;
689 if (inst.getWifiNetworks) {
692 'class': 'cbi-button cbi-button-neutral',
693 'title': _('Restart radio interface'),
694 'click': L.ui.createHandlerFn(this, radio_restart, section_id)
697 'class': 'cbi-button cbi-button-action important',
698 'title': _('Find and join network'),
699 'click': L.ui.createHandlerFn(this, 'handleScan', inst)
702 'class': 'cbi-button cbi-button-add',
703 'title': _('Provide new network'),
704 'click': L.ui.createHandlerFn(this, 'handleAdd', inst)
709 var isDisabled = (inst.get('disabled') == '1' ||
710 uci.get('wireless', inst.getWifiDeviceName(), 'disabled') == '1');
714 'class': 'cbi-button cbi-button-neutral enable-disable',
715 'title': isDisabled ? _('Enable this network') : _('Disable this network'),
716 'click': L.ui.createHandlerFn(this, network_updown, section_id, this.map)
717 }, isDisabled ? _('Enable') : _('Disable')),
719 'class': 'cbi-button cbi-button-action important',
720 'title': _('Edit this network'),
721 'click': L.ui.createHandlerFn(this, 'renderMoreOptionsModal', section_id)
724 'class': 'cbi-button cbi-button-negative remove',
725 'title': _('Delete this network'),
726 'click': L.ui.createHandlerFn(this, 'handleRemove', section_id)
731 return E('div', { 'class': 'td middle cbi-section-actions' }, E('div', btns));
734 s.addModalOptions = function(s) {
735 return network.getWifiNetwork(s.section).then(function(radioNet) {
736 var hwtype = uci.get('wireless', radioNet.getWifiDeviceName(), 'type');
739 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
743 ss.tab('general', _('General Setup'));
744 ss.tab('advanced', _('Advanced Settings'));
746 var isDisabled = (radioNet.get('disabled') == '1' ||
747 uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == 1);
749 o = ss.taboption('general', form.DummyValue, '_wifistat_modal', _('Status'));
750 o.cfgvalue = L.bind(function(radioNet) {
751 return render_modal_status(null, radioNet);
753 o.write = function() {};
755 o = ss.taboption('general', form.Button, '_toggle', isDisabled ? _('Wireless network is disabled') : _('Wireless network is enabled'));
756 o.inputstyle = isDisabled ? 'apply' : 'reset';
757 o.inputtitle = isDisabled ? _('Enable') : _('Disable');
758 o.onclick = L.ui.createHandlerFn(s, network_updown, s.section, s.map);
760 o = ss.taboption('general', CBIWifiFrequencyValue, '_freq', '<br />' + _('Operating frequency'));
761 o.ucisection = s.section;
763 if (hwtype == 'mac80211') {
764 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.'));
765 o.wifiNetwork = radioNet;
767 o = ss.taboption('advanced', CBIWifiCountryValue, 'country', _('Country Code'));
768 o.wifiNetwork = radioNet;
770 o = ss.taboption('advanced', form.Flag, 'legacy_rates', _('Allow legacy 802.11b rates'));
771 o.default = o.enabled;
773 o = ss.taboption('advanced', form.Value, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
774 o.datatype = 'range(0,114750)';
775 o.placeholder = 'auto';
777 o = ss.taboption('advanced', form.Value, 'frag', _('Fragmentation Threshold'));
778 o.datatype = 'min(256)';
779 o.placeholder = _('off');
781 o = ss.taboption('advanced', form.Value, 'rts', _('RTS/CTS Threshold'));
782 o.datatype = 'uinteger';
783 o.placeholder = _('off');
785 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!'));
788 o = ss.taboption('advanced', form.Value, 'beacon_int', _('Beacon Interval'));
789 o.datatype = 'range(15,65535)';
795 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getName(), 'wifi-iface', _('Interface Configuration'));
799 ss.tab('general', _('General Setup'));
800 ss.tab('encryption', _('Wireless Security'));
801 ss.tab('macfilter', _('MAC-Filter'));
802 ss.tab('advanced', _('Advanced Settings'));
804 o = ss.taboption('general', form.ListValue, 'mode', _('Mode'));
805 o.value('ap', _('Access Point'));
806 o.value('sta', _('Client'));
807 o.value('adhoc', _('Ad-Hoc'));
809 o = ss.taboption('general', form.Value, 'mesh_id', _('Mesh Id'));
810 o.depends('mode', 'mesh');
812 o = ss.taboption('advanced', form.Flag, 'mesh_fwding', _('Forward mesh peer traffic'));
815 o.depends('mode', 'mesh');
817 o = ss.taboption('advanced', form.Value, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
820 o.datatype = 'range(-255,1)';
821 o.depends('mode', 'mesh');
823 o = ss.taboption('general', form.Value, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
824 o.datatype = 'maxlength(32)';
825 o.depends('mode', 'ap');
826 o.depends('mode', 'sta');
827 o.depends('mode', 'adhoc');
828 o.depends('mode', 'ahdemo');
829 o.depends('mode', 'monitor');
830 o.depends('mode', 'ap-wds');
831 o.depends('mode', 'sta-wds');
832 o.depends('mode', 'wds');
834 o = ss.taboption('general', form.Value, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
835 o.datatype = 'macaddr';
837 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.'));
841 o.write = function(section_id, value) {
842 return network.getDevice(section_id).then(L.bind(function(dev) {
843 var old_networks = dev.getNetworks().reduce(function(o, v) { o[v.getName()] = v; return o }, {}),
845 values = L.toArray(value),
848 for (var i = 0; i < values.length; i++) {
849 new_networks[values[i]] = true;
851 if (old_networks[values[i]])
854 tasks.push(network.getNetwork(values[i]).then(L.bind(function(name, net) {
855 return net || network.addNetwork(name, { proto: 'none' });
856 }, this, values[i])).then(L.bind(function(dev, net) {
859 net.set('type', 'bridge');
865 for (var name in old_networks)
866 if (!new_networks[name])
867 tasks.push(network.getNetwork(name).then(L.bind(function(dev, net) {
869 net.deleteDevice(dev);
872 return Promise.all(tasks);
876 if (hwtype == 'mac80211') {
877 var mode = ss.children[0],
878 bssid = ss.children[5],
881 mode.value('mesh', '802.11s');
882 mode.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
883 mode.value('monitor', _('Monitor'));
885 bssid.depends('mode', 'adhoc');
886 bssid.depends('mode', 'sta');
887 bssid.depends('mode', 'sta-wds');
889 o = ss.taboption('macfilter', form.ListValue, 'macfilter', _('MAC-Address Filter'));
890 o.depends('mode', 'ap');
891 o.depends('mode', 'ap-wds');
892 o.value('', _('disable'));
893 o.value('allow', _('Allow listed only'));
894 o.value('deny', _('Allow all except listed'));
896 o = ss.taboption('macfilter', form.DynamicList, 'maclist', _('MAC-List'));
897 o.datatype = 'macaddr';
898 o.depends('macfilter', 'allow');
899 o.depends('macfilter', 'deny');
900 o.load = function(section_id) {
901 return network.getHostHints().then(L.bind(function(hints) {
902 hints.getMACHints().map(L.bind(function(hint) {
903 this.value(hint[0], hint[1] ? '%s (%s)'.format(hint[0], hint[1]) : hint[0]);
906 return form.DynamicList.prototype.load.apply(this, [section_id]);
910 mode.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
911 mode.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
913 mode.write = function(section_id, value) {
916 uci.set('wireless', section_id, 'mode', 'ap');
917 uci.set('wireless', section_id, 'wds', '1');
921 uci.set('wireless', section_id, 'mode', 'sta');
922 uci.set('wireless', section_id, 'wds', '1');
926 uci.set('wireless', section_id, 'mode', value);
927 uci.unset('wireless', section_id, 'wds');
932 mode.cfgvalue = function(section_id) {
933 var mode = uci.get('wireless', section_id, 'mode'),
934 wds = uci.get('wireless', section_id, 'wds');
936 if (mode == 'ap' && wds)
938 else if (mode == 'sta' && wds)
944 o = ss.taboption('general', form.Flag, 'hidden', _('Hide <abbr title="Extended Service Set Identifier">ESSID</abbr>'));
945 o.depends('mode', 'ap');
946 o.depends('mode', 'ap-wds');
948 o = ss.taboption('general', form.Flag, 'wmm', _('WMM Mode'));
949 o.depends('mode', 'ap');
950 o.depends('mode', 'ap-wds');
951 o.default = o.enabled;
953 o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
954 o.depends('mode', 'ap');
955 o.depends('mode', 'ap-wds');
957 o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name'));
959 o.placeholder = radioNet.getIfname();
960 if (/^radio\d+\.network/.test(o.placeholder))
963 o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble'));
964 o.default = o.enabled;
966 o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
969 o.datatype = 'range(1,255)';
971 o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
974 o.datatype = 'uinteger';
976 o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling'));
978 o.datatype = 'uinteger';
980 o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('sec'));
983 o.datatype = 'uinteger';
985 o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval'));
987 o.placeholder = 65535;
988 o.datatype = 'uinteger';
990 o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
991 o.default = o.enabled;
995 encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption'));
996 o.depends('mode', 'ap');
997 o.depends('mode', 'sta');
998 o.depends('mode', 'adhoc');
999 o.depends('mode', 'ahdemo');
1000 o.depends('mode', 'ap-wds');
1001 o.depends('mode', 'sta-wds');
1002 o.depends('mode', 'mesh');
1004 o.cfgvalue = function(section_id) {
1005 var v = String(uci.get('wireless', section_id, 'encryption'));
1008 else if (v.match(/\+/))
1009 return v.replace(/\+.+$/, '');
1013 o.write = function(section_id, value) {
1014 var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id),
1015 co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id);
1017 if (value == 'wpa' || value == 'wpa2')
1018 uci.unset('wireless', section_id, 'key');
1020 if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp'))
1023 uci.set('wireless', section_id, 'encryption', e);
1026 o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher'));
1027 o.depends('encryption', 'wpa');
1028 o.depends('encryption', 'wpa2');
1029 o.depends('encryption', 'psk');
1030 o.depends('encryption', 'psk2');
1031 o.depends('encryption', 'wpa-mixed');
1032 o.depends('encryption', 'psk-mixed');
1033 o.value('auto', _('auto'));
1034 o.value('ccmp', _('Force CCMP (AES)'));
1035 o.value('tkip', _('Force TKIP'));
1036 o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1037 o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write;
1039 o.cfgvalue = function(section_id) {
1040 var v = String(uci.get('wireless', section_id, 'encryption'));
1041 if (v.match(/\+/)) {
1042 v = v.replace(/^[^+]+\+/, '');
1045 else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip')
1052 var crypto_modes = [];
1054 if (hwtype == 'mac80211') {
1055 var has_supplicant = L.hasSystemFeature('wpasupplicant'),
1056 has_hostapd = L.hasSystemFeature('hostapd');
1058 // Probe EAP support
1059 var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'),
1060 has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap');
1062 // Probe SAE support
1063 var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'),
1064 has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae');
1066 // Probe OWE support
1067 var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
1068 has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
1071 if (has_hostapd || has_supplicant) {
1072 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1073 crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1074 crypto_modes.push(['psk', 'WPA-PSK', 21]);
1077 encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1080 if (has_ap_sae || has_sta_sae) {
1081 crypto_modes.push(['sae', 'WPA3-SAE', 31]);
1082 crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1085 if (has_ap_eap || has_sta_eap) {
1086 crypto_modes.push(['wpa2', 'WPA2-EAP', 32]);
1087 crypto_modes.push(['wpa', 'WPA-EAP', 20]);
1090 if (has_ap_owe || has_sta_owe) {
1091 crypto_modes.push(['owe', 'OWE', 1]);
1094 encr.crypto_support = {
1098 'psk': has_hostapd || _('Requires hostapd'),
1099 'psk2': has_hostapd || _('Requires hostapd'),
1100 'psk-mixed': has_hostapd || _('Requires hostapd'),
1101 'sae': has_ap_sae || _('Requires hostapd with SAE support'),
1102 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
1103 'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
1104 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
1105 'owe': has_ap_owe || _('Requires hostapd with OWE support')
1110 'psk': has_supplicant || _('Requires wpa-supplicant'),
1111 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1112 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1113 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1114 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1115 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1116 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1117 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
1122 'psk': has_supplicant || _('Requires wpa-supplicant'),
1123 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1124 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1127 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support')
1139 encr.crypto_support['ap-wds'] = encr.crypto_support['ap'];
1140 encr.crypto_support['sta-wds'] = encr.crypto_support['sta'];
1142 encr.validate = function(section_id, value) {
1143 var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
1144 modeval = modeopt.formvalue(section_id),
1145 modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)],
1146 enctitle = this.vallist[this.keylist.indexOf(value)];
1148 if (value == 'none')
1151 if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value))
1152 return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle);
1154 return this.crypto_support[modeval][value];
1157 else if (hwtype == 'broadcom') {
1158 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1159 crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1160 crypto_modes.push(['psk', 'WPA-PSK', 21]);
1163 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1164 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1165 crypto_modes.push(['none', _('No Encryption'), 0]);
1167 crypto_modes.sort(function(a, b) { return b[2] - a[2] });
1169 for (var i = 0; i < crypto_modes.length; i++) {
1170 var security_level = (crypto_modes[i][2] >= 30) ? _('strong security')
1171 : (crypto_modes[i][2] >= 20) ? _('medium security')
1172 : (crypto_modes[i][2] >= 10) ? _('weak security') : _('open network');
1174 encr.value(crypto_modes[i][0], '%s (%s)'.format(crypto_modes[i][1], security_level));
1178 o = ss.taboption('encryption', form.Value, 'auth_server', _('Radius-Authentication-Server'));
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' });
1184 o.datatype = 'host(0)';
1186 o = ss.taboption('encryption', form.Value, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
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 = 'port';
1194 o = ss.taboption('encryption', form.Value, 'auth_secret', _('Radius-Authentication-Secret'));
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' });
1202 o = ss.taboption('encryption', form.Value, 'acct_server', _('Radius-Accounting-Server'));
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' });
1208 o.datatype = 'host(0)';
1210 o = ss.taboption('encryption', form.Value, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
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 = 'port';
1218 o = ss.taboption('encryption', form.Value, 'acct_secret', _('Radius-Accounting-Secret'));
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' });
1226 o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client'));
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' });
1232 o.datatype = 'host(0)';
1234 o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
1235 o.depends({ mode: 'ap', encryption: 'wpa' });
1236 o.depends({ mode: 'ap', encryption: 'wpa2' });
1237 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1238 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1240 o.datatype = 'port';
1242 o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret'));
1243 o.depends({ mode: 'ap', encryption: 'wpa' });
1244 o.depends({ mode: 'ap', encryption: 'wpa2' });
1245 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1246 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1251 o = ss.taboption('encryption', form.Value, '_wpa_key', _('Key'));
1252 o.depends('encryption', 'psk');
1253 o.depends('encryption', 'psk2');
1254 o.depends('encryption', 'psk+psk2');
1255 o.depends('encryption', 'psk-mixed');
1256 o.depends('encryption', 'sae');
1257 o.depends('encryption', 'sae-mixed');
1258 o.datatype = 'wpakey';
1262 o.cfgvalue = function(section_id) {
1263 var key = uci.get('wireless', section_id, 'key');
1264 return /^[1234]$/.test(key) ? null : key;
1267 o.write = function(section_id, value) {
1268 uci.set('wireless', section_id, 'key', value);
1269 uci.unset('wireless', section_id, 'key1');
1270 uci.unset('wireless', section_id, 'key2');
1271 uci.unset('wireless', section_id, 'key3');
1272 uci.unset('wireless', section_id, 'key4');
1276 o = ss.taboption('encryption', form.ListValue, '_wep_key', _('Used Key Slot'));
1277 o.depends('encryption', 'wep-open');
1278 o.depends('encryption', 'wep-shared');
1279 o.value('1', _('Key #%d').format(1));
1280 o.value('2', _('Key #%d').format(2));
1281 o.value('3', _('Key #%d').format(3));
1282 o.value('4', _('Key #%d').format(4));
1284 o.cfgvalue = function(section_id) {
1285 var slot = +uci.get('wireless', section_id, 'key');
1286 return (slot >= 1 && slot <= 4) ? String(slot) : '';
1289 o.write = function(section_id, value) {
1290 uci.set('wireless', section_id, 'key', value);
1293 for (var slot = 1; slot <= 4; slot++) {
1294 o = ss.taboption('encryption', form.Value, 'key%d'.format(slot), _('Key #%d').format(slot));
1295 o.depends('encryption', 'wep-open');
1296 o.depends('encryption', 'wep-shared');
1297 o.datatype = 'wepkey';
1301 o.write = function(section_id, value) {
1302 if (value != null && (value.length == 5 || value.length == 13))
1303 value = 's:%s'.format(value);
1304 uci.set('wireless', section_id, this.option, value);
1309 if (hwtype == 'mac80211') {
1310 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1311 var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap');
1313 o = ss.taboption('encryption', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
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' });
1319 o.depends({ mode: 'ap', encryption: 'psk' });
1320 o.depends({ mode: 'ap', encryption: 'psk2' });
1321 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1322 o.depends({ mode: 'ap', encryption: 'sae' });
1323 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1324 o.depends({ mode: 'ap-wds', encryption: 'psk' });
1325 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1326 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1327 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1328 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1332 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.'));
1333 o.depends({ mode: 'ap', encryption: 'wpa' });
1334 o.depends({ mode: 'ap', encryption: 'wpa2' });
1335 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1336 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1337 o.depends({ ieee80211r: '1' });
1340 o = ss.taboption('encryption', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1341 o.depends({ ieee80211r: '1' });
1342 o.placeholder = '4f57';
1343 o.datatype = 'and(hexstring,length(4))';
1346 o = ss.taboption('encryption', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1347 o.depends({ ieee80211r: '1' });
1348 o.placeholder = '1000';
1349 o.datatype = 'range(1000,65535)';
1352 o = ss.taboption('encryption', form.ListValue, 'ft_over_ds', _('FT protocol'));
1353 o.depends({ ieee80211r: '1' });
1354 o.value('1', _('FT over DS'));
1355 o.value('0', _('FT over the Air'));
1358 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.'));
1359 o.depends({ ieee80211r: '1' });
1360 o.default = o.enabled;
1363 o = ss.taboption('encryption', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1364 o.depends({ ieee80211r: '1' });
1365 o.placeholder = '10000';
1366 o.datatype = 'uinteger';
1369 o = ss.taboption('encryption', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1370 o.depends({ ieee80211r: '1' });
1371 o.placeholder = '00004f577274';
1372 o.datatype = 'and(hexstring,length(12))';
1375 o = ss.taboption('encryption', form.Flag, 'pmk_r1_push', _('PMK R1 Push'));
1376 o.depends({ ieee80211r: '1' });
1377 o.placeholder = '0';
1380 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.'));
1381 o.depends({ ieee80211r: '1' });
1384 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.'));
1385 o.depends({ ieee80211r: '1' });
1387 // End of 802.11r options
1389 o = ss.taboption('encryption', form.ListValue, 'eap_type', _('EAP-Method'));
1390 o.value('tls', 'TLS');
1391 o.value('ttls', 'TTLS');
1392 o.value('peap', 'PEAP');
1393 o.value('fast', 'FAST');
1394 o.depends({ mode: 'sta', encryption: 'wpa' });
1395 o.depends({ mode: 'sta', encryption: 'wpa2' });
1396 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1397 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1399 o = ss.taboption('encryption', form.FileUpload, 'ca_cert', _('Path to CA-Certificate'));
1400 o.depends({ mode: 'sta', encryption: 'wpa' });
1401 o.depends({ mode: 'sta', encryption: 'wpa2' });
1402 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1403 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1405 o = ss.taboption('encryption', form.FileUpload, 'client_cert', _('Path to Client-Certificate'));
1406 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1407 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1408 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1409 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1411 o = ss.taboption('encryption', form.FileUpload, 'priv_key', _('Path to Private Key'));
1412 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1413 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1414 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1415 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1417 o = ss.taboption('encryption', form.Value, 'priv_key_pwd', _('Password of Private Key'));
1418 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1419 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1420 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1421 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1424 o = ss.taboption('encryption', form.ListValue, 'auth', _('Authentication'));
1425 o.value('PAP', 'PAP');
1426 o.value('CHAP', 'CHAP');
1427 o.value('MSCHAP', 'MSCHAP');
1428 o.value('MSCHAPV2', 'MSCHAPv2');
1431 o.value('EAP-MSCHAPV2');
1433 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1434 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1435 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1436 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1437 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1438 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1439 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1440 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1441 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1442 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1443 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1444 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1446 o.validate = function(section_id, value) {
1447 var eo = this.section.children.filter(function(o) { return o.option == 'eap_type' })[0],
1448 ev = eo.formvalue(section_id);
1450 if (ev != 'ttls' && (value == 'PAP' || value == 'CHAP' || value == 'MSCHAP' || value == 'MSCHAPV2'))
1451 return _('This authentication type is not applicable to the selected EAP method.');
1456 o = ss.taboption('encryption', form.FileUpload, 'ca_cert2', _('Path to inner CA-Certificate'));
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' });
1462 o = ss.taboption('encryption', form.FileUpload, 'client_cert2', _('Path to inner Client-Certificate'));
1463 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1464 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1465 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1466 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1468 o = ss.taboption('encryption', form.FileUpload, 'priv_key2', _('Path to inner Private Key'));
1469 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1470 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1471 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1472 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1474 o = ss.taboption('encryption', form.Value, 'priv_key2_pwd', _('Password of inner Private Key'));
1475 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1476 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1477 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1478 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1481 o = ss.taboption('encryption', form.Value, 'identity', _('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, 'anonymous_identity', _('Anonymous Identity'));
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' });
1512 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1513 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1514 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1515 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1517 o = ss.taboption('encryption', form.Value, 'password', _('Password'));
1518 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1519 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1520 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1521 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1522 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1523 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1524 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1525 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1526 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1527 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1528 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1529 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1533 if (hwtype == 'mac80211') {
1534 // ieee802.11w options
1535 if (L.hasSystemFeature('hostapd', '11w')) {
1536 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)"));
1537 o.value('', _('Disabled'));
1538 o.value('1', _('Optional'));
1539 o.value('2', _('Required'));
1540 o.depends({ mode: 'ap', encryption: 'wpa2' });
1541 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1542 o.depends({ mode: 'ap', encryption: 'psk2' });
1543 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1544 o.depends({ mode: 'ap', encryption: 'sae' });
1545 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1546 o.depends({ mode: 'ap', encryption: 'owe' });
1547 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1548 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1549 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1550 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1551 o.depends({ mode: 'ap-wds', encryption: 'owe' });
1552 o.depends({ mode: 'sta', encryption: 'wpa2' });
1553 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1554 o.depends({ mode: 'sta', encryption: 'psk2' });
1555 o.depends({ mode: 'sta', encryption: 'psk-mixed' });
1556 o.depends({ mode: 'sta', encryption: 'sae' });
1557 o.depends({ mode: 'sta', encryption: 'sae-mixed' });
1558 o.depends({ mode: 'sta', encryption: 'owe' });
1559 o.depends({ mode: 'sta-wds', encryption: 'psk2' });
1560 o.depends({ mode: 'sta-wds', encryption: 'psk-mixed' });
1561 o.depends({ mode: 'sta-wds', encryption: 'sae' });
1562 o.depends({ mode: 'sta-wds', encryption: 'sae-mixed' });
1563 o.depends({ mode: 'sta-wds', encryption: 'owe' });
1565 '2': [{ encryption: 'sae' }, { encryption: 'owe' }],
1566 '1': [{ encryption: 'sae-mixed'}],
1570 o = ss.taboption('encryption', form.Value, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1571 o.depends('ieee80211w', '1');
1572 o.depends('ieee80211w', '2');
1573 o.datatype = 'uinteger';
1574 o.placeholder = '1000';
1577 o = ss.taboption('encryption', form.Value, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1578 o.depends('ieee80211w', '1');
1579 o.depends('ieee80211w', '2');
1580 o.datatype = 'uinteger';
1581 o.placeholder = '201';
1585 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.'));
1586 o.depends({ mode: 'ap', encryption: 'wpa2' });
1587 o.depends({ mode: 'ap', encryption: 'psk2' });
1588 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1589 o.depends({ mode: 'ap', encryption: 'sae' });
1590 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1591 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1592 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1593 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1594 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1595 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1597 if (L.hasSystemFeature('hostapd', 'cli') && L.hasSystemFeature('wpasupplicant')) {
1598 o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1601 o.default = o.disabled;
1602 o.depends('encryption', 'psk');
1603 o.depends('encryption', 'psk2');
1604 o.depends('encryption', 'psk-mixed');
1605 o.depends('encryption', 'sae');
1606 o.depends('encryption', 'sae-mixed');
1613 s.handleRemove = function(section_id, ev) {
1614 document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id)).style.opacity = 0.5;
1615 return form.TypedSection.prototype.handleRemove.apply(this, [section_id, ev]);
1618 s.handleScan = function(radioDev, ev) {
1619 var table = E('div', { 'class': 'table' }, [
1620 E('div', { 'class': 'tr table-titles' }, [
1621 E('div', { 'class': 'th col-2 middle center' }, _('Signal')),
1622 E('div', { 'class': 'th col-4 middle left' }, _('SSID')),
1623 E('div', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1624 E('div', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1625 E('div', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1626 E('div', { 'class': 'th col-3 middle left' }, _('Encryption')),
1627 E('div', { 'class': 'th cbi-section-actions right' }, ' '),
1631 cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1633 var md = L.ui.showModal(_('Join Network: Wireless Scan'), [
1635 E('div', { 'class': 'right' },
1638 'click': L.bind(this.handleScanAbort, this)
1642 md.style.maxWidth = '90%';
1643 md.style.maxHeight = 'none';
1645 this.pollFn = L.bind(this.handleScanRefresh, this, radioDev, {}, table);
1647 L.Poll.add(this.pollFn);
1651 s.handleScanRefresh = function(radioDev, scanCache, table) {
1652 return radioDev.getScanList().then(L.bind(function(results) {
1655 for (var i = 0; i < results.length; i++)
1656 scanCache[results[i].bssid] = results[i];
1658 for (var k in scanCache)
1659 if (scanCache[k].stale)
1660 results.push(scanCache[k]);
1662 results.sort(function(a, b) {
1663 var diff = (b.quality - a.quality) || (a.channel - b.channel);
1668 if (a.ssid < b.ssid)
1670 else if (a.ssid > b.ssid)
1673 if (a.bssid < b.bssid)
1675 else if (a.bssid > b.bssid)
1679 for (var i = 0; i < results.length; i++) {
1680 var res = results[i],
1681 qv = res.quality || 0,
1682 qm = res.quality_max || 0,
1683 q = (qv > 0 && qm > 0) ? Math.floor((100 / qm) * qv) : 0,
1684 s = res.stale ? 'opacity:0.5' : '';
1687 E('span', { 'style': s }, render_signal_badge(q, res.signal, res.noise)),
1688 E('span', { 'style': s }, '%h'.format(res.ssid)),
1689 E('span', { 'style': s }, '%d'.format(res.channel)),
1690 E('span', { 'style': s }, '%h'.format(res.mode)),
1691 E('span', { 'style': s }, '%h'.format(res.bssid)),
1692 E('span', { 'style': s }, '%h'.format(network.formatWifiEncryption(res.encryption))),
1693 E('div', { 'class': 'right' }, E('button', {
1694 'class': 'cbi-button cbi-button-action important',
1695 'click': L.bind(this.handleJoin, this, radioDev, res)
1696 }, _('Join Network')))
1702 cbi_update_table(table, rows);
1706 s.handleScanAbort = function(ev) {
1707 var md = L.dom.parent(ev.target, 'div[aria-modal="true"]');
1709 md.style.maxWidth = '';
1710 md.style.maxHeight = '';
1714 L.Poll.remove(this.pollFn);
1719 s.handleJoinConfirm = function(radioDev, bss, form, ev) {
1720 var nameopt = L.toArray(form.lookupOption('name', '_new_'))[0],
1721 passopt = L.toArray(form.lookupOption('password', '_new_'))[0],
1722 zoneopt = L.toArray(form.lookupOption('zone', '_new_'))[0],
1723 replopt = L.toArray(form.lookupOption('replace', '_new_'))[0],
1724 nameval = (nameopt && nameopt.isValid('_new_')) ? nameopt.formvalue('_new_') : null,
1725 passval = (passopt && passopt.isValid('_new_')) ? passopt.formvalue('_new_') : null,
1726 zoneval = zoneopt ? zoneopt.formvalue('_new_') : null,
1727 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1728 is_wep = (enc && Array.isArray(enc.wep)),
1729 is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' })),
1730 is_sae = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'sae' }));
1732 if (nameval == null || (passopt && passval == null))
1735 var section_id = null;
1737 return this.map.save(function() {
1738 var wifi_sections = uci.sections('wireless', 'wifi-iface');
1740 if (replopt.formvalue('_new_') == '1') {
1741 for (var i = 0; i < wifi_sections.length; i++)
1742 if (wifi_sections[i].device == radioDev.getName())
1743 uci.remove('wireless', wifi_sections[i]['.name']);
1746 if (uci.get('wireless', radioDev.getName(), 'disabled') == '1') {
1747 for (var i = 0; i < wifi_sections.length; i++)
1748 if (wifi_sections[i].device == radioDev.getName())
1749 uci.set('wireless', wifi_sections[i]['.name'], 'disabled', '1');
1751 uci.unset('wireless', radioDev.getName(), 'disabled');
1754 section_id = next_free_sid(wifi_sections.length);
1756 uci.add('wireless', 'wifi-iface', section_id);
1757 uci.set('wireless', section_id, 'device', radioDev.getName());
1758 uci.set('wireless', section_id, 'mode', (bss.mode == 'Ad-Hoc') ? 'adhoc' : 'sta');
1759 uci.set('wireless', section_id, 'network', nameval);
1761 if (bss.ssid != null)
1762 uci.set('wireless', section_id, 'ssid', bss.ssid);
1763 else if (bss.bssid != null)
1764 uci.set('wireless', section_id, 'bssid', bss.bssid);
1767 uci.set('wireless', section_id, 'encryption', 'sae');
1768 uci.set('wireless', section_id, 'key', passval);
1771 for (var i = enc.wpa.length - 1; i >= 0; i--) {
1772 if (enc.wpa[i] == 2) {
1773 uci.set('wireless', section_id, 'encryption', 'psk2');
1776 else if (enc.wpa[i] == 1) {
1777 uci.set('wireless', section_id, 'encryption', 'psk');
1782 uci.set('wireless', section_id, 'key', passval);
1785 uci.set('wireless', section_id, 'encryption', 'wep-open');
1786 uci.set('wireless', section_id, 'key', '1');
1787 uci.set('wireless', section_id, 'key1', passval);
1790 return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) {
1791 firewall.deleteNetwork(net.getName());
1793 var zonePromise = zoneval
1794 ? firewall.getZone(zoneval).then(function(zone) { return zone || firewall.addZone(zoneval) })
1795 : Promise.resolve();
1797 return zonePromise.then(function(zone) {
1799 zone.addNetwork(net.getName());
1802 }).then(L.bind(function() {
1803 return this.renderMoreOptionsModal(section_id);
1807 s.handleJoin = function(radioDev, bss, ev) {
1808 this.handleScanAbort(ev);
1810 var m2 = new form.Map('wireless'),
1811 s2 = m2.section(form.NamedSection, '_new_'),
1812 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1813 is_wep = (enc && Array.isArray(enc.wep)),
1814 is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' || a == 'sae' })),
1815 replace, passphrase, name, zone;
1817 s2.render = function() {
1818 return Promise.all([
1820 this.renderUCISection('_new_')
1821 ]).then(this.renderContents.bind(this));
1824 replace = s2.option(form.Flag, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
1826 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>'));
1827 name.datatype = 'uciname';
1828 name.default = 'wwan';
1829 name.rmempty = false;
1830 name.validate = function(section_id, value) {
1831 if (uci.get('network', value))
1832 return _('The network name is already used');
1837 for (var i = 2; uci.get('network', name.default); i++)
1838 name.default = 'wwan%d'.format(i);
1840 if (is_wep || is_psk) {
1841 passphrase = s2.option(form.Value, 'password', is_wep ? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
1842 passphrase.datatype = is_wep ? 'wepkey' : 'wpakey';
1843 passphrase.password = true;
1844 passphrase.rmempty = false;
1847 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.'));
1848 zone.default = 'wan';
1850 return m2.render().then(L.bind(function(nodes) {
1851 L.ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [
1853 E('div', { 'class': 'right' }, [
1856 'click': L.ui.hideModal
1857 }, _('Cancel')), ' ',
1859 'class': 'cbi-button cbi-button-positive important',
1860 'click': L.ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2)
1863 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase || name).cbid('_new_'))).focus();
1867 s.handleAdd = function(radioDev, ev) {
1868 var section_id = next_free_sid(uci.sections('wireless', 'wifi-iface').length);
1870 uci.unset('wireless', radioDev.getName(), 'disabled');
1872 uci.add('wireless', 'wifi-iface', section_id);
1873 uci.set('wireless', section_id, 'device', radioDev.getName());
1874 uci.set('wireless', section_id, 'mode', 'ap');
1875 uci.set('wireless', section_id, 'ssid', 'OpenWrt');
1876 uci.set('wireless', section_id, 'encryption', 'none');
1878 this.addedSection = section_id;
1879 return this.renderMoreOptionsModal(section_id);
1882 o = s.option(form.DummyValue, '_badge');
1883 o.modalonly = false;
1884 o.textvalue = function(section_id) {
1885 var inst = this.section.lookupRadioOrNetwork(section_id),
1886 node = E('div', { 'class': 'center' });
1888 if (inst.getWifiNetworks)
1889 node.appendChild(render_radio_badge(inst));
1891 node.appendChild(render_network_badge(inst));
1896 o = s.option(form.DummyValue, '_stat');
1897 o.modalonly = false;
1898 o.textvalue = function(section_id) {
1899 var inst = this.section.lookupRadioOrNetwork(section_id);
1901 if (inst.getWifiNetworks)
1902 return render_radio_status(inst, this.section.wifis.filter(function(e) {
1903 return (e.getWifiDeviceName() == inst.getName());
1906 return render_network_status(inst);
1909 return m.render().then(L.bind(function(m, nodes) {
1910 L.Poll.add(L.bind(function() {
1911 var section_ids = m.children[0].cfgsections(),
1912 tasks = [ network.getHostHints(), network.getWifiDevices() ];
1914 for (var i = 0; i < section_ids.length; i++) {
1915 var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])),
1916 dsc = row.querySelector('[data-name="_stat"] > div'),
1917 btns = row.querySelectorAll('.cbi-section-actions button');
1919 if (dsc.getAttribute('restart') == '') {
1920 dsc.setAttribute('restart', '1');
1921 tasks.push(fs.exec('/sbin/wifi', ['up', section_ids[i]]).catch(function(e) {
1922 L.ui.addNotification(null, E('p', e.message));
1925 else if (dsc.getAttribute('restart') == '1') {
1926 dsc.removeAttribute('restart');
1927 btns[0].classList.remove('spinning');
1928 btns[0].disabled = false;
1932 return Promise.all(tasks)
1933 .then(L.bind(function(hosts_radios) {
1936 for (var i = 0; i < hosts_radios[1].length; i++)
1937 tasks.push(hosts_radios[1][i].getWifiNetworks());
1939 return Promise.all(tasks).then(function(data) {
1940 hosts_radios[2] = [];
1942 for (var i = 0; i < data.length; i++)
1943 hosts_radios[2].push.apply(hosts_radios[2], data[i]);
1945 return hosts_radios;
1948 .then(L.bind(function(hosts_radios_wifis) {
1951 for (var i = 0; i < hosts_radios_wifis[2].length; i++)
1952 tasks.push(hosts_radios_wifis[2][i].getAssocList());
1954 return Promise.all(tasks).then(function(data) {
1955 hosts_radios_wifis[3] = [];
1957 for (var i = 0; i < data.length; i++) {
1958 var wifiNetwork = hosts_radios_wifis[2][i],
1959 radioDev = hosts_radios_wifis[1].filter(function(d) { return d.getName() == wifiNetwork.getWifiDeviceName() })[0];
1961 for (var j = 0; j < data[i].length; j++)
1962 hosts_radios_wifis[3].push(Object.assign({ radio: radioDev, network: wifiNetwork }, data[i][j]));
1965 return hosts_radios_wifis;
1968 .then(L.bind(this.poll_status, this, nodes));
1971 var table = E('div', { 'class': 'table', 'id': 'wifi_assoclist_table' }, [
1972 E('div', { 'class': 'tr table-titles' }, [
1973 E('div', { 'class': 'th nowrap' }, _('Network')),
1974 E('div', { 'class': 'th hide-xs' }, _('MAC-Address')),
1975 E('div', { 'class': 'th nowrap' }, _('Host')),
1976 E('div', { 'class': 'th nowrap' }, _('Signal / Noise')),
1977 E('div', { 'class': 'th nowrap' }, _('RX Rate / TX Rate'))
1981 cbi_update_table(table, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
1983 return E([ nodes, E('h3', _('Associated Stations')), table ]);