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');
69 'class': wrap ? 'center' : 'ifacebadge',
71 'data-signal': signalValue,
72 'data-noise': noiseValue
74 E('img', { 'src': icon }),
82 function render_network_badge(radioNet) {
83 return render_signal_badge(radioNet.isUp() ? radioNet.getSignalPercent() : -1, radioNet.getSignal(), radioNet.getNoise());
86 function render_radio_status(radioDev, wifiNets) {
87 var name = radioDev.getI18n().replace(/ Wireless Controller .+$/, ''),
88 node = E('div', [ E('big', {}, E('strong', {}, name)), E('div') ]),
89 channel, frequency, bitrate;
91 for (var i = 0; i < wifiNets.length; i++) {
92 channel = channel || wifiNets[i].getChannel();
93 frequency = frequency || wifiNets[i].getFrequency();
94 bitrate = bitrate || wifiNets[i].getBitRate();
98 L.itemlist(node.lastElementChild, [
99 _('Channel'), '%s (%s %s)'.format(channel || '?', frequency || '?', _('GHz')),
100 _('Bitrate'), '%s %s'.format(bitrate || '?', _('Mbit/s'))
103 node.lastElementChild.appendChild(E('em', _('Device is not active')));
108 function render_network_status(radioNet) {
109 var mode = radioNet.getActiveMode(),
110 bssid = radioNet.getActiveBSSID(),
111 channel = radioNet.getChannel(),
112 disabled = (radioNet.get('disabled') == '1' || uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == '1'),
113 is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled),
114 is_mesh = (radioNet.getMode() == 'mesh'),
115 changecount = count_changes(radioNet.getName()),
119 status_text = E('a', {
121 click: L.bind(ui.changes.displayChanges, ui.changes)
122 }, _('Interface has %d pending changes').format(changecount));
124 status_text = E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated'));
126 return L.itemlist(E('div'), [
127 is_mesh ? _('Mesh ID') : _('SSID'), (is_mesh ? radioNet.getMeshID() : radioNet.getSSID()) || '?',
129 _('BSSID'), (!changecount && is_assoc) ? bssid : null,
130 _('Encryption'), (!changecount && is_assoc) ? radioNet.getActiveEncryption() || _('None') : null,
132 ], [ ' | ', E('br') ]);
135 function render_modal_status(node, radioNet) {
136 var mode = radioNet.getActiveMode(),
137 noise = radioNet.getNoise(),
138 bssid = radioNet.getActiveBSSID(),
139 channel = radioNet.getChannel(),
140 disabled = (radioNet.get('disabled') == '1'),
141 is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled);
144 node = E('span', { 'class': 'ifacebadge large', 'data-network': radioNet.getName() }, [ E('small'), E('span') ]);
146 L.dom.content(node.firstElementChild, render_signal_badge(disabled ? -1 : radioNet.getSignalPercent(), radioNet.getSignal(), noise, true));
148 L.itemlist(node.lastElementChild, [
150 _('SSID'), radioNet.getSSID() || '?',
151 _('BSSID'), is_assoc ? bssid : null,
152 _('Encryption'), is_assoc ? radioNet.getActiveEncryption() || _('None') : null,
153 _('Channel'), is_assoc ? '%d (%.3f %s)'.format(radioNet.getChannel(), radioNet.getFrequency() || 0, _('GHz')) : null,
154 _('Tx-Power'), is_assoc ? '%d %s'.format(radioNet.getTXPower(), _('dBm')) : null,
155 _('Signal'), is_assoc ? '%d %s'.format(radioNet.getSignal(), _('dBm')) : null,
156 _('Noise'), (is_assoc && noise != null) ? '%d %s'.format(noise, _('dBm')) : null,
157 _('Bitrate'), is_assoc ? '%.1f %s'.format(radioNet.getBitRate() || 0, _('Mbit/s')) : null,
158 _('Country'), is_assoc ? radioNet.getCountryCode() : null
159 ], [ ' | ', E('br'), E('br'), E('br'), E('br'), E('br'), ' | ', E('br'), ' | ' ]);
162 L.dom.append(node.lastElementChild, E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated')));
167 function format_wifirate(rate) {
168 var s = '%.1f Mbit/s, %dMHz'.format(rate.rate / 1000, rate.mhz);
170 if (rate.ht || rate.vht) {
171 if (rate.vht) s += ', VHT-MCS %d'.format(rate.mcs);
172 if (rate.nss) s += ', VHT-NSS %d'.format(rate.nss);
173 if (rate.ht) s += ', MCS %s'.format(rate.mcs);
174 if (rate.short_gi) s += ', Short GI';
180 function radio_restart(id, ev) {
181 var row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id)),
182 dsc = row.querySelector('[data-name="_stat"] > div'),
183 btn = row.querySelector('.cbi-section-actions button');
186 btn.classList.add('spinning');
189 dsc.setAttribute('restart', '');
190 L.dom.content(dsc, E('em', _('Device is restarting…')));
193 function network_updown(id, map, ev) {
194 var radio = uci.get('wireless', id, 'device'),
195 disabled = (uci.get('wireless', id, 'disabled') == '1') ||
196 (uci.get('wireless', radio, 'disabled') == '1');
199 uci.unset('wireless', id, 'disabled');
200 uci.unset('wireless', radio, 'disabled');
203 uci.set('wireless', id, 'disabled', '1');
205 var all_networks_disabled = true,
206 wifi_ifaces = uci.sections('wireless', 'wifi-iface');
208 for (var i = 0; i < wifi_ifaces.length; i++) {
209 if (wifi_ifaces[i].device == radio && wifi_ifaces[i].disabled != '1') {
210 all_networks_disabled = false;
215 if (all_networks_disabled)
216 uci.set('wireless', radio, 'disabled', '1');
219 return map.save().then(function() {
224 function next_free_sid(offset) {
225 var sid = 'wifinet' + offset;
227 while (uci.get('wireless', sid))
228 sid = 'wifinet' + (++offset);
233 var CBIWifiFrequencyValue = form.Value.extend({
234 callFrequencyList: rpc.declare({
237 params: [ 'device' ],
238 expect: { results: [] }
241 load: function(section_id) {
243 network.getWifiDevice(section_id),
244 this.callFrequencyList(section_id)
245 ]).then(L.bind(function(data) {
247 '11g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
248 '11a': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : []
251 for (var i = 0; i < data[1].length; i++)
252 this.channels[(data[1][i].mhz > 2484) ? '11a' : '11g'].push(
254 '%d (%d Mhz)'.format(data[1][i].channel, data[1][i].mhz),
255 !data[1][i].restricted
258 var hwmodelist = L.toArray(data[0] ? data[0].getHWModes() : null)
259 .reduce(function(o, v) { o[v] = true; return o }, {});
263 'n', 'N', hwmodelist.n,
264 'ac', 'AC', hwmodelist.ac
267 var htmodelist = L.toArray(data[0] ? data[0].getHTModes() : null)
268 .reduce(function(o, v) { o[v] = true; return o }, {});
271 '': [ '', '-', true ],
273 'HT20', '20 MHz', htmodelist.HT20,
274 'HT40', '40 MHz', htmodelist.HT40
277 'VHT20', '20 MHz', htmodelist.VHT20,
278 'VHT40', '40 MHz', htmodelist.VHT40,
279 'VHT80', '80 MHz', htmodelist.VHT80,
280 'VHT160', '160 MHz', htmodelist.VHT160
286 '11g', '2.4 GHz', this.channels['11g'].length > 3,
287 '11a', '5 GHz', this.channels['11a'].length > 3
290 '11g', '2.4 GHz', this.channels['11g'].length > 3,
291 '11a', '5 GHz', this.channels['11a'].length > 3
300 setValues: function(sel, vals) {
302 sel.vals.selected = sel.selectedIndex;
304 while (sel.options[0])
307 for (var i = 0; vals && i < vals.length; i += 3)
309 sel.add(E('option', { value: vals[i+0] }, [ vals[i+1] ]));
311 if (vals && !isNaN(vals.selected))
312 sel.selectedIndex = vals.selected;
314 sel.parentNode.style.display = (sel.options.length <= 1) ? 'none' : '';
318 toggleWifiMode: function(elem) {
319 this.toggleWifiHTMode(elem);
320 this.toggleWifiBand(elem);
323 toggleWifiHTMode: function(elem) {
324 var mode = elem.querySelector('.mode');
325 var bwdt = elem.querySelector('.htmode');
327 this.setValues(bwdt, this.htmodes[mode.value]);
330 toggleWifiBand: function(elem) {
331 var mode = elem.querySelector('.mode');
332 var band = elem.querySelector('.band');
334 this.setValues(band, this.bands[mode.value]);
335 this.toggleWifiChannel(elem);
338 toggleWifiChannel: function(elem) {
339 var band = elem.querySelector('.band');
340 var chan = elem.querySelector('.channel');
342 this.setValues(chan, this.channels[band.value]);
345 setInitialValues: function(section_id, elem) {
346 var mode = elem.querySelector('.mode'),
347 band = elem.querySelector('.band'),
348 chan = elem.querySelector('.channel'),
349 bwdt = elem.querySelector('.htmode'),
350 htval = uci.get('wireless', section_id, 'htmode'),
351 hwval = uci.get('wireless', section_id, 'hwmode'),
352 chval = uci.get('wireless', section_id, 'channel');
354 this.setValues(mode, this.modes);
356 if (/VHT20|VHT40|VHT80|VHT160/.test(htval))
358 else if (/HT20|HT40/.test(htval))
363 this.toggleWifiMode(elem);
370 this.toggleWifiBand(elem);
378 renderWidget: function(section_id, option_index, cfgvalue) {
381 L.dom.content(elem, [
382 E('label', { 'style': 'float:left; margin-right:3px' }, [
386 'style': 'width:auto',
387 'change': L.bind(this.toggleWifiMode, this, elem)
390 E('label', { 'style': 'float:left; margin-right:3px' }, [
394 'style': 'width:auto',
395 'change': L.bind(this.toggleWifiBand, this, elem)
398 E('label', { 'style': 'float:left; margin-right:3px' }, [
399 _('Channel'), E('br'),
402 'style': 'width:auto'
405 E('label', { 'style': 'float:left; margin-right:3px' }, [
409 'style': 'width:auto'
412 E('br', { 'style': 'clear:left' })
415 return this.setInitialValues(section_id, elem);
418 cfgvalue: function(section_id) {
420 uci.get('wireless', section_id, 'htmode'),
421 uci.get('wireless', section_id, 'hwmode'),
422 uci.get('wireless', section_id, 'channel')
426 formvalue: function(section_id) {
427 var node = this.map.findElement('data-field', this.cbid(section_id));
430 node.querySelector('.htmode').value,
431 node.querySelector('.band').value,
432 node.querySelector('.channel').value
436 write: function(section_id, value) {
437 uci.set('wireless', section_id, 'htmode', value[0] || null);
438 uci.set('wireless', section_id, 'hwmode', value[1]);
439 uci.set('wireless', section_id, 'channel', value[2]);
443 var CBIWifiTxPowerValue = form.ListValue.extend({
444 callTxPowerList: rpc.declare({
446 method: 'txpowerlist',
447 params: [ 'device' ],
448 expect: { results: [] }
451 load: function(section_id) {
452 return this.callTxPowerList(section_id).then(L.bind(function(pwrlist) {
453 this.powerval = this.wifiNetwork ? this.wifiNetwork.getTXPower() : null;
454 this.poweroff = this.wifiNetwork ? this.wifiNetwork.getTXPowerOffset() : null;
456 this.value('', _('driver default'));
458 for (var i = 0; i < pwrlist.length; i++)
459 this.value(pwrlist[i].dbm, '%d dBm (%d mW)'.format(pwrlist[i].dbm, pwrlist[i].mw));
461 return form.ListValue.prototype.load.apply(this, [section_id]);
465 renderWidget: function(section_id, option_index, cfgvalue) {
466 var widget = form.ListValue.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
467 widget.firstElementChild.style.width = 'auto';
469 L.dom.append(widget, E('span', [
470 ' - ', _('Current power'), ': ',
471 E('span', [ this.powerval != null ? '%d dBm'.format(this.powerval) : E('em', _('unknown')) ]),
472 this.poweroff ? ' + %d dB offset = %s dBm'.format(this.poweroff, this.powerval != null ? this.powerval + this.poweroff : '?') : ''
479 var CBIWifiCountryValue = form.Value.extend({
480 callCountryList: rpc.declare({
482 method: 'countrylist',
483 params: [ 'device' ],
484 expect: { results: [] }
487 load: function(section_id) {
488 return this.callCountryList(section_id).then(L.bind(function(countrylist) {
489 if (Array.isArray(countrylist) && countrylist.length > 0) {
490 this.value('', _('driver default'));
492 for (var i = 0; i < countrylist.length; i++)
493 this.value(countrylist[i].iso3166, '%s - %s'.format(countrylist[i].iso3166, countrylist[i].country));
496 return form.Value.prototype.load.apply(this, [section_id]);
500 validate: function(section_id, formvalue) {
501 if (formvalue != null && formvalue != '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue))
502 return _('Use ISO/IEC 3166 alpha2 country codes.');
507 renderWidget: function(section_id, option_index, cfgvalue) {
508 var typeClass = (this.keylist && this.keylist.length) ? form.ListValue : form.Value;
509 return typeClass.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
513 return L.view.extend({
514 poll_status: function(map, data) {
515 var rows = map.querySelectorAll('.cbi-section-table-row[data-sid]');
517 for (var i = 0; i < rows.length; i++) {
518 var section_id = rows[i].getAttribute('data-sid'),
519 radioDev = data[1].filter(function(d) { return d.getName() == section_id })[0],
520 radioNet = data[2].filter(function(n) { return n.getName() == section_id })[0],
521 badge = rows[i].querySelector('[data-name="_badge"] > div'),
522 stat = rows[i].querySelector('[data-name="_stat"]'),
523 btns = rows[i].querySelectorAll('.cbi-section-actions button'),
524 busy = btns[0].classList.contains('spinning') || btns[1].classList.contains('spinning') || btns[2].classList.contains('spinning');
527 L.dom.content(badge, render_radio_badge(radioDev));
528 L.dom.content(stat, render_radio_status(radioDev, data[2].filter(function(n) { return n.getWifiDeviceName() == radioDev.getName() })));
531 L.dom.content(badge, render_network_badge(radioNet));
532 L.dom.content(stat, render_network_status(radioNet));
535 if (stat.hasAttribute('restart'))
536 L.dom.content(stat, E('em', _('Device is restarting…')));
538 btns[0].disabled = busy;
539 btns[1].disabled = busy;
540 btns[2].disabled = busy;
543 var table = document.querySelector('#wifi_assoclist_table'),
547 for (var i = 0; i < data[3].length; i++) {
548 var bss = data[3][i],
549 name = hosts.getHostnameByMACAddr(bss.mac),
550 ipv4 = hosts.getIPAddrByMACAddr(bss.mac),
551 ipv6 = hosts.getIP6AddrByMACAddr(bss.mac);
555 if (name && ipv4 && ipv6)
556 hint = '%s <span class="hide-xs">(%s, %s)</span>'.format(name, ipv4, ipv6);
557 else if (name && (ipv4 || ipv6))
558 hint = '%s <span class="hide-xs">(%s)</span>'.format(name, ipv4 || ipv6);
560 hint = name || ipv4 || ipv6 || '?';
564 'class': 'ifacebadge',
565 'data-ifname': bss.network.getIfname(),
566 'data-ssid': bss.network.getSSID()
569 'src': L.resource('icons/wifi%s.png').format(bss.network.isUp() ? '' : '_disabled'),
570 'title': bss.radio.getI18n()
573 ' %s '.format(bss.network.getShortName()),
574 E('small', '(%s)'.format(bss.network.getIfname()))
579 render_signal_badge(Math.min((bss.signal + 110) / 70 * 100, 100), bss.signal, bss.noise),
581 E('span', format_wifirate(bss.rx)),
583 E('span', format_wifirate(bss.tx))
587 if (bss.network.isClientDisconnectSupported()) {
588 if (table.firstElementChild.childNodes.length < 6)
589 table.firstElementChild.appendChild(E('div', { 'class': 'th nowrap right'}, [ _('Disconnect') ]));
591 row.push(E('button', {
592 'class': 'cbi-button cbi-button-remove',
593 'click': L.bind(function(net, mac, ev) {
594 L.dom.parent(ev.currentTarget, '.tr').style.opacity = 0.5;
595 ev.currentTarget.classList.add('spinning');
596 ev.currentTarget.disabled = true;
597 ev.currentTarget.blur();
599 net.disconnectClient(mac, true, 5, 60000);
600 }, this, bss.network, bss.mac)
601 }, [ _('Disconnect') ]));
610 cbi_update_table(table, trows, E('em', _('No information available')));
612 var stat = document.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large');
615 render_modal_status(stat, data[2].filter(function(n) { return n.getName() == stat.getAttribute('data-network') })[0]);
617 return network.flushCache();
627 checkAnonymousSections: function() {
628 var wifiIfaces = uci.sections('wireless', 'wifi-iface');
630 for (var i = 0; i < wifiIfaces.length; i++)
631 if (wifiIfaces[i]['.anonymous'])
637 callUciRename: rpc.declare({
640 params: [ 'config', 'section', 'name' ]
644 if (this.checkAnonymousSections())
645 return this.renderMigration();
647 return this.renderOverview();
650 handleMigration: function(ev) {
651 var wifiIfaces = uci.sections('wireless', 'wifi-iface'),
655 for (var i = 0; i < wifiIfaces.length; i++) {
656 if (!wifiIfaces[i]['.anonymous'])
659 var new_name = next_free_sid(id_offset);
661 tasks.push(this.callUciRename('wireless', wifiIfaces[i]['.name'], new_name));
662 id_offset = +new_name.substring(7) + 1;
665 return Promise.all(tasks)
666 .then(L.bind(ui.changes.init, ui.changes))
667 .then(L.bind(ui.changes.apply, ui.changes));
670 renderMigration: function() {
671 ui.showModal(_('Wireless configuration migration'), [
672 E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')),
673 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.')),
674 E('div', { 'class': 'right' },
676 'class': 'btn cbi-button-action important',
677 'click': ui.createHandlerFn(this, 'handleMigration')
682 renderOverview: function() {
685 m = new form.Map('wireless');
689 s = m.section(form.GridSection, 'wifi-device', _('Wireless Overview'));
693 s.load = function() {
694 return network.getWifiDevices().then(L.bind(function(radios) {
695 this.radios = radios.sort(function(a, b) {
696 return a.getName() > b.getName();
701 for (var i = 0; i < radios.length; i++)
702 tasks.push(radios[i].getWifiNetworks());
704 return Promise.all(tasks);
705 }, this)).then(L.bind(function(data) {
708 for (var i = 0; i < data.length; i++)
709 this.wifis.push.apply(this.wifis, data[i]);
713 s.cfgsections = function() {
716 for (var i = 0; i < this.radios.length; i++) {
717 rv.push(this.radios[i].getName());
719 for (var j = 0; j < this.wifis.length; j++)
720 if (this.wifis[j].getWifiDeviceName() == this.radios[i].getName())
721 rv.push(this.wifis[j].getName());
727 s.modaltitle = function(section_id) {
728 var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id})[0];
729 return radioNet ? radioNet.getI18n() : _('Edit wireless network');
732 s.lookupRadioOrNetwork = function(section_id) {
733 var radioDev = this.radios.filter(function(r) { return r.getName() == section_id })[0];
737 var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id })[0];
744 s.renderRowActions = function(section_id) {
745 var inst = this.lookupRadioOrNetwork(section_id), btns;
747 if (inst.getWifiNetworks) {
750 'class': 'cbi-button cbi-button-neutral',
751 'title': _('Restart radio interface'),
752 'click': ui.createHandlerFn(this, radio_restart, section_id)
755 'class': 'cbi-button cbi-button-action important',
756 'title': _('Find and join network'),
757 'click': ui.createHandlerFn(this, 'handleScan', inst)
760 'class': 'cbi-button cbi-button-add',
761 'title': _('Provide new network'),
762 'click': ui.createHandlerFn(this, 'handleAdd', inst)
767 var isDisabled = (inst.get('disabled') == '1' ||
768 uci.get('wireless', inst.getWifiDeviceName(), 'disabled') == '1');
772 'class': 'cbi-button cbi-button-neutral enable-disable',
773 'title': isDisabled ? _('Enable this network') : _('Disable this network'),
774 'click': ui.createHandlerFn(this, network_updown, section_id, this.map)
775 }, isDisabled ? _('Enable') : _('Disable')),
777 'class': 'cbi-button cbi-button-action important',
778 'title': _('Edit this network'),
779 'click': ui.createHandlerFn(this, 'renderMoreOptionsModal', section_id)
782 'class': 'cbi-button cbi-button-negative remove',
783 'title': _('Delete this network'),
784 'click': ui.createHandlerFn(this, 'handleRemove', section_id)
789 return E('div', { 'class': 'td middle cbi-section-actions' }, E('div', btns));
792 s.addModalOptions = function(s) {
793 return network.getWifiNetwork(s.section).then(function(radioNet) {
794 var hwtype = uci.get('wireless', radioNet.getWifiDeviceName(), 'type');
797 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
801 ss.tab('general', _('General Setup'));
802 ss.tab('advanced', _('Advanced Settings'));
804 var isDisabled = (radioNet.get('disabled') == '1' ||
805 uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == 1);
807 o = ss.taboption('general', form.DummyValue, '_wifistat_modal', _('Status'));
808 o.cfgvalue = L.bind(function(radioNet) {
809 return render_modal_status(null, radioNet);
811 o.write = function() {};
813 o = ss.taboption('general', form.Button, '_toggle', isDisabled ? _('Wireless network is disabled') : _('Wireless network is enabled'));
814 o.inputstyle = isDisabled ? 'apply' : 'reset';
815 o.inputtitle = isDisabled ? _('Enable') : _('Disable');
816 o.onclick = ui.createHandlerFn(s, network_updown, s.section, s.map);
818 o = ss.taboption('general', CBIWifiFrequencyValue, '_freq', '<br />' + _('Operating frequency'));
819 o.ucisection = s.section;
821 if (hwtype == 'mac80211') {
822 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.'));
823 o.wifiNetwork = radioNet;
825 o = ss.taboption('advanced', CBIWifiCountryValue, 'country', _('Country Code'));
826 o.wifiNetwork = radioNet;
828 o = ss.taboption('advanced', form.Flag, 'legacy_rates', _('Allow legacy 802.11b rates'));
829 o.default = o.enabled;
831 o = ss.taboption('advanced', form.Value, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
832 o.datatype = 'range(0,114750)';
833 o.placeholder = 'auto';
835 o = ss.taboption('advanced', form.Value, 'frag', _('Fragmentation Threshold'));
836 o.datatype = 'min(256)';
837 o.placeholder = _('off');
839 o = ss.taboption('advanced', form.Value, 'rts', _('RTS/CTS Threshold'));
840 o.datatype = 'uinteger';
841 o.placeholder = _('off');
843 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!'));
846 o = ss.taboption('advanced', form.Value, 'beacon_int', _('Beacon Interval'));
847 o.datatype = 'range(15,65535)';
853 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getName(), 'wifi-iface', _('Interface Configuration'));
857 ss.tab('general', _('General Setup'));
858 ss.tab('encryption', _('Wireless Security'));
859 ss.tab('macfilter', _('MAC-Filter'));
860 ss.tab('advanced', _('Advanced Settings'));
862 o = ss.taboption('general', form.ListValue, 'mode', _('Mode'));
863 o.value('ap', _('Access Point'));
864 o.value('sta', _('Client'));
865 o.value('adhoc', _('Ad-Hoc'));
867 o = ss.taboption('general', form.Value, 'mesh_id', _('Mesh Id'));
868 o.depends('mode', 'mesh');
870 o = ss.taboption('advanced', form.Flag, 'mesh_fwding', _('Forward mesh peer traffic'));
873 o.depends('mode', 'mesh');
875 o = ss.taboption('advanced', form.Value, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
878 o.datatype = 'range(-255,1)';
879 o.depends('mode', 'mesh');
881 o = ss.taboption('general', form.Value, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
882 o.datatype = 'maxlength(32)';
883 o.depends('mode', 'ap');
884 o.depends('mode', 'sta');
885 o.depends('mode', 'adhoc');
886 o.depends('mode', 'ahdemo');
887 o.depends('mode', 'monitor');
888 o.depends('mode', 'ap-wds');
889 o.depends('mode', 'sta-wds');
890 o.depends('mode', 'wds');
892 o = ss.taboption('general', form.Value, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
893 o.datatype = 'macaddr';
895 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.'));
899 o.write = function(section_id, value) {
900 return network.getDevice(section_id).then(L.bind(function(dev) {
901 var old_networks = dev.getNetworks().reduce(function(o, v) { o[v.getName()] = v; return o }, {}),
903 values = L.toArray(value),
906 for (var i = 0; i < values.length; i++) {
907 new_networks[values[i]] = true;
909 if (old_networks[values[i]])
912 tasks.push(network.getNetwork(values[i]).then(L.bind(function(name, net) {
913 return net || network.addNetwork(name, { proto: 'none' });
914 }, this, values[i])).then(L.bind(function(dev, net) {
917 net.set('type', 'bridge');
923 for (var name in old_networks)
924 if (!new_networks[name])
925 tasks.push(network.getNetwork(name).then(L.bind(function(dev, net) {
927 net.deleteDevice(dev);
930 return Promise.all(tasks);
934 if (hwtype == 'mac80211') {
935 var mode = ss.children[0],
936 bssid = ss.children[5],
939 mode.value('mesh', '802.11s');
940 mode.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
941 mode.value('monitor', _('Monitor'));
943 bssid.depends('mode', 'adhoc');
944 bssid.depends('mode', 'sta');
945 bssid.depends('mode', 'sta-wds');
947 o = ss.taboption('macfilter', form.ListValue, 'macfilter', _('MAC-Address Filter'));
948 o.depends('mode', 'ap');
949 o.depends('mode', 'ap-wds');
950 o.value('', _('disable'));
951 o.value('allow', _('Allow listed only'));
952 o.value('deny', _('Allow all except listed'));
954 o = ss.taboption('macfilter', form.DynamicList, 'maclist', _('MAC-List'));
955 o.datatype = 'macaddr';
956 o.depends('macfilter', 'allow');
957 o.depends('macfilter', 'deny');
958 o.load = function(section_id) {
959 return network.getHostHints().then(L.bind(function(hints) {
960 hints.getMACHints().map(L.bind(function(hint) {
961 this.value(hint[0], hint[1] ? '%s (%s)'.format(hint[0], hint[1]) : hint[0]);
964 return form.DynamicList.prototype.load.apply(this, [section_id]);
968 mode.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
969 mode.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
971 mode.write = function(section_id, value) {
974 uci.set('wireless', section_id, 'mode', 'ap');
975 uci.set('wireless', section_id, 'wds', '1');
979 uci.set('wireless', section_id, 'mode', 'sta');
980 uci.set('wireless', section_id, 'wds', '1');
984 uci.set('wireless', section_id, 'mode', value);
985 uci.unset('wireless', section_id, 'wds');
990 mode.cfgvalue = function(section_id) {
991 var mode = uci.get('wireless', section_id, 'mode'),
992 wds = uci.get('wireless', section_id, 'wds');
994 if (mode == 'ap' && wds)
996 else if (mode == 'sta' && wds)
1002 o = ss.taboption('general', form.Flag, 'hidden', _('Hide <abbr title="Extended Service Set Identifier">ESSID</abbr>'));
1003 o.depends('mode', 'ap');
1004 o.depends('mode', 'ap-wds');
1006 o = ss.taboption('general', form.Flag, 'wmm', _('WMM Mode'));
1007 o.depends('mode', 'ap');
1008 o.depends('mode', 'ap-wds');
1009 o.default = o.enabled;
1011 o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
1012 o.depends('mode', 'ap');
1013 o.depends('mode', 'ap-wds');
1015 o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name'));
1017 o.placeholder = radioNet.getIfname();
1018 if (/^radio\d+\.network/.test(o.placeholder))
1021 o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble'));
1022 o.default = o.enabled;
1024 o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
1027 o.datatype = 'range(1,255)';
1029 o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
1031 o.placeholder = 600;
1032 o.datatype = 'uinteger';
1034 o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling'));
1036 o.datatype = 'uinteger';
1038 o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('sec'));
1040 o.placeholder = 300;
1041 o.datatype = 'uinteger';
1043 o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval'));
1045 o.placeholder = 65535;
1046 o.datatype = 'uinteger';
1048 o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
1049 o.default = o.enabled;
1053 encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption'));
1054 o.depends('mode', 'ap');
1055 o.depends('mode', 'sta');
1056 o.depends('mode', 'adhoc');
1057 o.depends('mode', 'ahdemo');
1058 o.depends('mode', 'ap-wds');
1059 o.depends('mode', 'sta-wds');
1060 o.depends('mode', 'mesh');
1062 o.cfgvalue = function(section_id) {
1063 var v = String(uci.get('wireless', section_id, 'encryption'));
1066 else if (v.match(/\+/))
1067 return v.replace(/\+.+$/, '');
1071 o.write = function(section_id, value) {
1072 var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id),
1073 co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id);
1075 if (value == 'wpa' || value == 'wpa2')
1076 uci.unset('wireless', section_id, 'key');
1078 if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp'))
1081 uci.set('wireless', section_id, 'encryption', e);
1084 o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher'));
1085 o.depends('encryption', 'wpa');
1086 o.depends('encryption', 'wpa2');
1087 o.depends('encryption', 'psk');
1088 o.depends('encryption', 'psk2');
1089 o.depends('encryption', 'wpa-mixed');
1090 o.depends('encryption', 'psk-mixed');
1091 o.value('auto', _('auto'));
1092 o.value('ccmp', _('Force CCMP (AES)'));
1093 o.value('tkip', _('Force TKIP'));
1094 o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1095 o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write;
1097 o.cfgvalue = function(section_id) {
1098 var v = String(uci.get('wireless', section_id, 'encryption'));
1099 if (v.match(/\+/)) {
1100 v = v.replace(/^[^+]+\+/, '');
1103 else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip')
1110 var crypto_modes = [];
1112 if (hwtype == 'mac80211') {
1113 var has_supplicant = L.hasSystemFeature('wpasupplicant'),
1114 has_hostapd = L.hasSystemFeature('hostapd');
1116 // Probe EAP support
1117 var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'),
1118 has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap');
1120 // Probe SAE support
1121 var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'),
1122 has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae');
1124 // Probe OWE support
1125 var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
1126 has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
1129 if (has_hostapd || has_supplicant) {
1130 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1131 crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1132 crypto_modes.push(['psk', 'WPA-PSK', 21]);
1135 encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1138 if (has_ap_sae || has_sta_sae) {
1139 crypto_modes.push(['sae', 'WPA3-SAE', 31]);
1140 crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1143 if (has_ap_eap || has_sta_eap) {
1144 crypto_modes.push(['wpa2', 'WPA2-EAP', 32]);
1145 crypto_modes.push(['wpa', 'WPA-EAP', 20]);
1148 if (has_ap_owe || has_sta_owe) {
1149 crypto_modes.push(['owe', 'OWE', 1]);
1152 encr.crypto_support = {
1156 'psk': has_hostapd || _('Requires hostapd'),
1157 'psk2': has_hostapd || _('Requires hostapd'),
1158 'psk-mixed': has_hostapd || _('Requires hostapd'),
1159 'sae': has_ap_sae || _('Requires hostapd with SAE support'),
1160 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
1161 'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
1162 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
1163 'owe': has_ap_owe || _('Requires hostapd with OWE support')
1168 'psk': has_supplicant || _('Requires wpa-supplicant'),
1169 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1170 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1171 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1172 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1173 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1174 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1175 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
1180 'psk': has_supplicant || _('Requires wpa-supplicant'),
1181 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1182 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1185 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support')
1197 encr.crypto_support['ap-wds'] = encr.crypto_support['ap'];
1198 encr.crypto_support['sta-wds'] = encr.crypto_support['sta'];
1200 encr.validate = function(section_id, value) {
1201 var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
1202 modeval = modeopt.formvalue(section_id),
1203 modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)],
1204 enctitle = this.vallist[this.keylist.indexOf(value)];
1206 if (value == 'none')
1209 if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value))
1210 return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle);
1212 return this.crypto_support[modeval][value];
1215 else if (hwtype == 'broadcom') {
1216 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1217 crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1218 crypto_modes.push(['psk', 'WPA-PSK', 21]);
1221 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1222 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1223 crypto_modes.push(['none', _('No Encryption'), 0]);
1225 crypto_modes.sort(function(a, b) { return b[2] - a[2] });
1227 for (var i = 0; i < crypto_modes.length; i++) {
1228 var security_level = (crypto_modes[i][2] >= 30) ? _('strong security')
1229 : (crypto_modes[i][2] >= 20) ? _('medium security')
1230 : (crypto_modes[i][2] >= 10) ? _('weak security') : _('open network');
1232 encr.value(crypto_modes[i][0], '%s (%s)'.format(crypto_modes[i][1], security_level));
1236 o = ss.taboption('encryption', form.Value, 'auth_server', _('Radius-Authentication-Server'));
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' });
1242 o.datatype = 'host(0)';
1244 o = ss.taboption('encryption', form.Value, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
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 = 'port';
1252 o = ss.taboption('encryption', form.Value, 'auth_secret', _('Radius-Authentication-Secret'));
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' });
1260 o = ss.taboption('encryption', form.Value, 'acct_server', _('Radius-Accounting-Server'));
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' });
1266 o.datatype = 'host(0)';
1268 o = ss.taboption('encryption', form.Value, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
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 = 'port';
1276 o = ss.taboption('encryption', form.Value, 'acct_secret', _('Radius-Accounting-Secret'));
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' });
1284 o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client'));
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' });
1290 o.datatype = 'host(0)';
1292 o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
1293 o.depends({ mode: 'ap', encryption: 'wpa' });
1294 o.depends({ mode: 'ap', encryption: 'wpa2' });
1295 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1296 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1298 o.datatype = 'port';
1300 o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret'));
1301 o.depends({ mode: 'ap', encryption: 'wpa' });
1302 o.depends({ mode: 'ap', encryption: 'wpa2' });
1303 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1304 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1309 o = ss.taboption('encryption', form.Value, '_wpa_key', _('Key'));
1310 o.depends('encryption', 'psk');
1311 o.depends('encryption', 'psk2');
1312 o.depends('encryption', 'psk+psk2');
1313 o.depends('encryption', 'psk-mixed');
1314 o.depends('encryption', 'sae');
1315 o.depends('encryption', 'sae-mixed');
1316 o.datatype = 'wpakey';
1320 o.cfgvalue = function(section_id) {
1321 var key = uci.get('wireless', section_id, 'key');
1322 return /^[1234]$/.test(key) ? null : key;
1325 o.write = function(section_id, value) {
1326 uci.set('wireless', section_id, 'key', value);
1327 uci.unset('wireless', section_id, 'key1');
1328 uci.unset('wireless', section_id, 'key2');
1329 uci.unset('wireless', section_id, 'key3');
1330 uci.unset('wireless', section_id, 'key4');
1334 o = ss.taboption('encryption', form.ListValue, '_wep_key', _('Used Key Slot'));
1335 o.depends('encryption', 'wep-open');
1336 o.depends('encryption', 'wep-shared');
1337 o.value('1', _('Key #%d').format(1));
1338 o.value('2', _('Key #%d').format(2));
1339 o.value('3', _('Key #%d').format(3));
1340 o.value('4', _('Key #%d').format(4));
1342 o.cfgvalue = function(section_id) {
1343 var slot = +uci.get('wireless', section_id, 'key');
1344 return (slot >= 1 && slot <= 4) ? String(slot) : '';
1347 o.write = function(section_id, value) {
1348 uci.set('wireless', section_id, 'key', value);
1351 for (var slot = 1; slot <= 4; slot++) {
1352 o = ss.taboption('encryption', form.Value, 'key%d'.format(slot), _('Key #%d').format(slot));
1353 o.depends('encryption', 'wep-open');
1354 o.depends('encryption', 'wep-shared');
1355 o.datatype = 'wepkey';
1359 o.write = function(section_id, value) {
1360 if (value != null && (value.length == 5 || value.length == 13))
1361 value = 's:%s'.format(value);
1362 uci.set('wireless', section_id, this.option, value);
1367 if (hwtype == 'mac80211') {
1368 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1369 var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap');
1371 o = ss.taboption('encryption', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1372 o.depends({ mode: 'ap', encryption: 'wpa' });
1373 o.depends({ mode: 'ap', encryption: 'wpa2' });
1374 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1375 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1377 o.depends({ mode: 'ap', encryption: 'psk' });
1378 o.depends({ mode: 'ap', encryption: 'psk2' });
1379 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1380 o.depends({ mode: 'ap', encryption: 'sae' });
1381 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1382 o.depends({ mode: 'ap-wds', encryption: 'psk' });
1383 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1384 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1385 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1386 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1390 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.'));
1391 o.depends({ mode: 'ap', encryption: 'wpa' });
1392 o.depends({ mode: 'ap', encryption: 'wpa2' });
1393 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1394 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1395 o.depends({ ieee80211r: '1' });
1398 o = ss.taboption('encryption', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1399 o.depends({ ieee80211r: '1' });
1400 o.placeholder = '4f57';
1401 o.datatype = 'and(hexstring,length(4))';
1404 o = ss.taboption('encryption', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1405 o.depends({ ieee80211r: '1' });
1406 o.placeholder = '1000';
1407 o.datatype = 'range(1000,65535)';
1410 o = ss.taboption('encryption', form.ListValue, 'ft_over_ds', _('FT protocol'));
1411 o.depends({ ieee80211r: '1' });
1412 o.value('1', _('FT over DS'));
1413 o.value('0', _('FT over the Air'));
1416 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.'));
1417 o.depends({ ieee80211r: '1' });
1418 o.default = o.enabled;
1421 o = ss.taboption('encryption', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1422 o.depends({ ieee80211r: '1' });
1423 o.placeholder = '10000';
1424 o.datatype = 'uinteger';
1427 o = ss.taboption('encryption', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1428 o.depends({ ieee80211r: '1' });
1429 o.placeholder = '00004f577274';
1430 o.datatype = 'and(hexstring,length(12))';
1433 o = ss.taboption('encryption', form.Flag, 'pmk_r1_push', _('PMK R1 Push'));
1434 o.depends({ ieee80211r: '1' });
1435 o.placeholder = '0';
1438 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.'));
1439 o.depends({ ieee80211r: '1' });
1442 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.'));
1443 o.depends({ ieee80211r: '1' });
1445 // End of 802.11r options
1447 o = ss.taboption('encryption', form.ListValue, 'eap_type', _('EAP-Method'));
1448 o.value('tls', 'TLS');
1449 o.value('ttls', 'TTLS');
1450 o.value('peap', 'PEAP');
1451 o.value('fast', 'FAST');
1452 o.depends({ mode: 'sta', encryption: 'wpa' });
1453 o.depends({ mode: 'sta', encryption: 'wpa2' });
1454 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1455 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1457 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"));
1460 o.default = o.disabled;
1461 o.depends({ mode: 'sta', encryption: 'wpa' });
1462 o.depends({ mode: 'sta', encryption: 'wpa2' });
1463 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1464 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1465 o.validate = function(section_id, value) {
1466 if (value == '1' && !L.hasSystemFeature('cabundle')) {
1467 return _("This option cannot be used because the ca-bundle package is not installed.");
1472 o = ss.taboption('encryption', form.FileUpload, 'ca_cert', _('Path to CA-Certificate'));
1473 o.depends({ mode: 'sta', encryption: 'wpa', ca_cert_usesystem: '0' });
1474 o.depends({ mode: 'sta', encryption: 'wpa2', ca_cert_usesystem: '0' });
1475 o.depends({ mode: 'sta-wds', encryption: 'wpa', ca_cert_usesystem: '0' });
1476 o.depends({ mode: 'sta-wds', encryption: 'wpa2', ca_cert_usesystem: '0' });
1478 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"));
1479 o.depends({ mode: 'sta', encryption: 'wpa' });
1480 o.depends({ mode: 'sta', encryption: 'wpa2' });
1481 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1482 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1484 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"));
1485 o.depends({ mode: 'sta', encryption: 'wpa' });
1486 o.depends({ mode: 'sta', encryption: 'wpa2' });
1487 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1488 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1490 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)"));
1491 o.depends({ mode: 'sta', encryption: 'wpa' });
1492 o.depends({ mode: 'sta', encryption: 'wpa2' });
1493 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1494 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1496 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)"));
1497 o.depends({ mode: 'sta', encryption: 'wpa' });
1498 o.depends({ mode: 'sta', encryption: 'wpa2' });
1499 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1500 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1502 o = ss.taboption('encryption', form.FileUpload, 'client_cert', _('Path to Client-Certificate'));
1503 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1504 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1505 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1506 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1508 o = ss.taboption('encryption', form.FileUpload, 'priv_key', _('Path to Private Key'));
1509 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1510 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1511 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1512 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1514 o = ss.taboption('encryption', form.Value, 'priv_key_pwd', _('Password of Private Key'));
1515 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1516 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1517 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1518 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1521 o = ss.taboption('encryption', form.ListValue, 'auth', _('Authentication'));
1522 o.value('PAP', 'PAP');
1523 o.value('CHAP', 'CHAP');
1524 o.value('MSCHAP', 'MSCHAP');
1525 o.value('MSCHAPV2', 'MSCHAPv2');
1528 o.value('EAP-MSCHAPV2');
1530 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1531 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1532 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1533 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1534 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1535 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1536 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1537 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1538 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1539 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1540 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1541 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1543 o.validate = function(section_id, value) {
1544 var eo = this.section.children.filter(function(o) { return o.option == 'eap_type' })[0],
1545 ev = eo.formvalue(section_id);
1547 if (ev != 'ttls' && (value == 'PAP' || value == 'CHAP' || value == 'MSCHAP' || value == 'MSCHAPV2'))
1548 return _('This authentication type is not applicable to the selected EAP method.');
1553 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"));
1556 o.default = o.disabled;
1557 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1558 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1559 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1560 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1561 o.validate = function(section_id, value) {
1562 if (value == '1' && !L.hasSystemFeature('cabundle')) {
1563 return _("This option cannot be used because the ca-bundle package is not installed.");
1568 o = ss.taboption('encryption', form.FileUpload, 'ca_cert2', _('Path to inner CA-Certificate'));
1569 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa', ca_cert2_usesystem: '0' });
1570 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2', ca_cert2_usesystem: '0' });
1571 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa', ca_cert2_usesystem: '0' });
1572 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2', ca_cert2_usesystem: '0' });
1574 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"));
1575 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1576 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1577 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1578 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1580 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"));
1581 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1582 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1583 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1584 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1586 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)"));
1587 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1588 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1589 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1590 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1592 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)"));
1593 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1594 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1595 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1596 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1598 o = ss.taboption('encryption', form.FileUpload, 'client_cert2', _('Path to inner Client-Certificate'));
1599 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1600 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1601 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1602 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1604 o = ss.taboption('encryption', form.FileUpload, 'priv_key2', _('Path to inner Private Key'));
1605 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1606 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1607 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1608 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1610 o = ss.taboption('encryption', form.Value, 'priv_key2_pwd', _('Password of inner Private Key'));
1611 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1612 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1613 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1614 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1617 o = ss.taboption('encryption', form.Value, 'identity', _('Identity'));
1618 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1619 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1620 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1621 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1622 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1623 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1624 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1625 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1626 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1627 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1628 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1629 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1630 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1631 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1632 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1633 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1635 o = ss.taboption('encryption', form.Value, 'anonymous_identity', _('Anonymous Identity'));
1636 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1637 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1638 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1639 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1640 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1641 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1642 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1643 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1644 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1645 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1646 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1647 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1648 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1649 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1650 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1651 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1653 o = ss.taboption('encryption', form.Value, 'password', _('Password'));
1654 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1655 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1656 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1657 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1658 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1659 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1660 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1661 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1662 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1663 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1664 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1665 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1669 if (hwtype == 'mac80211') {
1670 // ieee802.11w options
1671 if (L.hasSystemFeature('hostapd', '11w')) {
1672 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)"));
1673 o.value('', _('Disabled'));
1674 o.value('1', _('Optional'));
1675 o.value('2', _('Required'));
1676 o.depends({ mode: 'ap', encryption: 'wpa2' });
1677 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1678 o.depends({ mode: 'ap', encryption: 'psk2' });
1679 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1680 o.depends({ mode: 'ap', encryption: 'sae' });
1681 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1682 o.depends({ mode: 'ap', encryption: 'owe' });
1683 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1684 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1685 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1686 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1687 o.depends({ mode: 'ap-wds', encryption: 'owe' });
1688 o.depends({ mode: 'sta', encryption: 'wpa2' });
1689 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1690 o.depends({ mode: 'sta', encryption: 'psk2' });
1691 o.depends({ mode: 'sta', encryption: 'psk-mixed' });
1692 o.depends({ mode: 'sta', encryption: 'sae' });
1693 o.depends({ mode: 'sta', encryption: 'sae-mixed' });
1694 o.depends({ mode: 'sta', encryption: 'owe' });
1695 o.depends({ mode: 'sta-wds', encryption: 'psk2' });
1696 o.depends({ mode: 'sta-wds', encryption: 'psk-mixed' });
1697 o.depends({ mode: 'sta-wds', encryption: 'sae' });
1698 o.depends({ mode: 'sta-wds', encryption: 'sae-mixed' });
1699 o.depends({ mode: 'sta-wds', encryption: 'owe' });
1701 '2': [{ encryption: 'sae' }, { encryption: 'owe' }],
1702 '1': [{ encryption: 'sae-mixed'}],
1706 o = ss.taboption('encryption', form.Value, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1707 o.depends('ieee80211w', '1');
1708 o.depends('ieee80211w', '2');
1709 o.datatype = 'uinteger';
1710 o.placeholder = '1000';
1713 o = ss.taboption('encryption', form.Value, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1714 o.depends('ieee80211w', '1');
1715 o.depends('ieee80211w', '2');
1716 o.datatype = 'uinteger';
1717 o.placeholder = '201';
1721 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.'));
1722 o.depends({ mode: 'ap', encryption: 'wpa2' });
1723 o.depends({ mode: 'ap', encryption: 'psk2' });
1724 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1725 o.depends({ mode: 'ap', encryption: 'sae' });
1726 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1727 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1728 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1729 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1730 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1731 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1733 if (L.hasSystemFeature('hostapd', 'cli') && L.hasSystemFeature('wpasupplicant')) {
1734 o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1737 o.default = o.disabled;
1738 o.depends('encryption', 'psk');
1739 o.depends('encryption', 'psk2');
1740 o.depends('encryption', 'psk-mixed');
1741 o.depends('encryption', 'sae');
1742 o.depends('encryption', 'sae-mixed');
1749 s.handleRemove = function(section_id, ev) {
1750 document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id)).style.opacity = 0.5;
1751 return form.TypedSection.prototype.handleRemove.apply(this, [section_id, ev]);
1754 s.handleScan = function(radioDev, ev) {
1755 var table = E('div', { 'class': 'table' }, [
1756 E('div', { 'class': 'tr table-titles' }, [
1757 E('div', { 'class': 'th col-2 middle center' }, _('Signal')),
1758 E('div', { 'class': 'th col-4 middle left' }, _('SSID')),
1759 E('div', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1760 E('div', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1761 E('div', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1762 E('div', { 'class': 'th col-3 middle left' }, _('Encryption')),
1763 E('div', { 'class': 'th cbi-section-actions right' }, ' '),
1767 var stop = E('button', {
1769 'click': L.bind(this.handleScanStartStop, this),
1770 'style': 'display:none',
1771 'data-state': 'stop'
1772 }, _('Stop refresh'));
1774 cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1776 var md = ui.showModal(_('Join Network: Wireless Scan'), [
1778 E('div', { 'class': 'right' }, [
1783 'click': L.bind(this.handleScanAbort, this)
1788 md.style.maxWidth = '90%';
1789 md.style.maxHeight = 'none';
1791 this.pollFn = L.bind(this.handleScanRefresh, this, radioDev, {}, table, stop);
1793 L.Poll.add(this.pollFn);
1797 s.handleScanRefresh = function(radioDev, scanCache, table, stop) {
1798 return radioDev.getScanList().then(L.bind(function(results) {
1801 for (var i = 0; i < results.length; i++)
1802 scanCache[results[i].bssid] = results[i];
1804 for (var k in scanCache)
1805 if (scanCache[k].stale)
1806 results.push(scanCache[k]);
1808 results.sort(function(a, b) {
1809 var diff = (b.quality - a.quality) || (a.channel - b.channel);
1814 if (a.ssid < b.ssid)
1816 else if (a.ssid > b.ssid)
1819 if (a.bssid < b.bssid)
1821 else if (a.bssid > b.bssid)
1825 for (var i = 0; i < results.length; i++) {
1826 var res = results[i],
1827 qv = res.quality || 0,
1828 qm = res.quality_max || 0,
1829 q = (qv > 0 && qm > 0) ? Math.floor((100 / qm) * qv) : 0,
1830 s = res.stale ? 'opacity:0.5' : '';
1833 E('span', { 'style': s }, render_signal_badge(q, res.signal, res.noise)),
1834 E('span', { 'style': s }, (res.ssid != null) ? '%h'.format(res.ssid) : E('em', _('hidden'))),
1835 E('span', { 'style': s }, '%d'.format(res.channel)),
1836 E('span', { 'style': s }, '%h'.format(res.mode)),
1837 E('span', { 'style': s }, '%h'.format(res.bssid)),
1838 E('span', { 'style': s }, '%h'.format(network.formatWifiEncryption(res.encryption))),
1839 E('div', { 'class': 'right' }, E('button', {
1840 'class': 'cbi-button cbi-button-action important',
1841 'click': L.bind(this.handleJoin, this, radioDev, res)
1842 }, _('Join Network')))
1848 cbi_update_table(table, rows);
1850 stop.disabled = false;
1851 stop.style.display = '';
1852 stop.classList.remove('spinning');
1856 s.handleScanStartStop = function(ev) {
1857 var btn = ev.currentTarget;
1859 if (btn.getAttribute('data-state') == 'stop') {
1860 L.Poll.remove(this.pollFn);
1861 btn.firstChild.data = _('Start refresh');
1862 btn.setAttribute('data-state', 'start');
1865 L.Poll.add(this.pollFn);
1866 btn.firstChild.data = _('Stop refresh');
1867 btn.setAttribute('data-state', 'stop');
1868 btn.classList.add('spinning');
1869 btn.disabled = true;
1873 s.handleScanAbort = function(ev) {
1874 var md = L.dom.parent(ev.target, 'div[aria-modal="true"]');
1876 md.style.maxWidth = '';
1877 md.style.maxHeight = '';
1881 L.Poll.remove(this.pollFn);
1886 s.handleJoinConfirm = function(radioDev, bss, form, ev) {
1887 var nameopt = L.toArray(form.lookupOption('name', '_new_'))[0],
1888 passopt = L.toArray(form.lookupOption('password', '_new_'))[0],
1889 bssidopt = L.toArray(form.lookupOption('bssid', '_new_'))[0],
1890 zoneopt = L.toArray(form.lookupOption('zone', '_new_'))[0],
1891 replopt = L.toArray(form.lookupOption('replace', '_new_'))[0],
1892 nameval = (nameopt && nameopt.isValid('_new_')) ? nameopt.formvalue('_new_') : null,
1893 passval = (passopt && passopt.isValid('_new_')) ? passopt.formvalue('_new_') : null,
1894 bssidval = (bssidopt && bssidopt.isValid('_new_')) ? bssidopt.formvalue('_new_') : null,
1895 zoneval = zoneopt ? zoneopt.formvalue('_new_') : null,
1896 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1897 is_wep = (enc && Array.isArray(enc.wep)),
1898 is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' })),
1899 is_sae = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'sae' }));
1901 if (nameval == null || (passopt && passval == null))
1904 var section_id = null;
1906 return this.map.save(function() {
1907 var wifi_sections = uci.sections('wireless', 'wifi-iface');
1909 if (replopt.formvalue('_new_') == '1') {
1910 for (var i = 0; i < wifi_sections.length; i++)
1911 if (wifi_sections[i].device == radioDev.getName())
1912 uci.remove('wireless', wifi_sections[i]['.name']);
1915 if (uci.get('wireless', radioDev.getName(), 'disabled') == '1') {
1916 for (var i = 0; i < wifi_sections.length; i++)
1917 if (wifi_sections[i].device == radioDev.getName())
1918 uci.set('wireless', wifi_sections[i]['.name'], 'disabled', '1');
1920 uci.unset('wireless', radioDev.getName(), 'disabled');
1923 section_id = next_free_sid(wifi_sections.length);
1925 uci.add('wireless', 'wifi-iface', section_id);
1926 uci.set('wireless', section_id, 'device', radioDev.getName());
1927 uci.set('wireless', section_id, 'mode', (bss.mode == 'Ad-Hoc') ? 'adhoc' : 'sta');
1928 uci.set('wireless', section_id, 'network', nameval);
1930 if (bss.ssid != null) {
1931 uci.set('wireless', section_id, 'ssid', bss.ssid);
1933 if (bssidval == '1')
1934 uci.set('wireless', section_id, 'bssid', bss.bssid);
1936 else if (bss.bssid != null) {
1937 uci.set('wireless', section_id, 'bssid', bss.bssid);
1941 uci.set('wireless', section_id, 'encryption', 'sae');
1942 uci.set('wireless', section_id, 'key', passval);
1945 for (var i = enc.wpa.length - 1; i >= 0; i--) {
1946 if (enc.wpa[i] == 2) {
1947 uci.set('wireless', section_id, 'encryption', 'psk2');
1950 else if (enc.wpa[i] == 1) {
1951 uci.set('wireless', section_id, 'encryption', 'psk');
1956 uci.set('wireless', section_id, 'key', passval);
1959 uci.set('wireless', section_id, 'encryption', 'wep-open');
1960 uci.set('wireless', section_id, 'key', '1');
1961 uci.set('wireless', section_id, 'key1', passval);
1964 uci.set('wireless', section_id, 'encryption', 'none');
1967 return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) {
1968 firewall.deleteNetwork(net.getName());
1970 var zonePromise = zoneval
1971 ? firewall.getZone(zoneval).then(function(zone) { return zone || firewall.addZone(zoneval) })
1972 : Promise.resolve();
1974 return zonePromise.then(function(zone) {
1976 zone.addNetwork(net.getName());
1979 }).then(L.bind(function() {
1980 return this.renderMoreOptionsModal(section_id);
1984 s.handleJoin = function(radioDev, bss, ev) {
1985 this.handleScanAbort(ev);
1987 var m2 = new form.Map('wireless'),
1988 s2 = m2.section(form.NamedSection, '_new_'),
1989 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1990 is_wep = (enc && Array.isArray(enc.wep)),
1991 is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' || a == 'sae' })),
1992 replace, passphrase, name, bssid, zone;
1994 var nameUsed = function(name) {
1995 var s = uci.get('network', name);
1996 if (s != null && s['.type'] != 'interface')
1999 var net = (s != null) ? network.instantiateNetwork(name) : null;
2000 return (net != null && !net.isEmpty());
2003 s2.render = function() {
2004 return Promise.all([
2006 this.renderUCISection('_new_')
2007 ]).then(this.renderContents.bind(this));
2010 replace = s2.option(form.Flag, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
2012 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>'));
2013 name.datatype = 'uciname';
2014 name.default = 'wwan';
2015 name.rmempty = false;
2016 name.validate = function(section_id, value) {
2017 if (nameUsed(value))
2018 return _('The network name is already used');
2023 for (var i = 2; nameUsed(name.default); i++)
2024 name.default = 'wwan%d'.format(i);
2026 if (is_wep || is_psk) {
2027 passphrase = s2.option(form.Value, 'password', is_wep ? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
2028 passphrase.datatype = is_wep ? 'wepkey' : 'wpakey';
2029 passphrase.password = true;
2030 passphrase.rmempty = false;
2033 if (bss.ssid != null) {
2034 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));
2035 bssid.default = '0';
2038 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.'));
2039 zone.default = 'wan';
2041 return m2.render().then(L.bind(function(nodes) {
2042 ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [
2044 E('div', { 'class': 'right' }, [
2047 'click': ui.hideModal
2048 }, _('Cancel')), ' ',
2050 'class': 'cbi-button cbi-button-positive important',
2051 'click': ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2)
2054 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase || name).cbid('_new_'))).focus();
2058 s.handleAdd = function(radioDev, ev) {
2059 var section_id = next_free_sid(uci.sections('wireless', 'wifi-iface').length);
2061 uci.unset('wireless', radioDev.getName(), 'disabled');
2063 uci.add('wireless', 'wifi-iface', section_id);
2064 uci.set('wireless', section_id, 'device', radioDev.getName());
2065 uci.set('wireless', section_id, 'mode', 'ap');
2066 uci.set('wireless', section_id, 'ssid', 'OpenWrt');
2067 uci.set('wireless', section_id, 'encryption', 'none');
2069 this.addedSection = section_id;
2070 return this.renderMoreOptionsModal(section_id);
2073 o = s.option(form.DummyValue, '_badge');
2074 o.modalonly = false;
2075 o.textvalue = function(section_id) {
2076 var inst = this.section.lookupRadioOrNetwork(section_id),
2077 node = E('div', { 'class': 'center' });
2079 if (inst.getWifiNetworks)
2080 node.appendChild(render_radio_badge(inst));
2082 node.appendChild(render_network_badge(inst));
2087 o = s.option(form.DummyValue, '_stat');
2088 o.modalonly = false;
2089 o.textvalue = function(section_id) {
2090 var inst = this.section.lookupRadioOrNetwork(section_id);
2092 if (inst.getWifiNetworks)
2093 return render_radio_status(inst, this.section.wifis.filter(function(e) {
2094 return (e.getWifiDeviceName() == inst.getName());
2097 return render_network_status(inst);
2100 return m.render().then(L.bind(function(m, nodes) {
2101 L.Poll.add(L.bind(function() {
2102 var section_ids = m.children[0].cfgsections(),
2103 tasks = [ network.getHostHints(), network.getWifiDevices() ];
2105 for (var i = 0; i < section_ids.length; i++) {
2106 var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])),
2107 dsc = row.querySelector('[data-name="_stat"] > div'),
2108 btns = row.querySelectorAll('.cbi-section-actions button');
2110 if (dsc.getAttribute('restart') == '') {
2111 dsc.setAttribute('restart', '1');
2112 tasks.push(fs.exec('/sbin/wifi', ['up', section_ids[i]]).catch(function(e) {
2113 ui.addNotification(null, E('p', e.message));
2116 else if (dsc.getAttribute('restart') == '1') {
2117 dsc.removeAttribute('restart');
2118 btns[0].classList.remove('spinning');
2119 btns[0].disabled = false;
2123 return Promise.all(tasks)
2124 .then(L.bind(function(hosts_radios) {
2127 for (var i = 0; i < hosts_radios[1].length; i++)
2128 tasks.push(hosts_radios[1][i].getWifiNetworks());
2130 return Promise.all(tasks).then(function(data) {
2131 hosts_radios[2] = [];
2133 for (var i = 0; i < data.length; i++)
2134 hosts_radios[2].push.apply(hosts_radios[2], data[i]);
2136 return hosts_radios;
2139 .then(L.bind(function(hosts_radios_wifis) {
2142 for (var i = 0; i < hosts_radios_wifis[2].length; i++)
2143 tasks.push(hosts_radios_wifis[2][i].getAssocList());
2145 return Promise.all(tasks).then(function(data) {
2146 hosts_radios_wifis[3] = [];
2148 for (var i = 0; i < data.length; i++) {
2149 var wifiNetwork = hosts_radios_wifis[2][i],
2150 radioDev = hosts_radios_wifis[1].filter(function(d) { return d.getName() == wifiNetwork.getWifiDeviceName() })[0];
2152 for (var j = 0; j < data[i].length; j++)
2153 hosts_radios_wifis[3].push(Object.assign({ radio: radioDev, network: wifiNetwork }, data[i][j]));
2156 return hosts_radios_wifis;
2159 .then(L.bind(this.poll_status, this, nodes));
2162 var table = E('div', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [
2163 E('div', { 'class': 'tr table-titles' }, [
2164 E('div', { 'class': 'th nowrap' }, _('Network')),
2165 E('div', { 'class': 'th hide-xs' }, _('MAC-Address')),
2166 E('div', { 'class': 'th' }, _('Host')),
2167 E('div', { 'class': 'th nowrap' }, _('Signal / Noise')),
2168 E('div', { 'class': 'th nowrap' }, _('RX Rate / TX Rate'))
2172 cbi_update_table(table, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
2174 return E([ nodes, E('h3', _('Associated Stations')), table ]);