9 'require tools.widgets as widgets';
11 function count_changes(section_id) {
12 var changes = ui.changes.changes, n = 0;
14 if (!L.isObject(changes))
17 if (Array.isArray(changes.wireless))
18 for (var i = 0; i < changes.wireless.length; i++)
19 n += (changes.wireless[i][1] == section_id);
24 function render_radio_badge(radioDev) {
25 return E('span', { 'class': 'ifacebadge' }, [
26 E('img', { 'src': L.resource('icons/wifi%s.png').format(radioDev.isUp() ? '' : '_disabled') }),
32 function render_signal_badge(signalPercent, signalValue, noiseValue, wrap) {
35 if (signalPercent < 0)
36 icon = L.resource('icons/signal-none.png');
37 else if (signalPercent == 0)
38 icon = L.resource('icons/signal-0.png');
39 else if (signalPercent < 25)
40 icon = L.resource('icons/signal-0-25.png');
41 else if (signalPercent < 50)
42 icon = L.resource('icons/signal-25-50.png');
43 else if (signalPercent < 75)
44 icon = L.resource('icons/signal-50-75.png');
46 icon = L.resource('icons/signal-75-100.png');
48 if (signalValue != null && signalValue != 0) {
49 title = '%s %d %s'.format(_('Signal'), signalValue, _('dBm'));
51 if (noiseValue != null && noiseValue != 0)
52 title += ' / %s: %d %s'.format(_('Noise'), noiseValue, _('dBm'));
55 title = _('No signal');
58 return E('div', { 'class': wrap ? 'center' : 'ifacebadge', 'title': title },
59 [ E('img', { 'src': icon }), wrap ? E('br') : ' ', '%d%%'.format(Math.max(signalPercent, 0)) ]);
62 function render_network_badge(radioNet) {
63 return render_signal_badge(radioNet.isUp() ? radioNet.getSignalPercent() : -1, radioNet.getSignal(), radioNet.getNoise());
66 function render_radio_status(radioDev, wifiNets) {
67 var name = radioDev.getI18n().replace(/ Wireless Controller .+$/, ''),
68 node = E('div', [ E('big', {}, E('strong', {}, name)), E('div') ]),
69 channel, frequency, bitrate;
71 for (var i = 0; i < wifiNets.length; i++) {
72 channel = channel || wifiNets[i].getChannel();
73 frequency = frequency || wifiNets[i].getFrequency();
74 bitrate = bitrate || wifiNets[i].getBitRate();
78 L.itemlist(node.lastElementChild, [
79 _('Channel'), '%s (%s %s)'.format(channel || '?', frequency || '?', _('GHz')),
80 _('Bitrate'), '%s %s'.format(bitrate || '?', _('Mbit/s'))
83 node.lastElementChild.appendChild(E('em', _('Device is not active')));
88 function render_network_status(radioNet) {
89 var mode = radioNet.getActiveMode(),
90 bssid = radioNet.getActiveBSSID(),
91 channel = radioNet.getChannel(),
92 disabled = (radioNet.get('disabled') == '1' || uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == '1'),
93 is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled),
94 is_mesh = (radioNet.getMode() == 'mesh'),
95 changecount = count_changes(radioNet.getName()),
99 status_text = E('a', {
101 click: L.bind(ui.changes.displayChanges, ui.changes)
102 }, _('Interface has %d pending changes').format(changecount));
104 status_text = E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated'));
106 return L.itemlist(E('div'), [
107 is_mesh ? _('Mesh ID') : _('SSID'), (is_mesh ? radioNet.getMeshID() : radioNet.getSSID()) || '?',
109 _('BSSID'), (!changecount && is_assoc) ? bssid : null,
110 _('Encryption'), (!changecount && is_assoc) ? radioNet.getActiveEncryption() || _('None') : null,
112 ], [ ' | ', E('br') ]);
115 function render_modal_status(node, radioNet) {
116 var mode = radioNet.getActiveMode(),
117 noise = radioNet.getNoise(),
118 bssid = radioNet.getActiveBSSID(),
119 channel = radioNet.getChannel(),
120 disabled = (radioNet.get('disabled') == '1'),
121 is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled);
124 node = E('span', { 'class': 'ifacebadge large', 'data-network': radioNet.getName() }, [ E('small'), E('span') ]);
126 L.dom.content(node.firstElementChild, render_signal_badge(disabled ? -1 : radioNet.getSignalPercent(), radioNet.getSignal(), noise, true));
128 L.itemlist(node.lastElementChild, [
130 _('SSID'), radioNet.getSSID() || '?',
131 _('BSSID'), is_assoc ? bssid : null,
132 _('Encryption'), is_assoc ? radioNet.getActiveEncryption() || _('None') : null,
133 _('Channel'), is_assoc ? '%d (%.3f %s)'.format(radioNet.getChannel(), radioNet.getFrequency() || 0, _('GHz')) : null,
134 _('Tx-Power'), is_assoc ? '%d %s'.format(radioNet.getTXPower(), _('dBm')) : null,
135 _('Signal'), is_assoc ? '%d %s'.format(radioNet.getSignal(), _('dBm')) : null,
136 _('Noise'), (is_assoc && noise != null) ? '%d %s'.format(noise, _('dBm')) : null,
137 _('Bitrate'), is_assoc ? '%.1f %s'.format(radioNet.getBitRate() || 0, _('Mbit/s')) : null,
138 _('Country'), is_assoc ? radioNet.getCountryCode() : null
139 ], [ ' | ', E('br'), E('br'), E('br'), E('br'), E('br'), ' | ', E('br'), ' | ' ]);
142 L.dom.append(node.lastElementChild, E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated')));
147 function format_wifirate(rate) {
148 var s = '%.1f Mbit/s, %dMHz'.format(rate.rate / 1000, rate.mhz);
150 if (rate.ht || rate.vht) {
151 if (rate.vht) s += ', VHT-MCS %d'.format(rate.mcs);
152 if (rate.nss) s += ', VHT-NSS %d'.format(rate.nss);
153 if (rate.ht) s += ', MCS %s'.format(rate.mcs);
154 if (rate.short_gi) s += ', Short GI';
160 function radio_restart(id, ev) {
161 var row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id)),
162 dsc = row.querySelector('[data-name="_stat"] > div'),
163 btn = row.querySelector('.cbi-section-actions button');
166 btn.classList.add('spinning');
169 dsc.setAttribute('restart', '');
170 L.dom.content(dsc, E('em', _('Device is restarting…')));
173 function network_updown(id, map, ev) {
174 var radio = uci.get('wireless', id, 'device'),
175 disabled = (uci.get('wireless', id, 'disabled') == '1') ||
176 (uci.get('wireless', radio, 'disabled') == '1');
179 uci.unset('wireless', id, 'disabled');
180 uci.unset('wireless', radio, 'disabled');
183 uci.set('wireless', id, 'disabled', '1');
185 var all_networks_disabled = true,
186 wifi_ifaces = uci.sections('wireless', 'wifi-iface');
188 for (var i = 0; i < wifi_ifaces.length; i++) {
189 if (wifi_ifaces[i].device == radio && wifi_ifaces[i].disabled != '1') {
190 all_networks_disabled = false;
195 if (all_networks_disabled)
196 uci.set('wireless', radio, 'disabled', '1');
199 return map.save().then(function() {
204 function next_free_sid(offset) {
205 var sid = 'wifinet' + offset;
207 while (uci.get('wireless', sid))
208 sid = 'wifinet' + (++offset);
213 var CBIWifiFrequencyValue = form.Value.extend({
214 callFrequencyList: rpc.declare({
217 params: [ 'device' ],
218 expect: { results: [] }
221 load: function(section_id) {
223 network.getWifiDevice(section_id),
224 this.callFrequencyList(section_id)
225 ]).then(L.bind(function(data) {
227 '11g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
228 '11a': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : []
231 for (var i = 0; i < data[1].length; i++)
232 this.channels[(data[1][i].mhz > 2484) ? '11a' : '11g'].push(
234 '%d (%d Mhz)'.format(data[1][i].channel, data[1][i].mhz),
235 !data[1][i].restricted
238 var hwmodelist = L.toArray(data[0] ? data[0].getHWModes() : null)
239 .reduce(function(o, v) { o[v] = true; return o }, {});
243 'n', 'N', hwmodelist.n,
244 'ac', 'AC', hwmodelist.ac
247 var htmodelist = L.toArray(data[0] ? data[0].getHTModes() : null)
248 .reduce(function(o, v) { o[v] = true; return o }, {});
251 '': [ '', '-', true ],
253 'HT20', '20 MHz', htmodelist.HT20,
254 'HT40', '40 MHz', htmodelist.HT40
257 'VHT20', '20 MHz', htmodelist.VHT20,
258 'VHT40', '40 MHz', htmodelist.VHT40,
259 'VHT80', '80 MHz', htmodelist.VHT80,
260 'VHT160', '160 MHz', htmodelist.VHT160
266 '11g', '2.4 GHz', this.channels['11g'].length > 3,
267 '11a', '5 GHz', this.channels['11a'].length > 3
270 '11g', '2.4 GHz', this.channels['11g'].length > 3,
271 '11a', '5 GHz', this.channels['11a'].length > 3
280 setValues: function(sel, vals) {
282 sel.vals.selected = sel.selectedIndex;
284 while (sel.options[0])
287 for (var i = 0; vals && i < vals.length; i += 3)
289 sel.add(E('option', { value: vals[i+0] }, [ vals[i+1] ]));
291 if (vals && !isNaN(vals.selected))
292 sel.selectedIndex = vals.selected;
294 sel.parentNode.style.display = (sel.options.length <= 1) ? 'none' : '';
298 toggleWifiMode: function(elem) {
299 this.toggleWifiHTMode(elem);
300 this.toggleWifiBand(elem);
303 toggleWifiHTMode: function(elem) {
304 var mode = elem.querySelector('.mode');
305 var bwdt = elem.querySelector('.htmode');
307 this.setValues(bwdt, this.htmodes[mode.value]);
310 toggleWifiBand: function(elem) {
311 var mode = elem.querySelector('.mode');
312 var band = elem.querySelector('.band');
314 this.setValues(band, this.bands[mode.value]);
315 this.toggleWifiChannel(elem);
318 toggleWifiChannel: function(elem) {
319 var band = elem.querySelector('.band');
320 var chan = elem.querySelector('.channel');
322 this.setValues(chan, this.channels[band.value]);
325 setInitialValues: function(section_id, elem) {
326 var mode = elem.querySelector('.mode'),
327 band = elem.querySelector('.band'),
328 chan = elem.querySelector('.channel'),
329 bwdt = elem.querySelector('.htmode'),
330 htval = uci.get('wireless', section_id, 'htmode'),
331 hwval = uci.get('wireless', section_id, 'hwmode'),
332 chval = uci.get('wireless', section_id, 'channel');
334 this.setValues(mode, this.modes);
336 if (/VHT20|VHT40|VHT80|VHT160/.test(htval))
338 else if (/HT20|HT40/.test(htval))
343 this.toggleWifiMode(elem);
350 this.toggleWifiBand(elem);
358 renderWidget: function(section_id, option_index, cfgvalue) {
361 L.dom.content(elem, [
362 E('label', { 'style': 'float:left; margin-right:3px' }, [
366 'style': 'width:auto',
367 'change': L.bind(this.toggleWifiMode, this, elem)
370 E('label', { 'style': 'float:left; margin-right:3px' }, [
374 'style': 'width:auto',
375 'change': L.bind(this.toggleWifiBand, this, elem)
378 E('label', { 'style': 'float:left; margin-right:3px' }, [
379 _('Channel'), E('br'),
382 'style': 'width:auto'
385 E('label', { 'style': 'float:left; margin-right:3px' }, [
389 'style': 'width:auto'
392 E('br', { 'style': 'clear:left' })
395 return this.setInitialValues(section_id, elem);
398 cfgvalue: function(section_id) {
400 uci.get('wireless', section_id, 'htmode'),
401 uci.get('wireless', section_id, 'hwmode'),
402 uci.get('wireless', section_id, 'channel')
406 formvalue: function(section_id) {
407 var node = this.map.findElement('data-field', this.cbid(section_id));
410 node.querySelector('.htmode').value,
411 node.querySelector('.band').value,
412 node.querySelector('.channel').value
416 write: function(section_id, value) {
417 uci.set('wireless', section_id, 'htmode', value[0] || null);
418 uci.set('wireless', section_id, 'hwmode', value[1]);
419 uci.set('wireless', section_id, 'channel', value[2]);
423 var CBIWifiTxPowerValue = form.ListValue.extend({
424 callTxPowerList: rpc.declare({
426 method: 'txpowerlist',
427 params: [ 'device' ],
428 expect: { results: [] }
431 load: function(section_id) {
432 return this.callTxPowerList(section_id).then(L.bind(function(pwrlist) {
433 this.powerval = this.wifiNetwork ? this.wifiNetwork.getTXPower() : null;
434 this.poweroff = this.wifiNetwork ? this.wifiNetwork.getTXPowerOffset() : null;
436 this.value('', _('driver default'));
438 for (var i = 0; i < pwrlist.length; i++)
439 this.value(pwrlist[i].dbm, '%d dBm (%d mW)'.format(pwrlist[i].dbm, pwrlist[i].mw));
441 return form.ListValue.prototype.load.apply(this, [section_id]);
445 renderWidget: function(section_id, option_index, cfgvalue) {
446 var widget = form.ListValue.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
447 widget.firstElementChild.style.width = 'auto';
449 L.dom.append(widget, E('span', [
450 ' - ', _('Current power'), ': ',
451 E('span', [ this.powerval != null ? '%d dBm'.format(this.powerval) : E('em', _('unknown')) ]),
452 this.poweroff ? ' + %d dB offset = %s dBm'.format(this.poweroff, this.powerval != null ? this.powerval + this.poweroff : '?') : ''
459 var CBIWifiCountryValue = form.Value.extend({
460 callCountryList: rpc.declare({
462 method: 'countrylist',
463 params: [ 'device' ],
464 expect: { results: [] }
467 load: function(section_id) {
468 return this.callCountryList(section_id).then(L.bind(function(countrylist) {
469 if (Array.isArray(countrylist) && countrylist.length > 0) {
470 this.value('', _('driver default'));
472 for (var i = 0; i < countrylist.length; i++)
473 this.value(countrylist[i].iso3166, '%s - %s'.format(countrylist[i].iso3166, countrylist[i].country));
476 return form.Value.prototype.load.apply(this, [section_id]);
480 validate: function(section_id, formvalue) {
481 if (formvalue != null && formvalue != '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue))
482 return _('Use ISO/IEC 3166 alpha2 country codes.');
487 renderWidget: function(section_id, option_index, cfgvalue) {
488 var typeClass = (this.keylist && this.keylist.length) ? form.ListValue : form.Value;
489 return typeClass.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
493 return L.view.extend({
494 poll_status: function(map, data) {
495 var rows = map.querySelectorAll('.cbi-section-table-row[data-sid]');
497 for (var i = 0; i < rows.length; i++) {
498 var section_id = rows[i].getAttribute('data-sid'),
499 radioDev = data[1].filter(function(d) { return d.getName() == section_id })[0],
500 radioNet = data[2].filter(function(n) { return n.getName() == section_id })[0],
501 badge = rows[i].querySelector('[data-name="_badge"] > div'),
502 stat = rows[i].querySelector('[data-name="_stat"]'),
503 btns = rows[i].querySelectorAll('.cbi-section-actions button'),
504 busy = btns[0].classList.contains('spinning') || btns[1].classList.contains('spinning') || btns[2].classList.contains('spinning');
507 L.dom.content(badge, render_radio_badge(radioDev));
508 L.dom.content(stat, render_radio_status(radioDev, data[2].filter(function(n) { return n.getWifiDeviceName() == radioDev.getName() })));
511 L.dom.content(badge, render_network_badge(radioNet));
512 L.dom.content(stat, render_network_status(radioNet));
515 if (stat.hasAttribute('restart'))
516 L.dom.content(stat, E('em', _('Device is restarting…')));
518 btns[0].disabled = busy;
519 btns[1].disabled = busy;
520 btns[2].disabled = busy;
523 var table = document.querySelector('wifi_assoclist_table'),
527 for (var i = 0; i < data[3].length; i++) {
528 var bss = data[3][i],
529 name = hosts.getHostnameByMACAddr(bss.mac),
530 ipv4 = hosts.getIPAddrByMACAddr(bss.mac),
531 ipv6 = hosts.getIP6AddrByMACAddr(bss.mac);
534 E('span', { 'class': 'ifacebadge' }, [
536 'src': L.resource('icons/wifi%s.png').format(bss.network.isUp() ? '' : '_disabled'),
537 'title': bss.radio.getI18n()
539 ' %s '.format(bss.network.getShortName()),
540 E('small', '(%s)'.format(bss.network.getIfname()))
543 name ? '%s (%s)'.format(name, ipv4 || ipv6 || '?') : ipv4 || ipv6 || '?',
544 render_signal_badge(Math.min((bss.signal + 110) / 70 * 100, 100), bss.signal, bss.noise),
546 E('span', format_wifirate(bss.rx)),
548 E('span', format_wifirate(bss.tx))
553 cbi_update_table('#wifi_assoclist_table', trows, E('em', _('No information available')));
555 var stat = document.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large');
558 render_modal_status(stat, data[2].filter(function(n) { return n.getName() == stat.getAttribute('data-network') })[0]);
560 return network.flushCache();
570 checkAnonymousSections: function() {
571 var wifiIfaces = uci.sections('wireless', 'wifi-iface');
573 for (var i = 0; i < wifiIfaces.length; i++)
574 if (wifiIfaces[i]['.anonymous'])
580 callUciRename: rpc.declare({
583 params: [ 'config', 'section', 'name' ]
587 if (this.checkAnonymousSections())
588 return this.renderMigration();
590 return this.renderOverview();
593 handleMigration: function(ev) {
594 var wifiIfaces = uci.sections('wireless', 'wifi-iface'),
598 for (var i = 0; i < wifiIfaces.length; i++) {
599 if (!wifiIfaces[i]['.anonymous'])
602 var new_name = next_free_sid(id_offset);
604 tasks.push(this.callUciRename('wireless', wifiIfaces[i]['.name'], new_name));
605 id_offset = +new_name.substring(7) + 1;
608 return Promise.all(tasks)
609 .then(L.bind(ui.changes.init, ui.changes))
610 .then(L.bind(ui.changes.apply, ui.changes));
613 renderMigration: function() {
614 ui.showModal(_('Wireless configuration migration'), [
615 E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')),
616 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.')),
617 E('div', { 'class': 'right' },
619 'class': 'btn cbi-button-action important',
620 'click': ui.createHandlerFn(this, 'handleMigration')
625 renderOverview: function() {
628 m = new form.Map('wireless');
632 s = m.section(form.GridSection, 'wifi-device', _('Wireless Overview'));
636 s.load = function() {
637 return network.getWifiDevices().then(L.bind(function(radios) {
638 this.radios = radios.sort(function(a, b) {
639 return a.getName() > b.getName();
644 for (var i = 0; i < radios.length; i++)
645 tasks.push(radios[i].getWifiNetworks());
647 return Promise.all(tasks);
648 }, this)).then(L.bind(function(data) {
651 for (var i = 0; i < data.length; i++)
652 this.wifis.push.apply(this.wifis, data[i]);
656 s.cfgsections = function() {
659 for (var i = 0; i < this.radios.length; i++) {
660 rv.push(this.radios[i].getName());
662 for (var j = 0; j < this.wifis.length; j++)
663 if (this.wifis[j].getWifiDeviceName() == this.radios[i].getName())
664 rv.push(this.wifis[j].getName());
670 s.modaltitle = function(section_id) {
671 var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id})[0];
672 return radioNet ? radioNet.getI18n() : _('Edit wireless network');
675 s.lookupRadioOrNetwork = function(section_id) {
676 var radioDev = this.radios.filter(function(r) { return r.getName() == section_id })[0];
680 var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id })[0];
687 s.renderRowActions = function(section_id) {
688 var inst = this.lookupRadioOrNetwork(section_id), btns;
690 if (inst.getWifiNetworks) {
693 'class': 'cbi-button cbi-button-neutral',
694 'title': _('Restart radio interface'),
695 'click': ui.createHandlerFn(this, radio_restart, section_id)
698 'class': 'cbi-button cbi-button-action important',
699 'title': _('Find and join network'),
700 'click': ui.createHandlerFn(this, 'handleScan', inst)
703 'class': 'cbi-button cbi-button-add',
704 'title': _('Provide new network'),
705 'click': ui.createHandlerFn(this, 'handleAdd', inst)
710 var isDisabled = (inst.get('disabled') == '1' ||
711 uci.get('wireless', inst.getWifiDeviceName(), 'disabled') == '1');
715 'class': 'cbi-button cbi-button-neutral enable-disable',
716 'title': isDisabled ? _('Enable this network') : _('Disable this network'),
717 'click': ui.createHandlerFn(this, network_updown, section_id, this.map)
718 }, isDisabled ? _('Enable') : _('Disable')),
720 'class': 'cbi-button cbi-button-action important',
721 'title': _('Edit this network'),
722 'click': ui.createHandlerFn(this, 'renderMoreOptionsModal', section_id)
725 'class': 'cbi-button cbi-button-negative remove',
726 'title': _('Delete this network'),
727 'click': ui.createHandlerFn(this, 'handleRemove', section_id)
732 return E('div', { 'class': 'td middle cbi-section-actions' }, E('div', btns));
735 s.addModalOptions = function(s) {
736 return network.getWifiNetwork(s.section).then(function(radioNet) {
737 var hwtype = uci.get('wireless', radioNet.getWifiDeviceName(), 'type');
740 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
744 ss.tab('general', _('General Setup'));
745 ss.tab('advanced', _('Advanced Settings'));
747 var isDisabled = (radioNet.get('disabled') == '1' ||
748 uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == 1);
750 o = ss.taboption('general', form.DummyValue, '_wifistat_modal', _('Status'));
751 o.cfgvalue = L.bind(function(radioNet) {
752 return render_modal_status(null, radioNet);
754 o.write = function() {};
756 o = ss.taboption('general', form.Button, '_toggle', isDisabled ? _('Wireless network is disabled') : _('Wireless network is enabled'));
757 o.inputstyle = isDisabled ? 'apply' : 'reset';
758 o.inputtitle = isDisabled ? _('Enable') : _('Disable');
759 o.onclick = ui.createHandlerFn(s, network_updown, s.section, s.map);
761 o = ss.taboption('general', CBIWifiFrequencyValue, '_freq', '<br />' + _('Operating frequency'));
762 o.ucisection = s.section;
764 if (hwtype == 'mac80211') {
765 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.'));
766 o.wifiNetwork = radioNet;
768 o = ss.taboption('advanced', CBIWifiCountryValue, 'country', _('Country Code'));
769 o.wifiNetwork = radioNet;
771 o = ss.taboption('advanced', form.Flag, 'legacy_rates', _('Allow legacy 802.11b rates'));
772 o.default = o.enabled;
774 o = ss.taboption('advanced', form.Value, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
775 o.datatype = 'range(0,114750)';
776 o.placeholder = 'auto';
778 o = ss.taboption('advanced', form.Value, 'frag', _('Fragmentation Threshold'));
779 o.datatype = 'min(256)';
780 o.placeholder = _('off');
782 o = ss.taboption('advanced', form.Value, 'rts', _('RTS/CTS Threshold'));
783 o.datatype = 'uinteger';
784 o.placeholder = _('off');
786 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!'));
789 o = ss.taboption('advanced', form.Value, 'beacon_int', _('Beacon Interval'));
790 o.datatype = 'range(15,65535)';
796 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getName(), 'wifi-iface', _('Interface Configuration'));
800 ss.tab('general', _('General Setup'));
801 ss.tab('encryption', _('Wireless Security'));
802 ss.tab('macfilter', _('MAC-Filter'));
803 ss.tab('advanced', _('Advanced Settings'));
805 o = ss.taboption('general', form.ListValue, 'mode', _('Mode'));
806 o.value('ap', _('Access Point'));
807 o.value('sta', _('Client'));
808 o.value('adhoc', _('Ad-Hoc'));
810 o = ss.taboption('general', form.Value, 'mesh_id', _('Mesh Id'));
811 o.depends('mode', 'mesh');
813 o = ss.taboption('advanced', form.Flag, 'mesh_fwding', _('Forward mesh peer traffic'));
816 o.depends('mode', 'mesh');
818 o = ss.taboption('advanced', form.Value, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
821 o.datatype = 'range(-255,1)';
822 o.depends('mode', 'mesh');
824 o = ss.taboption('general', form.Value, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
825 o.datatype = 'maxlength(32)';
826 o.depends('mode', 'ap');
827 o.depends('mode', 'sta');
828 o.depends('mode', 'adhoc');
829 o.depends('mode', 'ahdemo');
830 o.depends('mode', 'monitor');
831 o.depends('mode', 'ap-wds');
832 o.depends('mode', 'sta-wds');
833 o.depends('mode', 'wds');
835 o = ss.taboption('general', form.Value, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
836 o.datatype = 'macaddr';
838 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.'));
842 o.write = function(section_id, value) {
843 return network.getDevice(section_id).then(L.bind(function(dev) {
844 var old_networks = dev.getNetworks().reduce(function(o, v) { o[v.getName()] = v; return o }, {}),
846 values = L.toArray(value),
849 for (var i = 0; i < values.length; i++) {
850 new_networks[values[i]] = true;
852 if (old_networks[values[i]])
855 tasks.push(network.getNetwork(values[i]).then(L.bind(function(name, net) {
856 return net || network.addNetwork(name, { proto: 'none' });
857 }, this, values[i])).then(L.bind(function(dev, net) {
860 net.set('type', 'bridge');
866 for (var name in old_networks)
867 if (!new_networks[name])
868 tasks.push(network.getNetwork(name).then(L.bind(function(dev, net) {
870 net.deleteDevice(dev);
873 return Promise.all(tasks);
877 if (hwtype == 'mac80211') {
878 var mode = ss.children[0],
879 bssid = ss.children[5],
882 mode.value('mesh', '802.11s');
883 mode.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
884 mode.value('monitor', _('Monitor'));
886 bssid.depends('mode', 'adhoc');
887 bssid.depends('mode', 'sta');
888 bssid.depends('mode', 'sta-wds');
890 o = ss.taboption('macfilter', form.ListValue, 'macfilter', _('MAC-Address Filter'));
891 o.depends('mode', 'ap');
892 o.depends('mode', 'ap-wds');
893 o.value('', _('disable'));
894 o.value('allow', _('Allow listed only'));
895 o.value('deny', _('Allow all except listed'));
897 o = ss.taboption('macfilter', form.DynamicList, 'maclist', _('MAC-List'));
898 o.datatype = 'macaddr';
899 o.depends('macfilter', 'allow');
900 o.depends('macfilter', 'deny');
901 o.load = function(section_id) {
902 return network.getHostHints().then(L.bind(function(hints) {
903 hints.getMACHints().map(L.bind(function(hint) {
904 this.value(hint[0], hint[1] ? '%s (%s)'.format(hint[0], hint[1]) : hint[0]);
907 return form.DynamicList.prototype.load.apply(this, [section_id]);
911 mode.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
912 mode.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
914 mode.write = function(section_id, value) {
917 uci.set('wireless', section_id, 'mode', 'ap');
918 uci.set('wireless', section_id, 'wds', '1');
922 uci.set('wireless', section_id, 'mode', 'sta');
923 uci.set('wireless', section_id, 'wds', '1');
927 uci.set('wireless', section_id, 'mode', value);
928 uci.unset('wireless', section_id, 'wds');
933 mode.cfgvalue = function(section_id) {
934 var mode = uci.get('wireless', section_id, 'mode'),
935 wds = uci.get('wireless', section_id, 'wds');
937 if (mode == 'ap' && wds)
939 else if (mode == 'sta' && wds)
945 o = ss.taboption('general', form.Flag, 'hidden', _('Hide <abbr title="Extended Service Set Identifier">ESSID</abbr>'));
946 o.depends('mode', 'ap');
947 o.depends('mode', 'ap-wds');
949 o = ss.taboption('general', form.Flag, 'wmm', _('WMM Mode'));
950 o.depends('mode', 'ap');
951 o.depends('mode', 'ap-wds');
952 o.default = o.enabled;
954 o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
955 o.depends('mode', 'ap');
956 o.depends('mode', 'ap-wds');
958 o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name'));
960 o.placeholder = radioNet.getIfname();
961 if (/^radio\d+\.network/.test(o.placeholder))
964 o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble'));
965 o.default = o.enabled;
967 o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
970 o.datatype = 'range(1,255)';
972 o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
975 o.datatype = 'uinteger';
977 o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling'));
979 o.datatype = 'uinteger';
981 o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('sec'));
984 o.datatype = 'uinteger';
986 o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval'));
988 o.placeholder = 65535;
989 o.datatype = 'uinteger';
991 o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
992 o.default = o.enabled;
996 encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption'));
997 o.depends('mode', 'ap');
998 o.depends('mode', 'sta');
999 o.depends('mode', 'adhoc');
1000 o.depends('mode', 'ahdemo');
1001 o.depends('mode', 'ap-wds');
1002 o.depends('mode', 'sta-wds');
1003 o.depends('mode', 'mesh');
1005 o.cfgvalue = function(section_id) {
1006 var v = String(uci.get('wireless', section_id, 'encryption'));
1009 else if (v.match(/\+/))
1010 return v.replace(/\+.+$/, '');
1014 o.write = function(section_id, value) {
1015 var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id),
1016 co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id);
1018 if (value == 'wpa' || value == 'wpa2')
1019 uci.unset('wireless', section_id, 'key');
1021 if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp'))
1024 uci.set('wireless', section_id, 'encryption', e);
1027 o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher'));
1028 o.depends('encryption', 'wpa');
1029 o.depends('encryption', 'wpa2');
1030 o.depends('encryption', 'psk');
1031 o.depends('encryption', 'psk2');
1032 o.depends('encryption', 'wpa-mixed');
1033 o.depends('encryption', 'psk-mixed');
1034 o.value('auto', _('auto'));
1035 o.value('ccmp', _('Force CCMP (AES)'));
1036 o.value('tkip', _('Force TKIP'));
1037 o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1038 o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write;
1040 o.cfgvalue = function(section_id) {
1041 var v = String(uci.get('wireless', section_id, 'encryption'));
1042 if (v.match(/\+/)) {
1043 v = v.replace(/^[^+]+\+/, '');
1046 else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip')
1053 var crypto_modes = [];
1055 if (hwtype == 'mac80211') {
1056 var has_supplicant = L.hasSystemFeature('wpasupplicant'),
1057 has_hostapd = L.hasSystemFeature('hostapd');
1059 // Probe EAP support
1060 var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'),
1061 has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap');
1063 // Probe SAE support
1064 var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'),
1065 has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae');
1067 // Probe OWE support
1068 var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
1069 has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
1072 if (has_hostapd || has_supplicant) {
1073 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1074 crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1075 crypto_modes.push(['psk', 'WPA-PSK', 21]);
1078 encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1081 if (has_ap_sae || has_sta_sae) {
1082 crypto_modes.push(['sae', 'WPA3-SAE', 31]);
1083 crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1086 if (has_ap_eap || has_sta_eap) {
1087 crypto_modes.push(['wpa2', 'WPA2-EAP', 32]);
1088 crypto_modes.push(['wpa', 'WPA-EAP', 20]);
1091 if (has_ap_owe || has_sta_owe) {
1092 crypto_modes.push(['owe', 'OWE', 1]);
1095 encr.crypto_support = {
1099 'psk': has_hostapd || _('Requires hostapd'),
1100 'psk2': has_hostapd || _('Requires hostapd'),
1101 'psk-mixed': has_hostapd || _('Requires hostapd'),
1102 'sae': has_ap_sae || _('Requires hostapd with SAE support'),
1103 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
1104 'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
1105 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
1106 'owe': has_ap_owe || _('Requires hostapd with OWE support')
1111 'psk': has_supplicant || _('Requires wpa-supplicant'),
1112 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1113 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1114 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1115 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1116 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1117 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1118 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
1123 'psk': has_supplicant || _('Requires wpa-supplicant'),
1124 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1125 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1128 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support')
1140 encr.crypto_support['ap-wds'] = encr.crypto_support['ap'];
1141 encr.crypto_support['sta-wds'] = encr.crypto_support['sta'];
1143 encr.validate = function(section_id, value) {
1144 var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
1145 modeval = modeopt.formvalue(section_id),
1146 modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)],
1147 enctitle = this.vallist[this.keylist.indexOf(value)];
1149 if (value == 'none')
1152 if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value))
1153 return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle);
1155 return this.crypto_support[modeval][value];
1158 else if (hwtype == 'broadcom') {
1159 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1160 crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1161 crypto_modes.push(['psk', 'WPA-PSK', 21]);
1164 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1165 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1166 crypto_modes.push(['none', _('No Encryption'), 0]);
1168 crypto_modes.sort(function(a, b) { return b[2] - a[2] });
1170 for (var i = 0; i < crypto_modes.length; i++) {
1171 var security_level = (crypto_modes[i][2] >= 30) ? _('strong security')
1172 : (crypto_modes[i][2] >= 20) ? _('medium security')
1173 : (crypto_modes[i][2] >= 10) ? _('weak security') : _('open network');
1175 encr.value(crypto_modes[i][0], '%s (%s)'.format(crypto_modes[i][1], security_level));
1179 o = ss.taboption('encryption', form.Value, 'auth_server', _('Radius-Authentication-Server'));
1180 o.depends({ mode: 'ap', encryption: 'wpa' });
1181 o.depends({ mode: 'ap', encryption: 'wpa2' });
1182 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1183 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1185 o.datatype = 'host(0)';
1187 o = ss.taboption('encryption', form.Value, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
1188 o.depends({ mode: 'ap', encryption: 'wpa' });
1189 o.depends({ mode: 'ap', encryption: 'wpa2' });
1190 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1191 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1193 o.datatype = 'port';
1195 o = ss.taboption('encryption', form.Value, 'auth_secret', _('Radius-Authentication-Secret'));
1196 o.depends({ mode: 'ap', encryption: 'wpa' });
1197 o.depends({ mode: 'ap', encryption: 'wpa2' });
1198 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1199 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1203 o = ss.taboption('encryption', form.Value, 'acct_server', _('Radius-Accounting-Server'));
1204 o.depends({ mode: 'ap', encryption: 'wpa' });
1205 o.depends({ mode: 'ap', encryption: 'wpa2' });
1206 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1207 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1209 o.datatype = 'host(0)';
1211 o = ss.taboption('encryption', form.Value, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
1212 o.depends({ mode: 'ap', encryption: 'wpa' });
1213 o.depends({ mode: 'ap', encryption: 'wpa2' });
1214 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1215 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1217 o.datatype = 'port';
1219 o = ss.taboption('encryption', form.Value, 'acct_secret', _('Radius-Accounting-Secret'));
1220 o.depends({ mode: 'ap', encryption: 'wpa' });
1221 o.depends({ mode: 'ap', encryption: 'wpa2' });
1222 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1223 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1227 o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client'));
1228 o.depends({ mode: 'ap', encryption: 'wpa' });
1229 o.depends({ mode: 'ap', encryption: 'wpa2' });
1230 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1231 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1233 o.datatype = 'host(0)';
1235 o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
1236 o.depends({ mode: 'ap', encryption: 'wpa' });
1237 o.depends({ mode: 'ap', encryption: 'wpa2' });
1238 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1239 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1241 o.datatype = 'port';
1243 o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret'));
1244 o.depends({ mode: 'ap', encryption: 'wpa' });
1245 o.depends({ mode: 'ap', encryption: 'wpa2' });
1246 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1247 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1252 o = ss.taboption('encryption', form.Value, '_wpa_key', _('Key'));
1253 o.depends('encryption', 'psk');
1254 o.depends('encryption', 'psk2');
1255 o.depends('encryption', 'psk+psk2');
1256 o.depends('encryption', 'psk-mixed');
1257 o.depends('encryption', 'sae');
1258 o.depends('encryption', 'sae-mixed');
1259 o.datatype = 'wpakey';
1263 o.cfgvalue = function(section_id) {
1264 var key = uci.get('wireless', section_id, 'key');
1265 return /^[1234]$/.test(key) ? null : key;
1268 o.write = function(section_id, value) {
1269 uci.set('wireless', section_id, 'key', value);
1270 uci.unset('wireless', section_id, 'key1');
1271 uci.unset('wireless', section_id, 'key2');
1272 uci.unset('wireless', section_id, 'key3');
1273 uci.unset('wireless', section_id, 'key4');
1277 o = ss.taboption('encryption', form.ListValue, '_wep_key', _('Used Key Slot'));
1278 o.depends('encryption', 'wep-open');
1279 o.depends('encryption', 'wep-shared');
1280 o.value('1', _('Key #%d').format(1));
1281 o.value('2', _('Key #%d').format(2));
1282 o.value('3', _('Key #%d').format(3));
1283 o.value('4', _('Key #%d').format(4));
1285 o.cfgvalue = function(section_id) {
1286 var slot = +uci.get('wireless', section_id, 'key');
1287 return (slot >= 1 && slot <= 4) ? String(slot) : '';
1290 o.write = function(section_id, value) {
1291 uci.set('wireless', section_id, 'key', value);
1294 for (var slot = 1; slot <= 4; slot++) {
1295 o = ss.taboption('encryption', form.Value, 'key%d'.format(slot), _('Key #%d').format(slot));
1296 o.depends('encryption', 'wep-open');
1297 o.depends('encryption', 'wep-shared');
1298 o.datatype = 'wepkey';
1302 o.write = function(section_id, value) {
1303 if (value != null && (value.length == 5 || value.length == 13))
1304 value = 's:%s'.format(value);
1305 uci.set('wireless', section_id, this.option, value);
1310 if (hwtype == 'mac80211') {
1311 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1312 var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap');
1314 o = ss.taboption('encryption', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1315 o.depends({ mode: 'ap', encryption: 'wpa' });
1316 o.depends({ mode: 'ap', encryption: 'wpa2' });
1317 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1318 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1320 o.depends({ mode: 'ap', encryption: 'psk' });
1321 o.depends({ mode: 'ap', encryption: 'psk2' });
1322 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1323 o.depends({ mode: 'ap', encryption: 'sae' });
1324 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1325 o.depends({ mode: 'ap-wds', encryption: 'psk' });
1326 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1327 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1328 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1329 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1333 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.'));
1334 o.depends({ mode: 'ap', encryption: 'wpa' });
1335 o.depends({ mode: 'ap', encryption: 'wpa2' });
1336 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1337 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1338 o.depends({ ieee80211r: '1' });
1341 o = ss.taboption('encryption', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1342 o.depends({ ieee80211r: '1' });
1343 o.placeholder = '4f57';
1344 o.datatype = 'and(hexstring,length(4))';
1347 o = ss.taboption('encryption', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1348 o.depends({ ieee80211r: '1' });
1349 o.placeholder = '1000';
1350 o.datatype = 'range(1000,65535)';
1353 o = ss.taboption('encryption', form.ListValue, 'ft_over_ds', _('FT protocol'));
1354 o.depends({ ieee80211r: '1' });
1355 o.value('1', _('FT over DS'));
1356 o.value('0', _('FT over the Air'));
1359 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.'));
1360 o.depends({ ieee80211r: '1' });
1361 o.default = o.enabled;
1364 o = ss.taboption('encryption', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1365 o.depends({ ieee80211r: '1' });
1366 o.placeholder = '10000';
1367 o.datatype = 'uinteger';
1370 o = ss.taboption('encryption', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1371 o.depends({ ieee80211r: '1' });
1372 o.placeholder = '00004f577274';
1373 o.datatype = 'and(hexstring,length(12))';
1376 o = ss.taboption('encryption', form.Flag, 'pmk_r1_push', _('PMK R1 Push'));
1377 o.depends({ ieee80211r: '1' });
1378 o.placeholder = '0';
1381 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.'));
1382 o.depends({ ieee80211r: '1' });
1385 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.'));
1386 o.depends({ ieee80211r: '1' });
1388 // End of 802.11r options
1390 o = ss.taboption('encryption', form.ListValue, 'eap_type', _('EAP-Method'));
1391 o.value('tls', 'TLS');
1392 o.value('ttls', 'TTLS');
1393 o.value('peap', 'PEAP');
1394 o.value('fast', 'FAST');
1395 o.depends({ mode: 'sta', encryption: 'wpa' });
1396 o.depends({ mode: 'sta', encryption: 'wpa2' });
1397 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1398 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1400 o = ss.taboption('encryption', form.FileUpload, 'ca_cert', _('Path to CA-Certificate'));
1401 o.depends({ mode: 'sta', encryption: 'wpa' });
1402 o.depends({ mode: 'sta', encryption: 'wpa2' });
1403 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1404 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1406 o = ss.taboption('encryption', form.FileUpload, 'client_cert', _('Path to Client-Certificate'));
1407 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1408 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1409 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1410 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1412 o = ss.taboption('encryption', form.FileUpload, 'priv_key', _('Path to Private Key'));
1413 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1414 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1415 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1416 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1418 o = ss.taboption('encryption', form.Value, 'priv_key_pwd', _('Password of Private Key'));
1419 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1420 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1421 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1422 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1425 o = ss.taboption('encryption', form.ListValue, 'auth', _('Authentication'));
1426 o.value('PAP', 'PAP');
1427 o.value('CHAP', 'CHAP');
1428 o.value('MSCHAP', 'MSCHAP');
1429 o.value('MSCHAPV2', 'MSCHAPv2');
1432 o.value('EAP-MSCHAPV2');
1434 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1435 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1436 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1437 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1438 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1439 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1440 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1441 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1442 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1443 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1444 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1445 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1447 o.validate = function(section_id, value) {
1448 var eo = this.section.children.filter(function(o) { return o.option == 'eap_type' })[0],
1449 ev = eo.formvalue(section_id);
1451 if (ev != 'ttls' && (value == 'PAP' || value == 'CHAP' || value == 'MSCHAP' || value == 'MSCHAPV2'))
1452 return _('This authentication type is not applicable to the selected EAP method.');
1457 o = ss.taboption('encryption', form.FileUpload, 'ca_cert2', _('Path to inner CA-Certificate'));
1458 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1459 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1460 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1461 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1463 o = ss.taboption('encryption', form.FileUpload, 'client_cert2', _('Path to inner Client-Certificate'));
1464 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1465 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1466 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1467 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1469 o = ss.taboption('encryption', form.FileUpload, 'priv_key2', _('Path to inner Private Key'));
1470 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1471 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1472 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1473 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1475 o = ss.taboption('encryption', form.Value, 'priv_key2_pwd', _('Password of inner Private Key'));
1476 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1477 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1478 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1479 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1482 o = ss.taboption('encryption', form.Value, 'identity', _('Identity'));
1483 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1484 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1485 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1486 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1487 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1488 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1489 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1490 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1491 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1492 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1493 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1494 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1495 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1496 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1497 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1498 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1500 o = ss.taboption('encryption', form.Value, 'anonymous_identity', _('Anonymous Identity'));
1501 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1502 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1503 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1504 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1505 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1506 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1507 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1508 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1509 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1510 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1511 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1512 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1513 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1514 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1515 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1516 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1518 o = ss.taboption('encryption', form.Value, 'password', _('Password'));
1519 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1520 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1521 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1522 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1523 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1524 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1525 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1526 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1527 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1528 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1529 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1530 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1534 if (hwtype == 'mac80211') {
1535 // ieee802.11w options
1536 if (L.hasSystemFeature('hostapd', '11w')) {
1537 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)"));
1538 o.value('', _('Disabled'));
1539 o.value('1', _('Optional'));
1540 o.value('2', _('Required'));
1541 o.depends({ mode: 'ap', encryption: 'wpa2' });
1542 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1543 o.depends({ mode: 'ap', encryption: 'psk2' });
1544 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1545 o.depends({ mode: 'ap', encryption: 'sae' });
1546 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1547 o.depends({ mode: 'ap', encryption: 'owe' });
1548 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1549 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1550 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1551 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1552 o.depends({ mode: 'ap-wds', encryption: 'owe' });
1553 o.depends({ mode: 'sta', encryption: 'wpa2' });
1554 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1555 o.depends({ mode: 'sta', encryption: 'psk2' });
1556 o.depends({ mode: 'sta', encryption: 'psk-mixed' });
1557 o.depends({ mode: 'sta', encryption: 'sae' });
1558 o.depends({ mode: 'sta', encryption: 'sae-mixed' });
1559 o.depends({ mode: 'sta', encryption: 'owe' });
1560 o.depends({ mode: 'sta-wds', encryption: 'psk2' });
1561 o.depends({ mode: 'sta-wds', encryption: 'psk-mixed' });
1562 o.depends({ mode: 'sta-wds', encryption: 'sae' });
1563 o.depends({ mode: 'sta-wds', encryption: 'sae-mixed' });
1564 o.depends({ mode: 'sta-wds', encryption: 'owe' });
1566 '2': [{ encryption: 'sae' }, { encryption: 'owe' }],
1567 '1': [{ encryption: 'sae-mixed'}],
1571 o = ss.taboption('encryption', form.Value, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1572 o.depends('ieee80211w', '1');
1573 o.depends('ieee80211w', '2');
1574 o.datatype = 'uinteger';
1575 o.placeholder = '1000';
1578 o = ss.taboption('encryption', form.Value, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1579 o.depends('ieee80211w', '1');
1580 o.depends('ieee80211w', '2');
1581 o.datatype = 'uinteger';
1582 o.placeholder = '201';
1586 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.'));
1587 o.depends({ mode: 'ap', encryption: 'wpa2' });
1588 o.depends({ mode: 'ap', encryption: 'psk2' });
1589 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1590 o.depends({ mode: 'ap', encryption: 'sae' });
1591 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1592 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1593 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1594 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1595 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1596 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1598 if (L.hasSystemFeature('hostapd', 'cli') && L.hasSystemFeature('wpasupplicant')) {
1599 o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1602 o.default = o.disabled;
1603 o.depends('encryption', 'psk');
1604 o.depends('encryption', 'psk2');
1605 o.depends('encryption', 'psk-mixed');
1606 o.depends('encryption', 'sae');
1607 o.depends('encryption', 'sae-mixed');
1614 s.handleRemove = function(section_id, ev) {
1615 document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id)).style.opacity = 0.5;
1616 return form.TypedSection.prototype.handleRemove.apply(this, [section_id, ev]);
1619 s.handleScan = function(radioDev, ev) {
1620 var table = E('div', { 'class': 'table' }, [
1621 E('div', { 'class': 'tr table-titles' }, [
1622 E('div', { 'class': 'th col-2 middle center' }, _('Signal')),
1623 E('div', { 'class': 'th col-4 middle left' }, _('SSID')),
1624 E('div', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1625 E('div', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1626 E('div', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1627 E('div', { 'class': 'th col-3 middle left' }, _('Encryption')),
1628 E('div', { 'class': 'th cbi-section-actions right' }, ' '),
1632 cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1634 var md = ui.showModal(_('Join Network: Wireless Scan'), [
1636 E('div', { 'class': 'right' },
1639 'click': L.bind(this.handleScanAbort, this)
1643 md.style.maxWidth = '90%';
1644 md.style.maxHeight = 'none';
1646 this.pollFn = L.bind(this.handleScanRefresh, this, radioDev, {}, table);
1648 L.Poll.add(this.pollFn);
1652 s.handleScanRefresh = function(radioDev, scanCache, table) {
1653 return radioDev.getScanList().then(L.bind(function(results) {
1656 for (var i = 0; i < results.length; i++)
1657 scanCache[results[i].bssid] = results[i];
1659 for (var k in scanCache)
1660 if (scanCache[k].stale)
1661 results.push(scanCache[k]);
1663 results.sort(function(a, b) {
1664 var diff = (b.quality - a.quality) || (a.channel - b.channel);
1669 if (a.ssid < b.ssid)
1671 else if (a.ssid > b.ssid)
1674 if (a.bssid < b.bssid)
1676 else if (a.bssid > b.bssid)
1680 for (var i = 0; i < results.length; i++) {
1681 var res = results[i],
1682 qv = res.quality || 0,
1683 qm = res.quality_max || 0,
1684 q = (qv > 0 && qm > 0) ? Math.floor((100 / qm) * qv) : 0,
1685 s = res.stale ? 'opacity:0.5' : '';
1688 E('span', { 'style': s }, render_signal_badge(q, res.signal, res.noise)),
1689 E('span', { 'style': s }, '%h'.format(res.ssid)),
1690 E('span', { 'style': s }, '%d'.format(res.channel)),
1691 E('span', { 'style': s }, '%h'.format(res.mode)),
1692 E('span', { 'style': s }, '%h'.format(res.bssid)),
1693 E('span', { 'style': s }, '%h'.format(network.formatWifiEncryption(res.encryption))),
1694 E('div', { 'class': 'right' }, E('button', {
1695 'class': 'cbi-button cbi-button-action important',
1696 'click': L.bind(this.handleJoin, this, radioDev, res)
1697 }, _('Join Network')))
1703 cbi_update_table(table, rows);
1707 s.handleScanAbort = function(ev) {
1708 var md = L.dom.parent(ev.target, 'div[aria-modal="true"]');
1710 md.style.maxWidth = '';
1711 md.style.maxHeight = '';
1715 L.Poll.remove(this.pollFn);
1720 s.handleJoinConfirm = function(radioDev, bss, form, ev) {
1721 var nameopt = L.toArray(form.lookupOption('name', '_new_'))[0],
1722 passopt = L.toArray(form.lookupOption('password', '_new_'))[0],
1723 zoneopt = L.toArray(form.lookupOption('zone', '_new_'))[0],
1724 replopt = L.toArray(form.lookupOption('replace', '_new_'))[0],
1725 nameval = (nameopt && nameopt.isValid('_new_')) ? nameopt.formvalue('_new_') : null,
1726 passval = (passopt && passopt.isValid('_new_')) ? passopt.formvalue('_new_') : null,
1727 zoneval = zoneopt ? zoneopt.formvalue('_new_') : null,
1728 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1729 is_wep = (enc && Array.isArray(enc.wep)),
1730 is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' })),
1731 is_sae = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'sae' }));
1733 if (nameval == null || (passopt && passval == null))
1736 var section_id = null;
1738 return this.map.save(function() {
1739 var wifi_sections = uci.sections('wireless', 'wifi-iface');
1741 if (replopt.formvalue('_new_') == '1') {
1742 for (var i = 0; i < wifi_sections.length; i++)
1743 if (wifi_sections[i].device == radioDev.getName())
1744 uci.remove('wireless', wifi_sections[i]['.name']);
1747 if (uci.get('wireless', radioDev.getName(), 'disabled') == '1') {
1748 for (var i = 0; i < wifi_sections.length; i++)
1749 if (wifi_sections[i].device == radioDev.getName())
1750 uci.set('wireless', wifi_sections[i]['.name'], 'disabled', '1');
1752 uci.unset('wireless', radioDev.getName(), 'disabled');
1755 section_id = next_free_sid(wifi_sections.length);
1757 uci.add('wireless', 'wifi-iface', section_id);
1758 uci.set('wireless', section_id, 'device', radioDev.getName());
1759 uci.set('wireless', section_id, 'mode', (bss.mode == 'Ad-Hoc') ? 'adhoc' : 'sta');
1760 uci.set('wireless', section_id, 'network', nameval);
1762 if (bss.ssid != null)
1763 uci.set('wireless', section_id, 'ssid', bss.ssid);
1764 else if (bss.bssid != null)
1765 uci.set('wireless', section_id, 'bssid', bss.bssid);
1768 uci.set('wireless', section_id, 'encryption', 'sae');
1769 uci.set('wireless', section_id, 'key', passval);
1772 for (var i = enc.wpa.length - 1; i >= 0; i--) {
1773 if (enc.wpa[i] == 2) {
1774 uci.set('wireless', section_id, 'encryption', 'psk2');
1777 else if (enc.wpa[i] == 1) {
1778 uci.set('wireless', section_id, 'encryption', 'psk');
1783 uci.set('wireless', section_id, 'key', passval);
1786 uci.set('wireless', section_id, 'encryption', 'wep-open');
1787 uci.set('wireless', section_id, 'key', '1');
1788 uci.set('wireless', section_id, 'key1', passval);
1791 return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) {
1792 firewall.deleteNetwork(net.getName());
1794 var zonePromise = zoneval
1795 ? firewall.getZone(zoneval).then(function(zone) { return zone || firewall.addZone(zoneval) })
1796 : Promise.resolve();
1798 return zonePromise.then(function(zone) {
1800 zone.addNetwork(net.getName());
1803 }).then(L.bind(function() {
1804 return this.renderMoreOptionsModal(section_id);
1808 s.handleJoin = function(radioDev, bss, ev) {
1809 this.handleScanAbort(ev);
1811 var m2 = new form.Map('wireless'),
1812 s2 = m2.section(form.NamedSection, '_new_'),
1813 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1814 is_wep = (enc && Array.isArray(enc.wep)),
1815 is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' || a == 'sae' })),
1816 replace, passphrase, name, zone;
1818 s2.render = function() {
1819 return Promise.all([
1821 this.renderUCISection('_new_')
1822 ]).then(this.renderContents.bind(this));
1825 replace = s2.option(form.Flag, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
1827 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>'));
1828 name.datatype = 'uciname';
1829 name.default = 'wwan';
1830 name.rmempty = false;
1831 name.validate = function(section_id, value) {
1832 if (uci.get('network', value))
1833 return _('The network name is already used');
1838 for (var i = 2; uci.get('network', name.default); i++)
1839 name.default = 'wwan%d'.format(i);
1841 if (is_wep || is_psk) {
1842 passphrase = s2.option(form.Value, 'password', is_wep ? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
1843 passphrase.datatype = is_wep ? 'wepkey' : 'wpakey';
1844 passphrase.password = true;
1845 passphrase.rmempty = false;
1848 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.'));
1849 zone.default = 'wan';
1851 return m2.render().then(L.bind(function(nodes) {
1852 ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [
1854 E('div', { 'class': 'right' }, [
1857 'click': ui.hideModal
1858 }, _('Cancel')), ' ',
1860 'class': 'cbi-button cbi-button-positive important',
1861 'click': ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2)
1864 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase || name).cbid('_new_'))).focus();
1868 s.handleAdd = function(radioDev, ev) {
1869 var section_id = next_free_sid(uci.sections('wireless', 'wifi-iface').length);
1871 uci.unset('wireless', radioDev.getName(), 'disabled');
1873 uci.add('wireless', 'wifi-iface', section_id);
1874 uci.set('wireless', section_id, 'device', radioDev.getName());
1875 uci.set('wireless', section_id, 'mode', 'ap');
1876 uci.set('wireless', section_id, 'ssid', 'OpenWrt');
1877 uci.set('wireless', section_id, 'encryption', 'none');
1879 this.addedSection = section_id;
1880 return this.renderMoreOptionsModal(section_id);
1883 o = s.option(form.DummyValue, '_badge');
1884 o.modalonly = false;
1885 o.textvalue = function(section_id) {
1886 var inst = this.section.lookupRadioOrNetwork(section_id),
1887 node = E('div', { 'class': 'center' });
1889 if (inst.getWifiNetworks)
1890 node.appendChild(render_radio_badge(inst));
1892 node.appendChild(render_network_badge(inst));
1897 o = s.option(form.DummyValue, '_stat');
1898 o.modalonly = false;
1899 o.textvalue = function(section_id) {
1900 var inst = this.section.lookupRadioOrNetwork(section_id);
1902 if (inst.getWifiNetworks)
1903 return render_radio_status(inst, this.section.wifis.filter(function(e) {
1904 return (e.getWifiDeviceName() == inst.getName());
1907 return render_network_status(inst);
1910 return m.render().then(L.bind(function(m, nodes) {
1911 L.Poll.add(L.bind(function() {
1912 var section_ids = m.children[0].cfgsections(),
1913 tasks = [ network.getHostHints(), network.getWifiDevices() ];
1915 for (var i = 0; i < section_ids.length; i++) {
1916 var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])),
1917 dsc = row.querySelector('[data-name="_stat"] > div'),
1918 btns = row.querySelectorAll('.cbi-section-actions button');
1920 if (dsc.getAttribute('restart') == '') {
1921 dsc.setAttribute('restart', '1');
1922 tasks.push(fs.exec('/sbin/wifi', ['up', section_ids[i]]).catch(function(e) {
1923 ui.addNotification(null, E('p', e.message));
1926 else if (dsc.getAttribute('restart') == '1') {
1927 dsc.removeAttribute('restart');
1928 btns[0].classList.remove('spinning');
1929 btns[0].disabled = false;
1933 return Promise.all(tasks)
1934 .then(L.bind(function(hosts_radios) {
1937 for (var i = 0; i < hosts_radios[1].length; i++)
1938 tasks.push(hosts_radios[1][i].getWifiNetworks());
1940 return Promise.all(tasks).then(function(data) {
1941 hosts_radios[2] = [];
1943 for (var i = 0; i < data.length; i++)
1944 hosts_radios[2].push.apply(hosts_radios[2], data[i]);
1946 return hosts_radios;
1949 .then(L.bind(function(hosts_radios_wifis) {
1952 for (var i = 0; i < hosts_radios_wifis[2].length; i++)
1953 tasks.push(hosts_radios_wifis[2][i].getAssocList());
1955 return Promise.all(tasks).then(function(data) {
1956 hosts_radios_wifis[3] = [];
1958 for (var i = 0; i < data.length; i++) {
1959 var wifiNetwork = hosts_radios_wifis[2][i],
1960 radioDev = hosts_radios_wifis[1].filter(function(d) { return d.getName() == wifiNetwork.getWifiDeviceName() })[0];
1962 for (var j = 0; j < data[i].length; j++)
1963 hosts_radios_wifis[3].push(Object.assign({ radio: radioDev, network: wifiNetwork }, data[i][j]));
1966 return hosts_radios_wifis;
1969 .then(L.bind(this.poll_status, this, nodes));
1972 var table = E('div', { 'class': 'table', 'id': 'wifi_assoclist_table' }, [
1973 E('div', { 'class': 'tr table-titles' }, [
1974 E('div', { 'class': 'th nowrap' }, _('Network')),
1975 E('div', { 'class': 'th hide-xs' }, _('MAC-Address')),
1976 E('div', { 'class': 'th nowrap' }, _('Host')),
1977 E('div', { 'class': 'th nowrap' }, _('Signal / Noise')),
1978 E('div', { 'class': 'th nowrap' }, _('RX Rate / TX Rate'))
1982 cbi_update_table(table, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
1984 return E([ nodes, E('h3', _('Associated Stations')), table ]);