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) {
33 var icon, title, value;
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 && noiseValue != null && noiseValue != 0) {
49 value = '%d / %d %s'.format(signalValue, noiseValue, _('dBm'));
50 title = '%s: %d %s / %s: %d %s / %s %d'.format(
51 _('Signal'), signalValue, _('dBm'),
52 _('Noise'), noiseValue, _('dBm'),
53 _('SNR'), signalValue - noiseValue);
55 else if (signalValue != null && signalValue != 0) {
56 value = '%d %s'.format(signalValue, _('dBm'));
57 title = '%s: %d %s'.format(_('Signal'), signalValue, _('dBm'));
59 else if (signalPercent > -1) {
60 value = '\xa0---\xa0';
61 title = _('No signal');
64 value = E('em', {}, E('small', {}, [ _('disabled') ]));
65 title = _('Interface is disabled');
68 return E('div', { 'class': wrap ? 'center' : 'ifacebadge', 'title': title },
69 [ E('img', { 'src': icon }), wrap ? E('br') : ' ', value ]);
72 function render_network_badge(radioNet) {
73 return render_signal_badge(radioNet.isUp() ? radioNet.getSignalPercent() : -1, radioNet.getSignal(), radioNet.getNoise());
76 function render_radio_status(radioDev, wifiNets) {
77 var name = radioDev.getI18n().replace(/ Wireless Controller .+$/, ''),
78 node = E('div', [ E('big', {}, E('strong', {}, name)), E('div') ]),
79 channel, frequency, bitrate;
81 for (var i = 0; i < wifiNets.length; i++) {
82 channel = channel || wifiNets[i].getChannel();
83 frequency = frequency || wifiNets[i].getFrequency();
84 bitrate = bitrate || wifiNets[i].getBitRate();
88 L.itemlist(node.lastElementChild, [
89 _('Channel'), '%s (%s %s)'.format(channel || '?', frequency || '?', _('GHz')),
90 _('Bitrate'), '%s %s'.format(bitrate || '?', _('Mbit/s'))
93 node.lastElementChild.appendChild(E('em', _('Device is not active')));
98 function render_network_status(radioNet) {
99 var mode = radioNet.getActiveMode(),
100 bssid = radioNet.getActiveBSSID(),
101 channel = radioNet.getChannel(),
102 disabled = (radioNet.get('disabled') == '1' || uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == '1'),
103 is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled),
104 is_mesh = (radioNet.getMode() == 'mesh'),
105 changecount = count_changes(radioNet.getName()),
109 status_text = E('a', {
111 click: L.bind(ui.changes.displayChanges, ui.changes)
112 }, _('Interface has %d pending changes').format(changecount));
114 status_text = E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated'));
116 return L.itemlist(E('div'), [
117 is_mesh ? _('Mesh ID') : _('SSID'), (is_mesh ? radioNet.getMeshID() : radioNet.getSSID()) || '?',
119 _('BSSID'), (!changecount && is_assoc) ? bssid : null,
120 _('Encryption'), (!changecount && is_assoc) ? radioNet.getActiveEncryption() || _('None') : null,
122 ], [ ' | ', E('br') ]);
125 function render_modal_status(node, radioNet) {
126 var mode = radioNet.getActiveMode(),
127 noise = radioNet.getNoise(),
128 bssid = radioNet.getActiveBSSID(),
129 channel = radioNet.getChannel(),
130 disabled = (radioNet.get('disabled') == '1'),
131 is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled);
134 node = E('span', { 'class': 'ifacebadge large', 'data-network': radioNet.getName() }, [ E('small'), E('span') ]);
136 L.dom.content(node.firstElementChild, render_signal_badge(disabled ? -1 : radioNet.getSignalPercent(), radioNet.getSignal(), noise, true));
138 L.itemlist(node.lastElementChild, [
140 _('SSID'), radioNet.getSSID() || '?',
141 _('BSSID'), is_assoc ? bssid : null,
142 _('Encryption'), is_assoc ? radioNet.getActiveEncryption() || _('None') : null,
143 _('Channel'), is_assoc ? '%d (%.3f %s)'.format(radioNet.getChannel(), radioNet.getFrequency() || 0, _('GHz')) : null,
144 _('Tx-Power'), is_assoc ? '%d %s'.format(radioNet.getTXPower(), _('dBm')) : null,
145 _('Signal'), is_assoc ? '%d %s'.format(radioNet.getSignal(), _('dBm')) : null,
146 _('Noise'), (is_assoc && noise != null) ? '%d %s'.format(noise, _('dBm')) : null,
147 _('Bitrate'), is_assoc ? '%.1f %s'.format(radioNet.getBitRate() || 0, _('Mbit/s')) : null,
148 _('Country'), is_assoc ? radioNet.getCountryCode() : null
149 ], [ ' | ', E('br'), E('br'), E('br'), E('br'), E('br'), ' | ', E('br'), ' | ' ]);
152 L.dom.append(node.lastElementChild, E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated')));
157 function format_wifirate(rate) {
158 var s = '%.1f Mbit/s, %dMHz'.format(rate.rate / 1000, rate.mhz);
160 if (rate.ht || rate.vht) {
161 if (rate.vht) s += ', VHT-MCS %d'.format(rate.mcs);
162 if (rate.nss) s += ', VHT-NSS %d'.format(rate.nss);
163 if (rate.ht) s += ', MCS %s'.format(rate.mcs);
164 if (rate.short_gi) s += ', Short GI';
170 function radio_restart(id, ev) {
171 var row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id)),
172 dsc = row.querySelector('[data-name="_stat"] > div'),
173 btn = row.querySelector('.cbi-section-actions button');
176 btn.classList.add('spinning');
179 dsc.setAttribute('restart', '');
180 L.dom.content(dsc, E('em', _('Device is restarting…')));
183 function network_updown(id, map, ev) {
184 var radio = uci.get('wireless', id, 'device'),
185 disabled = (uci.get('wireless', id, 'disabled') == '1') ||
186 (uci.get('wireless', radio, 'disabled') == '1');
189 uci.unset('wireless', id, 'disabled');
190 uci.unset('wireless', radio, 'disabled');
193 uci.set('wireless', id, 'disabled', '1');
195 var all_networks_disabled = true,
196 wifi_ifaces = uci.sections('wireless', 'wifi-iface');
198 for (var i = 0; i < wifi_ifaces.length; i++) {
199 if (wifi_ifaces[i].device == radio && wifi_ifaces[i].disabled != '1') {
200 all_networks_disabled = false;
205 if (all_networks_disabled)
206 uci.set('wireless', radio, 'disabled', '1');
209 return map.save().then(function() {
214 function next_free_sid(offset) {
215 var sid = 'wifinet' + offset;
217 while (uci.get('wireless', sid))
218 sid = 'wifinet' + (++offset);
223 var CBIWifiFrequencyValue = form.Value.extend({
224 callFrequencyList: rpc.declare({
227 params: [ 'device' ],
228 expect: { results: [] }
231 load: function(section_id) {
233 network.getWifiDevice(section_id),
234 this.callFrequencyList(section_id)
235 ]).then(L.bind(function(data) {
237 '11g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
238 '11a': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : []
241 for (var i = 0; i < data[1].length; i++)
242 this.channels[(data[1][i].mhz > 2484) ? '11a' : '11g'].push(
244 '%d (%d Mhz)'.format(data[1][i].channel, data[1][i].mhz),
245 !data[1][i].restricted
248 var hwmodelist = L.toArray(data[0] ? data[0].getHWModes() : null)
249 .reduce(function(o, v) { o[v] = true; return o }, {});
253 'n', 'N', hwmodelist.n,
254 'ac', 'AC', hwmodelist.ac
257 var htmodelist = L.toArray(data[0] ? data[0].getHTModes() : null)
258 .reduce(function(o, v) { o[v] = true; return o }, {});
261 '': [ '', '-', true ],
263 'HT20', '20 MHz', htmodelist.HT20,
264 'HT40', '40 MHz', htmodelist.HT40
267 'VHT20', '20 MHz', htmodelist.VHT20,
268 'VHT40', '40 MHz', htmodelist.VHT40,
269 'VHT80', '80 MHz', htmodelist.VHT80,
270 'VHT160', '160 MHz', htmodelist.VHT160
276 '11g', '2.4 GHz', this.channels['11g'].length > 3,
277 '11a', '5 GHz', this.channels['11a'].length > 3
280 '11g', '2.4 GHz', this.channels['11g'].length > 3,
281 '11a', '5 GHz', this.channels['11a'].length > 3
290 setValues: function(sel, vals) {
292 sel.vals.selected = sel.selectedIndex;
294 while (sel.options[0])
297 for (var i = 0; vals && i < vals.length; i += 3)
299 sel.add(E('option', { value: vals[i+0] }, [ vals[i+1] ]));
301 if (vals && !isNaN(vals.selected))
302 sel.selectedIndex = vals.selected;
304 sel.parentNode.style.display = (sel.options.length <= 1) ? 'none' : '';
308 toggleWifiMode: function(elem) {
309 this.toggleWifiHTMode(elem);
310 this.toggleWifiBand(elem);
313 toggleWifiHTMode: function(elem) {
314 var mode = elem.querySelector('.mode');
315 var bwdt = elem.querySelector('.htmode');
317 this.setValues(bwdt, this.htmodes[mode.value]);
320 toggleWifiBand: function(elem) {
321 var mode = elem.querySelector('.mode');
322 var band = elem.querySelector('.band');
324 this.setValues(band, this.bands[mode.value]);
325 this.toggleWifiChannel(elem);
328 toggleWifiChannel: function(elem) {
329 var band = elem.querySelector('.band');
330 var chan = elem.querySelector('.channel');
332 this.setValues(chan, this.channels[band.value]);
335 setInitialValues: function(section_id, elem) {
336 var mode = elem.querySelector('.mode'),
337 band = elem.querySelector('.band'),
338 chan = elem.querySelector('.channel'),
339 bwdt = elem.querySelector('.htmode'),
340 htval = uci.get('wireless', section_id, 'htmode'),
341 hwval = uci.get('wireless', section_id, 'hwmode'),
342 chval = uci.get('wireless', section_id, 'channel');
344 this.setValues(mode, this.modes);
346 if (/VHT20|VHT40|VHT80|VHT160/.test(htval))
348 else if (/HT20|HT40/.test(htval))
353 this.toggleWifiMode(elem);
360 this.toggleWifiBand(elem);
368 renderWidget: function(section_id, option_index, cfgvalue) {
371 L.dom.content(elem, [
372 E('label', { 'style': 'float:left; margin-right:3px' }, [
376 'style': 'width:auto',
377 'change': L.bind(this.toggleWifiMode, this, elem)
380 E('label', { 'style': 'float:left; margin-right:3px' }, [
384 'style': 'width:auto',
385 'change': L.bind(this.toggleWifiBand, this, elem)
388 E('label', { 'style': 'float:left; margin-right:3px' }, [
389 _('Channel'), E('br'),
392 'style': 'width:auto'
395 E('label', { 'style': 'float:left; margin-right:3px' }, [
399 'style': 'width:auto'
402 E('br', { 'style': 'clear:left' })
405 return this.setInitialValues(section_id, elem);
408 cfgvalue: function(section_id) {
410 uci.get('wireless', section_id, 'htmode'),
411 uci.get('wireless', section_id, 'hwmode'),
412 uci.get('wireless', section_id, 'channel')
416 formvalue: function(section_id) {
417 var node = this.map.findElement('data-field', this.cbid(section_id));
420 node.querySelector('.htmode').value,
421 node.querySelector('.band').value,
422 node.querySelector('.channel').value
426 write: function(section_id, value) {
427 uci.set('wireless', section_id, 'htmode', value[0] || null);
428 uci.set('wireless', section_id, 'hwmode', value[1]);
429 uci.set('wireless', section_id, 'channel', value[2]);
433 var CBIWifiTxPowerValue = form.ListValue.extend({
434 callTxPowerList: rpc.declare({
436 method: 'txpowerlist',
437 params: [ 'device' ],
438 expect: { results: [] }
441 load: function(section_id) {
442 return this.callTxPowerList(section_id).then(L.bind(function(pwrlist) {
443 this.powerval = this.wifiNetwork ? this.wifiNetwork.getTXPower() : null;
444 this.poweroff = this.wifiNetwork ? this.wifiNetwork.getTXPowerOffset() : null;
446 this.value('', _('driver default'));
448 for (var i = 0; i < pwrlist.length; i++)
449 this.value(pwrlist[i].dbm, '%d dBm (%d mW)'.format(pwrlist[i].dbm, pwrlist[i].mw));
451 return form.ListValue.prototype.load.apply(this, [section_id]);
455 renderWidget: function(section_id, option_index, cfgvalue) {
456 var widget = form.ListValue.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
457 widget.firstElementChild.style.width = 'auto';
459 L.dom.append(widget, E('span', [
460 ' - ', _('Current power'), ': ',
461 E('span', [ this.powerval != null ? '%d dBm'.format(this.powerval) : E('em', _('unknown')) ]),
462 this.poweroff ? ' + %d dB offset = %s dBm'.format(this.poweroff, this.powerval != null ? this.powerval + this.poweroff : '?') : ''
469 var CBIWifiCountryValue = form.Value.extend({
470 callCountryList: rpc.declare({
472 method: 'countrylist',
473 params: [ 'device' ],
474 expect: { results: [] }
477 load: function(section_id) {
478 return this.callCountryList(section_id).then(L.bind(function(countrylist) {
479 if (Array.isArray(countrylist) && countrylist.length > 0) {
480 this.value('', _('driver default'));
482 for (var i = 0; i < countrylist.length; i++)
483 this.value(countrylist[i].iso3166, '%s - %s'.format(countrylist[i].iso3166, countrylist[i].country));
486 return form.Value.prototype.load.apply(this, [section_id]);
490 validate: function(section_id, formvalue) {
491 if (formvalue != null && formvalue != '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue))
492 return _('Use ISO/IEC 3166 alpha2 country codes.');
497 renderWidget: function(section_id, option_index, cfgvalue) {
498 var typeClass = (this.keylist && this.keylist.length) ? form.ListValue : form.Value;
499 return typeClass.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
503 return L.view.extend({
504 poll_status: function(map, data) {
505 var rows = map.querySelectorAll('.cbi-section-table-row[data-sid]');
507 for (var i = 0; i < rows.length; i++) {
508 var section_id = rows[i].getAttribute('data-sid'),
509 radioDev = data[1].filter(function(d) { return d.getName() == section_id })[0],
510 radioNet = data[2].filter(function(n) { return n.getName() == section_id })[0],
511 badge = rows[i].querySelector('[data-name="_badge"] > div'),
512 stat = rows[i].querySelector('[data-name="_stat"]'),
513 btns = rows[i].querySelectorAll('.cbi-section-actions button'),
514 busy = btns[0].classList.contains('spinning') || btns[1].classList.contains('spinning') || btns[2].classList.contains('spinning');
517 L.dom.content(badge, render_radio_badge(radioDev));
518 L.dom.content(stat, render_radio_status(radioDev, data[2].filter(function(n) { return n.getWifiDeviceName() == radioDev.getName() })));
521 L.dom.content(badge, render_network_badge(radioNet));
522 L.dom.content(stat, render_network_status(radioNet));
525 if (stat.hasAttribute('restart'))
526 L.dom.content(stat, E('em', _('Device is restarting…')));
528 btns[0].disabled = busy;
529 btns[1].disabled = busy;
530 btns[2].disabled = busy;
533 var table = document.querySelector('#wifi_assoclist_table'),
537 for (var i = 0; i < data[3].length; i++) {
538 var bss = data[3][i],
539 name = hosts.getHostnameByMACAddr(bss.mac),
540 ipv4 = hosts.getIPAddrByMACAddr(bss.mac),
541 ipv6 = hosts.getIP6AddrByMACAddr(bss.mac);
545 if (name && ipv4 && ipv6)
546 hint = '%s (%s, %s)'.format(name, ipv4, ipv6);
547 else if (name && (ipv4 || ipv6))
548 hint = '%s (%s)'.format(name, ipv4 || ipv6);
550 hint = name || ipv4 || ipv6 || '?';
553 E('span', { 'class': 'ifacebadge' }, [
555 'src': L.resource('icons/wifi%s.png').format(bss.network.isUp() ? '' : '_disabled'),
556 'title': bss.radio.getI18n()
558 ' %s '.format(bss.network.getShortName()),
559 E('small', '(%s)'.format(bss.network.getIfname()))
563 render_signal_badge(Math.min((bss.signal + 110) / 70 * 100, 100), bss.signal, bss.noise),
565 E('span', format_wifirate(bss.rx)),
567 E('span', format_wifirate(bss.tx))
571 if (bss.network.isClientDisconnectSupported()) {
572 if (table.firstElementChild.childNodes.length < 6)
573 table.firstElementChild.appendChild(E('div', { 'class': 'th nowrap right'}, [ _('Disconnect') ]));
575 row.push(E('button', {
576 'class': 'cbi-button cbi-button-remove',
577 'click': L.bind(function(net, mac, ev) {
578 L.dom.parent(ev.currentTarget, '.tr').style.opacity = 0.5;
579 ev.currentTarget.classList.add('spinning');
580 ev.currentTarget.disabled = true;
581 ev.currentTarget.blur();
583 net.disconnectClient(mac, true, 5, 60000);
584 }, this, bss.network, bss.mac)
585 }, [ _('Disconnect') ]));
594 cbi_update_table(table, trows, E('em', _('No information available')));
596 var stat = document.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large');
599 render_modal_status(stat, data[2].filter(function(n) { return n.getName() == stat.getAttribute('data-network') })[0]);
601 return network.flushCache();
611 checkAnonymousSections: function() {
612 var wifiIfaces = uci.sections('wireless', 'wifi-iface');
614 for (var i = 0; i < wifiIfaces.length; i++)
615 if (wifiIfaces[i]['.anonymous'])
621 callUciRename: rpc.declare({
624 params: [ 'config', 'section', 'name' ]
628 if (this.checkAnonymousSections())
629 return this.renderMigration();
631 return this.renderOverview();
634 handleMigration: function(ev) {
635 var wifiIfaces = uci.sections('wireless', 'wifi-iface'),
639 for (var i = 0; i < wifiIfaces.length; i++) {
640 if (!wifiIfaces[i]['.anonymous'])
643 var new_name = next_free_sid(id_offset);
645 tasks.push(this.callUciRename('wireless', wifiIfaces[i]['.name'], new_name));
646 id_offset = +new_name.substring(7) + 1;
649 return Promise.all(tasks)
650 .then(L.bind(ui.changes.init, ui.changes))
651 .then(L.bind(ui.changes.apply, ui.changes));
654 renderMigration: function() {
655 ui.showModal(_('Wireless configuration migration'), [
656 E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')),
657 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.')),
658 E('div', { 'class': 'right' },
660 'class': 'btn cbi-button-action important',
661 'click': ui.createHandlerFn(this, 'handleMigration')
666 renderOverview: function() {
669 m = new form.Map('wireless');
673 s = m.section(form.GridSection, 'wifi-device', _('Wireless Overview'));
677 s.load = function() {
678 return network.getWifiDevices().then(L.bind(function(radios) {
679 this.radios = radios.sort(function(a, b) {
680 return a.getName() > b.getName();
685 for (var i = 0; i < radios.length; i++)
686 tasks.push(radios[i].getWifiNetworks());
688 return Promise.all(tasks);
689 }, this)).then(L.bind(function(data) {
692 for (var i = 0; i < data.length; i++)
693 this.wifis.push.apply(this.wifis, data[i]);
697 s.cfgsections = function() {
700 for (var i = 0; i < this.radios.length; i++) {
701 rv.push(this.radios[i].getName());
703 for (var j = 0; j < this.wifis.length; j++)
704 if (this.wifis[j].getWifiDeviceName() == this.radios[i].getName())
705 rv.push(this.wifis[j].getName());
711 s.modaltitle = function(section_id) {
712 var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id})[0];
713 return radioNet ? radioNet.getI18n() : _('Edit wireless network');
716 s.lookupRadioOrNetwork = function(section_id) {
717 var radioDev = this.radios.filter(function(r) { return r.getName() == section_id })[0];
721 var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id })[0];
728 s.renderRowActions = function(section_id) {
729 var inst = this.lookupRadioOrNetwork(section_id), btns;
731 if (inst.getWifiNetworks) {
734 'class': 'cbi-button cbi-button-neutral',
735 'title': _('Restart radio interface'),
736 'click': ui.createHandlerFn(this, radio_restart, section_id)
739 'class': 'cbi-button cbi-button-action important',
740 'title': _('Find and join network'),
741 'click': ui.createHandlerFn(this, 'handleScan', inst)
744 'class': 'cbi-button cbi-button-add',
745 'title': _('Provide new network'),
746 'click': ui.createHandlerFn(this, 'handleAdd', inst)
751 var isDisabled = (inst.get('disabled') == '1' ||
752 uci.get('wireless', inst.getWifiDeviceName(), 'disabled') == '1');
756 'class': 'cbi-button cbi-button-neutral enable-disable',
757 'title': isDisabled ? _('Enable this network') : _('Disable this network'),
758 'click': ui.createHandlerFn(this, network_updown, section_id, this.map)
759 }, isDisabled ? _('Enable') : _('Disable')),
761 'class': 'cbi-button cbi-button-action important',
762 'title': _('Edit this network'),
763 'click': ui.createHandlerFn(this, 'renderMoreOptionsModal', section_id)
766 'class': 'cbi-button cbi-button-negative remove',
767 'title': _('Delete this network'),
768 'click': ui.createHandlerFn(this, 'handleRemove', section_id)
773 return E('div', { 'class': 'td middle cbi-section-actions' }, E('div', btns));
776 s.addModalOptions = function(s) {
777 return network.getWifiNetwork(s.section).then(function(radioNet) {
778 var hwtype = uci.get('wireless', radioNet.getWifiDeviceName(), 'type');
781 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
785 ss.tab('general', _('General Setup'));
786 ss.tab('advanced', _('Advanced Settings'));
788 var isDisabled = (radioNet.get('disabled') == '1' ||
789 uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == 1);
791 o = ss.taboption('general', form.DummyValue, '_wifistat_modal', _('Status'));
792 o.cfgvalue = L.bind(function(radioNet) {
793 return render_modal_status(null, radioNet);
795 o.write = function() {};
797 o = ss.taboption('general', form.Button, '_toggle', isDisabled ? _('Wireless network is disabled') : _('Wireless network is enabled'));
798 o.inputstyle = isDisabled ? 'apply' : 'reset';
799 o.inputtitle = isDisabled ? _('Enable') : _('Disable');
800 o.onclick = ui.createHandlerFn(s, network_updown, s.section, s.map);
802 o = ss.taboption('general', CBIWifiFrequencyValue, '_freq', '<br />' + _('Operating frequency'));
803 o.ucisection = s.section;
805 if (hwtype == 'mac80211') {
806 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.'));
807 o.wifiNetwork = radioNet;
809 o = ss.taboption('advanced', CBIWifiCountryValue, 'country', _('Country Code'));
810 o.wifiNetwork = radioNet;
812 o = ss.taboption('advanced', form.Flag, 'legacy_rates', _('Allow legacy 802.11b rates'));
813 o.default = o.enabled;
815 o = ss.taboption('advanced', form.Value, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
816 o.datatype = 'range(0,114750)';
817 o.placeholder = 'auto';
819 o = ss.taboption('advanced', form.Value, 'frag', _('Fragmentation Threshold'));
820 o.datatype = 'min(256)';
821 o.placeholder = _('off');
823 o = ss.taboption('advanced', form.Value, 'rts', _('RTS/CTS Threshold'));
824 o.datatype = 'uinteger';
825 o.placeholder = _('off');
827 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!'));
830 o = ss.taboption('advanced', form.Value, 'beacon_int', _('Beacon Interval'));
831 o.datatype = 'range(15,65535)';
837 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getName(), 'wifi-iface', _('Interface Configuration'));
841 ss.tab('general', _('General Setup'));
842 ss.tab('encryption', _('Wireless Security'));
843 ss.tab('macfilter', _('MAC-Filter'));
844 ss.tab('advanced', _('Advanced Settings'));
846 o = ss.taboption('general', form.ListValue, 'mode', _('Mode'));
847 o.value('ap', _('Access Point'));
848 o.value('sta', _('Client'));
849 o.value('adhoc', _('Ad-Hoc'));
851 o = ss.taboption('general', form.Value, 'mesh_id', _('Mesh Id'));
852 o.depends('mode', 'mesh');
854 o = ss.taboption('advanced', form.Flag, 'mesh_fwding', _('Forward mesh peer traffic'));
857 o.depends('mode', 'mesh');
859 o = ss.taboption('advanced', form.Value, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
862 o.datatype = 'range(-255,1)';
863 o.depends('mode', 'mesh');
865 o = ss.taboption('general', form.Value, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
866 o.datatype = 'maxlength(32)';
867 o.depends('mode', 'ap');
868 o.depends('mode', 'sta');
869 o.depends('mode', 'adhoc');
870 o.depends('mode', 'ahdemo');
871 o.depends('mode', 'monitor');
872 o.depends('mode', 'ap-wds');
873 o.depends('mode', 'sta-wds');
874 o.depends('mode', 'wds');
876 o = ss.taboption('general', form.Value, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
877 o.datatype = 'macaddr';
879 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.'));
883 o.write = function(section_id, value) {
884 return network.getDevice(section_id).then(L.bind(function(dev) {
885 var old_networks = dev.getNetworks().reduce(function(o, v) { o[v.getName()] = v; return o }, {}),
887 values = L.toArray(value),
890 for (var i = 0; i < values.length; i++) {
891 new_networks[values[i]] = true;
893 if (old_networks[values[i]])
896 tasks.push(network.getNetwork(values[i]).then(L.bind(function(name, net) {
897 return net || network.addNetwork(name, { proto: 'none' });
898 }, this, values[i])).then(L.bind(function(dev, net) {
901 net.set('type', 'bridge');
907 for (var name in old_networks)
908 if (!new_networks[name])
909 tasks.push(network.getNetwork(name).then(L.bind(function(dev, net) {
911 net.deleteDevice(dev);
914 return Promise.all(tasks);
918 if (hwtype == 'mac80211') {
919 var mode = ss.children[0],
920 bssid = ss.children[5],
923 mode.value('mesh', '802.11s');
924 mode.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
925 mode.value('monitor', _('Monitor'));
927 bssid.depends('mode', 'adhoc');
928 bssid.depends('mode', 'sta');
929 bssid.depends('mode', 'sta-wds');
931 o = ss.taboption('macfilter', form.ListValue, 'macfilter', _('MAC-Address Filter'));
932 o.depends('mode', 'ap');
933 o.depends('mode', 'ap-wds');
934 o.value('', _('disable'));
935 o.value('allow', _('Allow listed only'));
936 o.value('deny', _('Allow all except listed'));
938 o = ss.taboption('macfilter', form.DynamicList, 'maclist', _('MAC-List'));
939 o.datatype = 'macaddr';
940 o.depends('macfilter', 'allow');
941 o.depends('macfilter', 'deny');
942 o.load = function(section_id) {
943 return network.getHostHints().then(L.bind(function(hints) {
944 hints.getMACHints().map(L.bind(function(hint) {
945 this.value(hint[0], hint[1] ? '%s (%s)'.format(hint[0], hint[1]) : hint[0]);
948 return form.DynamicList.prototype.load.apply(this, [section_id]);
952 mode.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
953 mode.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
955 mode.write = function(section_id, value) {
958 uci.set('wireless', section_id, 'mode', 'ap');
959 uci.set('wireless', section_id, 'wds', '1');
963 uci.set('wireless', section_id, 'mode', 'sta');
964 uci.set('wireless', section_id, 'wds', '1');
968 uci.set('wireless', section_id, 'mode', value);
969 uci.unset('wireless', section_id, 'wds');
974 mode.cfgvalue = function(section_id) {
975 var mode = uci.get('wireless', section_id, 'mode'),
976 wds = uci.get('wireless', section_id, 'wds');
978 if (mode == 'ap' && wds)
980 else if (mode == 'sta' && wds)
986 o = ss.taboption('general', form.Flag, 'hidden', _('Hide <abbr title="Extended Service Set Identifier">ESSID</abbr>'));
987 o.depends('mode', 'ap');
988 o.depends('mode', 'ap-wds');
990 o = ss.taboption('general', form.Flag, 'wmm', _('WMM Mode'));
991 o.depends('mode', 'ap');
992 o.depends('mode', 'ap-wds');
993 o.default = o.enabled;
995 o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
996 o.depends('mode', 'ap');
997 o.depends('mode', 'ap-wds');
999 o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name'));
1001 o.placeholder = radioNet.getIfname();
1002 if (/^radio\d+\.network/.test(o.placeholder))
1005 o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble'));
1006 o.default = o.enabled;
1008 o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
1011 o.datatype = 'range(1,255)';
1013 o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
1015 o.placeholder = 600;
1016 o.datatype = 'uinteger';
1018 o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling'));
1020 o.datatype = 'uinteger';
1022 o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('sec'));
1024 o.placeholder = 300;
1025 o.datatype = 'uinteger';
1027 o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval'));
1029 o.placeholder = 65535;
1030 o.datatype = 'uinteger';
1032 o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
1033 o.default = o.enabled;
1037 encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption'));
1038 o.depends('mode', 'ap');
1039 o.depends('mode', 'sta');
1040 o.depends('mode', 'adhoc');
1041 o.depends('mode', 'ahdemo');
1042 o.depends('mode', 'ap-wds');
1043 o.depends('mode', 'sta-wds');
1044 o.depends('mode', 'mesh');
1046 o.cfgvalue = function(section_id) {
1047 var v = String(uci.get('wireless', section_id, 'encryption'));
1050 else if (v.match(/\+/))
1051 return v.replace(/\+.+$/, '');
1055 o.write = function(section_id, value) {
1056 var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id),
1057 co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id);
1059 if (value == 'wpa' || value == 'wpa2')
1060 uci.unset('wireless', section_id, 'key');
1062 if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp'))
1065 uci.set('wireless', section_id, 'encryption', e);
1068 o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher'));
1069 o.depends('encryption', 'wpa');
1070 o.depends('encryption', 'wpa2');
1071 o.depends('encryption', 'psk');
1072 o.depends('encryption', 'psk2');
1073 o.depends('encryption', 'wpa-mixed');
1074 o.depends('encryption', 'psk-mixed');
1075 o.value('auto', _('auto'));
1076 o.value('ccmp', _('Force CCMP (AES)'));
1077 o.value('tkip', _('Force TKIP'));
1078 o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1079 o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write;
1081 o.cfgvalue = function(section_id) {
1082 var v = String(uci.get('wireless', section_id, 'encryption'));
1083 if (v.match(/\+/)) {
1084 v = v.replace(/^[^+]+\+/, '');
1087 else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip')
1094 var crypto_modes = [];
1096 if (hwtype == 'mac80211') {
1097 var has_supplicant = L.hasSystemFeature('wpasupplicant'),
1098 has_hostapd = L.hasSystemFeature('hostapd');
1100 // Probe EAP support
1101 var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'),
1102 has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap');
1104 // Probe SAE support
1105 var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'),
1106 has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae');
1108 // Probe OWE support
1109 var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
1110 has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
1113 if (has_hostapd || has_supplicant) {
1114 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1115 crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1116 crypto_modes.push(['psk', 'WPA-PSK', 21]);
1119 encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1122 if (has_ap_sae || has_sta_sae) {
1123 crypto_modes.push(['sae', 'WPA3-SAE', 31]);
1124 crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1127 if (has_ap_eap || has_sta_eap) {
1128 crypto_modes.push(['wpa2', 'WPA2-EAP', 32]);
1129 crypto_modes.push(['wpa', 'WPA-EAP', 20]);
1132 if (has_ap_owe || has_sta_owe) {
1133 crypto_modes.push(['owe', 'OWE', 1]);
1136 encr.crypto_support = {
1140 'psk': has_hostapd || _('Requires hostapd'),
1141 'psk2': has_hostapd || _('Requires hostapd'),
1142 'psk-mixed': has_hostapd || _('Requires hostapd'),
1143 'sae': has_ap_sae || _('Requires hostapd with SAE support'),
1144 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
1145 'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
1146 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
1147 'owe': has_ap_owe || _('Requires hostapd with OWE support')
1152 'psk': has_supplicant || _('Requires wpa-supplicant'),
1153 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1154 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1155 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1156 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1157 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1158 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1159 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
1164 'psk': has_supplicant || _('Requires wpa-supplicant'),
1165 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1166 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1169 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support')
1181 encr.crypto_support['ap-wds'] = encr.crypto_support['ap'];
1182 encr.crypto_support['sta-wds'] = encr.crypto_support['sta'];
1184 encr.validate = function(section_id, value) {
1185 var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
1186 modeval = modeopt.formvalue(section_id),
1187 modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)],
1188 enctitle = this.vallist[this.keylist.indexOf(value)];
1190 if (value == 'none')
1193 if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value))
1194 return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle);
1196 return this.crypto_support[modeval][value];
1199 else if (hwtype == 'broadcom') {
1200 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1201 crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1202 crypto_modes.push(['psk', 'WPA-PSK', 21]);
1205 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1206 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1207 crypto_modes.push(['none', _('No Encryption'), 0]);
1209 crypto_modes.sort(function(a, b) { return b[2] - a[2] });
1211 for (var i = 0; i < crypto_modes.length; i++) {
1212 var security_level = (crypto_modes[i][2] >= 30) ? _('strong security')
1213 : (crypto_modes[i][2] >= 20) ? _('medium security')
1214 : (crypto_modes[i][2] >= 10) ? _('weak security') : _('open network');
1216 encr.value(crypto_modes[i][0], '%s (%s)'.format(crypto_modes[i][1], security_level));
1220 o = ss.taboption('encryption', form.Value, 'auth_server', _('Radius-Authentication-Server'));
1221 o.depends({ mode: 'ap', encryption: 'wpa' });
1222 o.depends({ mode: 'ap', encryption: 'wpa2' });
1223 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1224 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1226 o.datatype = 'host(0)';
1228 o = ss.taboption('encryption', form.Value, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
1229 o.depends({ mode: 'ap', encryption: 'wpa' });
1230 o.depends({ mode: 'ap', encryption: 'wpa2' });
1231 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1232 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1234 o.datatype = 'port';
1236 o = ss.taboption('encryption', form.Value, 'auth_secret', _('Radius-Authentication-Secret'));
1237 o.depends({ mode: 'ap', encryption: 'wpa' });
1238 o.depends({ mode: 'ap', encryption: 'wpa2' });
1239 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1240 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1244 o = ss.taboption('encryption', form.Value, 'acct_server', _('Radius-Accounting-Server'));
1245 o.depends({ mode: 'ap', encryption: 'wpa' });
1246 o.depends({ mode: 'ap', encryption: 'wpa2' });
1247 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1248 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1250 o.datatype = 'host(0)';
1252 o = ss.taboption('encryption', form.Value, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
1253 o.depends({ mode: 'ap', encryption: 'wpa' });
1254 o.depends({ mode: 'ap', encryption: 'wpa2' });
1255 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1256 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1258 o.datatype = 'port';
1260 o = ss.taboption('encryption', form.Value, 'acct_secret', _('Radius-Accounting-Secret'));
1261 o.depends({ mode: 'ap', encryption: 'wpa' });
1262 o.depends({ mode: 'ap', encryption: 'wpa2' });
1263 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1264 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1268 o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client'));
1269 o.depends({ mode: 'ap', encryption: 'wpa' });
1270 o.depends({ mode: 'ap', encryption: 'wpa2' });
1271 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1272 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1274 o.datatype = 'host(0)';
1276 o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
1277 o.depends({ mode: 'ap', encryption: 'wpa' });
1278 o.depends({ mode: 'ap', encryption: 'wpa2' });
1279 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1280 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1282 o.datatype = 'port';
1284 o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret'));
1285 o.depends({ mode: 'ap', encryption: 'wpa' });
1286 o.depends({ mode: 'ap', encryption: 'wpa2' });
1287 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1288 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1293 o = ss.taboption('encryption', form.Value, '_wpa_key', _('Key'));
1294 o.depends('encryption', 'psk');
1295 o.depends('encryption', 'psk2');
1296 o.depends('encryption', 'psk+psk2');
1297 o.depends('encryption', 'psk-mixed');
1298 o.depends('encryption', 'sae');
1299 o.depends('encryption', 'sae-mixed');
1300 o.datatype = 'wpakey';
1304 o.cfgvalue = function(section_id) {
1305 var key = uci.get('wireless', section_id, 'key');
1306 return /^[1234]$/.test(key) ? null : key;
1309 o.write = function(section_id, value) {
1310 uci.set('wireless', section_id, 'key', value);
1311 uci.unset('wireless', section_id, 'key1');
1312 uci.unset('wireless', section_id, 'key2');
1313 uci.unset('wireless', section_id, 'key3');
1314 uci.unset('wireless', section_id, 'key4');
1318 o = ss.taboption('encryption', form.ListValue, '_wep_key', _('Used Key Slot'));
1319 o.depends('encryption', 'wep-open');
1320 o.depends('encryption', 'wep-shared');
1321 o.value('1', _('Key #%d').format(1));
1322 o.value('2', _('Key #%d').format(2));
1323 o.value('3', _('Key #%d').format(3));
1324 o.value('4', _('Key #%d').format(4));
1326 o.cfgvalue = function(section_id) {
1327 var slot = +uci.get('wireless', section_id, 'key');
1328 return (slot >= 1 && slot <= 4) ? String(slot) : '';
1331 o.write = function(section_id, value) {
1332 uci.set('wireless', section_id, 'key', value);
1335 for (var slot = 1; slot <= 4; slot++) {
1336 o = ss.taboption('encryption', form.Value, 'key%d'.format(slot), _('Key #%d').format(slot));
1337 o.depends('encryption', 'wep-open');
1338 o.depends('encryption', 'wep-shared');
1339 o.datatype = 'wepkey';
1343 o.write = function(section_id, value) {
1344 if (value != null && (value.length == 5 || value.length == 13))
1345 value = 's:%s'.format(value);
1346 uci.set('wireless', section_id, this.option, value);
1351 if (hwtype == 'mac80211') {
1352 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1353 var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap');
1355 o = ss.taboption('encryption', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1356 o.depends({ mode: 'ap', encryption: 'wpa' });
1357 o.depends({ mode: 'ap', encryption: 'wpa2' });
1358 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1359 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1361 o.depends({ mode: 'ap', encryption: 'psk' });
1362 o.depends({ mode: 'ap', encryption: 'psk2' });
1363 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1364 o.depends({ mode: 'ap', encryption: 'sae' });
1365 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1366 o.depends({ mode: 'ap-wds', encryption: 'psk' });
1367 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1368 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1369 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1370 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1374 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.'));
1375 o.depends({ mode: 'ap', encryption: 'wpa' });
1376 o.depends({ mode: 'ap', encryption: 'wpa2' });
1377 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1378 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1379 o.depends({ ieee80211r: '1' });
1382 o = ss.taboption('encryption', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1383 o.depends({ ieee80211r: '1' });
1384 o.placeholder = '4f57';
1385 o.datatype = 'and(hexstring,length(4))';
1388 o = ss.taboption('encryption', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1389 o.depends({ ieee80211r: '1' });
1390 o.placeholder = '1000';
1391 o.datatype = 'range(1000,65535)';
1394 o = ss.taboption('encryption', form.ListValue, 'ft_over_ds', _('FT protocol'));
1395 o.depends({ ieee80211r: '1' });
1396 o.value('1', _('FT over DS'));
1397 o.value('0', _('FT over the Air'));
1400 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.'));
1401 o.depends({ ieee80211r: '1' });
1402 o.default = o.enabled;
1405 o = ss.taboption('encryption', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1406 o.depends({ ieee80211r: '1' });
1407 o.placeholder = '10000';
1408 o.datatype = 'uinteger';
1411 o = ss.taboption('encryption', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1412 o.depends({ ieee80211r: '1' });
1413 o.placeholder = '00004f577274';
1414 o.datatype = 'and(hexstring,length(12))';
1417 o = ss.taboption('encryption', form.Flag, 'pmk_r1_push', _('PMK R1 Push'));
1418 o.depends({ ieee80211r: '1' });
1419 o.placeholder = '0';
1422 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.'));
1423 o.depends({ ieee80211r: '1' });
1426 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.'));
1427 o.depends({ ieee80211r: '1' });
1429 // End of 802.11r options
1431 o = ss.taboption('encryption', form.ListValue, 'eap_type', _('EAP-Method'));
1432 o.value('tls', 'TLS');
1433 o.value('ttls', 'TTLS');
1434 o.value('peap', 'PEAP');
1435 o.value('fast', 'FAST');
1436 o.depends({ mode: 'sta', encryption: 'wpa' });
1437 o.depends({ mode: 'sta', encryption: 'wpa2' });
1438 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1439 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1441 o = ss.taboption('encryption', form.Flag, 'ca_cert_usesystem', _('Use system certificates'), _("Validate server certificate using built-in system CA bundle,<br />requires the \"ca-bundle\" package"));
1444 o.default = o.disabled;
1445 o.depends({ mode: 'sta', encryption: 'wpa' });
1446 o.depends({ mode: 'sta', encryption: 'wpa2' });
1447 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1448 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1449 o.validate = function(section_id, value) {
1450 if (value == '1' && !L.hasSystemFeature('cabundle')) {
1451 return _("This option cannot be used because the ca-bundle package is not installed.");
1456 o = ss.taboption('encryption', form.FileUpload, 'ca_cert', _('Path to CA-Certificate'));
1457 o.depends({ mode: 'sta', encryption: 'wpa', ca_cert_usesystem: '0' });
1458 o.depends({ mode: 'sta', encryption: 'wpa2', ca_cert_usesystem: '0' });
1459 o.depends({ mode: 'sta-wds', encryption: 'wpa', ca_cert_usesystem: '0' });
1460 o.depends({ mode: 'sta-wds', encryption: 'wpa2', ca_cert_usesystem: '0' });
1462 o = ss.taboption('encryption', form.Value, 'subject_match', _('Certificate constraint (Subject)'), _("Certificate constraint substring - e.g. /CN=wifi.mycompany.com<br />See `logread -f` during handshake for actual values"));
1463 o.depends({ mode: 'sta', encryption: 'wpa' });
1464 o.depends({ mode: 'sta', encryption: 'wpa2' });
1465 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1466 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1468 o = ss.taboption('encryption', form.DynamicList, 'altsubject_match', _('Certificate constraint (SAN)'), _("Certificate constraint(s) via Subject Alternate Name values<br />(supported attributes: EMAIL, DNS, URI) - e.g. DNS:wifi.mycompany.com"));
1469 o.depends({ mode: 'sta', encryption: 'wpa' });
1470 o.depends({ mode: 'sta', encryption: 'wpa2' });
1471 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1472 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1474 o = ss.taboption('encryption', form.DynamicList, 'domain_match', _('Certificate constraint (Domain)'), _("Certificate constraint(s) against DNS SAN values (if available)<br />or Subject CN (exact match)"));
1475 o.depends({ mode: 'sta', encryption: 'wpa' });
1476 o.depends({ mode: 'sta', encryption: 'wpa2' });
1477 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1478 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1480 o = ss.taboption('encryption', form.DynamicList, 'domain_suffix_match', _('Certificate constraint (Wildcard)'), _("Certificate constraint(s) against DNS SAN values (if available)<br />or Subject CN (suffix match)"));
1481 o.depends({ mode: 'sta', encryption: 'wpa' });
1482 o.depends({ mode: 'sta', encryption: 'wpa2' });
1483 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1484 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1486 o = ss.taboption('encryption', form.FileUpload, 'client_cert', _('Path to Client-Certificate'));
1487 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1488 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1489 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1490 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1492 o = ss.taboption('encryption', form.FileUpload, 'priv_key', _('Path to Private Key'));
1493 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1494 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1495 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1496 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1498 o = ss.taboption('encryption', form.Value, 'priv_key_pwd', _('Password of Private Key'));
1499 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1500 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1501 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1502 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1505 o = ss.taboption('encryption', form.ListValue, 'auth', _('Authentication'));
1506 o.value('PAP', 'PAP');
1507 o.value('CHAP', 'CHAP');
1508 o.value('MSCHAP', 'MSCHAP');
1509 o.value('MSCHAPV2', 'MSCHAPv2');
1512 o.value('EAP-MSCHAPV2');
1514 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1515 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1516 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1517 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1518 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1519 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1520 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1521 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1522 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1523 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1524 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1525 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1527 o.validate = function(section_id, value) {
1528 var eo = this.section.children.filter(function(o) { return o.option == 'eap_type' })[0],
1529 ev = eo.formvalue(section_id);
1531 if (ev != 'ttls' && (value == 'PAP' || value == 'CHAP' || value == 'MSCHAP' || value == 'MSCHAPV2'))
1532 return _('This authentication type is not applicable to the selected EAP method.');
1537 o = ss.taboption('encryption', form.Flag, 'ca_cert2_usesystem', _('Use system certificates for inner-tunnel'), _("Validate server certificate using built-in system CA bundle,<br />requires the \"ca-bundle\" package"));
1540 o.default = o.disabled;
1541 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1542 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1543 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1544 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1545 o.validate = function(section_id, value) {
1546 if (value == '1' && !L.hasSystemFeature('cabundle')) {
1547 return _("This option cannot be used because the ca-bundle package is not installed.");
1552 o = ss.taboption('encryption', form.FileUpload, 'ca_cert2', _('Path to inner CA-Certificate'));
1553 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa', ca_cert2_usesystem: '0' });
1554 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2', ca_cert2_usesystem: '0' });
1555 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa', ca_cert2_usesystem: '0' });
1556 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2', ca_cert2_usesystem: '0' });
1558 o = ss.taboption('encryption', form.Value, 'subject_match2', _('Inner certificate constraint (Subject)'), _("Certificate constraint substring - e.g. /CN=wifi.mycompany.com<br />See `logread -f` during handshake for actual values"));
1559 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1560 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1561 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1562 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1564 o = ss.taboption('encryption', form.DynamicList, 'altsubject_match2', _('Inner certificate constraint (SAN)'), _("Certificate constraint(s) via Subject Alternate Name values<br />(supported attributes: EMAIL, DNS, URI) - e.g. DNS:wifi.mycompany.com"));
1565 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1566 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1567 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1568 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1570 o = ss.taboption('encryption', form.DynamicList, 'domain_match2', _('Inner certificate constraint (Domain)'), _("Certificate constraint(s) against DNS SAN values (if available)<br />or Subject CN (exact match)"));
1571 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1572 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1573 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1574 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1576 o = ss.taboption('encryption', form.DynamicList, 'domain_suffix_match2', _('Inner certificate constraint (Wildcard)'), _("Certificate constraint(s) against DNS SAN values (if available)<br />or Subject CN (suffix match)"));
1577 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1578 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1579 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1580 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1582 o = ss.taboption('encryption', form.FileUpload, 'client_cert2', _('Path to inner Client-Certificate'));
1583 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1584 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1585 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1586 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1588 o = ss.taboption('encryption', form.FileUpload, 'priv_key2', _('Path to inner Private Key'));
1589 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1590 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1591 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1592 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1594 o = ss.taboption('encryption', form.Value, 'priv_key2_pwd', _('Password of inner Private Key'));
1595 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1596 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1597 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1598 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1601 o = ss.taboption('encryption', form.Value, 'identity', _('Identity'));
1602 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1603 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1604 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1605 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1606 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1607 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1608 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1609 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1610 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1611 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1612 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1613 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1614 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1615 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1616 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1617 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1619 o = ss.taboption('encryption', form.Value, 'anonymous_identity', _('Anonymous Identity'));
1620 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1621 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1622 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1623 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1624 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1625 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1626 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1627 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1628 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1629 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1630 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1631 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1632 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1633 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1634 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1635 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1637 o = ss.taboption('encryption', form.Value, 'password', _('Password'));
1638 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1639 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1640 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1641 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1642 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1643 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1644 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1645 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1646 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1647 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1648 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1649 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1653 if (hwtype == 'mac80211') {
1654 // ieee802.11w options
1655 if (L.hasSystemFeature('hostapd', '11w')) {
1656 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)"));
1657 o.value('', _('Disabled'));
1658 o.value('1', _('Optional'));
1659 o.value('2', _('Required'));
1660 o.depends({ mode: 'ap', encryption: 'wpa2' });
1661 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1662 o.depends({ mode: 'ap', encryption: 'psk2' });
1663 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1664 o.depends({ mode: 'ap', encryption: 'sae' });
1665 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1666 o.depends({ mode: 'ap', encryption: 'owe' });
1667 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1668 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1669 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1670 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1671 o.depends({ mode: 'ap-wds', encryption: 'owe' });
1672 o.depends({ mode: 'sta', encryption: 'wpa2' });
1673 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1674 o.depends({ mode: 'sta', encryption: 'psk2' });
1675 o.depends({ mode: 'sta', encryption: 'psk-mixed' });
1676 o.depends({ mode: 'sta', encryption: 'sae' });
1677 o.depends({ mode: 'sta', encryption: 'sae-mixed' });
1678 o.depends({ mode: 'sta', encryption: 'owe' });
1679 o.depends({ mode: 'sta-wds', encryption: 'psk2' });
1680 o.depends({ mode: 'sta-wds', encryption: 'psk-mixed' });
1681 o.depends({ mode: 'sta-wds', encryption: 'sae' });
1682 o.depends({ mode: 'sta-wds', encryption: 'sae-mixed' });
1683 o.depends({ mode: 'sta-wds', encryption: 'owe' });
1685 '2': [{ encryption: 'sae' }, { encryption: 'owe' }],
1686 '1': [{ encryption: 'sae-mixed'}],
1690 o = ss.taboption('encryption', form.Value, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1691 o.depends('ieee80211w', '1');
1692 o.depends('ieee80211w', '2');
1693 o.datatype = 'uinteger';
1694 o.placeholder = '1000';
1697 o = ss.taboption('encryption', form.Value, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1698 o.depends('ieee80211w', '1');
1699 o.depends('ieee80211w', '2');
1700 o.datatype = 'uinteger';
1701 o.placeholder = '201';
1705 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.'));
1706 o.depends({ mode: 'ap', encryption: 'wpa2' });
1707 o.depends({ mode: 'ap', encryption: 'psk2' });
1708 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1709 o.depends({ mode: 'ap', encryption: 'sae' });
1710 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1711 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1712 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1713 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1714 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1715 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1717 if (L.hasSystemFeature('hostapd', 'cli') && L.hasSystemFeature('wpasupplicant')) {
1718 o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1721 o.default = o.disabled;
1722 o.depends('encryption', 'psk');
1723 o.depends('encryption', 'psk2');
1724 o.depends('encryption', 'psk-mixed');
1725 o.depends('encryption', 'sae');
1726 o.depends('encryption', 'sae-mixed');
1733 s.handleRemove = function(section_id, ev) {
1734 document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id)).style.opacity = 0.5;
1735 return form.TypedSection.prototype.handleRemove.apply(this, [section_id, ev]);
1738 s.handleScan = function(radioDev, ev) {
1739 var table = E('div', { 'class': 'table' }, [
1740 E('div', { 'class': 'tr table-titles' }, [
1741 E('div', { 'class': 'th col-2 middle center' }, _('Signal')),
1742 E('div', { 'class': 'th col-4 middle left' }, _('SSID')),
1743 E('div', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1744 E('div', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1745 E('div', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1746 E('div', { 'class': 'th col-3 middle left' }, _('Encryption')),
1747 E('div', { 'class': 'th cbi-section-actions right' }, ' '),
1751 var stop = E('button', {
1753 'click': L.bind(this.handleScanStartStop, this),
1754 'style': 'display:none',
1755 'data-state': 'stop'
1756 }, _('Stop refresh'));
1758 cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1760 var md = ui.showModal(_('Join Network: Wireless Scan'), [
1762 E('div', { 'class': 'right' }, [
1767 'click': L.bind(this.handleScanAbort, this)
1772 md.style.maxWidth = '90%';
1773 md.style.maxHeight = 'none';
1775 this.pollFn = L.bind(this.handleScanRefresh, this, radioDev, {}, table, stop);
1777 L.Poll.add(this.pollFn);
1781 s.handleScanRefresh = function(radioDev, scanCache, table, stop) {
1782 return radioDev.getScanList().then(L.bind(function(results) {
1785 for (var i = 0; i < results.length; i++)
1786 scanCache[results[i].bssid] = results[i];
1788 for (var k in scanCache)
1789 if (scanCache[k].stale)
1790 results.push(scanCache[k]);
1792 results.sort(function(a, b) {
1793 var diff = (b.quality - a.quality) || (a.channel - b.channel);
1798 if (a.ssid < b.ssid)
1800 else if (a.ssid > b.ssid)
1803 if (a.bssid < b.bssid)
1805 else if (a.bssid > b.bssid)
1809 for (var i = 0; i < results.length; i++) {
1810 var res = results[i],
1811 qv = res.quality || 0,
1812 qm = res.quality_max || 0,
1813 q = (qv > 0 && qm > 0) ? Math.floor((100 / qm) * qv) : 0,
1814 s = res.stale ? 'opacity:0.5' : '';
1817 E('span', { 'style': s }, render_signal_badge(q, res.signal, res.noise)),
1818 E('span', { 'style': s }, (res.ssid != null) ? '%h'.format(res.ssid) : E('em', _('hidden'))),
1819 E('span', { 'style': s }, '%d'.format(res.channel)),
1820 E('span', { 'style': s }, '%h'.format(res.mode)),
1821 E('span', { 'style': s }, '%h'.format(res.bssid)),
1822 E('span', { 'style': s }, '%h'.format(network.formatWifiEncryption(res.encryption))),
1823 E('div', { 'class': 'right' }, E('button', {
1824 'class': 'cbi-button cbi-button-action important',
1825 'click': L.bind(this.handleJoin, this, radioDev, res)
1826 }, _('Join Network')))
1832 cbi_update_table(table, rows);
1834 stop.disabled = false;
1835 stop.style.display = '';
1836 stop.classList.remove('spinning');
1840 s.handleScanStartStop = function(ev) {
1841 var btn = ev.currentTarget;
1843 if (btn.getAttribute('data-state') == 'stop') {
1844 L.Poll.remove(this.pollFn);
1845 btn.firstChild.data = _('Start refresh');
1846 btn.setAttribute('data-state', 'start');
1849 L.Poll.add(this.pollFn);
1850 btn.firstChild.data = _('Stop refresh');
1851 btn.setAttribute('data-state', 'stop');
1852 btn.classList.add('spinning');
1853 btn.disabled = true;
1857 s.handleScanAbort = function(ev) {
1858 var md = L.dom.parent(ev.target, 'div[aria-modal="true"]');
1860 md.style.maxWidth = '';
1861 md.style.maxHeight = '';
1865 L.Poll.remove(this.pollFn);
1870 s.handleJoinConfirm = function(radioDev, bss, form, ev) {
1871 var nameopt = L.toArray(form.lookupOption('name', '_new_'))[0],
1872 passopt = L.toArray(form.lookupOption('password', '_new_'))[0],
1873 bssidopt = L.toArray(form.lookupOption('bssid', '_new_'))[0],
1874 zoneopt = L.toArray(form.lookupOption('zone', '_new_'))[0],
1875 replopt = L.toArray(form.lookupOption('replace', '_new_'))[0],
1876 nameval = (nameopt && nameopt.isValid('_new_')) ? nameopt.formvalue('_new_') : null,
1877 passval = (passopt && passopt.isValid('_new_')) ? passopt.formvalue('_new_') : null,
1878 bssidval = (bssidopt && bssidopt.isValid('_new_')) ? bssidopt.formvalue('_new_') : null,
1879 zoneval = zoneopt ? zoneopt.formvalue('_new_') : null,
1880 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1881 is_wep = (enc && Array.isArray(enc.wep)),
1882 is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' })),
1883 is_sae = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'sae' }));
1885 if (nameval == null || (passopt && passval == null))
1888 var section_id = null;
1890 return this.map.save(function() {
1891 var wifi_sections = uci.sections('wireless', 'wifi-iface');
1893 if (replopt.formvalue('_new_') == '1') {
1894 for (var i = 0; i < wifi_sections.length; i++)
1895 if (wifi_sections[i].device == radioDev.getName())
1896 uci.remove('wireless', wifi_sections[i]['.name']);
1899 if (uci.get('wireless', radioDev.getName(), 'disabled') == '1') {
1900 for (var i = 0; i < wifi_sections.length; i++)
1901 if (wifi_sections[i].device == radioDev.getName())
1902 uci.set('wireless', wifi_sections[i]['.name'], 'disabled', '1');
1904 uci.unset('wireless', radioDev.getName(), 'disabled');
1907 section_id = next_free_sid(wifi_sections.length);
1909 uci.add('wireless', 'wifi-iface', section_id);
1910 uci.set('wireless', section_id, 'device', radioDev.getName());
1911 uci.set('wireless', section_id, 'mode', (bss.mode == 'Ad-Hoc') ? 'adhoc' : 'sta');
1912 uci.set('wireless', section_id, 'network', nameval);
1914 if (bss.ssid != null) {
1915 uci.set('wireless', section_id, 'ssid', bss.ssid);
1917 if (bssidval == '1')
1918 uci.set('wireless', section_id, 'bssid', bss.bssid);
1920 else if (bss.bssid != null) {
1921 uci.set('wireless', section_id, 'bssid', bss.bssid);
1925 uci.set('wireless', section_id, 'encryption', 'sae');
1926 uci.set('wireless', section_id, 'key', passval);
1929 for (var i = enc.wpa.length - 1; i >= 0; i--) {
1930 if (enc.wpa[i] == 2) {
1931 uci.set('wireless', section_id, 'encryption', 'psk2');
1934 else if (enc.wpa[i] == 1) {
1935 uci.set('wireless', section_id, 'encryption', 'psk');
1940 uci.set('wireless', section_id, 'key', passval);
1943 uci.set('wireless', section_id, 'encryption', 'wep-open');
1944 uci.set('wireless', section_id, 'key', '1');
1945 uci.set('wireless', section_id, 'key1', passval);
1948 uci.set('wireless', section_id, 'encryption', 'none');
1951 return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) {
1952 firewall.deleteNetwork(net.getName());
1954 var zonePromise = zoneval
1955 ? firewall.getZone(zoneval).then(function(zone) { return zone || firewall.addZone(zoneval) })
1956 : Promise.resolve();
1958 return zonePromise.then(function(zone) {
1960 zone.addNetwork(net.getName());
1963 }).then(L.bind(function() {
1964 return this.renderMoreOptionsModal(section_id);
1968 s.handleJoin = function(radioDev, bss, ev) {
1969 this.handleScanAbort(ev);
1971 var m2 = new form.Map('wireless'),
1972 s2 = m2.section(form.NamedSection, '_new_'),
1973 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1974 is_wep = (enc && Array.isArray(enc.wep)),
1975 is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' || a == 'sae' })),
1976 replace, passphrase, name, bssid, zone;
1978 var nameUsed = function(name) {
1979 var s = uci.get('network', name);
1980 if (s != null && s['.type'] != 'interface')
1983 var net = (s != null) ? network.instantiateNetwork(name) : null;
1984 return (net != null && !net.isEmpty());
1987 s2.render = function() {
1988 return Promise.all([
1990 this.renderUCISection('_new_')
1991 ]).then(this.renderContents.bind(this));
1994 replace = s2.option(form.Flag, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
1996 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>'));
1997 name.datatype = 'uciname';
1998 name.default = 'wwan';
1999 name.rmempty = false;
2000 name.validate = function(section_id, value) {
2001 if (nameUsed(value))
2002 return _('The network name is already used');
2007 for (var i = 2; nameUsed(name.default); i++)
2008 name.default = 'wwan%d'.format(i);
2010 if (is_wep || is_psk) {
2011 passphrase = s2.option(form.Value, 'password', is_wep ? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
2012 passphrase.datatype = is_wep ? 'wepkey' : 'wpakey';
2013 passphrase.password = true;
2014 passphrase.rmempty = false;
2017 if (bss.ssid != null) {
2018 bssid = s2.option(form.Flag, 'bssid', _('Lock to BSSID'), _('Instead of joining any network with a matching SSID, only connect to the BSSID <code>%h</code>.').format(bss.bssid));
2019 bssid.default = '0';
2022 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.'));
2023 zone.default = 'wan';
2025 return m2.render().then(L.bind(function(nodes) {
2026 ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [
2028 E('div', { 'class': 'right' }, [
2031 'click': ui.hideModal
2032 }, _('Cancel')), ' ',
2034 'class': 'cbi-button cbi-button-positive important',
2035 'click': ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2)
2038 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase || name).cbid('_new_'))).focus();
2042 s.handleAdd = function(radioDev, ev) {
2043 var section_id = next_free_sid(uci.sections('wireless', 'wifi-iface').length);
2045 uci.unset('wireless', radioDev.getName(), 'disabled');
2047 uci.add('wireless', 'wifi-iface', section_id);
2048 uci.set('wireless', section_id, 'device', radioDev.getName());
2049 uci.set('wireless', section_id, 'mode', 'ap');
2050 uci.set('wireless', section_id, 'ssid', 'OpenWrt');
2051 uci.set('wireless', section_id, 'encryption', 'none');
2053 this.addedSection = section_id;
2054 return this.renderMoreOptionsModal(section_id);
2057 o = s.option(form.DummyValue, '_badge');
2058 o.modalonly = false;
2059 o.textvalue = function(section_id) {
2060 var inst = this.section.lookupRadioOrNetwork(section_id),
2061 node = E('div', { 'class': 'center' });
2063 if (inst.getWifiNetworks)
2064 node.appendChild(render_radio_badge(inst));
2066 node.appendChild(render_network_badge(inst));
2071 o = s.option(form.DummyValue, '_stat');
2072 o.modalonly = false;
2073 o.textvalue = function(section_id) {
2074 var inst = this.section.lookupRadioOrNetwork(section_id);
2076 if (inst.getWifiNetworks)
2077 return render_radio_status(inst, this.section.wifis.filter(function(e) {
2078 return (e.getWifiDeviceName() == inst.getName());
2081 return render_network_status(inst);
2084 return m.render().then(L.bind(function(m, nodes) {
2085 L.Poll.add(L.bind(function() {
2086 var section_ids = m.children[0].cfgsections(),
2087 tasks = [ network.getHostHints(), network.getWifiDevices() ];
2089 for (var i = 0; i < section_ids.length; i++) {
2090 var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])),
2091 dsc = row.querySelector('[data-name="_stat"] > div'),
2092 btns = row.querySelectorAll('.cbi-section-actions button');
2094 if (dsc.getAttribute('restart') == '') {
2095 dsc.setAttribute('restart', '1');
2096 tasks.push(fs.exec('/sbin/wifi', ['up', section_ids[i]]).catch(function(e) {
2097 ui.addNotification(null, E('p', e.message));
2100 else if (dsc.getAttribute('restart') == '1') {
2101 dsc.removeAttribute('restart');
2102 btns[0].classList.remove('spinning');
2103 btns[0].disabled = false;
2107 return Promise.all(tasks)
2108 .then(L.bind(function(hosts_radios) {
2111 for (var i = 0; i < hosts_radios[1].length; i++)
2112 tasks.push(hosts_radios[1][i].getWifiNetworks());
2114 return Promise.all(tasks).then(function(data) {
2115 hosts_radios[2] = [];
2117 for (var i = 0; i < data.length; i++)
2118 hosts_radios[2].push.apply(hosts_radios[2], data[i]);
2120 return hosts_radios;
2123 .then(L.bind(function(hosts_radios_wifis) {
2126 for (var i = 0; i < hosts_radios_wifis[2].length; i++)
2127 tasks.push(hosts_radios_wifis[2][i].getAssocList());
2129 return Promise.all(tasks).then(function(data) {
2130 hosts_radios_wifis[3] = [];
2132 for (var i = 0; i < data.length; i++) {
2133 var wifiNetwork = hosts_radios_wifis[2][i],
2134 radioDev = hosts_radios_wifis[1].filter(function(d) { return d.getName() == wifiNetwork.getWifiDeviceName() })[0];
2136 for (var j = 0; j < data[i].length; j++)
2137 hosts_radios_wifis[3].push(Object.assign({ radio: radioDev, network: wifiNetwork }, data[i][j]));
2140 return hosts_radios_wifis;
2143 .then(L.bind(this.poll_status, this, nodes));
2146 var table = E('div', { 'class': 'table', 'id': 'wifi_assoclist_table' }, [
2147 E('div', { 'class': 'tr table-titles' }, [
2148 E('div', { 'class': 'th nowrap' }, _('Network')),
2149 E('div', { 'class': 'th hide-xs' }, _('MAC-Address')),
2150 E('div', { 'class': 'th' }, _('Host')),
2151 E('div', { 'class': 'th nowrap' }, _('Signal / Noise')),
2152 E('div', { 'class': 'th nowrap' }, _('RX Rate / TX Rate'))
2156 cbi_update_table(table, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
2158 return E([ nodes, E('h3', _('Associated Stations')), table ]);