12 'require tools.widgets as widgets';
14 function count_changes(section_id) {
15 var changes = ui.changes.changes, n = 0;
17 if (!L.isObject(changes))
20 if (Array.isArray(changes.wireless))
21 for (var i = 0; i < changes.wireless.length; i++)
22 n += (changes.wireless[i][1] == section_id);
27 function render_radio_badge(radioDev) {
28 return E('span', { 'class': 'ifacebadge' }, [
29 E('img', { 'src': L.resource('icons/wifi%s.png').format(radioDev.isUp() ? '' : '_disabled') }),
35 function render_signal_badge(signalPercent, signalValue, noiseValue, wrap, mode) {
36 var icon, title, value;
38 if (signalPercent < 0)
39 icon = L.resource('icons/signal-none.png');
40 else if (signalPercent == 0)
41 icon = L.resource('icons/signal-0.png');
42 else if (signalPercent < 25)
43 icon = L.resource('icons/signal-0-25.png');
44 else if (signalPercent < 50)
45 icon = L.resource('icons/signal-25-50.png');
46 else if (signalPercent < 75)
47 icon = L.resource('icons/signal-50-75.png');
49 icon = L.resource('icons/signal-75-100.png');
51 if (signalValue != null && signalValue != 0) {
52 if (noiseValue != null && noiseValue != 0) {
53 value = '%d/%d\xa0%s'.format(signalValue, noiseValue, _('dBm'));
54 title = '%s: %d %s / %s: %d %s / %s %d'.format(
55 _('Signal'), signalValue, _('dBm'),
56 _('Noise'), noiseValue, _('dBm'),
57 _('SNR'), signalValue - noiseValue);
60 value = '%d\xa0%s'.format(signalValue, _('dBm'));
61 title = '%s: %d %s'.format(_('Signal'), signalValue, _('dBm'));
64 else if (signalPercent > -1) {
67 title = _('No client associated');
73 title = _('Not associated');
77 title = _('No RX signal');
80 if (noiseValue != null && noiseValue != 0) {
81 value = '---/%d\x0a%s'.format(noiseValue, _('dBm'));
82 title = '%s / %s: %d %s'.format(title, _('Noise'), noiseValue, _('dBm'));
85 value = '---\xa0%s'.format(_('dBm'));
89 value = E('em', {}, E('small', {}, [ _('disabled') ]));
90 title = _('Interface is disabled');
94 'class': wrap ? 'center' : 'ifacebadge',
96 'data-signal': signalValue,
97 'data-noise': noiseValue
99 E('img', { 'src': icon }),
101 wrap ? E('br') : ' ',
107 function render_network_badge(radioNet) {
108 return render_signal_badge(
109 radioNet.isUp() ? radioNet.getSignalPercent() : -1,
110 radioNet.getSignal(), radioNet.getNoise(), false, radioNet.getMode());
113 function render_radio_status(radioDev, wifiNets) {
114 var name = radioDev.getI18n().replace(/ Wireless Controller .+$/, ''),
115 node = E('div', [ E('big', {}, E('strong', {}, name)), E('div') ]),
116 channel, frequency, bitrate;
118 for (var i = 0; i < wifiNets.length; i++) {
119 channel = channel || wifiNets[i].getChannel();
120 frequency = frequency || wifiNets[i].getFrequency();
121 bitrate = bitrate || wifiNets[i].getBitRate();
125 L.itemlist(node.lastElementChild, [
126 _('Channel'), '%s (%s %s)'.format(channel || '?', frequency || '?', _('GHz')),
127 _('Bitrate'), '%s %s'.format(bitrate || '?', _('Mbit/s'))
130 node.lastElementChild.appendChild(E('em', _('Device is not active')));
135 function render_network_status(radioNet) {
136 var mode = radioNet.getActiveMode(),
137 bssid = radioNet.getActiveBSSID(),
138 channel = radioNet.getChannel(),
139 disabled = (radioNet.get('disabled') == '1' || uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == '1'),
140 is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled),
141 is_mesh = (radioNet.getMode() == 'mesh'),
142 changecount = count_changes(radioNet.getName()),
146 status_text = E('a', {
148 click: L.bind(ui.changes.displayChanges, ui.changes)
149 }, _('Interface has %d pending changes').format(changecount));
151 status_text = E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated'));
153 return L.itemlist(E('div'), [
154 is_mesh ? _('Mesh ID') : _('SSID'), (is_mesh ? radioNet.getMeshID() : radioNet.getSSID()) || '?',
156 _('BSSID'), (!changecount && is_assoc) ? bssid : null,
157 _('Encryption'), (!changecount && is_assoc) ? radioNet.getActiveEncryption() || _('None') : null,
159 ], [ ' | ', E('br') ]);
162 function render_modal_status(node, radioNet) {
163 var mode = radioNet.getActiveMode(),
164 noise = radioNet.getNoise(),
165 bssid = radioNet.getActiveBSSID(),
166 channel = radioNet.getChannel(),
167 disabled = (radioNet.get('disabled') == '1'),
168 is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled);
171 node = E('span', { 'class': 'ifacebadge large', 'data-network': radioNet.getName() }, [ E('small'), E('span') ]);
173 dom.content(node.firstElementChild, render_signal_badge(
174 disabled ? -1 : radioNet.getSignalPercent(),
175 radioNet.getSignal(), noise, true, radioNet.getMode()));
177 L.itemlist(node.lastElementChild, [
179 _('SSID'), radioNet.getSSID() || '?',
180 _('BSSID'), is_assoc ? bssid : null,
181 _('Encryption'), is_assoc ? radioNet.getActiveEncryption() || _('None') : null,
182 _('Channel'), is_assoc ? '%d (%.3f %s)'.format(radioNet.getChannel(), radioNet.getFrequency() || 0, _('GHz')) : null,
183 _('Tx-Power'), is_assoc ? '%d %s'.format(radioNet.getTXPower(), _('dBm')) : null,
184 _('Signal'), is_assoc ? '%d %s'.format(radioNet.getSignal(), _('dBm')) : null,
185 _('Noise'), (is_assoc && noise != null) ? '%d %s'.format(noise, _('dBm')) : null,
186 _('Bitrate'), is_assoc ? '%.1f %s'.format(radioNet.getBitRate() || 0, _('Mbit/s')) : null,
187 _('Country'), is_assoc ? radioNet.getCountryCode() : null
188 ], [ ' | ', E('br'), E('br'), E('br'), E('br'), E('br'), ' | ', E('br'), ' | ' ]);
191 dom.append(node.lastElementChild, E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated')));
196 function format_wifirate(rate) {
197 var s = '%.1f\xa0%s, %d\xa0%s'.format(rate.rate / 1000, _('Mbit/s'), rate.mhz, _('MHz')),
198 ht = rate.ht, vht = rate.vht,
199 mhz = rate.mhz, nss = rate.nss,
200 mcs = rate.mcs, sgi = rate.short_gi;
203 if (vht) s += ', VHT-MCS\xa0%d'.format(mcs);
204 if (nss) s += ', VHT-NSS\xa0%d'.format(nss);
205 if (ht) s += ', MCS\xa0%s'.format(mcs);
206 if (sgi) s += ', ' + _('Short GI').replace(/ /g, '\xa0');
212 function radio_restart(id, ev) {
213 var row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id)),
214 dsc = row.querySelector('[data-name="_stat"] > div'),
215 btn = row.querySelector('.cbi-section-actions button');
218 btn.classList.add('spinning');
221 dsc.setAttribute('restart', '');
222 dom.content(dsc, E('em', _('Device is restarting…')));
225 function network_updown(id, map, ev) {
226 var radio = uci.get('wireless', id, 'device'),
227 disabled = (uci.get('wireless', id, 'disabled') == '1') ||
228 (uci.get('wireless', radio, 'disabled') == '1');
231 uci.unset('wireless', id, 'disabled');
232 uci.unset('wireless', radio, 'disabled');
235 uci.set('wireless', id, 'disabled', '1');
237 var all_networks_disabled = true,
238 wifi_ifaces = uci.sections('wireless', 'wifi-iface');
240 for (var i = 0; i < wifi_ifaces.length; i++) {
241 if (wifi_ifaces[i].device == radio && wifi_ifaces[i].disabled != '1') {
242 all_networks_disabled = false;
247 if (all_networks_disabled)
248 uci.set('wireless', radio, 'disabled', '1');
251 return map.save().then(function() {
256 function next_free_sid(offset) {
257 var sid = 'wifinet' + offset;
259 while (uci.get('wireless', sid))
260 sid = 'wifinet' + (++offset);
265 function add_dependency_permutations(o, deps) {
268 for (var key in deps) {
269 if (!deps.hasOwnProperty(key) || !Array.isArray(deps[key]))
272 var list = deps[key],
275 for (var j = 0; j < list.length; j++) {
276 for (var k = 0; k < (res ? res.length : 1); k++) {
277 var item = (res ? Object.assign({}, res[k]) : {});
286 for (var i = 0; i < (res ? res.length : 0); i++)
290 var CBIWifiFrequencyValue = form.Value.extend({
291 callFrequencyList: rpc.declare({
294 params: [ 'device' ],
295 expect: { results: [] }
298 load: function(section_id) {
300 network.getWifiDevice(section_id),
301 this.callFrequencyList(section_id)
302 ]).then(L.bind(function(data) {
304 '11g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
305 '11a': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : []
308 for (var i = 0; i < data[1].length; i++)
309 this.channels[(data[1][i].mhz > 2484) ? '11a' : '11g'].push(
311 '%d (%d Mhz)'.format(data[1][i].channel, data[1][i].mhz),
312 !data[1][i].restricted
315 var hwmodelist = L.toArray(data[0] ? data[0].getHWModes() : null)
316 .reduce(function(o, v) { o[v] = true; return o }, {});
320 'n', 'N', hwmodelist.n,
321 'ac', 'AC', hwmodelist.ac
324 var htmodelist = L.toArray(data[0] ? data[0].getHTModes() : null)
325 .reduce(function(o, v) { o[v] = true; return o }, {});
328 '': [ '', '-', true ],
330 'HT20', '20 MHz', htmodelist.HT20,
331 'HT40', '40 MHz', htmodelist.HT40
334 'VHT20', '20 MHz', htmodelist.VHT20,
335 'VHT40', '40 MHz', htmodelist.VHT40,
336 'VHT80', '80 MHz', htmodelist.VHT80,
337 'VHT160', '160 MHz', htmodelist.VHT160
343 '11g', '2.4 GHz', this.channels['11g'].length > 3,
344 '11a', '5 GHz', this.channels['11a'].length > 3
347 '11g', '2.4 GHz', this.channels['11g'].length > 3,
348 '11a', '5 GHz', this.channels['11a'].length > 3
357 setValues: function(sel, vals) {
359 sel.vals.selected = sel.selectedIndex;
361 while (sel.options[0])
364 for (var i = 0; vals && i < vals.length; i += 3)
366 sel.add(E('option', { value: vals[i+0] }, [ vals[i+1] ]));
368 if (vals && !isNaN(vals.selected))
369 sel.selectedIndex = vals.selected;
371 sel.parentNode.style.display = (sel.options.length <= 1) ? 'none' : '';
375 toggleWifiMode: function(elem) {
376 this.toggleWifiHTMode(elem);
377 this.toggleWifiBand(elem);
380 toggleWifiHTMode: function(elem) {
381 var mode = elem.querySelector('.mode');
382 var bwdt = elem.querySelector('.htmode');
384 this.setValues(bwdt, this.htmodes[mode.value]);
387 toggleWifiBand: function(elem) {
388 var mode = elem.querySelector('.mode');
389 var band = elem.querySelector('.band');
391 this.setValues(band, this.bands[mode.value]);
392 this.toggleWifiChannel(elem);
395 toggleWifiChannel: function(elem) {
396 var band = elem.querySelector('.band');
397 var chan = elem.querySelector('.channel');
399 this.setValues(chan, this.channels[band.value]);
402 setInitialValues: function(section_id, elem) {
403 var mode = elem.querySelector('.mode'),
404 band = elem.querySelector('.band'),
405 chan = elem.querySelector('.channel'),
406 bwdt = elem.querySelector('.htmode'),
407 htval = uci.get('wireless', section_id, 'htmode'),
408 hwval = uci.get('wireless', section_id, 'hwmode'),
409 chval = uci.get('wireless', section_id, 'channel');
411 this.setValues(mode, this.modes);
413 if (/VHT20|VHT40|VHT80|VHT160/.test(htval))
415 else if (/HT20|HT40/.test(htval))
420 this.toggleWifiMode(elem);
427 this.toggleWifiBand(elem);
435 renderWidget: function(section_id, option_index, cfgvalue) {
439 E('label', { 'style': 'float:left; margin-right:3px' }, [
443 'style': 'width:auto',
444 'change': L.bind(this.toggleWifiMode, this, elem)
447 E('label', { 'style': 'float:left; margin-right:3px' }, [
451 'style': 'width:auto',
452 'change': L.bind(this.toggleWifiBand, this, elem)
455 E('label', { 'style': 'float:left; margin-right:3px' }, [
456 _('Channel'), E('br'),
459 'style': 'width:auto'
462 E('label', { 'style': 'float:left; margin-right:3px' }, [
466 'style': 'width:auto'
469 E('br', { 'style': 'clear:left' })
472 return this.setInitialValues(section_id, elem);
475 cfgvalue: function(section_id) {
477 uci.get('wireless', section_id, 'htmode'),
478 uci.get('wireless', section_id, 'hwmode'),
479 uci.get('wireless', section_id, 'channel')
483 formvalue: function(section_id) {
484 var node = this.map.findElement('data-field', this.cbid(section_id));
487 node.querySelector('.htmode').value,
488 node.querySelector('.band').value,
489 node.querySelector('.channel').value
493 write: function(section_id, value) {
494 uci.set('wireless', section_id, 'htmode', value[0] || null);
495 uci.set('wireless', section_id, 'hwmode', value[1]);
496 uci.set('wireless', section_id, 'channel', value[2]);
500 var CBIWifiTxPowerValue = form.ListValue.extend({
501 callTxPowerList: rpc.declare({
503 method: 'txpowerlist',
504 params: [ 'device' ],
505 expect: { results: [] }
508 load: function(section_id) {
509 return this.callTxPowerList(section_id).then(L.bind(function(pwrlist) {
510 this.powerval = this.wifiNetwork ? this.wifiNetwork.getTXPower() : null;
511 this.poweroff = this.wifiNetwork ? this.wifiNetwork.getTXPowerOffset() : null;
513 this.value('', _('driver default'));
515 for (var i = 0; i < pwrlist.length; i++)
516 this.value(pwrlist[i].dbm, '%d dBm (%d mW)'.format(pwrlist[i].dbm, pwrlist[i].mw));
518 return form.ListValue.prototype.load.apply(this, [section_id]);
522 renderWidget: function(section_id, option_index, cfgvalue) {
523 var widget = form.ListValue.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
524 widget.firstElementChild.style.width = 'auto';
526 dom.append(widget, E('span', [
527 ' - ', _('Current power'), ': ',
528 E('span', [ this.powerval != null ? '%d dBm'.format(this.powerval) : E('em', _('unknown')) ]),
529 this.poweroff ? ' + %d dB offset = %s dBm'.format(this.poweroff, this.powerval != null ? this.powerval + this.poweroff : '?') : ''
536 var CBIWifiCountryValue = form.Value.extend({
537 callCountryList: rpc.declare({
539 method: 'countrylist',
540 params: [ 'device' ],
541 expect: { results: [] }
544 load: function(section_id) {
545 return this.callCountryList(section_id).then(L.bind(function(countrylist) {
546 if (Array.isArray(countrylist) && countrylist.length > 0) {
547 this.value('', _('driver default'));
549 for (var i = 0; i < countrylist.length; i++)
550 this.value(countrylist[i].iso3166, '%s - %s'.format(countrylist[i].iso3166, countrylist[i].country));
553 return form.Value.prototype.load.apply(this, [section_id]);
557 validate: function(section_id, formvalue) {
558 if (formvalue != null && formvalue != '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue))
559 return _('Use ISO/IEC 3166 alpha2 country codes.');
564 renderWidget: function(section_id, option_index, cfgvalue) {
565 var typeClass = (this.keylist && this.keylist.length) ? form.ListValue : form.Value;
566 return typeClass.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
571 poll_status: function(map, data) {
572 var rows = map.querySelectorAll('.cbi-section-table-row[data-sid]');
574 for (var i = 0; i < rows.length; i++) {
575 var section_id = rows[i].getAttribute('data-sid'),
576 radioDev = data[1].filter(function(d) { return d.getName() == section_id })[0],
577 radioNet = data[2].filter(function(n) { return n.getName() == section_id })[0],
578 badge = rows[i].querySelector('[data-name="_badge"] > div'),
579 stat = rows[i].querySelector('[data-name="_stat"]'),
580 btns = rows[i].querySelectorAll('.cbi-section-actions button'),
581 busy = btns[0].classList.contains('spinning') || btns[1].classList.contains('spinning') || btns[2].classList.contains('spinning');
584 dom.content(badge, render_radio_badge(radioDev));
585 dom.content(stat, render_radio_status(radioDev, data[2].filter(function(n) { return n.getWifiDeviceName() == radioDev.getName() })));
588 dom.content(badge, render_network_badge(radioNet));
589 dom.content(stat, render_network_status(radioNet));
592 if (stat.hasAttribute('restart'))
593 dom.content(stat, E('em', _('Device is restarting…')));
595 btns[0].disabled = busy;
596 btns[1].disabled = busy;
597 btns[2].disabled = busy;
600 var table = document.querySelector('#wifi_assoclist_table'),
604 for (var i = 0; i < data[3].length; i++) {
605 var bss = data[3][i],
606 name = hosts.getHostnameByMACAddr(bss.mac),
607 ipv4 = hosts.getIPAddrByMACAddr(bss.mac),
608 ipv6 = hosts.getIP6AddrByMACAddr(bss.mac);
612 if (name && ipv4 && ipv6)
613 hint = '%s <span class="hide-xs">(%s, %s)</span>'.format(name, ipv4, ipv6);
614 else if (name && (ipv4 || ipv6))
615 hint = '%s <span class="hide-xs">(%s)</span>'.format(name, ipv4 || ipv6);
617 hint = name || ipv4 || ipv6 || '?';
621 'class': 'ifacebadge',
622 'data-ifname': bss.network.getIfname(),
623 'data-ssid': bss.network.getSSID()
626 'src': L.resource('icons/wifi%s.png').format(bss.network.isUp() ? '' : '_disabled'),
627 'title': bss.radio.getI18n()
630 ' %s '.format(bss.network.getShortName()),
631 E('small', '(%s)'.format(bss.network.getIfname()))
636 render_signal_badge(Math.min((bss.signal + 110) / 70 * 100, 100), bss.signal, bss.noise),
638 E('span', format_wifirate(bss.rx)),
640 E('span', format_wifirate(bss.tx))
644 if (bss.network.isClientDisconnectSupported()) {
645 if (table.firstElementChild.childNodes.length < 6)
646 table.firstElementChild.appendChild(E('div', { 'class': 'th cbi-section-actions'}));
648 row.push(E('button', {
649 'class': 'cbi-button cbi-button-remove',
650 'click': L.bind(function(net, mac, ev) {
651 dom.parent(ev.currentTarget, '.tr').style.opacity = 0.5;
652 ev.currentTarget.classList.add('spinning');
653 ev.currentTarget.disabled = true;
654 ev.currentTarget.blur();
656 net.disconnectClient(mac, true, 5, 60000);
657 }, this, bss.network, bss.mac)
658 }, [ _('Disconnect') ]));
667 cbi_update_table(table, trows, E('em', _('No information available')));
669 var stat = document.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large');
672 render_modal_status(stat, data[2].filter(function(n) { return n.getName() == stat.getAttribute('data-network') })[0]);
674 return network.flushCache();
684 checkAnonymousSections: function() {
685 var wifiIfaces = uci.sections('wireless', 'wifi-iface');
687 for (var i = 0; i < wifiIfaces.length; i++)
688 if (wifiIfaces[i]['.anonymous'])
694 callUciRename: rpc.declare({
697 params: [ 'config', 'section', 'name' ]
701 if (this.checkAnonymousSections())
702 return this.renderMigration();
704 return this.renderOverview();
707 handleMigration: function(ev) {
708 var wifiIfaces = uci.sections('wireless', 'wifi-iface'),
712 for (var i = 0; i < wifiIfaces.length; i++) {
713 if (!wifiIfaces[i]['.anonymous'])
716 var new_name = next_free_sid(id_offset);
718 tasks.push(this.callUciRename('wireless', wifiIfaces[i]['.name'], new_name));
719 id_offset = +new_name.substring(7) + 1;
722 return Promise.all(tasks)
723 .then(L.bind(ui.changes.init, ui.changes))
724 .then(L.bind(ui.changes.apply, ui.changes));
727 renderMigration: function() {
728 ui.showModal(_('Wireless configuration migration'), [
729 E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')),
730 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.')),
731 E('div', { 'class': 'right' },
733 'class': 'btn cbi-button-action important',
734 'click': ui.createHandlerFn(this, 'handleMigration')
739 renderOverview: function() {
742 m = new form.Map('wireless');
746 s = m.section(form.GridSection, 'wifi-device', _('Wireless Overview'));
750 s.load = function() {
751 return network.getWifiDevices().then(L.bind(function(radios) {
752 this.radios = radios.sort(function(a, b) {
753 return a.getName() > b.getName();
758 for (var i = 0; i < radios.length; i++)
759 tasks.push(radios[i].getWifiNetworks());
761 return Promise.all(tasks);
762 }, this)).then(L.bind(function(data) {
765 for (var i = 0; i < data.length; i++)
766 this.wifis.push.apply(this.wifis, data[i]);
770 s.cfgsections = function() {
773 for (var i = 0; i < this.radios.length; i++) {
774 rv.push(this.radios[i].getName());
776 for (var j = 0; j < this.wifis.length; j++)
777 if (this.wifis[j].getWifiDeviceName() == this.radios[i].getName())
778 rv.push(this.wifis[j].getName());
784 s.modaltitle = function(section_id) {
785 var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id})[0];
786 return radioNet ? radioNet.getI18n() : _('Edit wireless network');
789 s.lookupRadioOrNetwork = function(section_id) {
790 var radioDev = this.radios.filter(function(r) { return r.getName() == section_id })[0];
794 var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id })[0];
801 s.renderRowActions = function(section_id) {
802 var inst = this.lookupRadioOrNetwork(section_id), btns;
804 if (inst.getWifiNetworks) {
807 'class': 'cbi-button cbi-button-neutral',
808 'title': _('Restart radio interface'),
809 'click': ui.createHandlerFn(this, radio_restart, section_id)
812 'class': 'cbi-button cbi-button-action important',
813 'title': _('Find and join network'),
814 'click': ui.createHandlerFn(this, 'handleScan', inst)
817 'class': 'cbi-button cbi-button-add',
818 'title': _('Provide new network'),
819 'click': ui.createHandlerFn(this, 'handleAdd', inst)
824 var isDisabled = (inst.get('disabled') == '1' ||
825 uci.get('wireless', inst.getWifiDeviceName(), 'disabled') == '1');
829 'class': 'cbi-button cbi-button-neutral enable-disable',
830 'title': isDisabled ? _('Enable this network') : _('Disable this network'),
831 'click': ui.createHandlerFn(this, network_updown, section_id, this.map)
832 }, isDisabled ? _('Enable') : _('Disable')),
834 'class': 'cbi-button cbi-button-action important',
835 'title': _('Edit this network'),
836 'click': ui.createHandlerFn(this, 'renderMoreOptionsModal', section_id)
839 'class': 'cbi-button cbi-button-negative remove',
840 'title': _('Delete this network'),
841 'click': ui.createHandlerFn(this, 'handleRemove', section_id)
846 return E('div', { 'class': 'td middle cbi-section-actions' }, E('div', btns));
849 s.addModalOptions = function(s) {
850 return network.getWifiNetwork(s.section).then(function(radioNet) {
851 var hwtype = uci.get('wireless', radioNet.getWifiDeviceName(), 'type');
854 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
858 ss.tab('general', _('General Setup'));
859 ss.tab('advanced', _('Advanced Settings'));
861 var isDisabled = (radioNet.get('disabled') == '1' ||
862 uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == 1);
864 o = ss.taboption('general', form.DummyValue, '_wifistat_modal', _('Status'));
865 o.cfgvalue = L.bind(function(radioNet) {
866 return render_modal_status(null, radioNet);
868 o.write = function() {};
870 o = ss.taboption('general', form.Button, '_toggle', isDisabled ? _('Wireless network is disabled') : _('Wireless network is enabled'));
871 o.inputstyle = isDisabled ? 'apply' : 'reset';
872 o.inputtitle = isDisabled ? _('Enable') : _('Disable');
873 o.onclick = ui.createHandlerFn(s, network_updown, s.section, s.map);
875 o = ss.taboption('general', CBIWifiFrequencyValue, '_freq', '<br />' + _('Operating frequency'));
876 o.ucisection = s.section;
878 if (hwtype == 'mac80211') {
879 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.'));
880 o.wifiNetwork = radioNet;
882 o = ss.taboption('advanced', CBIWifiCountryValue, 'country', _('Country Code'));
883 o.wifiNetwork = radioNet;
885 o = ss.taboption('advanced', form.Flag, 'legacy_rates', _('Allow legacy 802.11b rates'));
886 o.default = o.enabled;
888 o = ss.taboption('advanced', form.Value, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
889 o.datatype = 'range(0,114750)';
890 o.placeholder = 'auto';
892 o = ss.taboption('advanced', form.Value, 'frag', _('Fragmentation Threshold'));
893 o.datatype = 'min(256)';
894 o.placeholder = _('off');
896 o = ss.taboption('advanced', form.Value, 'rts', _('RTS/CTS Threshold'));
897 o.datatype = 'uinteger';
898 o.placeholder = _('off');
900 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!'));
903 o = ss.taboption('advanced', form.Value, 'beacon_int', _('Beacon Interval'));
904 o.datatype = 'range(15,65535)';
910 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getName(), 'wifi-iface', _('Interface Configuration'));
914 ss.tab('general', _('General Setup'));
915 ss.tab('encryption', _('Wireless Security'));
916 ss.tab('macfilter', _('MAC-Filter'));
917 ss.tab('advanced', _('Advanced Settings'));
919 o = ss.taboption('general', form.ListValue, 'mode', _('Mode'));
920 o.value('ap', _('Access Point'));
921 o.value('sta', _('Client'));
922 o.value('adhoc', _('Ad-Hoc'));
924 o = ss.taboption('general', form.Value, 'mesh_id', _('Mesh Id'));
925 o.depends('mode', 'mesh');
927 o = ss.taboption('advanced', form.Flag, 'mesh_fwding', _('Forward mesh peer traffic'));
930 o.depends('mode', 'mesh');
932 o = ss.taboption('advanced', form.Value, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
935 o.datatype = 'range(-255,1)';
936 o.depends('mode', 'mesh');
938 o = ss.taboption('general', form.Value, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
939 o.datatype = 'maxlength(32)';
940 o.depends('mode', 'ap');
941 o.depends('mode', 'sta');
942 o.depends('mode', 'adhoc');
943 o.depends('mode', 'ahdemo');
944 o.depends('mode', 'monitor');
945 o.depends('mode', 'ap-wds');
946 o.depends('mode', 'sta-wds');
947 o.depends('mode', 'wds');
949 o = ss.taboption('general', form.Value, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
950 o.datatype = 'macaddr';
952 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>custom</em> field to define a new network.'));
956 o.write = function(section_id, value) {
957 return network.getDevice(section_id).then(L.bind(function(dev) {
958 var old_networks = dev.getNetworks().reduce(function(o, v) { o[v.getName()] = v; return o }, {}),
960 values = L.toArray(value),
963 for (var i = 0; i < values.length; i++) {
964 new_networks[values[i]] = true;
966 if (old_networks[values[i]])
969 tasks.push(network.getNetwork(values[i]).then(L.bind(function(name, net) {
970 return net || network.addNetwork(name, { proto: 'none' });
971 }, this, values[i])).then(L.bind(function(dev, net) {
974 net.set('type', 'bridge');
980 for (var name in old_networks)
981 if (!new_networks[name])
982 tasks.push(network.getNetwork(name).then(L.bind(function(dev, net) {
984 net.deleteDevice(dev);
987 return Promise.all(tasks);
991 if (hwtype == 'mac80211') {
992 var mode = ss.children[0],
993 bssid = ss.children[5],
996 mode.value('mesh', '802.11s');
997 mode.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
998 mode.value('monitor', _('Monitor'));
1000 bssid.depends('mode', 'adhoc');
1001 bssid.depends('mode', 'sta');
1002 bssid.depends('mode', 'sta-wds');
1004 o = ss.taboption('macfilter', form.ListValue, 'macfilter', _('MAC-Address Filter'));
1005 o.depends('mode', 'ap');
1006 o.depends('mode', 'ap-wds');
1007 o.value('', _('disable'));
1008 o.value('allow', _('Allow listed only'));
1009 o.value('deny', _('Allow all except listed'));
1011 o = ss.taboption('macfilter', form.DynamicList, 'maclist', _('MAC-List'));
1012 o.datatype = 'macaddr';
1013 o.depends('macfilter', 'allow');
1014 o.depends('macfilter', 'deny');
1015 o.load = function(section_id) {
1016 return network.getHostHints().then(L.bind(function(hints) {
1017 hints.getMACHints().map(L.bind(function(hint) {
1018 this.value(hint[0], hint[1] ? '%s (%s)'.format(hint[0], hint[1]) : hint[0]);
1021 return form.DynamicList.prototype.load.apply(this, [section_id]);
1025 mode.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
1026 mode.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
1028 mode.write = function(section_id, value) {
1031 uci.set('wireless', section_id, 'mode', 'ap');
1032 uci.set('wireless', section_id, 'wds', '1');
1036 uci.set('wireless', section_id, 'mode', 'sta');
1037 uci.set('wireless', section_id, 'wds', '1');
1041 uci.set('wireless', section_id, 'mode', value);
1042 uci.unset('wireless', section_id, 'wds');
1047 mode.cfgvalue = function(section_id) {
1048 var mode = uci.get('wireless', section_id, 'mode'),
1049 wds = uci.get('wireless', section_id, 'wds');
1051 if (mode == 'ap' && wds)
1053 else if (mode == 'sta' && wds)
1059 o = ss.taboption('general', form.Flag, 'hidden', _('Hide <abbr title="Extended Service Set Identifier">ESSID</abbr>'));
1060 o.depends('mode', 'ap');
1061 o.depends('mode', 'ap-wds');
1063 o = ss.taboption('general', form.Flag, 'wmm', _('WMM Mode'));
1064 o.depends('mode', 'ap');
1065 o.depends('mode', 'ap-wds');
1066 o.default = o.enabled;
1068 o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
1069 o.depends('mode', 'ap');
1070 o.depends('mode', 'ap-wds');
1072 o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name'));
1074 o.placeholder = radioNet.getIfname();
1075 if (/^radio\d+\.network/.test(o.placeholder))
1078 o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble'));
1079 o.default = o.enabled;
1081 o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
1084 o.datatype = 'range(1,255)';
1086 o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
1088 o.placeholder = 600;
1089 o.datatype = 'uinteger';
1091 o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling'));
1093 o.datatype = 'uinteger';
1095 o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('sec'));
1097 o.placeholder = 300;
1098 o.datatype = 'uinteger';
1100 o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval'));
1102 o.placeholder = 65535;
1103 o.datatype = 'uinteger';
1105 o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
1106 o.default = o.enabled;
1110 encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption'));
1111 o.depends('mode', 'ap');
1112 o.depends('mode', 'sta');
1113 o.depends('mode', 'adhoc');
1114 o.depends('mode', 'ahdemo');
1115 o.depends('mode', 'ap-wds');
1116 o.depends('mode', 'sta-wds');
1117 o.depends('mode', 'mesh');
1119 o.cfgvalue = function(section_id) {
1120 var v = String(uci.get('wireless', section_id, 'encryption'));
1123 else if (v.match(/\+/))
1124 return v.replace(/\+.+$/, '');
1128 o.write = function(section_id, value) {
1129 var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id),
1130 co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id);
1132 if (value == 'wpa' || value == 'wpa2' || value == 'wpa3' || value == 'wpa3-mixed')
1133 uci.unset('wireless', section_id, 'key');
1135 if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp'))
1138 uci.set('wireless', section_id, 'encryption', e);
1141 o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher'));
1142 o.depends('encryption', 'wpa');
1143 o.depends('encryption', 'wpa2');
1144 o.depends('encryption', 'wpa3');
1145 o.depends('encryption', 'wpa3-mixed');
1146 o.depends('encryption', 'psk');
1147 o.depends('encryption', 'psk2');
1148 o.depends('encryption', 'wpa-mixed');
1149 o.depends('encryption', 'psk-mixed');
1150 o.value('auto', _('auto'));
1151 o.value('ccmp', _('Force CCMP (AES)'));
1152 o.value('tkip', _('Force TKIP'));
1153 o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1154 o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write;
1156 o.cfgvalue = function(section_id) {
1157 var v = String(uci.get('wireless', section_id, 'encryption'));
1158 if (v.match(/\+/)) {
1159 v = v.replace(/^[^+]+\+/, '');
1162 else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip')
1169 var crypto_modes = [];
1171 if (hwtype == 'mac80211') {
1172 var has_supplicant = L.hasSystemFeature('wpasupplicant'),
1173 has_hostapd = L.hasSystemFeature('hostapd');
1175 // Probe EAP support
1176 var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'),
1177 has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap');
1179 // Probe SAE support
1180 var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'),
1181 has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae');
1183 // Probe OWE support
1184 var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
1185 has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
1187 // Probe Suite-B support
1188 var has_ap_eap192 = L.hasSystemFeature('hostapd', 'suiteb192'),
1189 has_sta_eap192 = L.hasSystemFeature('wpasupplicant', 'suiteb192');
1192 if (has_hostapd || has_supplicant) {
1193 crypto_modes.push(['psk2', 'WPA2-PSK', 35]);
1194 crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1195 crypto_modes.push(['psk', 'WPA-PSK', 21]);
1198 encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1201 if (has_ap_sae || has_sta_sae) {
1202 crypto_modes.push(['sae', 'WPA3-SAE', 31]);
1203 crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1206 if (has_ap_eap || has_sta_eap) {
1207 if (has_ap_eap192 || has_sta_eap192) {
1208 crypto_modes.push(['wpa3', 'WPA3-EAP', 33]);
1209 crypto_modes.push(['wpa3-mixed', 'WPA2-EAP/WPA3-EAP Mixed Mode', 32]);
1212 crypto_modes.push(['wpa2', 'WPA2-EAP', 34]);
1213 crypto_modes.push(['wpa', 'WPA-EAP', 20]);
1216 if (has_ap_owe || has_sta_owe) {
1217 crypto_modes.push(['owe', 'OWE', 1]);
1220 encr.crypto_support = {
1224 'psk': has_hostapd || _('Requires hostapd'),
1225 'psk2': has_hostapd || _('Requires hostapd'),
1226 'psk-mixed': has_hostapd || _('Requires hostapd'),
1227 'sae': has_ap_sae || _('Requires hostapd with SAE support'),
1228 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
1229 'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
1230 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
1231 'wpa3': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
1232 'wpa3-mixed': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
1233 'owe': has_ap_owe || _('Requires hostapd with OWE support')
1238 'psk': has_supplicant || _('Requires wpa-supplicant'),
1239 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1240 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1241 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1242 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1243 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1244 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1245 'wpa3': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
1246 'wpa3-mixed': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
1247 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
1252 'psk': has_supplicant || _('Requires wpa-supplicant'),
1253 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1254 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1257 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support')
1269 encr.crypto_support['ap-wds'] = encr.crypto_support['ap'];
1270 encr.crypto_support['sta-wds'] = encr.crypto_support['sta'];
1272 encr.validate = function(section_id, value) {
1273 var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
1274 modeval = modeopt.formvalue(section_id),
1275 modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)],
1276 enctitle = this.vallist[this.keylist.indexOf(value)];
1278 if (value == 'none')
1281 if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value))
1282 return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle);
1284 return this.crypto_support[modeval][value];
1287 else if (hwtype == 'broadcom') {
1288 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1289 crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1290 crypto_modes.push(['psk', 'WPA-PSK', 21]);
1293 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1294 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1295 crypto_modes.push(['none', _('No Encryption'), 0]);
1297 crypto_modes.sort(function(a, b) { return b[2] - a[2] });
1299 for (var i = 0; i < crypto_modes.length; i++) {
1300 var security_level = (crypto_modes[i][2] >= 30) ? _('strong security')
1301 : (crypto_modes[i][2] >= 20) ? _('medium security')
1302 : (crypto_modes[i][2] >= 10) ? _('weak security') : _('open network');
1304 encr.value(crypto_modes[i][0], '%s (%s)'.format(crypto_modes[i][1], security_level));
1308 o = ss.taboption('encryption', form.Value, 'auth_server', _('Radius-Authentication-Server'));
1309 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1311 o.datatype = 'host(0)';
1313 o = ss.taboption('encryption', form.Value, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
1314 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1316 o.datatype = 'port';
1318 o = ss.taboption('encryption', form.Value, 'auth_secret', _('Radius-Authentication-Secret'));
1319 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1323 o = ss.taboption('encryption', form.Value, 'acct_server', _('Radius-Accounting-Server'));
1324 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1326 o.datatype = 'host(0)';
1328 o = ss.taboption('encryption', form.Value, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
1329 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1331 o.datatype = 'port';
1333 o = ss.taboption('encryption', form.Value, 'acct_secret', _('Radius-Accounting-Secret'));
1334 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1338 o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client'));
1339 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1341 o.datatype = 'host(0)';
1343 o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
1344 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1346 o.datatype = 'port';
1348 o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret'));
1349 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1354 o = ss.taboption('encryption', form.Value, '_wpa_key', _('Key'));
1355 o.depends('encryption', 'psk');
1356 o.depends('encryption', 'psk2');
1357 o.depends('encryption', 'psk+psk2');
1358 o.depends('encryption', 'psk-mixed');
1359 o.depends('encryption', 'sae');
1360 o.depends('encryption', 'sae-mixed');
1361 o.datatype = 'wpakey';
1365 o.cfgvalue = function(section_id) {
1366 var key = uci.get('wireless', section_id, 'key');
1367 return /^[1234]$/.test(key) ? null : key;
1370 o.write = function(section_id, value) {
1371 uci.set('wireless', section_id, 'key', value);
1372 uci.unset('wireless', section_id, 'key1');
1373 uci.unset('wireless', section_id, 'key2');
1374 uci.unset('wireless', section_id, 'key3');
1375 uci.unset('wireless', section_id, 'key4');
1379 o = ss.taboption('encryption', form.ListValue, '_wep_key', _('Used Key Slot'));
1380 o.depends('encryption', 'wep-open');
1381 o.depends('encryption', 'wep-shared');
1382 o.value('1', _('Key #%d').format(1));
1383 o.value('2', _('Key #%d').format(2));
1384 o.value('3', _('Key #%d').format(3));
1385 o.value('4', _('Key #%d').format(4));
1387 o.cfgvalue = function(section_id) {
1388 var slot = +uci.get('wireless', section_id, 'key');
1389 return (slot >= 1 && slot <= 4) ? String(slot) : '';
1392 o.write = function(section_id, value) {
1393 uci.set('wireless', section_id, 'key', value);
1396 for (var slot = 1; slot <= 4; slot++) {
1397 o = ss.taboption('encryption', form.Value, 'key%d'.format(slot), _('Key #%d').format(slot));
1398 o.depends('encryption', 'wep-open');
1399 o.depends('encryption', 'wep-shared');
1400 o.datatype = 'wepkey';
1404 o.write = function(section_id, value) {
1405 if (value != null && (value.length == 5 || value.length == 13))
1406 value = 's:%s'.format(value);
1407 uci.set('wireless', section_id, this.option, value);
1412 if (hwtype == 'mac80211') {
1413 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1414 var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap');
1416 o = ss.taboption('encryption', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1417 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1419 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1422 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.'));
1423 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1424 o.depends({ ieee80211r: '1' });
1427 o = ss.taboption('encryption', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1428 o.depends({ ieee80211r: '1' });
1429 o.placeholder = '4f57';
1430 o.datatype = 'and(hexstring,length(4))';
1433 o = ss.taboption('encryption', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1434 o.depends({ ieee80211r: '1' });
1435 o.placeholder = '1000';
1436 o.datatype = 'range(1000,65535)';
1439 o = ss.taboption('encryption', form.ListValue, 'ft_over_ds', _('FT protocol'));
1440 o.depends({ ieee80211r: '1' });
1441 o.value('1', _('FT over DS'));
1442 o.value('0', _('FT over the Air'));
1445 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.'));
1446 o.depends({ ieee80211r: '1' });
1447 o.default = o.enabled;
1450 o = ss.taboption('encryption', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1451 o.depends({ ieee80211r: '1' });
1452 o.placeholder = '10000';
1453 o.datatype = 'uinteger';
1456 o = ss.taboption('encryption', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1457 o.depends({ ieee80211r: '1' });
1458 o.placeholder = '00004f577274';
1459 o.datatype = 'and(hexstring,length(12))';
1462 o = ss.taboption('encryption', form.Flag, 'pmk_r1_push', _('PMK R1 Push'));
1463 o.depends({ ieee80211r: '1' });
1464 o.placeholder = '0';
1467 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.'));
1468 o.depends({ ieee80211r: '1' });
1471 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.'));
1472 o.depends({ ieee80211r: '1' });
1474 // End of 802.11r options
1476 o = ss.taboption('encryption', form.ListValue, 'eap_type', _('EAP-Method'));
1477 o.value('tls', 'TLS');
1478 o.value('ttls', 'TTLS');
1479 o.value('peap', 'PEAP');
1480 o.value('fast', 'FAST');
1481 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1483 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"));
1486 o.default = o.disabled;
1487 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1488 o.validate = function(section_id, value) {
1489 if (value == '1' && !L.hasSystemFeature('cabundle')) {
1490 return _("This option cannot be used because the ca-bundle package is not installed.");
1495 o = ss.taboption('encryption', form.FileUpload, 'ca_cert', _('Path to CA-Certificate'));
1496 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], ca_cert_usesystem: ['0'] });
1498 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"));
1499 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1501 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"));
1502 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1504 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)"));
1505 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1507 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)"));
1508 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1510 o = ss.taboption('encryption', form.FileUpload, 'client_cert', _('Path to Client-Certificate'));
1511 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['tls'] });
1513 o = ss.taboption('encryption', form.FileUpload, 'priv_key', _('Path to Private Key'));
1514 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['tls'] });
1516 o = ss.taboption('encryption', form.Value, 'priv_key_pwd', _('Password of Private Key'));
1517 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['tls'] });
1520 o = ss.taboption('encryption', form.ListValue, 'auth', _('Authentication'));
1521 o.value('PAP', 'PAP');
1522 o.value('CHAP', 'CHAP');
1523 o.value('MSCHAP', 'MSCHAP');
1524 o.value('MSCHAPV2', 'MSCHAPv2');
1525 o.value('EAP-GTC', 'EAP-GTC');
1526 o.value('EAP-MD5', 'EAP-MD5');
1527 o.value('EAP-MSCHAPV2', 'EAP-MSCHAPv2');
1528 o.value('EAP-TLS', 'EAP-TLS');
1529 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'ttls'] });
1531 o.validate = function(section_id, value) {
1532 var eo = this.section.children.filter(function(o) { return o.option == 'eap_type' })[0],
1533 ev = eo.formvalue(section_id);
1535 if (ev != 'ttls' && (value == 'PAP' || value == 'CHAP' || value == 'MSCHAP' || value == 'MSCHAPV2'))
1536 return _('This authentication type is not applicable to the selected EAP method.');
1541 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"));
1544 o.default = o.disabled;
1545 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
1546 o.validate = function(section_id, value) {
1547 if (value == '1' && !L.hasSystemFeature('cabundle')) {
1548 return _("This option cannot be used because the ca-bundle package is not installed.");
1553 o = ss.taboption('encryption', form.FileUpload, 'ca_cert2', _('Path to inner CA-Certificate'));
1554 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'], ca_cert2_usesystem: ['0'] });
1556 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"));
1557 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
1559 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"));
1560 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
1562 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)"));
1563 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
1565 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)"));
1566 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
1568 o = ss.taboption('encryption', form.FileUpload, 'client_cert2', _('Path to inner Client-Certificate'));
1569 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
1571 o = ss.taboption('encryption', form.FileUpload, 'priv_key2', _('Path to inner Private Key'));
1572 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
1574 o = ss.taboption('encryption', form.Value, 'priv_key2_pwd', _('Password of inner Private Key'));
1575 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
1578 o = ss.taboption('encryption', form.Value, 'identity', _('Identity'));
1579 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'tls', 'ttls'] });
1581 o = ss.taboption('encryption', form.Value, 'anonymous_identity', _('Anonymous Identity'));
1582 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'tls', 'ttls'] });
1584 o = ss.taboption('encryption', form.Value, 'password', _('Password'));
1585 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'ttls'] });
1589 if (hwtype == 'mac80211') {
1590 // ieee802.11w options
1591 if (L.hasSystemFeature('hostapd', '11w')) {
1592 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)"));
1593 o.value('', _('Disabled'));
1594 o.value('1', _('Optional'));
1595 o.value('2', _('Required'));
1596 add_dependency_permutations(o, { mode: ['ap', 'ap-wds', 'sta', 'sta-wds'], encryption: ['owe', 'psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1599 '2': [{ encryption: 'sae' }, { encryption: 'owe' }, { encryption: 'wpa3' }, { encryption: 'wpa3-mixed' }],
1600 '1': [{ encryption: 'sae-mixed'}],
1604 o = ss.taboption('encryption', form.Value, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1605 o.depends('ieee80211w', '1');
1606 o.depends('ieee80211w', '2');
1607 o.datatype = 'uinteger';
1608 o.placeholder = '1000';
1611 o = ss.taboption('encryption', form.Value, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1612 o.depends('ieee80211w', '1');
1613 o.depends('ieee80211w', '2');
1614 o.datatype = 'uinteger';
1615 o.placeholder = '201';
1619 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.'));
1620 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1622 if (L.hasSystemFeature('hostapd', 'cli') && L.hasSystemFeature('wpasupplicant')) {
1623 o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1626 o.default = o.disabled;
1627 o.depends('encryption', 'psk');
1628 o.depends('encryption', 'psk2');
1629 o.depends('encryption', 'psk-mixed');
1630 o.depends('encryption', 'sae');
1631 o.depends('encryption', 'sae-mixed');
1638 s.handleRemove = function(section_id, ev) {
1639 document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id)).style.opacity = 0.5;
1640 return form.TypedSection.prototype.handleRemove.apply(this, [section_id, ev]);
1643 s.handleScan = function(radioDev, ev) {
1644 var table = E('div', { 'class': 'table' }, [
1645 E('div', { 'class': 'tr table-titles' }, [
1646 E('div', { 'class': 'th col-2 middle center' }, _('Signal')),
1647 E('div', { 'class': 'th col-4 middle left' }, _('SSID')),
1648 E('div', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1649 E('div', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1650 E('div', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1651 E('div', { 'class': 'th col-3 middle left' }, _('Encryption')),
1652 E('div', { 'class': 'th cbi-section-actions right' }, ' '),
1656 var stop = E('button', {
1658 'click': L.bind(this.handleScanStartStop, this),
1659 'style': 'display:none',
1660 'data-state': 'stop'
1661 }, _('Stop refresh'));
1663 cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1665 var md = ui.showModal(_('Join Network: Wireless Scan'), [
1667 E('div', { 'class': 'right' }, [
1672 'click': L.bind(this.handleScanAbort, this)
1677 md.style.maxWidth = '90%';
1678 md.style.maxHeight = 'none';
1680 this.pollFn = L.bind(this.handleScanRefresh, this, radioDev, {}, table, stop);
1682 poll.add(this.pollFn);
1686 s.handleScanRefresh = function(radioDev, scanCache, table, stop) {
1687 return radioDev.getScanList().then(L.bind(function(results) {
1690 for (var i = 0; i < results.length; i++)
1691 scanCache[results[i].bssid] = results[i];
1693 for (var k in scanCache)
1694 if (scanCache[k].stale)
1695 results.push(scanCache[k]);
1697 results.sort(function(a, b) {
1698 var diff = (b.quality - a.quality) || (a.channel - b.channel);
1703 if (a.ssid < b.ssid)
1705 else if (a.ssid > b.ssid)
1708 if (a.bssid < b.bssid)
1710 else if (a.bssid > b.bssid)
1714 for (var i = 0; i < results.length; i++) {
1715 var res = results[i],
1716 qv = res.quality || 0,
1717 qm = res.quality_max || 0,
1718 q = (qv > 0 && qm > 0) ? Math.floor((100 / qm) * qv) : 0,
1719 s = res.stale ? 'opacity:0.5' : '';
1722 E('span', { 'style': s }, render_signal_badge(q, res.signal, res.noise)),
1723 E('span', { 'style': s }, (res.ssid != null) ? '%h'.format(res.ssid) : E('em', _('hidden'))),
1724 E('span', { 'style': s }, '%d'.format(res.channel)),
1725 E('span', { 'style': s }, '%h'.format(res.mode)),
1726 E('span', { 'style': s }, '%h'.format(res.bssid)),
1727 E('span', { 'style': s }, '%h'.format(network.formatWifiEncryption(res.encryption))),
1728 E('div', { 'class': 'right' }, E('button', {
1729 'class': 'cbi-button cbi-button-action important',
1730 'click': L.bind(this.handleJoin, this, radioDev, res)
1731 }, _('Join Network')))
1737 cbi_update_table(table, rows);
1739 stop.disabled = false;
1740 stop.style.display = '';
1741 stop.classList.remove('spinning');
1745 s.handleScanStartStop = function(ev) {
1746 var btn = ev.currentTarget;
1748 if (btn.getAttribute('data-state') == 'stop') {
1749 poll.remove(this.pollFn);
1750 btn.firstChild.data = _('Start refresh');
1751 btn.setAttribute('data-state', 'start');
1754 poll.add(this.pollFn);
1755 btn.firstChild.data = _('Stop refresh');
1756 btn.setAttribute('data-state', 'stop');
1757 btn.classList.add('spinning');
1758 btn.disabled = true;
1762 s.handleScanAbort = function(ev) {
1763 var md = dom.parent(ev.target, 'div[aria-modal="true"]');
1765 md.style.maxWidth = '';
1766 md.style.maxHeight = '';
1770 poll.remove(this.pollFn);
1775 s.handleJoinConfirm = function(radioDev, bss, form, ev) {
1776 var nameopt = L.toArray(form.lookupOption('name', '_new_'))[0],
1777 passopt = L.toArray(form.lookupOption('password', '_new_'))[0],
1778 bssidopt = L.toArray(form.lookupOption('bssid', '_new_'))[0],
1779 zoneopt = L.toArray(form.lookupOption('zone', '_new_'))[0],
1780 replopt = L.toArray(form.lookupOption('replace', '_new_'))[0],
1781 nameval = (nameopt && nameopt.isValid('_new_')) ? nameopt.formvalue('_new_') : null,
1782 passval = (passopt && passopt.isValid('_new_')) ? passopt.formvalue('_new_') : null,
1783 bssidval = (bssidopt && bssidopt.isValid('_new_')) ? bssidopt.formvalue('_new_') : null,
1784 zoneval = zoneopt ? zoneopt.formvalue('_new_') : null,
1785 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1786 is_wep = (enc && Array.isArray(enc.wep)),
1787 is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' })),
1788 is_sae = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'sae' }));
1790 if (nameval == null || (passopt && passval == null))
1793 var section_id = null;
1795 return this.map.save(function() {
1796 var wifi_sections = uci.sections('wireless', 'wifi-iface');
1798 if (replopt.formvalue('_new_') == '1') {
1799 for (var i = 0; i < wifi_sections.length; i++)
1800 if (wifi_sections[i].device == radioDev.getName())
1801 uci.remove('wireless', wifi_sections[i]['.name']);
1804 if (uci.get('wireless', radioDev.getName(), 'disabled') == '1') {
1805 for (var i = 0; i < wifi_sections.length; i++)
1806 if (wifi_sections[i].device == radioDev.getName())
1807 uci.set('wireless', wifi_sections[i]['.name'], 'disabled', '1');
1809 uci.unset('wireless', radioDev.getName(), 'disabled');
1812 section_id = next_free_sid(wifi_sections.length);
1814 uci.add('wireless', 'wifi-iface', section_id);
1815 uci.set('wireless', section_id, 'device', radioDev.getName());
1816 uci.set('wireless', section_id, 'mode', (bss.mode == 'Ad-Hoc') ? 'adhoc' : 'sta');
1817 uci.set('wireless', section_id, 'network', nameval);
1819 if (bss.ssid != null) {
1820 uci.set('wireless', section_id, 'ssid', bss.ssid);
1822 if (bssidval == '1')
1823 uci.set('wireless', section_id, 'bssid', bss.bssid);
1825 else if (bss.bssid != null) {
1826 uci.set('wireless', section_id, 'bssid', bss.bssid);
1830 uci.set('wireless', section_id, 'encryption', 'sae');
1831 uci.set('wireless', section_id, 'key', passval);
1834 for (var i = enc.wpa.length - 1; i >= 0; i--) {
1835 if (enc.wpa[i] == 2) {
1836 uci.set('wireless', section_id, 'encryption', 'psk2');
1839 else if (enc.wpa[i] == 1) {
1840 uci.set('wireless', section_id, 'encryption', 'psk');
1845 uci.set('wireless', section_id, 'key', passval);
1848 uci.set('wireless', section_id, 'encryption', 'wep-open');
1849 uci.set('wireless', section_id, 'key', '1');
1850 uci.set('wireless', section_id, 'key1', passval);
1853 uci.set('wireless', section_id, 'encryption', 'none');
1856 return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) {
1857 firewall.deleteNetwork(net.getName());
1859 var zonePromise = zoneval
1860 ? firewall.getZone(zoneval).then(function(zone) { return zone || firewall.addZone(zoneval) })
1861 : Promise.resolve();
1863 return zonePromise.then(function(zone) {
1865 zone.addNetwork(net.getName());
1868 }).then(L.bind(function() {
1869 return this.renderMoreOptionsModal(section_id);
1873 s.handleJoin = function(radioDev, bss, ev) {
1874 this.handleScanAbort(ev);
1876 var m2 = new form.Map('wireless'),
1877 s2 = m2.section(form.NamedSection, '_new_'),
1878 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1879 is_wep = (enc && Array.isArray(enc.wep)),
1880 is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' || a == 'sae' })),
1881 replace, passphrase, name, bssid, zone;
1883 var nameUsed = function(name) {
1884 var s = uci.get('network', name);
1885 if (s != null && s['.type'] != 'interface')
1888 var net = (s != null) ? network.instantiateNetwork(name) : null;
1889 return (net != null && !net.isEmpty());
1892 s2.render = function() {
1893 return Promise.all([
1895 this.renderUCISection('_new_')
1896 ]).then(this.renderContents.bind(this));
1899 replace = s2.option(form.Flag, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
1901 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>'));
1902 name.datatype = 'uciname';
1903 name.default = 'wwan';
1904 name.rmempty = false;
1905 name.validate = function(section_id, value) {
1906 if (nameUsed(value))
1907 return _('The network name is already used');
1912 for (var i = 2; nameUsed(name.default); i++)
1913 name.default = 'wwan%d'.format(i);
1915 if (is_wep || is_psk) {
1916 passphrase = s2.option(form.Value, 'password', is_wep ? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
1917 passphrase.datatype = is_wep ? 'wepkey' : 'wpakey';
1918 passphrase.password = true;
1919 passphrase.rmempty = false;
1922 if (bss.ssid != null) {
1923 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));
1924 bssid.default = '0';
1927 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>custom</em> field to define a new zone and attach the interface to it.'));
1928 zone.default = 'wan';
1930 return m2.render().then(L.bind(function(nodes) {
1931 ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [
1933 E('div', { 'class': 'right' }, [
1936 'click': ui.hideModal
1937 }, _('Cancel')), ' ',
1939 'class': 'cbi-button cbi-button-positive important',
1940 'click': ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2)
1943 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase || name).cbid('_new_'))).focus();
1947 s.handleAdd = function(radioDev, ev) {
1948 var section_id = next_free_sid(uci.sections('wireless', 'wifi-iface').length);
1950 uci.unset('wireless', radioDev.getName(), 'disabled');
1952 uci.add('wireless', 'wifi-iface', section_id);
1953 uci.set('wireless', section_id, 'device', radioDev.getName());
1954 uci.set('wireless', section_id, 'mode', 'ap');
1955 uci.set('wireless', section_id, 'ssid', 'OpenWrt');
1956 uci.set('wireless', section_id, 'encryption', 'none');
1958 this.addedSection = section_id;
1959 return this.renderMoreOptionsModal(section_id);
1962 o = s.option(form.DummyValue, '_badge');
1963 o.modalonly = false;
1964 o.textvalue = function(section_id) {
1965 var inst = this.section.lookupRadioOrNetwork(section_id),
1966 node = E('div', { 'class': 'center' });
1968 if (inst.getWifiNetworks)
1969 node.appendChild(render_radio_badge(inst));
1971 node.appendChild(render_network_badge(inst));
1976 o = s.option(form.DummyValue, '_stat');
1977 o.modalonly = false;
1978 o.textvalue = function(section_id) {
1979 var inst = this.section.lookupRadioOrNetwork(section_id);
1981 if (inst.getWifiNetworks)
1982 return render_radio_status(inst, this.section.wifis.filter(function(e) {
1983 return (e.getWifiDeviceName() == inst.getName());
1986 return render_network_status(inst);
1989 return m.render().then(L.bind(function(m, nodes) {
1990 poll.add(L.bind(function() {
1991 var section_ids = m.children[0].cfgsections(),
1992 tasks = [ network.getHostHints(), network.getWifiDevices() ];
1994 for (var i = 0; i < section_ids.length; i++) {
1995 var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])),
1996 dsc = row.querySelector('[data-name="_stat"] > div'),
1997 btns = row.querySelectorAll('.cbi-section-actions button');
1999 if (dsc.getAttribute('restart') == '') {
2000 dsc.setAttribute('restart', '1');
2001 tasks.push(fs.exec('/sbin/wifi', ['up', section_ids[i]]).catch(function(e) {
2002 ui.addNotification(null, E('p', e.message));
2005 else if (dsc.getAttribute('restart') == '1') {
2006 dsc.removeAttribute('restart');
2007 btns[0].classList.remove('spinning');
2008 btns[0].disabled = false;
2012 return Promise.all(tasks)
2013 .then(L.bind(function(hosts_radios) {
2016 for (var i = 0; i < hosts_radios[1].length; i++)
2017 tasks.push(hosts_radios[1][i].getWifiNetworks());
2019 return Promise.all(tasks).then(function(data) {
2020 hosts_radios[2] = [];
2022 for (var i = 0; i < data.length; i++)
2023 hosts_radios[2].push.apply(hosts_radios[2], data[i]);
2025 return hosts_radios;
2028 .then(L.bind(function(hosts_radios_wifis) {
2031 for (var i = 0; i < hosts_radios_wifis[2].length; i++)
2032 tasks.push(hosts_radios_wifis[2][i].getAssocList());
2034 return Promise.all(tasks).then(function(data) {
2035 hosts_radios_wifis[3] = [];
2037 for (var i = 0; i < data.length; i++) {
2038 var wifiNetwork = hosts_radios_wifis[2][i],
2039 radioDev = hosts_radios_wifis[1].filter(function(d) { return d.getName() == wifiNetwork.getWifiDeviceName() })[0];
2041 for (var j = 0; j < data[i].length; j++)
2042 hosts_radios_wifis[3].push(Object.assign({ radio: radioDev, network: wifiNetwork }, data[i][j]));
2045 return hosts_radios_wifis;
2048 .then(L.bind(this.poll_status, this, nodes));
2051 var table = E('div', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [
2052 E('div', { 'class': 'tr table-titles' }, [
2053 E('div', { 'class': 'th nowrap' }, _('Network')),
2054 E('div', { 'class': 'th hide-xs' }, _('MAC-Address')),
2055 E('div', { 'class': 'th' }, _('Host')),
2056 E('div', { 'class': 'th' }, _('Signal / Noise')),
2057 E('div', { 'class': 'th' }, _('RX Rate / TX Rate'))
2061 cbi_update_table(table, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
2063 return E([ nodes, E('h3', _('Associated Stations')), table ]);