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 var crypto_modes = [];
1051 if (hwtype == 'mac80211') {
1052 var has_supplicant = L.hasSystemFeature('wpasupplicant'),
1053 has_hostapd = L.hasSystemFeature('hostapd');
1055 // Probe EAP support
1056 var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'),
1057 has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap');
1059 // Probe SAE support
1060 var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'),
1061 has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae');
1063 // Probe OWE support
1064 var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
1065 has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
1068 if (has_hostapd || has_supplicant) {
1069 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1070 crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1071 crypto_modes.push(['psk', 'WPA-PSK', 21]);
1074 encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1077 if (has_ap_sae || has_sta_sae) {
1078 crypto_modes.push(['sae', 'WPA3-SAE', 31]);
1079 crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1082 if (has_ap_eap || has_sta_eap) {
1083 crypto_modes.push(['wpa2', 'WPA2-EAP', 32]);
1084 crypto_modes.push(['wpa', 'WPA-EAP', 20]);
1087 if (has_ap_owe || has_sta_owe) {
1088 crypto_modes.push(['owe', 'OWE', 1]);
1091 encr.crypto_support = {
1095 'psk': has_hostapd || _('Requires hostapd'),
1096 'psk2': has_hostapd || _('Requires hostapd'),
1097 'psk-mixed': has_hostapd || _('Requires hostapd'),
1098 'sae': has_ap_sae || _('Requires hostapd with SAE support'),
1099 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
1100 'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
1101 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
1102 'owe': has_ap_owe || _('Requires hostapd with OWE support')
1107 'psk': has_supplicant || _('Requires wpa-supplicant'),
1108 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1109 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1110 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1111 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1112 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1113 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1114 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
1119 'psk': has_supplicant || _('Requires wpa-supplicant'),
1120 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1121 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1124 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support')
1136 encr.crypto_support['ap-wds'] = encr.crypto_support['ap'];
1137 encr.crypto_support['sta-wds'] = encr.crypto_support['sta'];
1139 encr.validate = function(section_id, value) {
1140 var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
1141 modeval = modeopt.formvalue(section_id),
1142 modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)],
1143 enctitle = this.vallist[this.keylist.indexOf(value)];
1145 if (value == 'none')
1148 if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value))
1149 return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle);
1151 return this.crypto_support[modeval][value];
1154 else if (hwtype == 'broadcom') {
1155 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1156 crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1157 crypto_modes.push(['psk', 'WPA-PSK', 21]);
1160 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1161 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1162 crypto_modes.push(['none', _('No Encryption'), 0]);
1164 crypto_modes.sort(function(a, b) { return b[2] - a[2] });
1166 for (var i = 0; i < crypto_modes.length; i++) {
1167 var security_level = (crypto_modes[i][2] >= 30) ? _('strong security')
1168 : (crypto_modes[i][2] >= 20) ? _('medium security')
1169 : (crypto_modes[i][2] >= 10) ? _('weak security') : _('open network');
1171 encr.value(crypto_modes[i][0], '%s (%s)'.format(crypto_modes[i][1], security_level));
1175 o = ss.taboption('encryption', form.Value, 'auth_server', _('Radius-Authentication-Server'));
1176 o.depends({ mode: 'ap', encryption: 'wpa' });
1177 o.depends({ mode: 'ap', encryption: 'wpa2' });
1178 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1179 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1181 o.datatype = 'host(0)';
1183 o = ss.taboption('encryption', form.Value, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
1184 o.depends({ mode: 'ap', encryption: 'wpa' });
1185 o.depends({ mode: 'ap', encryption: 'wpa2' });
1186 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1187 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1189 o.datatype = 'port';
1191 o = ss.taboption('encryption', form.Value, 'auth_secret', _('Radius-Authentication-Secret'));
1192 o.depends({ mode: 'ap', encryption: 'wpa' });
1193 o.depends({ mode: 'ap', encryption: 'wpa2' });
1194 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1195 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1199 o = ss.taboption('encryption', form.Value, 'acct_server', _('Radius-Accounting-Server'));
1200 o.depends({ mode: 'ap', encryption: 'wpa' });
1201 o.depends({ mode: 'ap', encryption: 'wpa2' });
1202 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1203 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1205 o.datatype = 'host(0)';
1207 o = ss.taboption('encryption', form.Value, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
1208 o.depends({ mode: 'ap', encryption: 'wpa' });
1209 o.depends({ mode: 'ap', encryption: 'wpa2' });
1210 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1211 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1213 o.datatype = 'port';
1215 o = ss.taboption('encryption', form.Value, 'acct_secret', _('Radius-Accounting-Secret'));
1216 o.depends({ mode: 'ap', encryption: 'wpa' });
1217 o.depends({ mode: 'ap', encryption: 'wpa2' });
1218 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1219 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1223 o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client'));
1224 o.depends({ mode: 'ap', encryption: 'wpa' });
1225 o.depends({ mode: 'ap', encryption: 'wpa2' });
1226 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1227 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1229 o.datatype = 'host(0)';
1231 o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
1232 o.depends({ mode: 'ap', encryption: 'wpa' });
1233 o.depends({ mode: 'ap', encryption: 'wpa2' });
1234 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1235 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1237 o.datatype = 'port';
1239 o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret'));
1240 o.depends({ mode: 'ap', encryption: 'wpa' });
1241 o.depends({ mode: 'ap', encryption: 'wpa2' });
1242 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1243 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1248 o = ss.taboption('encryption', form.Value, '_wpa_key', _('Key'));
1249 o.depends('encryption', 'psk');
1250 o.depends('encryption', 'psk2');
1251 o.depends('encryption', 'psk+psk2');
1252 o.depends('encryption', 'psk-mixed');
1253 o.depends('encryption', 'sae');
1254 o.depends('encryption', 'sae-mixed');
1255 o.datatype = 'wpakey';
1259 o.cfgvalue = function(section_id) {
1260 var key = uci.get('wireless', section_id, 'key');
1261 return /^[1234]$/.test(key) ? null : key;
1264 o.write = function(section_id, value) {
1265 uci.set('wireless', section_id, 'key', value);
1266 uci.unset('wireless', section_id, 'key1');
1267 uci.unset('wireless', section_id, 'key2');
1268 uci.unset('wireless', section_id, 'key3');
1269 uci.unset('wireless', section_id, 'key4');
1273 o = ss.taboption('encryption', form.ListValue, '_wep_key', _('Used Key Slot'));
1274 o.depends('encryption', 'wep-open');
1275 o.depends('encryption', 'wep-shared');
1276 o.value('1', _('Key #%d').format(1));
1277 o.value('2', _('Key #%d').format(2));
1278 o.value('3', _('Key #%d').format(3));
1279 o.value('4', _('Key #%d').format(4));
1281 o.cfgvalue = function(section_id) {
1282 var slot = +uci.get('wireless', section_id, 'key');
1283 return (slot >= 1 && slot <= 4) ? String(slot) : '';
1286 o.write = function(section_id, value) {
1287 uci.set('wireless', section_id, 'key', value);
1290 for (var slot = 1; slot <= 4; slot++) {
1291 o = ss.taboption('encryption', form.Value, 'key%d'.format(slot), _('Key #%d').format(slot));
1292 o.depends('encryption', 'wep-open');
1293 o.depends('encryption', 'wep-shared');
1294 o.datatype = 'wepkey';
1298 o.write = function(section_id, value) {
1299 if (value != null && (value.length == 5 || value.length == 13))
1300 value = 's:%s'.format(value);
1301 uci.set('wireless', section_id, this.option, value);
1306 if (hwtype == 'mac80211') {
1307 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1308 var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap');
1310 o = ss.taboption('encryption', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1311 o.depends({ mode: 'ap', encryption: 'wpa' });
1312 o.depends({ mode: 'ap', encryption: 'wpa2' });
1313 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1314 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1316 o.depends({ mode: 'ap', encryption: 'psk' });
1317 o.depends({ mode: 'ap', encryption: 'psk2' });
1318 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1319 o.depends({ mode: 'ap', encryption: 'sae' });
1320 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1321 o.depends({ mode: 'ap-wds', encryption: 'psk' });
1322 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1323 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1324 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1325 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1329 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.'));
1330 o.depends({ mode: 'ap', encryption: 'wpa' });
1331 o.depends({ mode: 'ap', encryption: 'wpa2' });
1332 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1333 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1334 o.depends({ ieee80211r: '1' });
1337 o = ss.taboption('encryption', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1338 o.depends({ ieee80211r: '1' });
1339 o.placeholder = '4f57';
1340 o.datatype = 'and(hexstring,length(4))';
1343 o = ss.taboption('encryption', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1344 o.depends({ ieee80211r: '1' });
1345 o.placeholder = '1000';
1346 o.datatype = 'range(1000,65535)';
1349 o = ss.taboption('encryption', form.ListValue, 'ft_over_ds', _('FT protocol'));
1350 o.depends({ ieee80211r: '1' });
1351 o.value('1', _('FT over DS'));
1352 o.value('0', _('FT over the Air'));
1355 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.'));
1356 o.depends({ ieee80211r: '1' });
1357 o.default = o.enabled;
1360 o = ss.taboption('encryption', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1361 o.depends({ ieee80211r: '1' });
1362 o.placeholder = '10000';
1363 o.datatype = 'uinteger';
1366 o = ss.taboption('encryption', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1367 o.depends({ ieee80211r: '1' });
1368 o.placeholder = '00004f577274';
1369 o.datatype = 'and(hexstring,length(12))';
1372 o = ss.taboption('encryption', form.Flag, 'pmk_r1_push', _('PMK R1 Push'));
1373 o.depends({ ieee80211r: '1' });
1374 o.placeholder = '0';
1377 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.'));
1378 o.depends({ ieee80211r: '1' });
1381 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.'));
1382 o.depends({ ieee80211r: '1' });
1384 // End of 802.11r options
1386 o = ss.taboption('encryption', form.ListValue, 'eap_type', _('EAP-Method'));
1387 o.value('tls', 'TLS');
1388 o.value('ttls', 'TTLS');
1389 o.value('peap', 'PEAP');
1390 o.value('fast', 'FAST');
1391 o.depends({ mode: 'sta', encryption: 'wpa' });
1392 o.depends({ mode: 'sta', encryption: 'wpa2' });
1393 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1394 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1396 o = ss.taboption('encryption', form.FileUpload, 'ca_cert', _('Path to CA-Certificate'));
1397 o.depends({ mode: 'sta', encryption: 'wpa' });
1398 o.depends({ mode: 'sta', encryption: 'wpa2' });
1399 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1400 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1402 o = ss.taboption('encryption', form.FileUpload, 'client_cert', _('Path to Client-Certificate'));
1403 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1404 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1405 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1406 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1408 o = ss.taboption('encryption', form.FileUpload, 'priv_key', _('Path to Private Key'));
1409 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1410 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1411 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1412 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1414 o = ss.taboption('encryption', form.Value, 'priv_key_pwd', _('Password of Private Key'));
1415 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1416 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1417 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1418 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1421 o = ss.taboption('encryption', form.ListValue, 'auth', _('Authentication'));
1422 o.value('PAP', 'PAP');
1423 o.value('CHAP', 'CHAP');
1424 o.value('MSCHAP', 'MSCHAP');
1425 o.value('MSCHAPV2', 'MSCHAPv2');
1428 o.value('EAP-MSCHAPV2');
1430 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1431 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1432 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1433 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1434 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1435 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1436 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1437 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1438 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1439 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1440 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1441 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1443 o.validate = function(section_id, value) {
1444 var eo = this.section.children.filter(function(o) { return o.option == 'eap_type' })[0],
1445 ev = eo.formvalue(section_id);
1447 if (ev != 'ttls' && (value == 'PAP' || value == 'CHAP' || value == 'MSCHAP' || value == 'MSCHAPV2'))
1448 return _('This authentication type is not applicable to the selected EAP method.');
1453 o = ss.taboption('encryption', form.FileUpload, 'ca_cert2', _('Path to inner CA-Certificate'));
1454 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1455 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1456 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1457 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1459 o = ss.taboption('encryption', form.FileUpload, 'client_cert2', _('Path to inner Client-Certificate'));
1460 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1461 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1462 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1463 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1465 o = ss.taboption('encryption', form.FileUpload, 'priv_key2', _('Path to inner Private Key'));
1466 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1467 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1468 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1469 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1471 o = ss.taboption('encryption', form.Value, 'priv_key2_pwd', _('Password of inner Private Key'));
1472 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1473 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1474 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1475 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1478 o = ss.taboption('encryption', form.Value, 'identity', _('Identity'));
1479 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1480 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1481 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1482 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1483 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1484 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1485 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1486 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1487 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1488 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1489 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1490 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1491 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1492 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1493 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1494 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1496 o = ss.taboption('encryption', form.Value, 'anonymous_identity', _('Anonymous Identity'));
1497 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1498 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1499 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1500 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1501 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1502 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1503 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1504 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1505 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1506 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1507 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1508 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1509 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1510 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1511 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1512 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1514 o = ss.taboption('encryption', form.Value, 'password', _('Password'));
1515 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1516 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1517 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1518 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1519 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1520 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1521 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1522 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1523 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1524 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1525 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1526 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1530 if (hwtype == 'mac80211') {
1531 // ieee802.11w options
1532 if (L.hasSystemFeature('hostapd', '11w')) {
1533 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)"));
1534 o.value('', _('Disabled'));
1535 o.value('1', _('Optional'));
1536 o.value('2', _('Required'));
1537 o.depends({ mode: 'ap', encryption: 'wpa2' });
1538 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1539 o.depends({ mode: 'ap', encryption: 'psk2' });
1540 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1541 o.depends({ mode: 'ap', encryption: 'sae' });
1542 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1543 o.depends({ mode: 'ap', encryption: 'owe' });
1544 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1545 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1546 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1547 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1548 o.depends({ mode: 'ap-wds', encryption: 'owe' });
1549 o.depends({ mode: 'sta', encryption: 'wpa2' });
1550 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1551 o.depends({ mode: 'sta', encryption: 'psk2' });
1552 o.depends({ mode: 'sta', encryption: 'psk-mixed' });
1553 o.depends({ mode: 'sta', encryption: 'sae' });
1554 o.depends({ mode: 'sta', encryption: 'sae-mixed' });
1555 o.depends({ mode: 'sta', encryption: 'owe' });
1556 o.depends({ mode: 'sta-wds', encryption: 'psk2' });
1557 o.depends({ mode: 'sta-wds', encryption: 'psk-mixed' });
1558 o.depends({ mode: 'sta-wds', encryption: 'sae' });
1559 o.depends({ mode: 'sta-wds', encryption: 'sae-mixed' });
1560 o.depends({ mode: 'sta-wds', encryption: 'owe' });
1562 '2': [{ encryption: 'sae' }, { encryption: 'owe' }],
1563 '1': [{ encryption: 'sae-mixed'}],
1567 o = ss.taboption('encryption', form.Value, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1568 o.depends('ieee80211w', '1');
1569 o.depends('ieee80211w', '2');
1570 o.datatype = 'uinteger';
1571 o.placeholder = '1000';
1574 o = ss.taboption('encryption', form.Value, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1575 o.depends('ieee80211w', '1');
1576 o.depends('ieee80211w', '2');
1577 o.datatype = 'uinteger';
1578 o.placeholder = '201';
1582 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.'));
1583 o.depends({ mode: 'ap', encryption: 'wpa2' });
1584 o.depends({ mode: 'ap', encryption: 'psk2' });
1585 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1586 o.depends({ mode: 'ap', encryption: 'sae' });
1587 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1588 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1589 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1590 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1591 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1592 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1594 if (L.hasSystemFeature('hostapd', 'cli') && L.hasSystemFeature('wpasupplicant')) {
1595 o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK'))
1598 o.default = o.disabled;
1599 o.depends('encryption', 'psk');
1600 o.depends('encryption', 'psk2');
1601 o.depends('encryption', 'psk-mixed');
1608 s.handleRemove = function(section_id, ev) {
1609 document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id)).style.opacity = 0.5;
1610 return form.TypedSection.prototype.handleRemove.apply(this, [section_id, ev]);
1613 s.handleScan = function(radioDev, ev) {
1614 var table = E('div', { 'class': 'table' }, [
1615 E('div', { 'class': 'tr table-titles' }, [
1616 E('div', { 'class': 'th col-2 middle center' }, _('Signal')),
1617 E('div', { 'class': 'th col-4 middle left' }, _('SSID')),
1618 E('div', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1619 E('div', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1620 E('div', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1621 E('div', { 'class': 'th col-3 middle left' }, _('Encryption')),
1622 E('div', { 'class': 'th cbi-section-actions right' }, ' '),
1626 cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1628 var md = L.ui.showModal(_('Join Network: Wireless Scan'), [
1630 E('div', { 'class': 'right' },
1633 'click': L.bind(this.handleScanAbort, this)
1637 md.style.maxWidth = '90%';
1638 md.style.maxHeight = 'none';
1640 this.pollFn = L.bind(this.handleScanRefresh, this, radioDev, {}, table);
1642 L.Poll.add(this.pollFn);
1646 s.handleScanRefresh = function(radioDev, scanCache, table) {
1647 return radioDev.getScanList().then(L.bind(function(results) {
1650 for (var i = 0; i < results.length; i++)
1651 scanCache[results[i].bssid] = results[i];
1653 for (var k in scanCache)
1654 if (scanCache[k].stale)
1655 results.push(scanCache[k]);
1657 results.sort(function(a, b) {
1658 var diff = (b.quality - a.quality) || (a.channel - b.channel);
1663 if (a.ssid < b.ssid)
1665 else if (a.ssid > b.ssid)
1668 if (a.bssid < b.bssid)
1670 else if (a.bssid > b.bssid)
1674 for (var i = 0; i < results.length; i++) {
1675 var res = results[i],
1676 qv = res.quality || 0,
1677 qm = res.quality_max || 0,
1678 q = (qv > 0 && qm > 0) ? Math.floor((100 / qm) * qv) : 0,
1679 s = res.stale ? 'opacity:0.5' : '';
1682 E('span', { 'style': s }, render_signal_badge(q, res.signal, res.noise)),
1683 E('span', { 'style': s }, '%h'.format(res.ssid)),
1684 E('span', { 'style': s }, '%d'.format(res.channel)),
1685 E('span', { 'style': s }, '%h'.format(res.mode)),
1686 E('span', { 'style': s }, '%h'.format(res.bssid)),
1687 E('span', { 'style': s }, '%h'.format(network.formatWifiEncryption(res.encryption))),
1688 E('div', { 'class': 'right' }, E('button', {
1689 'class': 'cbi-button cbi-button-action important',
1690 'click': L.bind(this.handleJoin, this, radioDev, res)
1691 }, _('Join Network')))
1697 cbi_update_table(table, rows);
1701 s.handleScanAbort = function(ev) {
1702 var md = L.dom.parent(ev.target, 'div[aria-modal="true"]');
1704 md.style.maxWidth = '';
1705 md.style.maxHeight = '';
1709 L.Poll.remove(this.pollFn);
1714 s.handleJoinConfirm = function(radioDev, bss, form, ev) {
1715 var nameopt = L.toArray(form.lookupOption('name', '_new_'))[0],
1716 passopt = L.toArray(form.lookupOption('password', '_new_'))[0],
1717 zoneopt = L.toArray(form.lookupOption('zone', '_new_'))[0],
1718 replopt = L.toArray(form.lookupOption('replace', '_new_'))[0],
1719 nameval = (nameopt && nameopt.isValid('_new_')) ? nameopt.formvalue('_new_') : null,
1720 passval = (passopt && passopt.isValid('_new_')) ? passopt.formvalue('_new_') : null,
1721 zoneval = zoneopt ? zoneopt.formvalue('_new_') : null,
1722 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1723 is_wep = (enc && Array.isArray(enc.wep)),
1724 is_psk = (enc && Array.isArray(enc.wpa) && Array.isArray(enc.authentication) && enc.authentication[0] == 'psk');
1726 if (nameval == null || (passopt && passval == null))
1729 var section_id = null;
1731 return this.map.save(function() {
1732 if (replopt.formvalue('_new_') == '1') {
1733 var sections = uci.sections('wireless', 'wifi-iface');
1735 for (var i = 0; i < sections.length; i++)
1736 if (sections[i].device == radioDev.getName())
1737 uci.remove('wireless', sections[i]['.name']);
1740 section_id = next_free_sid(uci.sections('wifi-iface').length);
1742 uci.add('wireless', 'wifi-iface', section_id);
1743 uci.set('wireless', section_id, 'device', radioDev.getName());
1744 uci.set('wireless', section_id, 'mode', (bss.mode == 'Ad-Hoc') ? 'adhoc' : 'sta');
1745 uci.set('wireless', section_id, 'network', nameval);
1747 if (bss.ssid != null)
1748 uci.set('wireless', section_id, 'ssid', bss.ssid);
1749 else if (bss.bssid != null)
1750 uci.set('wireless', section_id, 'bssid', bss.bssid);
1753 for (var i = enc.wpa.length - 1; i >= 0; i--) {
1754 if (enc.wpa[i] == 2) {
1755 uci.set('wireless', section_id, 'encryption', 'psk2');
1758 else if (enc.wpa[i] == 1) {
1759 uci.set('wireless', section_id, 'encryption', 'psk');
1764 uci.set('wireless', section_id, 'key', passval);
1767 uci.set('wireless', section_id, 'encryption', 'wep-open');
1768 uci.set('wireless', section_id, 'key', '1');
1769 uci.set('wireless', section_id, 'key1', passval);
1772 return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) {
1773 firewall.deleteNetwork(net.getName());
1775 var zonePromise = zoneval
1776 ? firewall.getZone(zoneval).then(function(zone) { return zone || firewall.addZone(zoneval) })
1777 : Promise.resolve();
1779 return zonePromise.then(function(zone) {
1781 zone.addNetwork(net.getName());
1784 }).then(L.bind(function() {
1785 return this.renderMoreOptionsModal(section_id);
1789 s.handleJoin = function(radioDev, bss, ev) {
1790 this.handleScanAbort(ev);
1792 var m2 = new form.Map('wireless'),
1793 s2 = m2.section(form.NamedSection, '_new_'),
1794 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1795 is_wep = (enc && Array.isArray(enc.wep)),
1796 is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' || a == 'sae' })),
1797 replace, passphrase, name, zone;
1799 s2.render = function() {
1800 return Promise.all([
1802 this.renderUCISection('_new_')
1803 ]).then(this.renderContents.bind(this));
1806 replace = s2.option(form.Flag, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
1808 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>'));
1809 name.datatype = 'uciname';
1810 name.default = 'wwan';
1811 name.rmempty = false;
1812 name.validate = function(section_id, value) {
1813 if (uci.get('network', value))
1814 return _('The network name is already used');
1819 for (var i = 2; uci.get('network', name.default); i++)
1820 name.default = 'wwan%d'.format(i);
1822 if (is_wep || is_psk) {
1823 passphrase = s2.option(form.Value, 'password', is_wep ? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
1824 passphrase.datatype = is_wep ? 'wepkey' : 'wpakey';
1825 passphrase.password = true;
1826 passphrase.rmempty = false;
1829 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.'));
1830 zone.default = 'wan';
1832 return m2.render().then(L.bind(function(nodes) {
1833 L.ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [
1835 E('div', { 'class': 'right' }, [
1838 'click': L.ui.hideModal
1839 }, _('Cancel')), ' ',
1841 'class': 'cbi-button cbi-button-positive important',
1842 'click': L.ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2)
1845 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase || name).cbid('_new_'))).focus();
1849 s.handleAdd = function(radioDev, ev) {
1850 var section_id = next_free_sid(uci.sections('wireless', 'wifi-iface').length);
1852 uci.unset('wireless', radioDev.getName(), 'disabled');
1854 uci.add('wireless', 'wifi-iface', section_id);
1855 uci.set('wireless', section_id, 'device', radioDev.getName());
1856 uci.set('wireless', section_id, 'mode', 'ap');
1857 uci.set('wireless', section_id, 'ssid', 'OpenWrt');
1858 uci.set('wireless', section_id, 'encryption', 'none');
1860 this.addedSection = section_id;
1861 return this.renderMoreOptionsModal(section_id);
1864 o = s.option(form.DummyValue, '_badge');
1865 o.modalonly = false;
1866 o.textvalue = function(section_id) {
1867 var inst = this.section.lookupRadioOrNetwork(section_id),
1868 node = E('div', { 'class': 'center' });
1870 if (inst.getWifiNetworks)
1871 node.appendChild(render_radio_badge(inst));
1873 node.appendChild(render_network_badge(inst));
1878 o = s.option(form.DummyValue, '_stat');
1879 o.modalonly = false;
1880 o.textvalue = function(section_id) {
1881 var inst = this.section.lookupRadioOrNetwork(section_id);
1883 if (inst.getWifiNetworks)
1884 return render_radio_status(inst, this.section.wifis.filter(function(e) {
1885 return (e.getWifiDeviceName() == inst.getName());
1888 return render_network_status(inst);
1891 return m.render().then(L.bind(function(m, nodes) {
1892 L.Poll.add(L.bind(function() {
1893 var section_ids = m.children[0].cfgsections(),
1894 tasks = [ network.getHostHints(), network.getWifiDevices() ];
1896 for (var i = 0; i < section_ids.length; i++) {
1897 var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])),
1898 dsc = row.querySelector('[data-name="_stat"] > div'),
1899 btns = row.querySelectorAll('.cbi-section-actions button');
1901 if (dsc.getAttribute('restart') == '') {
1902 dsc.setAttribute('restart', '1');
1903 tasks.push(L.Request.post(
1904 L.url('admin/network/wireless_reconnect', section_ids[i]),
1905 'token=' + L.env.token,
1906 { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
1907 ).catch(function() {}));
1909 else if (dsc.getAttribute('restart') == '1') {
1910 dsc.removeAttribute('restart');
1911 btns[0].classList.remove('spinning');
1912 btns[0].disabled = false;
1916 return Promise.all(tasks)
1917 .then(L.bind(function(hosts_radios) {
1920 for (var i = 0; i < hosts_radios[1].length; i++)
1921 tasks.push(hosts_radios[1][i].getWifiNetworks());
1923 return Promise.all(tasks).then(function(data) {
1924 hosts_radios[2] = [];
1926 for (var i = 0; i < data.length; i++)
1927 hosts_radios[2].push.apply(hosts_radios[2], data[i]);
1929 return hosts_radios;
1932 .then(L.bind(function(hosts_radios_wifis) {
1935 for (var i = 0; i < hosts_radios_wifis[2].length; i++)
1936 tasks.push(hosts_radios_wifis[2][i].getAssocList());
1938 return Promise.all(tasks).then(function(data) {
1939 hosts_radios_wifis[3] = [];
1941 for (var i = 0; i < data.length; i++) {
1942 var wifiNetwork = hosts_radios_wifis[2][i],
1943 radioDev = hosts_radios_wifis[1].filter(function(d) { return d.getName() == wifiNetwork.getWifiDeviceName() })[0];
1945 for (var j = 0; j < data[i].length; j++)
1946 hosts_radios_wifis[3].push(Object.assign({ radio: radioDev, network: wifiNetwork }, data[i][j]));
1949 return hosts_radios_wifis;
1952 .then(L.bind(this.poll_status, this, nodes));
1955 var table = E('div', { 'class': 'table', 'id': 'wifi_assoclist_table' }, [
1956 E('div', { 'class': 'tr table-titles' }, [
1957 E('div', { 'class': 'th nowrap' }, _('Network')),
1958 E('div', { 'class': 'th hide-xs' }, _('MAC-Address')),
1959 E('div', { 'class': 'th nowrap' }, _('Host')),
1960 E('div', { 'class': 'th nowrap' }, _('Signal / Noise')),
1961 E('div', { 'class': 'th nowrap' }, _('RX Rate / TX Rate'))
1965 cbi_update_table(table, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
1967 return E([ nodes, E('h3', _('Associated Stations')), table ]);