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 is_mesh = (radioNet.getMode() == 'mesh'),
93 changecount = count_changes(radioNet.getName()),
97 status_text = E('a', {
99 click: L.bind(L.ui.changes.displayChanges, L.ui.changes)
100 }, _('Interface has %d pending changes').format(changecount));
102 status_text = E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated'));
104 return L.itemlist(E('div'), [
105 is_mesh ? _('Mesh ID') : _('SSID'), (is_mesh ? radioNet.getMeshID() : radioNet.getSSID()) || '?',
107 _('BSSID'), (!changecount && is_assoc) ? bssid : null,
108 _('Encryption'), (!changecount && is_assoc) ? radioNet.getActiveEncryption() || _('None') : null,
110 ], [ ' | ', E('br') ]);
113 function render_modal_status(node, radioNet) {
114 var mode = radioNet.getActiveMode(),
115 noise = radioNet.getNoise(),
116 bssid = radioNet.getActiveBSSID(),
117 channel = radioNet.getChannel(),
118 disabled = (radioNet.get('disabled') == '1'),
119 is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled);
122 node = E('span', { 'class': 'ifacebadge large', 'data-network': radioNet.getName() }, [ E('small'), E('span') ]);
124 L.dom.content(node.firstElementChild, render_signal_badge(disabled ? -1 : radioNet.getSignalPercent(), radioNet.getSignal(), noise, true));
126 L.itemlist(node.lastElementChild, [
128 _('SSID'), radioNet.getSSID() || '?',
129 _('BSSID'), is_assoc ? bssid : null,
130 _('Encryption'), is_assoc ? radioNet.getActiveEncryption() || _('None') : null,
131 _('Channel'), is_assoc ? '%d (%.3f %s)'.format(radioNet.getChannel(), radioNet.getFrequency() || 0, _('GHz')) : null,
132 _('Tx-Power'), is_assoc ? '%d %s'.format(radioNet.getTXPower(), _('dBm')) : null,
133 _('Signal'), is_assoc ? '%d %s'.format(radioNet.getSignal(), _('dBm')) : null,
134 _('Noise'), (is_assoc && noise != null) ? '%d %s'.format(noise, _('dBm')) : null,
135 _('Bitrate'), is_assoc ? '%.1f %s'.format(radioNet.getBitRate() || 0, _('Mbit/s')) : null,
136 _('Country'), is_assoc ? radioNet.getCountryCode() : null
137 ], [ ' | ', E('br'), E('br'), E('br'), E('br'), E('br'), ' | ', E('br'), ' | ' ]);
140 L.dom.append(node.lastElementChild, E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated')));
145 function format_wifirate(rate) {
146 var s = '%.1f Mbit/s, %dMHz'.format(rate.rate / 1000, rate.mhz);
148 if (rate.ht || rate.vht) {
149 if (rate.vht) s += ', VHT-MCS %d'.format(rate.mcs);
150 if (rate.nss) s += ', VHT-NSS %d'.format(rate.nss);
151 if (rate.ht) s += ', MCS %s'.format(rate.mcs);
152 if (rate.short_gi) s += ', Short GI';
158 function radio_restart(id, ev) {
159 var row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id)),
160 dsc = row.querySelector('[data-name="_stat"] > div'),
161 btn = row.querySelector('.cbi-section-actions button');
164 btn.classList.add('spinning');
167 dsc.setAttribute('restart', '');
168 L.dom.content(dsc, E('em', _('Device is restarting…')));
171 function network_updown(id, map, ev) {
172 var radio = uci.get('wireless', id, 'device'),
173 disabled = (uci.get('wireless', id, 'disabled') == '1') ||
174 (uci.get('wireless', radio, 'disabled') == '1');
177 uci.unset('wireless', id, 'disabled');
178 uci.unset('wireless', radio, 'disabled');
181 uci.set('wireless', id, 'disabled', '1');
183 var all_networks_disabled = true,
184 wifi_ifaces = uci.sections('wireless', 'wifi-iface');
186 for (var i = 0; i < wifi_ifaces.length; i++) {
187 if (wifi_ifaces[i].device == radio && wifi_ifaces[i].disabled != '1') {
188 all_networks_disabled = false;
193 if (all_networks_disabled)
194 uci.set('wireless', radio, 'disabled', '1');
197 return map.save().then(function() {
202 function next_free_sid(offset) {
203 var sid = 'wifinet' + offset;
205 while (uci.get('wireless', sid))
206 sid = 'wifinet' + (++offset);
211 var CBIWifiFrequencyValue = form.Value.extend({
212 callFrequencyList: rpc.declare({
215 params: [ 'device' ],
216 expect: { results: [] }
219 load: function(section_id) {
221 network.getWifiDevice(section_id),
222 this.callFrequencyList(section_id)
223 ]).then(L.bind(function(data) {
225 '11g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
226 '11a': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : []
229 for (var i = 0; Array.isArray(data[1]) && i < data[1].length; i++)
230 this.channels[(data[1][i].mhz > 2484) ? '11a' : '11g'].push(
232 '%d (%d Mhz)'.format(data[1][i].channel, data[1][i].mhz),
233 !data[1][i].restricted
236 var hwmodelist = L.toArray(data[0] ? data[0].getHWModes() : null)
237 .reduce(function(o, v) { o[v] = true; return o }, {});
241 'n', 'N', hwmodelist.n,
242 'ac', 'AC', hwmodelist.ac
245 var htmodelist = L.toArray(data[0] ? data[0].getHTModes() : null)
246 .reduce(function(o, v) { o[v] = true; return o }, {});
249 '': [ '', '-', true ],
251 'HT20', '20 MHz', htmodelist.HT20,
252 'HT40', '40 MHz', htmodelist.HT40
255 'VHT20', '20 MHz', htmodelist.VHT20,
256 'VHT40', '40 MHz', htmodelist.VHT40,
257 'VHT80', '80 MHz', htmodelist.VHT80,
258 'VHT160', '160 MHz', htmodelist.VHT160
264 '11g', '2.4 GHz', this.channels['11g'].length > 3,
265 '11a', '5 GHz', this.channels['11a'].length > 3
268 '11g', '2.4 GHz', this.channels['11g'].length > 3,
269 '11a', '5 GHz', this.channels['11a'].length > 3
278 setValues: function(sel, vals) {
280 sel.vals.selected = sel.selectedIndex;
282 while (sel.options[0])
285 for (var i = 0; vals && i < vals.length; i += 3)
287 sel.add(E('option', { value: vals[i+0] }, [ vals[i+1] ]));
289 if (!isNaN(vals.selected))
290 sel.selectedIndex = vals.selected;
292 sel.parentNode.style.display = (sel.options.length <= 1) ? 'none' : '';
296 toggleWifiMode: function(elem) {
297 this.toggleWifiHTMode(elem);
298 this.toggleWifiBand(elem);
301 toggleWifiHTMode: function(elem) {
302 var mode = elem.querySelector('.mode');
303 var bwdt = elem.querySelector('.htmode');
305 this.setValues(bwdt, this.htmodes[mode.value]);
308 toggleWifiBand: function(elem) {
309 var mode = elem.querySelector('.mode');
310 var band = elem.querySelector('.band');
312 this.setValues(band, this.bands[mode.value]);
313 this.toggleWifiChannel(elem);
316 toggleWifiChannel: function(elem) {
317 var band = elem.querySelector('.band');
318 var chan = elem.querySelector('.channel');
320 this.setValues(chan, this.channels[band.value]);
323 setInitialValues: function(section_id, elem) {
324 var mode = elem.querySelector('.mode'),
325 band = elem.querySelector('.band'),
326 chan = elem.querySelector('.channel'),
327 bwdt = elem.querySelector('.htmode'),
328 htval = uci.get('wireless', section_id, 'htmode'),
329 hwval = uci.get('wireless', section_id, 'hwmode'),
330 chval = uci.get('wireless', section_id, 'channel');
332 this.setValues(mode, this.modes);
334 if (/VHT20|VHT40|VHT80|VHT160/.test(htval))
336 else if (/HT20|HT40/.test(htval))
341 this.toggleWifiMode(elem);
348 this.toggleWifiBand(elem);
356 renderWidget: function(section_id, option_index, cfgvalue) {
359 L.dom.content(elem, [
360 E('label', { 'style': 'float:left; margin-right:3px' }, [
364 'style': 'width:auto',
365 'change': L.bind(this.toggleWifiMode, this, elem)
368 E('label', { 'style': 'float:left; margin-right:3px' }, [
372 'style': 'width:auto',
373 'change': L.bind(this.toggleWifiBand, this, elem)
376 E('label', { 'style': 'float:left; margin-right:3px' }, [
377 _('Channel'), E('br'),
380 'style': 'width:auto'
383 E('label', { 'style': 'float:left; margin-right:3px' }, [
387 'style': 'width:auto'
390 E('br', { 'style': 'clear:left' })
393 return this.setInitialValues(section_id, elem);
396 cfgvalue: function(section_id) {
398 uci.get('wireless', section_id, 'htmode'),
399 uci.get('wireless', section_id, 'hwmode'),
400 uci.get('wireless', section_id, 'channel')
404 formvalue: function(section_id) {
405 var node = this.map.findElement('data-field', this.cbid(section_id));
408 node.querySelector('.htmode').value,
409 node.querySelector('.band').value,
410 node.querySelector('.channel').value
414 write: function(section_id, value) {
415 uci.set('wireless', section_id, 'htmode', value[0] || null);
416 uci.set('wireless', section_id, 'hwmode', value[1]);
417 uci.set('wireless', section_id, 'channel', value[2]);
421 var CBIWifiTxPowerValue = form.ListValue.extend({
422 callTxPowerList: rpc.declare({
424 method: 'txpowerlist',
425 params: [ 'device' ],
426 expect: { results: [] }
429 load: function(section_id) {
430 return this.callTxPowerList(section_id).then(L.bind(function(pwrlist) {
431 this.powerval = this.wifiNetwork ? this.wifiNetwork.getTXPower() : null;
432 this.poweroff = this.wifiNetwork ? this.wifiNetwork.getTXPowerOffset() : null;
434 this.value('', _('driver default'));
436 for (var i = 0; i < pwrlist.length; i++)
437 this.value(pwrlist[i].dbm, '%d dBm (%d mW)'.format(pwrlist[i].dbm, pwrlist[i].mw));
439 return form.ListValue.prototype.load.apply(this, [section_id]);
443 renderWidget: function(section_id, option_index, cfgvalue) {
444 var widget = form.ListValue.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
445 widget.firstElementChild.style.width = 'auto';
447 L.dom.append(widget, E('span', [
448 ' - ', _('Current power'), ': ',
449 E('span', [ this.powerval != null ? '%d dBm'.format(this.powerval) : E('em', _('unknown')) ]),
450 this.poweroff ? ' + %d dB offset = %s dBm'.format(this.poweroff, this.powerval != null ? this.powerval + this.poweroff : '?') : ''
457 var CBIWifiCountryValue = form.Value.extend({
458 callCountryList: rpc.declare({
460 method: 'countrylist',
461 params: [ 'device' ],
462 expect: { results: [] }
465 load: function(section_id) {
466 return this.callCountryList(section_id).then(L.bind(function(countrylist) {
467 if (Array.isArray(countrylist) && countrylist.length > 0) {
468 this.value('', _('driver default'));
470 for (var i = 0; i < countrylist.length; i++)
471 this.value(countrylist[i].iso3166, '%s - %s'.format(countrylist[i].iso3166, countrylist[i].country));
474 return form.Value.prototype.load.apply(this, [section_id]);
478 validate: function(section_id, formvalue) {
479 if (formvalue != null && formvalue != '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue))
480 return _('Use ISO/IEC 3166 alpha2 country codes.');
485 renderWidget: function(section_id, option_index, cfgvalue) {
486 var typeClass = (this.keylist && this.keylist.length) ? form.ListValue : form.Value;
487 return typeClass.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
491 return L.view.extend({
492 poll_status: function(map, data) {
493 var rows = map.querySelectorAll('.cbi-section-table-row[data-sid]');
495 for (var i = 0; i < rows.length; i++) {
496 var section_id = rows[i].getAttribute('data-sid'),
497 radioDev = data[1].filter(function(d) { return d.getName() == section_id })[0],
498 radioNet = data[2].filter(function(n) { return n.getName() == section_id })[0],
499 badge = rows[i].querySelector('[data-name="_badge"] > div'),
500 stat = rows[i].querySelector('[data-name="_stat"]'),
501 btns = rows[i].querySelectorAll('.cbi-section-actions button'),
502 busy = btns[0].classList.contains('spinning') || btns[1].classList.contains('spinning') || btns[2].classList.contains('spinning');
505 L.dom.content(badge, render_radio_badge(radioDev));
506 L.dom.content(stat, render_radio_status(radioDev, data[2].filter(function(n) { return n.getWifiDeviceName() == radioDev.getName() })));
509 L.dom.content(badge, render_network_badge(radioNet));
510 L.dom.content(stat, render_network_status(radioNet));
513 if (stat.hasAttribute('restart'))
514 L.dom.content(stat, E('em', _('Device is restarting…')));
516 btns[0].disabled = busy;
517 btns[1].disabled = busy;
518 btns[2].disabled = busy;
521 var table = document.querySelector('wifi_assoclist_table'),
525 for (var i = 0; i < data[3].length; i++) {
526 var bss = data[3][i],
527 name = hosts.getHostnameByMACAddr(bss.mac),
528 ipv4 = hosts.getIPAddrByMACAddr(bss.mac),
529 ipv6 = hosts.getIP6AddrByMACAddr(bss.mac);
532 E('span', { 'class': 'ifacebadge' }, [
534 'src': L.resource('icons/wifi%s.png').format(bss.network.isUp() ? '' : '_disabled'),
535 'title': bss.radio.getI18n()
537 ' %s '.format(bss.network.getShortName()),
538 E('small', '(%s)'.format(bss.network.getIfname()))
541 name ? '%s (%s)'.format(name, ipv4 || ipv6 || '?') : ipv4 || ipv6 || '?',
542 render_signal_badge(Math.min((bss.signal + 110) / 70 * 100, 100), bss.signal, bss.noise),
544 E('span', format_wifirate(bss.rx)),
546 E('span', format_wifirate(bss.tx))
551 cbi_update_table('#wifi_assoclist_table', trows, E('em', _('No information available')));
553 var stat = document.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large');
556 render_modal_status(stat, data[2].filter(function(n) { return n.getName() == stat.getAttribute('data-network') })[0]);
558 return network.flushCache();
568 checkAnonymousSections: function() {
569 var wifiIfaces = uci.sections('wireless', 'wifi-iface');
571 for (var i = 0; i < wifiIfaces.length; i++)
572 if (wifiIfaces[i]['.anonymous'])
578 callUciRename: rpc.declare({
581 params: [ 'config', 'section', 'name' ]
585 if (this.checkAnonymousSections())
586 return this.renderMigration();
588 return this.renderOverview();
591 handleMigration: function(ev) {
592 var wifiIfaces = uci.sections('wireless', 'wifi-iface'),
596 for (var i = 0; i < wifiIfaces.length; i++) {
597 if (!wifiIfaces[i]['.anonymous'])
600 var new_name = next_free_sid(id_offset);
602 tasks.push(this.callUciRename('wireless', wifiIfaces[i]['.name'], new_name));
603 id_offset = +new_name.substring(7) + 1;
606 return Promise.all(tasks)
607 .then(L.bind(L.ui.changes.init, L.ui.changes))
608 .then(L.bind(L.ui.changes.apply, L.ui.changes));
611 renderMigration: function() {
612 L.ui.showModal(_('Wireless configuration migration'), [
613 E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')),
614 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.')),
615 E('div', { 'class': 'right' },
617 'class': 'btn cbi-button-action important',
618 'click': L.ui.createHandlerFn(this, 'handleMigration')
623 renderOverview: function() {
626 m = new form.Map('wireless');
630 s = m.section(form.GridSection, 'wifi-device', _('Wireless Overview'));
634 s.load = function() {
635 return network.getWifiDevices().then(L.bind(function(radios) {
636 this.radios = radios.sort(function(a, b) {
637 return a.getName() > b.getName();
642 for (var i = 0; i < radios.length; i++)
643 tasks.push(radios[i].getWifiNetworks());
645 return Promise.all(tasks);
646 }, this)).then(L.bind(function(data) {
649 for (var i = 0; i < data.length; i++)
650 this.wifis.push.apply(this.wifis, data[i]);
654 s.cfgsections = function() {
657 for (var i = 0; i < this.radios.length; i++) {
658 rv.push(this.radios[i].getName());
660 for (var j = 0; j < this.wifis.length; j++)
661 if (this.wifis[j].getWifiDeviceName() == this.radios[i].getName())
662 rv.push(this.wifis[j].getName());
668 s.modaltitle = function(section_id) {
669 var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id})[0];
670 return radioNet ? radioNet.getI18n() : _('Edit wireless network');
673 s.lookupRadioOrNetwork = function(section_id) {
674 var radioDev = this.radios.filter(function(r) { return r.getName() == section_id })[0];
678 var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id })[0];
685 s.renderRowActions = function(section_id) {
686 var inst = this.lookupRadioOrNetwork(section_id), btns;
688 if (inst.getWifiNetworks) {
691 'class': 'cbi-button cbi-button-neutral',
692 'title': _('Restart radio interface'),
693 'click': L.ui.createHandlerFn(this, radio_restart, section_id)
696 'class': 'cbi-button cbi-button-action important',
697 'title': _('Find and join network'),
698 'click': L.ui.createHandlerFn(this, 'handleScan', inst)
701 'class': 'cbi-button cbi-button-add',
702 'title': _('Provide new network'),
703 'click': L.ui.createHandlerFn(this, 'handleAdd', inst)
708 var isDisabled = (inst.get('disabled') == '1' ||
709 uci.get('wireless', inst.getWifiDeviceName(), 'disabled') == '1');
713 'class': 'cbi-button cbi-button-neutral enable-disable',
714 'title': isDisabled ? _('Enable this network') : _('Disable this network'),
715 'click': L.ui.createHandlerFn(this, network_updown, section_id, this.map)
716 }, isDisabled ? _('Enable') : _('Disable')),
718 'class': 'cbi-button cbi-button-action important',
719 'title': _('Edit this network'),
720 'click': L.ui.createHandlerFn(this, 'renderMoreOptionsModal', section_id)
723 'class': 'cbi-button cbi-button-negative remove',
724 'title': _('Delete this network'),
725 'click': L.ui.createHandlerFn(this, 'handleRemove', section_id)
730 return E('div', { 'class': 'td middle cbi-section-actions' }, E('div', btns));
733 s.addModalOptions = function(s) {
734 return network.getWifiNetwork(s.section).then(function(radioNet) {
735 var hwtype = uci.get('wireless', radioNet.getWifiDeviceName(), 'type');
738 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
742 ss.tab('general', _('General Setup'));
743 ss.tab('advanced', _('Advanced Settings'));
745 var isDisabled = (radioNet.get('disabled') == '1');
747 o = ss.taboption('general', form.DummyValue, '_wifistat_modal', _('Status'));
748 o.cfgvalue = L.bind(function(radioNet) {
749 return render_modal_status(null, radioNet);
751 o.write = function() {};
753 o = ss.taboption('general', form.Button, '_toggle', isDisabled ? _('Wireless network is disabled') : _('Wireless network is enabled'));
754 o.inputstyle = isDisabled ? 'apply' : 'reset';
755 o.inputtitle = isDisabled ? _('Enable') : _('Disable');
756 o.onclick = L.ui.createHandlerFn(s, network_updown, s.section, s.map);
758 o = ss.taboption('general', CBIWifiFrequencyValue, '_freq', '<br />' + _('Operating frequency'));
759 o.ucisection = s.section;
761 if (hwtype == 'mac80211') {
762 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.'));
763 o.wifiNetwork = radioNet;
765 o = ss.taboption('advanced', CBIWifiCountryValue, 'country', _('Country Code'));
766 o.wifiNetwork = radioNet;
768 o = ss.taboption('advanced', form.Flag, 'legacy_rates', _('Allow legacy 802.11b rates'));
769 o.default = o.enabled;
771 o = ss.taboption('advanced', form.Value, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
772 o.datatype = 'range(0,114750)';
773 o.placeholder = 'auto';
775 o = ss.taboption('advanced', form.Value, 'frag', _('Fragmentation Threshold'));
776 o.datatype = 'min(256)';
777 o.placeholder = _('off');
779 o = ss.taboption('advanced', form.Value, 'rts', _('RTS/CTS Threshold'));
780 o.datatype = 'uinteger';
781 o.placeholder = _('off');
783 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!'));
786 o = ss.taboption('advanced', form.Value, 'beacon_int', _('Beacon Interval'));
787 o.datatype = 'range(15,65535)';
793 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getName(), 'wifi-iface', _('Interface Configuration'));
797 ss.tab('general', _('General Setup'));
798 ss.tab('encryption', _('Wireless Security'));
799 ss.tab('macfilter', _('MAC-Filter'));
800 ss.tab('advanced', _('Advanced Settings'));
802 o = ss.taboption('general', form.ListValue, 'mode', _('Mode'));
803 o.value('ap', _('Access Point'));
804 o.value('sta', _('Client'));
805 o.value('adhoc', _('Ad-Hoc'));
807 o = ss.taboption('general', form.Value, 'mesh_id', _('Mesh Id'));
808 o.depends('mode', 'mesh');
810 o = ss.taboption('advanced', form.Flag, 'mesh_fwding', _('Forward mesh peer traffic'));
813 o.depends('mode', 'mesh');
815 o = ss.taboption('advanced', form.Value, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
818 o.datatype = 'range(-255,1)';
819 o.depends('mode', 'mesh');
821 o = ss.taboption('general', form.Value, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
822 o.datatype = 'maxlength(32)';
823 o.depends('mode', 'ap');
824 o.depends('mode', 'sta');
825 o.depends('mode', 'adhoc');
826 o.depends('mode', 'ahdemo');
827 o.depends('mode', 'monitor');
828 o.depends('mode', 'ap-wds');
829 o.depends('mode', 'sta-wds');
830 o.depends('mode', 'wds');
832 o = ss.taboption('general', form.Value, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
833 o.datatype = 'macaddr';
835 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.'));
839 o.write = function(section_id, value) {
840 return network.getDevice(section_id).then(L.bind(function(dev) {
841 var old_networks = dev.getNetworks().reduce(function(o, v) { o[v.getName()] = v; return o }, {}),
843 values = L.toArray(value),
846 for (var i = 0; i < values.length; i++) {
847 new_networks[values[i]] = true;
849 if (old_networks[values[i]])
852 tasks.push(network.getNetwork(values[i]).then(L.bind(function(name, net) {
853 return net || network.addNetwork(name, { proto: 'none' });
854 }, this, values[i])).then(L.bind(function(dev, net) {
857 net.set('type', 'bridge');
863 for (var name in old_networks)
864 if (!new_networks[name])
865 tasks.push(network.getNetwork(name).then(L.bind(function(dev, net) {
867 net.deleteDevice(dev);
870 return Promise.all(tasks);
874 if (hwtype == 'mac80211') {
875 var mode = ss.children[0],
876 bssid = ss.children[5],
879 mode.value('mesh', '802.11s');
880 mode.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
881 mode.value('monitor', _('Monitor'));
883 bssid.depends('mode', 'adhoc');
884 bssid.depends('mode', 'sta');
885 bssid.depends('mode', 'sta-wds');
887 o = ss.taboption('macfilter', form.ListValue, 'macfilter', _('MAC-Address Filter'));
888 o.depends('mode', 'ap');
889 o.depends('mode', 'ap-wds');
890 o.value('', _('disable'));
891 o.value('allow', _('Allow listed only'));
892 o.value('deny', _('Allow all except listed'));
894 o = ss.taboption('macfilter', form.DynamicList, 'maclist', _('MAC-List'));
895 o.datatype = 'macaddr';
896 o.depends('macfilter', 'allow');
897 o.depends('macfilter', 'deny');
898 o.load = function(section_id) {
899 return network.getHostHints().then(L.bind(function(hints) {
900 hints.getMACHints().map(L.bind(function(hint) {
901 this.value(hint[0], hint[1] ? '%s (%s)'.format(hint[0], hint[1]) : hint[0]);
904 return form.DynamicList.prototype.load.apply(this, [section_id]);
908 mode.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
909 mode.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
911 mode.write = function(section_id, value) {
914 uci.set('wireless', section_id, 'mode', 'ap');
915 uci.set('wireless', section_id, 'wds', '1');
919 uci.set('wireless', section_id, 'mode', 'sta');
920 uci.set('wireless', section_id, 'wds', '1');
924 uci.set('wireless', section_id, 'mode', value);
925 uci.unset('wireless', section_id, 'wds');
930 mode.cfgvalue = function(section_id) {
931 var mode = uci.get('wireless', section_id, 'mode'),
932 wds = uci.get('wireless', section_id, 'wds');
934 if (mode == 'ap' && wds)
936 else if (mode == 'sta' && wds)
942 o = ss.taboption('general', form.Flag, 'hidden', _('Hide <abbr title="Extended Service Set Identifier">ESSID</abbr>'));
943 o.depends('mode', 'ap');
944 o.depends('mode', 'ap-wds');
946 o = ss.taboption('general', form.Flag, 'wmm', _('WMM Mode'));
947 o.depends('mode', 'ap');
948 o.depends('mode', 'ap-wds');
949 o.default = o.enabled;
951 o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
952 o.depends('mode', 'ap');
953 o.depends('mode', 'ap-wds');
955 o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name'));
957 o.placeholder = radioNet.getIfname();
958 if (/^radio\d+\.network/.test(o.placeholder))
961 o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble'));
962 o.default = o.enabled;
964 o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
967 o.datatype = 'range(1,255)';
969 o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
972 o.datatype = 'uinteger';
974 o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling'));
976 o.datatype = 'uinteger';
978 o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('sec'));
981 o.datatype = 'uinteger';
983 o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval'));
985 o.placeholder = 65535;
986 o.datatype = 'uinteger';
988 o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
989 o.default = o.enabled;
993 encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption'));
994 o.depends('mode', 'ap');
995 o.depends('mode', 'sta');
996 o.depends('mode', 'adhoc');
997 o.depends('mode', 'ahdemo');
998 o.depends('mode', 'ap-wds');
999 o.depends('mode', 'sta-wds');
1000 o.depends('mode', 'mesh');
1002 o.cfgvalue = function(section_id) {
1003 var v = String(uci.get('wireless', section_id, 'encryption'));
1006 else if (v.match(/\+/))
1007 return v.replace(/\+.+$/, '');
1011 o.write = function(section_id, value) {
1012 var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id),
1013 co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id);
1015 if (value == 'wpa' || value == 'wpa2')
1016 uci.unset('wireless', section_id, 'key');
1018 if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp'))
1021 uci.set('wireless', section_id, 'encryption', e);
1024 o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher'));
1025 o.depends('encryption', 'wpa');
1026 o.depends('encryption', 'wpa2');
1027 o.depends('encryption', 'psk');
1028 o.depends('encryption', 'psk2');
1029 o.depends('encryption', 'wpa-mixed');
1030 o.depends('encryption', 'psk-mixed');
1031 o.value('auto', _('auto'));
1032 o.value('ccmp', _('Force CCMP (AES)'));
1033 o.value('tkip', _('Force TKIP'));
1034 o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1035 o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write;
1037 o.cfgvalue = function(section_id) {
1038 var v = String(uci.get('wireless', section_id, 'encryption'));
1039 if (v.match(/\+/)) {
1040 v = v.replace(/^[^+]+\+/, '');
1043 else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip')
1050 var crypto_modes = [];
1052 if (hwtype == 'mac80211') {
1053 var has_supplicant = L.hasSystemFeature('wpasupplicant'),
1054 has_hostapd = L.hasSystemFeature('hostapd');
1056 // Probe EAP support
1057 var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'),
1058 has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap');
1060 // Probe SAE support
1061 var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'),
1062 has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae');
1064 // Probe OWE support
1065 var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
1066 has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
1069 if (has_hostapd || has_supplicant) {
1070 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1071 crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1072 crypto_modes.push(['psk', 'WPA-PSK', 21]);
1075 encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1078 if (has_ap_sae || has_sta_sae) {
1079 crypto_modes.push(['sae', 'WPA3-SAE', 31]);
1080 crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1083 if (has_ap_eap || has_sta_eap) {
1084 crypto_modes.push(['wpa2', 'WPA2-EAP', 32]);
1085 crypto_modes.push(['wpa', 'WPA-EAP', 20]);
1088 if (has_ap_owe || has_sta_owe) {
1089 crypto_modes.push(['owe', 'OWE', 1]);
1092 encr.crypto_support = {
1096 'psk': has_hostapd || _('Requires hostapd'),
1097 'psk2': has_hostapd || _('Requires hostapd'),
1098 'psk-mixed': has_hostapd || _('Requires hostapd'),
1099 'sae': has_ap_sae || _('Requires hostapd with SAE support'),
1100 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
1101 'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
1102 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
1103 'owe': has_ap_owe || _('Requires hostapd with OWE support')
1108 'psk': has_supplicant || _('Requires wpa-supplicant'),
1109 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1110 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1111 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1112 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1113 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1114 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1115 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
1120 'psk': has_supplicant || _('Requires wpa-supplicant'),
1121 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1122 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1125 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support')
1137 encr.crypto_support['ap-wds'] = encr.crypto_support['ap'];
1138 encr.crypto_support['sta-wds'] = encr.crypto_support['sta'];
1140 encr.validate = function(section_id, value) {
1141 var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
1142 modeval = modeopt.formvalue(section_id),
1143 modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)],
1144 enctitle = this.vallist[this.keylist.indexOf(value)];
1146 if (value == 'none')
1149 if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value))
1150 return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle);
1152 return this.crypto_support[modeval][value];
1155 else if (hwtype == 'broadcom') {
1156 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1157 crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1158 crypto_modes.push(['psk', 'WPA-PSK', 21]);
1161 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1162 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1163 crypto_modes.push(['none', _('No Encryption'), 0]);
1165 crypto_modes.sort(function(a, b) { return b[2] - a[2] });
1167 for (var i = 0; i < crypto_modes.length; i++) {
1168 var security_level = (crypto_modes[i][2] >= 30) ? _('strong security')
1169 : (crypto_modes[i][2] >= 20) ? _('medium security')
1170 : (crypto_modes[i][2] >= 10) ? _('weak security') : _('open network');
1172 encr.value(crypto_modes[i][0], '%s (%s)'.format(crypto_modes[i][1], security_level));
1176 o = ss.taboption('encryption', form.Value, 'auth_server', _('Radius-Authentication-Server'));
1177 o.depends({ mode: 'ap', encryption: 'wpa' });
1178 o.depends({ mode: 'ap', encryption: 'wpa2' });
1179 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1180 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1182 o.datatype = 'host(0)';
1184 o = ss.taboption('encryption', form.Value, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
1185 o.depends({ mode: 'ap', encryption: 'wpa' });
1186 o.depends({ mode: 'ap', encryption: 'wpa2' });
1187 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1188 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1190 o.datatype = 'port';
1192 o = ss.taboption('encryption', form.Value, 'auth_secret', _('Radius-Authentication-Secret'));
1193 o.depends({ mode: 'ap', encryption: 'wpa' });
1194 o.depends({ mode: 'ap', encryption: 'wpa2' });
1195 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1196 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1200 o = ss.taboption('encryption', form.Value, 'acct_server', _('Radius-Accounting-Server'));
1201 o.depends({ mode: 'ap', encryption: 'wpa' });
1202 o.depends({ mode: 'ap', encryption: 'wpa2' });
1203 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1204 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1206 o.datatype = 'host(0)';
1208 o = ss.taboption('encryption', form.Value, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
1209 o.depends({ mode: 'ap', encryption: 'wpa' });
1210 o.depends({ mode: 'ap', encryption: 'wpa2' });
1211 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1212 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1214 o.datatype = 'port';
1216 o = ss.taboption('encryption', form.Value, 'acct_secret', _('Radius-Accounting-Secret'));
1217 o.depends({ mode: 'ap', encryption: 'wpa' });
1218 o.depends({ mode: 'ap', encryption: 'wpa2' });
1219 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1220 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1224 o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client'));
1225 o.depends({ mode: 'ap', encryption: 'wpa' });
1226 o.depends({ mode: 'ap', encryption: 'wpa2' });
1227 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1228 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1230 o.datatype = 'host(0)';
1232 o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
1233 o.depends({ mode: 'ap', encryption: 'wpa' });
1234 o.depends({ mode: 'ap', encryption: 'wpa2' });
1235 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1236 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1238 o.datatype = 'port';
1240 o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret'));
1241 o.depends({ mode: 'ap', encryption: 'wpa' });
1242 o.depends({ mode: 'ap', encryption: 'wpa2' });
1243 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1244 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1249 o = ss.taboption('encryption', form.Value, '_wpa_key', _('Key'));
1250 o.depends('encryption', 'psk');
1251 o.depends('encryption', 'psk2');
1252 o.depends('encryption', 'psk+psk2');
1253 o.depends('encryption', 'psk-mixed');
1254 o.depends('encryption', 'sae');
1255 o.depends('encryption', 'sae-mixed');
1256 o.datatype = 'wpakey';
1260 o.cfgvalue = function(section_id) {
1261 var key = uci.get('wireless', section_id, 'key');
1262 return /^[1234]$/.test(key) ? null : key;
1265 o.write = function(section_id, value) {
1266 uci.set('wireless', section_id, 'key', value);
1267 uci.unset('wireless', section_id, 'key1');
1268 uci.unset('wireless', section_id, 'key2');
1269 uci.unset('wireless', section_id, 'key3');
1270 uci.unset('wireless', section_id, 'key4');
1274 o = ss.taboption('encryption', form.ListValue, '_wep_key', _('Used Key Slot'));
1275 o.depends('encryption', 'wep-open');
1276 o.depends('encryption', 'wep-shared');
1277 o.value('1', _('Key #%d').format(1));
1278 o.value('2', _('Key #%d').format(2));
1279 o.value('3', _('Key #%d').format(3));
1280 o.value('4', _('Key #%d').format(4));
1282 o.cfgvalue = function(section_id) {
1283 var slot = +uci.get('wireless', section_id, 'key');
1284 return (slot >= 1 && slot <= 4) ? String(slot) : '';
1287 o.write = function(section_id, value) {
1288 uci.set('wireless', section_id, 'key', value);
1291 for (var slot = 1; slot <= 4; slot++) {
1292 o = ss.taboption('encryption', form.Value, 'key%d'.format(slot), _('Key #%d').format(slot));
1293 o.depends('encryption', 'wep-open');
1294 o.depends('encryption', 'wep-shared');
1295 o.datatype = 'wepkey';
1299 o.write = function(section_id, value) {
1300 if (value != null && (value.length == 5 || value.length == 13))
1301 value = 's:%s'.format(value);
1302 uci.set('wireless', section_id, this.option, value);
1307 if (hwtype == 'mac80211') {
1308 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1309 var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap');
1311 o = ss.taboption('encryption', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1312 o.depends({ mode: 'ap', encryption: 'wpa' });
1313 o.depends({ mode: 'ap', encryption: 'wpa2' });
1314 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1315 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1317 o.depends({ mode: 'ap', encryption: 'psk' });
1318 o.depends({ mode: 'ap', encryption: 'psk2' });
1319 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1320 o.depends({ mode: 'ap', encryption: 'sae' });
1321 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1322 o.depends({ mode: 'ap-wds', encryption: 'psk' });
1323 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1324 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1325 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1326 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1330 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.'));
1331 o.depends({ mode: 'ap', encryption: 'wpa' });
1332 o.depends({ mode: 'ap', encryption: 'wpa2' });
1333 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1334 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1335 o.depends({ ieee80211r: '1' });
1338 o = ss.taboption('encryption', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1339 o.depends({ ieee80211r: '1' });
1340 o.placeholder = '4f57';
1341 o.datatype = 'and(hexstring,length(4))';
1344 o = ss.taboption('encryption', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1345 o.depends({ ieee80211r: '1' });
1346 o.placeholder = '1000';
1347 o.datatype = 'range(1000,65535)';
1350 o = ss.taboption('encryption', form.ListValue, 'ft_over_ds', _('FT protocol'));
1351 o.depends({ ieee80211r: '1' });
1352 o.value('1', _('FT over DS'));
1353 o.value('0', _('FT over the Air'));
1356 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.'));
1357 o.depends({ ieee80211r: '1' });
1358 o.default = o.enabled;
1361 o = ss.taboption('encryption', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1362 o.depends({ ieee80211r: '1' });
1363 o.placeholder = '10000';
1364 o.datatype = 'uinteger';
1367 o = ss.taboption('encryption', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1368 o.depends({ ieee80211r: '1' });
1369 o.placeholder = '00004f577274';
1370 o.datatype = 'and(hexstring,length(12))';
1373 o = ss.taboption('encryption', form.Flag, 'pmk_r1_push', _('PMK R1 Push'));
1374 o.depends({ ieee80211r: '1' });
1375 o.placeholder = '0';
1378 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.'));
1379 o.depends({ ieee80211r: '1' });
1382 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.'));
1383 o.depends({ ieee80211r: '1' });
1385 // End of 802.11r options
1387 o = ss.taboption('encryption', form.ListValue, 'eap_type', _('EAP-Method'));
1388 o.value('tls', 'TLS');
1389 o.value('ttls', 'TTLS');
1390 o.value('peap', 'PEAP');
1391 o.value('fast', 'FAST');
1392 o.depends({ mode: 'sta', encryption: 'wpa' });
1393 o.depends({ mode: 'sta', encryption: 'wpa2' });
1394 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1395 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1397 o = ss.taboption('encryption', form.FileUpload, 'ca_cert', _('Path to CA-Certificate'));
1398 o.depends({ mode: 'sta', encryption: 'wpa' });
1399 o.depends({ mode: 'sta', encryption: 'wpa2' });
1400 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1401 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1403 o = ss.taboption('encryption', form.FileUpload, 'client_cert', _('Path to Client-Certificate'));
1404 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1405 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1406 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1407 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1409 o = ss.taboption('encryption', form.FileUpload, 'priv_key', _('Path to Private Key'));
1410 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1411 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1412 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1413 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1415 o = ss.taboption('encryption', form.Value, 'priv_key_pwd', _('Password of Private Key'));
1416 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1417 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1418 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1419 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1422 o = ss.taboption('encryption', form.ListValue, 'auth', _('Authentication'));
1423 o.value('PAP', 'PAP');
1424 o.value('CHAP', 'CHAP');
1425 o.value('MSCHAP', 'MSCHAP');
1426 o.value('MSCHAPV2', 'MSCHAPv2');
1429 o.value('EAP-MSCHAPV2');
1431 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1432 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1433 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1434 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1435 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1436 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1437 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1438 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1439 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1440 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1441 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1442 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1444 o.validate = function(section_id, value) {
1445 var eo = this.section.children.filter(function(o) { return o.option == 'eap_type' })[0],
1446 ev = eo.formvalue(section_id);
1448 if (ev != 'ttls' && (value == 'PAP' || value == 'CHAP' || value == 'MSCHAP' || value == 'MSCHAPV2'))
1449 return _('This authentication type is not applicable to the selected EAP method.');
1454 o = ss.taboption('encryption', form.FileUpload, 'ca_cert2', _('Path to inner CA-Certificate'));
1455 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1456 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1457 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1458 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1460 o = ss.taboption('encryption', form.FileUpload, 'client_cert2', _('Path to inner Client-Certificate'));
1461 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1462 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1463 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1464 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1466 o = ss.taboption('encryption', form.FileUpload, 'priv_key2', _('Path to inner Private Key'));
1467 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1468 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1469 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1470 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1472 o = ss.taboption('encryption', form.Value, 'priv_key2_pwd', _('Password of inner Private Key'));
1473 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1474 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1475 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1476 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1479 o = ss.taboption('encryption', form.Value, 'identity', _('Identity'));
1480 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1481 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1482 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1483 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1484 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1485 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1486 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1487 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1488 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1489 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1490 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1491 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1492 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1493 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1494 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1495 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1497 o = ss.taboption('encryption', form.Value, 'anonymous_identity', _('Anonymous Identity'));
1498 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1499 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1500 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1501 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1502 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1503 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1504 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1505 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1506 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1507 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1508 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1509 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1510 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1511 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1512 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1513 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1515 o = ss.taboption('encryption', form.Value, 'password', _('Password'));
1516 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1517 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1518 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1519 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1520 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1521 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1522 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1523 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1524 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1525 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1526 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1527 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1531 if (hwtype == 'mac80211') {
1532 // ieee802.11w options
1533 if (L.hasSystemFeature('hostapd', '11w')) {
1534 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)"));
1535 o.value('', _('Disabled'));
1536 o.value('1', _('Optional'));
1537 o.value('2', _('Required'));
1538 o.depends({ mode: 'ap', encryption: 'wpa2' });
1539 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1540 o.depends({ mode: 'ap', encryption: 'psk2' });
1541 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1542 o.depends({ mode: 'ap', encryption: 'sae' });
1543 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1544 o.depends({ mode: 'ap', encryption: 'owe' });
1545 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1546 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1547 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1548 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1549 o.depends({ mode: 'ap-wds', encryption: 'owe' });
1550 o.depends({ mode: 'sta', encryption: 'wpa2' });
1551 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1552 o.depends({ mode: 'sta', encryption: 'psk2' });
1553 o.depends({ mode: 'sta', encryption: 'psk-mixed' });
1554 o.depends({ mode: 'sta', encryption: 'sae' });
1555 o.depends({ mode: 'sta', encryption: 'sae-mixed' });
1556 o.depends({ mode: 'sta', encryption: 'owe' });
1557 o.depends({ mode: 'sta-wds', encryption: 'psk2' });
1558 o.depends({ mode: 'sta-wds', encryption: 'psk-mixed' });
1559 o.depends({ mode: 'sta-wds', encryption: 'sae' });
1560 o.depends({ mode: 'sta-wds', encryption: 'sae-mixed' });
1561 o.depends({ mode: 'sta-wds', encryption: 'owe' });
1563 '2': [{ encryption: 'sae' }, { encryption: 'owe' }],
1564 '1': [{ encryption: 'sae-mixed'}],
1568 o = ss.taboption('encryption', form.Value, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1569 o.depends('ieee80211w', '1');
1570 o.depends('ieee80211w', '2');
1571 o.datatype = 'uinteger';
1572 o.placeholder = '1000';
1575 o = ss.taboption('encryption', form.Value, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1576 o.depends('ieee80211w', '1');
1577 o.depends('ieee80211w', '2');
1578 o.datatype = 'uinteger';
1579 o.placeholder = '201';
1583 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.'));
1584 o.depends({ mode: 'ap', encryption: 'wpa2' });
1585 o.depends({ mode: 'ap', encryption: 'psk2' });
1586 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1587 o.depends({ mode: 'ap', encryption: 'sae' });
1588 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1589 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1590 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1591 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1592 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1593 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1595 if (L.hasSystemFeature('hostapd', 'cli') && L.hasSystemFeature('wpasupplicant')) {
1596 o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1599 o.default = o.disabled;
1600 o.depends('encryption', 'psk');
1601 o.depends('encryption', 'psk2');
1602 o.depends('encryption', 'psk-mixed');
1603 o.depends('encryption', 'sae');
1604 o.depends('encryption', 'sae-mixed');
1611 s.handleRemove = function(section_id, ev) {
1612 document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id)).style.opacity = 0.5;
1613 return form.TypedSection.prototype.handleRemove.apply(this, [section_id, ev]);
1616 s.handleScan = function(radioDev, ev) {
1617 var table = E('div', { 'class': 'table' }, [
1618 E('div', { 'class': 'tr table-titles' }, [
1619 E('div', { 'class': 'th col-2 middle center' }, _('Signal')),
1620 E('div', { 'class': 'th col-4 middle left' }, _('SSID')),
1621 E('div', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1622 E('div', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1623 E('div', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1624 E('div', { 'class': 'th col-3 middle left' }, _('Encryption')),
1625 E('div', { 'class': 'th cbi-section-actions right' }, ' '),
1629 cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1631 var md = L.ui.showModal(_('Join Network: Wireless Scan'), [
1633 E('div', { 'class': 'right' },
1636 'click': L.bind(this.handleScanAbort, this)
1640 md.style.maxWidth = '90%';
1641 md.style.maxHeight = 'none';
1643 this.pollFn = L.bind(this.handleScanRefresh, this, radioDev, {}, table);
1645 L.Poll.add(this.pollFn);
1649 s.handleScanRefresh = function(radioDev, scanCache, table) {
1650 return radioDev.getScanList().then(L.bind(function(results) {
1653 for (var i = 0; i < results.length; i++)
1654 scanCache[results[i].bssid] = results[i];
1656 for (var k in scanCache)
1657 if (scanCache[k].stale)
1658 results.push(scanCache[k]);
1660 results.sort(function(a, b) {
1661 var diff = (b.quality - a.quality) || (a.channel - b.channel);
1666 if (a.ssid < b.ssid)
1668 else if (a.ssid > b.ssid)
1671 if (a.bssid < b.bssid)
1673 else if (a.bssid > b.bssid)
1677 for (var i = 0; i < results.length; i++) {
1678 var res = results[i],
1679 qv = res.quality || 0,
1680 qm = res.quality_max || 0,
1681 q = (qv > 0 && qm > 0) ? Math.floor((100 / qm) * qv) : 0,
1682 s = res.stale ? 'opacity:0.5' : '';
1685 E('span', { 'style': s }, render_signal_badge(q, res.signal, res.noise)),
1686 E('span', { 'style': s }, '%h'.format(res.ssid)),
1687 E('span', { 'style': s }, '%d'.format(res.channel)),
1688 E('span', { 'style': s }, '%h'.format(res.mode)),
1689 E('span', { 'style': s }, '%h'.format(res.bssid)),
1690 E('span', { 'style': s }, '%h'.format(network.formatWifiEncryption(res.encryption))),
1691 E('div', { 'class': 'right' }, E('button', {
1692 'class': 'cbi-button cbi-button-action important',
1693 'click': L.bind(this.handleJoin, this, radioDev, res)
1694 }, _('Join Network')))
1700 cbi_update_table(table, rows);
1704 s.handleScanAbort = function(ev) {
1705 var md = L.dom.parent(ev.target, 'div[aria-modal="true"]');
1707 md.style.maxWidth = '';
1708 md.style.maxHeight = '';
1712 L.Poll.remove(this.pollFn);
1717 s.handleJoinConfirm = function(radioDev, bss, form, ev) {
1718 var nameopt = L.toArray(form.lookupOption('name', '_new_'))[0],
1719 passopt = L.toArray(form.lookupOption('password', '_new_'))[0],
1720 zoneopt = L.toArray(form.lookupOption('zone', '_new_'))[0],
1721 replopt = L.toArray(form.lookupOption('replace', '_new_'))[0],
1722 nameval = (nameopt && nameopt.isValid('_new_')) ? nameopt.formvalue('_new_') : null,
1723 passval = (passopt && passopt.isValid('_new_')) ? passopt.formvalue('_new_') : null,
1724 zoneval = zoneopt ? zoneopt.formvalue('_new_') : null,
1725 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1726 is_wep = (enc && Array.isArray(enc.wep)),
1727 is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' })),
1728 is_sae = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'sae' }));
1730 if (nameval == null || (passopt && passval == null))
1733 var section_id = null;
1735 return this.map.save(function() {
1736 if (replopt.formvalue('_new_') == '1') {
1737 var sections = uci.sections('wireless', 'wifi-iface');
1739 for (var i = 0; i < sections.length; i++)
1740 if (sections[i].device == radioDev.getName())
1741 uci.remove('wireless', sections[i]['.name']);
1744 section_id = next_free_sid(uci.sections('wifi-iface').length);
1746 uci.add('wireless', 'wifi-iface', section_id);
1747 uci.set('wireless', section_id, 'device', radioDev.getName());
1748 uci.set('wireless', section_id, 'mode', (bss.mode == 'Ad-Hoc') ? 'adhoc' : 'sta');
1749 uci.set('wireless', section_id, 'network', nameval);
1751 if (bss.ssid != null)
1752 uci.set('wireless', section_id, 'ssid', bss.ssid);
1753 else if (bss.bssid != null)
1754 uci.set('wireless', section_id, 'bssid', bss.bssid);
1757 uci.set('wireless', section_id, 'encryption', 'sae');
1758 uci.set('wireless', section_id, 'key', passval);
1761 for (var i = enc.wpa.length - 1; i >= 0; i--) {
1762 if (enc.wpa[i] == 2) {
1763 uci.set('wireless', section_id, 'encryption', 'psk2');
1766 else if (enc.wpa[i] == 1) {
1767 uci.set('wireless', section_id, 'encryption', 'psk');
1772 uci.set('wireless', section_id, 'key', passval);
1775 uci.set('wireless', section_id, 'encryption', 'wep-open');
1776 uci.set('wireless', section_id, 'key', '1');
1777 uci.set('wireless', section_id, 'key1', passval);
1780 return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) {
1781 firewall.deleteNetwork(net.getName());
1783 var zonePromise = zoneval
1784 ? firewall.getZone(zoneval).then(function(zone) { return zone || firewall.addZone(zoneval) })
1785 : Promise.resolve();
1787 return zonePromise.then(function(zone) {
1789 zone.addNetwork(net.getName());
1792 }).then(L.bind(function() {
1793 return this.renderMoreOptionsModal(section_id);
1797 s.handleJoin = function(radioDev, bss, ev) {
1798 this.handleScanAbort(ev);
1800 var m2 = new form.Map('wireless'),
1801 s2 = m2.section(form.NamedSection, '_new_'),
1802 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1803 is_wep = (enc && Array.isArray(enc.wep)),
1804 is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' || a == 'sae' })),
1805 replace, passphrase, name, zone;
1807 s2.render = function() {
1808 return Promise.all([
1810 this.renderUCISection('_new_')
1811 ]).then(this.renderContents.bind(this));
1814 replace = s2.option(form.Flag, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
1816 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>'));
1817 name.datatype = 'uciname';
1818 name.default = 'wwan';
1819 name.rmempty = false;
1820 name.validate = function(section_id, value) {
1821 if (uci.get('network', value))
1822 return _('The network name is already used');
1827 for (var i = 2; uci.get('network', name.default); i++)
1828 name.default = 'wwan%d'.format(i);
1830 if (is_wep || is_psk) {
1831 passphrase = s2.option(form.Value, 'password', is_wep ? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
1832 passphrase.datatype = is_wep ? 'wepkey' : 'wpakey';
1833 passphrase.password = true;
1834 passphrase.rmempty = false;
1837 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.'));
1838 zone.default = 'wan';
1840 return m2.render().then(L.bind(function(nodes) {
1841 L.ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [
1843 E('div', { 'class': 'right' }, [
1846 'click': L.ui.hideModal
1847 }, _('Cancel')), ' ',
1849 'class': 'cbi-button cbi-button-positive important',
1850 'click': L.ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2)
1853 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase || name).cbid('_new_'))).focus();
1857 s.handleAdd = function(radioDev, ev) {
1858 var section_id = next_free_sid(uci.sections('wireless', 'wifi-iface').length);
1860 uci.unset('wireless', radioDev.getName(), 'disabled');
1862 uci.add('wireless', 'wifi-iface', section_id);
1863 uci.set('wireless', section_id, 'device', radioDev.getName());
1864 uci.set('wireless', section_id, 'mode', 'ap');
1865 uci.set('wireless', section_id, 'ssid', 'OpenWrt');
1866 uci.set('wireless', section_id, 'encryption', 'none');
1868 this.addedSection = section_id;
1869 return this.renderMoreOptionsModal(section_id);
1872 o = s.option(form.DummyValue, '_badge');
1873 o.modalonly = false;
1874 o.textvalue = function(section_id) {
1875 var inst = this.section.lookupRadioOrNetwork(section_id),
1876 node = E('div', { 'class': 'center' });
1878 if (inst.getWifiNetworks)
1879 node.appendChild(render_radio_badge(inst));
1881 node.appendChild(render_network_badge(inst));
1886 o = s.option(form.DummyValue, '_stat');
1887 o.modalonly = false;
1888 o.textvalue = function(section_id) {
1889 var inst = this.section.lookupRadioOrNetwork(section_id);
1891 if (inst.getWifiNetworks)
1892 return render_radio_status(inst, this.section.wifis.filter(function(e) {
1893 return (e.getWifiDeviceName() == inst.getName());
1896 return render_network_status(inst);
1899 return m.render().then(L.bind(function(m, nodes) {
1900 L.Poll.add(L.bind(function() {
1901 var section_ids = m.children[0].cfgsections(),
1902 tasks = [ network.getHostHints(), network.getWifiDevices() ];
1904 for (var i = 0; i < section_ids.length; i++) {
1905 var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])),
1906 dsc = row.querySelector('[data-name="_stat"] > div'),
1907 btns = row.querySelectorAll('.cbi-section-actions button');
1909 if (dsc.getAttribute('restart') == '') {
1910 dsc.setAttribute('restart', '1');
1911 tasks.push(L.Request.post(
1912 L.url('admin/network/wireless_reconnect', section_ids[i]),
1913 'token=' + L.env.token,
1914 { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
1915 ).catch(function() {}));
1917 else if (dsc.getAttribute('restart') == '1') {
1918 dsc.removeAttribute('restart');
1919 btns[0].classList.remove('spinning');
1920 btns[0].disabled = false;
1924 return Promise.all(tasks)
1925 .then(L.bind(function(hosts_radios) {
1928 for (var i = 0; i < hosts_radios[1].length; i++)
1929 tasks.push(hosts_radios[1][i].getWifiNetworks());
1931 return Promise.all(tasks).then(function(data) {
1932 hosts_radios[2] = [];
1934 for (var i = 0; i < data.length; i++)
1935 hosts_radios[2].push.apply(hosts_radios[2], data[i]);
1937 return hosts_radios;
1940 .then(L.bind(function(hosts_radios_wifis) {
1943 for (var i = 0; i < hosts_radios_wifis[2].length; i++)
1944 tasks.push(hosts_radios_wifis[2][i].getAssocList());
1946 return Promise.all(tasks).then(function(data) {
1947 hosts_radios_wifis[3] = [];
1949 for (var i = 0; i < data.length; i++) {
1950 var wifiNetwork = hosts_radios_wifis[2][i],
1951 radioDev = hosts_radios_wifis[1].filter(function(d) { return d.getName() == wifiNetwork.getWifiDeviceName() })[0];
1953 for (var j = 0; j < data[i].length; j++)
1954 hosts_radios_wifis[3].push(Object.assign({ radio: radioDev, network: wifiNetwork }, data[i][j]));
1957 return hosts_radios_wifis;
1960 .then(L.bind(this.poll_status, this, nodes));
1963 var table = E('div', { 'class': 'table', 'id': 'wifi_assoclist_table' }, [
1964 E('div', { 'class': 'tr table-titles' }, [
1965 E('div', { 'class': 'th nowrap' }, _('Network')),
1966 E('div', { 'class': 'th hide-xs' }, _('MAC-Address')),
1967 E('div', { 'class': 'th nowrap' }, _('Host')),
1968 E('div', { 'class': 'th nowrap' }, _('Signal / Noise')),
1969 E('div', { 'class': 'th nowrap' }, _('RX Rate / TX Rate'))
1973 cbi_update_table(table, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
1975 return E([ nodes, E('h3', _('Associated Stations')), table ]);