9 'require tools.widgets as widgets';
11 function count_changes(section_id) {
12 var changes = ui.changes.changes, n = 0;
14 if (!L.isObject(changes))
17 if (Array.isArray(changes.wireless))
18 for (var i = 0; i < changes.wireless.length; i++)
19 n += (changes.wireless[i][1] == section_id);
24 function render_radio_badge(radioDev) {
25 return E('span', { 'class': 'ifacebadge' }, [
26 E('img', { 'src': L.resource('icons/wifi%s.png').format(radioDev.isUp() ? '' : '_disabled') }),
32 function render_signal_badge(signalPercent, signalValue, noiseValue, wrap) {
35 if (signalPercent < 0)
36 icon = L.resource('icons/signal-none.png');
37 else if (signalPercent == 0)
38 icon = L.resource('icons/signal-0.png');
39 else if (signalPercent < 25)
40 icon = L.resource('icons/signal-0-25.png');
41 else if (signalPercent < 50)
42 icon = L.resource('icons/signal-25-50.png');
43 else if (signalPercent < 75)
44 icon = L.resource('icons/signal-50-75.png');
46 icon = L.resource('icons/signal-75-100.png');
48 if (signalValue != null && signalValue != 0) {
49 title = '%s %d %s'.format(_('Signal'), signalValue, _('dBm'));
51 if (noiseValue != null && noiseValue != 0)
52 title += ' / %s: %d %s'.format(_('Noise'), noiseValue, _('dBm'));
55 title = _('No signal');
58 return E('div', { 'class': wrap ? 'center' : 'ifacebadge', 'title': title },
59 [ E('img', { 'src': icon }), wrap ? E('br') : ' ', '%d%%'.format(Math.max(signalPercent, 0)) ]);
62 function render_network_badge(radioNet) {
63 return render_signal_badge(radioNet.isUp() ? radioNet.getSignalPercent() : -1, radioNet.getSignal(), radioNet.getNoise());
66 function render_radio_status(radioDev, wifiNets) {
67 var name = radioDev.getI18n().replace(/ Wireless Controller .+$/, ''),
68 node = E('div', [ E('big', {}, E('strong', {}, name)), E('div') ]),
69 channel, frequency, bitrate;
71 for (var i = 0; i < wifiNets.length; i++) {
72 channel = channel || wifiNets[i].getChannel();
73 frequency = frequency || wifiNets[i].getFrequency();
74 bitrate = bitrate || wifiNets[i].getBitRate();
78 L.itemlist(node.lastElementChild, [
79 _('Channel'), '%s (%s %s)'.format(channel || '?', frequency || '?', _('GHz')),
80 _('Bitrate'), '%s %s'.format(bitrate || '?', _('Mbit/s'))
83 node.lastElementChild.appendChild(E('em', _('Device is not active')));
88 function render_network_status(radioNet) {
89 var mode = radioNet.getActiveMode(),
90 bssid = radioNet.getActiveBSSID(),
91 channel = radioNet.getChannel(),
92 disabled = (radioNet.get('disabled') == '1' || uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == '1'),
93 is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled),
94 is_mesh = (radioNet.getMode() == 'mesh'),
95 changecount = count_changes(radioNet.getName()),
99 status_text = E('a', {
101 click: L.bind(ui.changes.displayChanges, ui.changes)
102 }, _('Interface has %d pending changes').format(changecount));
104 status_text = E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated'));
106 return L.itemlist(E('div'), [
107 is_mesh ? _('Mesh ID') : _('SSID'), (is_mesh ? radioNet.getMeshID() : radioNet.getSSID()) || '?',
109 _('BSSID'), (!changecount && is_assoc) ? bssid : null,
110 _('Encryption'), (!changecount && is_assoc) ? radioNet.getActiveEncryption() || _('None') : null,
112 ], [ ' | ', E('br') ]);
115 function render_modal_status(node, radioNet) {
116 var mode = radioNet.getActiveMode(),
117 noise = radioNet.getNoise(),
118 bssid = radioNet.getActiveBSSID(),
119 channel = radioNet.getChannel(),
120 disabled = (radioNet.get('disabled') == '1'),
121 is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled);
124 node = E('span', { 'class': 'ifacebadge large', 'data-network': radioNet.getName() }, [ E('small'), E('span') ]);
126 L.dom.content(node.firstElementChild, render_signal_badge(disabled ? -1 : radioNet.getSignalPercent(), radioNet.getSignal(), noise, true));
128 L.itemlist(node.lastElementChild, [
130 _('SSID'), radioNet.getSSID() || '?',
131 _('BSSID'), is_assoc ? bssid : null,
132 _('Encryption'), is_assoc ? radioNet.getActiveEncryption() || _('None') : null,
133 _('Channel'), is_assoc ? '%d (%.3f %s)'.format(radioNet.getChannel(), radioNet.getFrequency() || 0, _('GHz')) : null,
134 _('Tx-Power'), is_assoc ? '%d %s'.format(radioNet.getTXPower(), _('dBm')) : null,
135 _('Signal'), is_assoc ? '%d %s'.format(radioNet.getSignal(), _('dBm')) : null,
136 _('Noise'), (is_assoc && noise != null) ? '%d %s'.format(noise, _('dBm')) : null,
137 _('Bitrate'), is_assoc ? '%.1f %s'.format(radioNet.getBitRate() || 0, _('Mbit/s')) : null,
138 _('Country'), is_assoc ? radioNet.getCountryCode() : null
139 ], [ ' | ', E('br'), E('br'), E('br'), E('br'), E('br'), ' | ', E('br'), ' | ' ]);
142 L.dom.append(node.lastElementChild, E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated')));
147 function format_wifirate(rate) {
148 var s = '%.1f Mbit/s, %dMHz'.format(rate.rate / 1000, rate.mhz);
150 if (rate.ht || rate.vht) {
151 if (rate.vht) s += ', VHT-MCS %d'.format(rate.mcs);
152 if (rate.nss) s += ', VHT-NSS %d'.format(rate.nss);
153 if (rate.ht) s += ', MCS %s'.format(rate.mcs);
154 if (rate.short_gi) s += ', Short GI';
160 function radio_restart(id, ev) {
161 var row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id)),
162 dsc = row.querySelector('[data-name="_stat"] > div'),
163 btn = row.querySelector('.cbi-section-actions button');
166 btn.classList.add('spinning');
169 dsc.setAttribute('restart', '');
170 L.dom.content(dsc, E('em', _('Device is restarting…')));
173 function network_updown(id, map, ev) {
174 var radio = uci.get('wireless', id, 'device'),
175 disabled = (uci.get('wireless', id, 'disabled') == '1') ||
176 (uci.get('wireless', radio, 'disabled') == '1');
179 uci.unset('wireless', id, 'disabled');
180 uci.unset('wireless', radio, 'disabled');
183 uci.set('wireless', id, 'disabled', '1');
185 var all_networks_disabled = true,
186 wifi_ifaces = uci.sections('wireless', 'wifi-iface');
188 for (var i = 0; i < wifi_ifaces.length; i++) {
189 if (wifi_ifaces[i].device == radio && wifi_ifaces[i].disabled != '1') {
190 all_networks_disabled = false;
195 if (all_networks_disabled)
196 uci.set('wireless', radio, 'disabled', '1');
199 return map.save().then(function() {
204 function next_free_sid(offset) {
205 var sid = 'wifinet' + offset;
207 while (uci.get('wireless', sid))
208 sid = 'wifinet' + (++offset);
213 var CBIWifiFrequencyValue = form.Value.extend({
214 callFrequencyList: rpc.declare({
217 params: [ 'device' ],
218 expect: { results: [] }
221 load: function(section_id) {
223 network.getWifiDevice(section_id),
224 this.callFrequencyList(section_id)
225 ]).then(L.bind(function(data) {
227 '11g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
228 '11a': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : []
231 for (var i = 0; i < data[1].length; i++)
232 this.channels[(data[1][i].mhz > 2484) ? '11a' : '11g'].push(
234 '%d (%d Mhz)'.format(data[1][i].channel, data[1][i].mhz),
235 !data[1][i].restricted
238 var hwmodelist = L.toArray(data[0] ? data[0].getHWModes() : null)
239 .reduce(function(o, v) { o[v] = true; return o }, {});
243 'n', 'N', hwmodelist.n,
244 'ac', 'AC', hwmodelist.ac
247 var htmodelist = L.toArray(data[0] ? data[0].getHTModes() : null)
248 .reduce(function(o, v) { o[v] = true; return o }, {});
251 '': [ '', '-', true ],
253 'HT20', '20 MHz', htmodelist.HT20,
254 'HT40', '40 MHz', htmodelist.HT40
257 'VHT20', '20 MHz', htmodelist.VHT20,
258 'VHT40', '40 MHz', htmodelist.VHT40,
259 'VHT80', '80 MHz', htmodelist.VHT80,
260 'VHT160', '160 MHz', htmodelist.VHT160
266 '11g', '2.4 GHz', this.channels['11g'].length > 3,
267 '11a', '5 GHz', this.channels['11a'].length > 3
270 '11g', '2.4 GHz', this.channels['11g'].length > 3,
271 '11a', '5 GHz', this.channels['11a'].length > 3
280 setValues: function(sel, vals) {
282 sel.vals.selected = sel.selectedIndex;
284 while (sel.options[0])
287 for (var i = 0; vals && i < vals.length; i += 3)
289 sel.add(E('option', { value: vals[i+0] }, [ vals[i+1] ]));
291 if (vals && !isNaN(vals.selected))
292 sel.selectedIndex = vals.selected;
294 sel.parentNode.style.display = (sel.options.length <= 1) ? 'none' : '';
298 toggleWifiMode: function(elem) {
299 this.toggleWifiHTMode(elem);
300 this.toggleWifiBand(elem);
303 toggleWifiHTMode: function(elem) {
304 var mode = elem.querySelector('.mode');
305 var bwdt = elem.querySelector('.htmode');
307 this.setValues(bwdt, this.htmodes[mode.value]);
310 toggleWifiBand: function(elem) {
311 var mode = elem.querySelector('.mode');
312 var band = elem.querySelector('.band');
314 this.setValues(band, this.bands[mode.value]);
315 this.toggleWifiChannel(elem);
318 toggleWifiChannel: function(elem) {
319 var band = elem.querySelector('.band');
320 var chan = elem.querySelector('.channel');
322 this.setValues(chan, this.channels[band.value]);
325 setInitialValues: function(section_id, elem) {
326 var mode = elem.querySelector('.mode'),
327 band = elem.querySelector('.band'),
328 chan = elem.querySelector('.channel'),
329 bwdt = elem.querySelector('.htmode'),
330 htval = uci.get('wireless', section_id, 'htmode'),
331 hwval = uci.get('wireless', section_id, 'hwmode'),
332 chval = uci.get('wireless', section_id, 'channel');
334 this.setValues(mode, this.modes);
336 if (/VHT20|VHT40|VHT80|VHT160/.test(htval))
338 else if (/HT20|HT40/.test(htval))
343 this.toggleWifiMode(elem);
350 this.toggleWifiBand(elem);
358 renderWidget: function(section_id, option_index, cfgvalue) {
361 L.dom.content(elem, [
362 E('label', { 'style': 'float:left; margin-right:3px' }, [
366 'style': 'width:auto',
367 'change': L.bind(this.toggleWifiMode, this, elem)
370 E('label', { 'style': 'float:left; margin-right:3px' }, [
374 'style': 'width:auto',
375 'change': L.bind(this.toggleWifiBand, this, elem)
378 E('label', { 'style': 'float:left; margin-right:3px' }, [
379 _('Channel'), E('br'),
382 'style': 'width:auto'
385 E('label', { 'style': 'float:left; margin-right:3px' }, [
389 'style': 'width:auto'
392 E('br', { 'style': 'clear:left' })
395 return this.setInitialValues(section_id, elem);
398 cfgvalue: function(section_id) {
400 uci.get('wireless', section_id, 'htmode'),
401 uci.get('wireless', section_id, 'hwmode'),
402 uci.get('wireless', section_id, 'channel')
406 formvalue: function(section_id) {
407 var node = this.map.findElement('data-field', this.cbid(section_id));
410 node.querySelector('.htmode').value,
411 node.querySelector('.band').value,
412 node.querySelector('.channel').value
416 write: function(section_id, value) {
417 uci.set('wireless', section_id, 'htmode', value[0] || null);
418 uci.set('wireless', section_id, 'hwmode', value[1]);
419 uci.set('wireless', section_id, 'channel', value[2]);
423 var CBIWifiTxPowerValue = form.ListValue.extend({
424 callTxPowerList: rpc.declare({
426 method: 'txpowerlist',
427 params: [ 'device' ],
428 expect: { results: [] }
431 load: function(section_id) {
432 return this.callTxPowerList(section_id).then(L.bind(function(pwrlist) {
433 this.powerval = this.wifiNetwork ? this.wifiNetwork.getTXPower() : null;
434 this.poweroff = this.wifiNetwork ? this.wifiNetwork.getTXPowerOffset() : null;
436 this.value('', _('driver default'));
438 for (var i = 0; i < pwrlist.length; i++)
439 this.value(pwrlist[i].dbm, '%d dBm (%d mW)'.format(pwrlist[i].dbm, pwrlist[i].mw));
441 return form.ListValue.prototype.load.apply(this, [section_id]);
445 renderWidget: function(section_id, option_index, cfgvalue) {
446 var widget = form.ListValue.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
447 widget.firstElementChild.style.width = 'auto';
449 L.dom.append(widget, E('span', [
450 ' - ', _('Current power'), ': ',
451 E('span', [ this.powerval != null ? '%d dBm'.format(this.powerval) : E('em', _('unknown')) ]),
452 this.poweroff ? ' + %d dB offset = %s dBm'.format(this.poweroff, this.powerval != null ? this.powerval + this.poweroff : '?') : ''
459 var CBIWifiCountryValue = form.Value.extend({
460 callCountryList: rpc.declare({
462 method: 'countrylist',
463 params: [ 'device' ],
464 expect: { results: [] }
467 load: function(section_id) {
468 return this.callCountryList(section_id).then(L.bind(function(countrylist) {
469 if (Array.isArray(countrylist) && countrylist.length > 0) {
470 this.value('', _('driver default'));
472 for (var i = 0; i < countrylist.length; i++)
473 this.value(countrylist[i].iso3166, '%s - %s'.format(countrylist[i].iso3166, countrylist[i].country));
476 return form.Value.prototype.load.apply(this, [section_id]);
480 validate: function(section_id, formvalue) {
481 if (formvalue != null && formvalue != '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue))
482 return _('Use ISO/IEC 3166 alpha2 country codes.');
487 renderWidget: function(section_id, option_index, cfgvalue) {
488 var typeClass = (this.keylist && this.keylist.length) ? form.ListValue : form.Value;
489 return typeClass.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
493 return L.view.extend({
494 poll_status: function(map, data) {
495 var rows = map.querySelectorAll('.cbi-section-table-row[data-sid]');
497 for (var i = 0; i < rows.length; i++) {
498 var section_id = rows[i].getAttribute('data-sid'),
499 radioDev = data[1].filter(function(d) { return d.getName() == section_id })[0],
500 radioNet = data[2].filter(function(n) { return n.getName() == section_id })[0],
501 badge = rows[i].querySelector('[data-name="_badge"] > div'),
502 stat = rows[i].querySelector('[data-name="_stat"]'),
503 btns = rows[i].querySelectorAll('.cbi-section-actions button'),
504 busy = btns[0].classList.contains('spinning') || btns[1].classList.contains('spinning') || btns[2].classList.contains('spinning');
507 L.dom.content(badge, render_radio_badge(radioDev));
508 L.dom.content(stat, render_radio_status(radioDev, data[2].filter(function(n) { return n.getWifiDeviceName() == radioDev.getName() })));
511 L.dom.content(badge, render_network_badge(radioNet));
512 L.dom.content(stat, render_network_status(radioNet));
515 if (stat.hasAttribute('restart'))
516 L.dom.content(stat, E('em', _('Device is restarting…')));
518 btns[0].disabled = busy;
519 btns[1].disabled = busy;
520 btns[2].disabled = busy;
523 var table = document.querySelector('#wifi_assoclist_table'),
527 for (var i = 0; i < data[3].length; i++) {
528 var bss = data[3][i],
529 name = hosts.getHostnameByMACAddr(bss.mac),
530 ipv4 = hosts.getIPAddrByMACAddr(bss.mac),
531 ipv6 = hosts.getIP6AddrByMACAddr(bss.mac);
535 if (name && ipv4 && ipv6)
536 hint = '%s (%s, %s)'.format(name, ipv4, ipv6);
537 else if (name && (ipv4 || ipv6))
538 hint = '%s (%s)'.format(name, ipv4 || ipv6);
540 hint = name || ipv4 || ipv6 || '?';
543 E('span', { 'class': 'ifacebadge' }, [
545 'src': L.resource('icons/wifi%s.png').format(bss.network.isUp() ? '' : '_disabled'),
546 'title': bss.radio.getI18n()
548 ' %s '.format(bss.network.getShortName()),
549 E('small', '(%s)'.format(bss.network.getIfname()))
553 render_signal_badge(Math.min((bss.signal + 110) / 70 * 100, 100), bss.signal, bss.noise),
555 E('span', format_wifirate(bss.rx)),
557 E('span', format_wifirate(bss.tx))
561 if (bss.network.isClientDisconnectSupported()) {
562 if (table.firstElementChild.childNodes.length < 6)
563 table.firstElementChild.appendChild(E('div', { 'class': 'th nowrap right'}, [ _('Disconnect') ]));
565 row.push(E('button', {
566 'class': 'cbi-button cbi-button-remove',
567 'click': L.bind(function(net, mac, ev) {
568 L.dom.parent(ev.currentTarget, '.tr').style.opacity = 0.5;
569 ev.currentTarget.classList.add('spinning');
570 ev.currentTarget.disabled = true;
571 ev.currentTarget.blur();
573 net.disconnectClient(mac, true, 5, 60000);
574 }, this, bss.network, bss.mac)
575 }, [ _('Disconnect') ]));
584 cbi_update_table(table, trows, E('em', _('No information available')));
586 var stat = document.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large');
589 render_modal_status(stat, data[2].filter(function(n) { return n.getName() == stat.getAttribute('data-network') })[0]);
591 return network.flushCache();
601 checkAnonymousSections: function() {
602 var wifiIfaces = uci.sections('wireless', 'wifi-iface');
604 for (var i = 0; i < wifiIfaces.length; i++)
605 if (wifiIfaces[i]['.anonymous'])
611 callUciRename: rpc.declare({
614 params: [ 'config', 'section', 'name' ]
618 if (this.checkAnonymousSections())
619 return this.renderMigration();
621 return this.renderOverview();
624 handleMigration: function(ev) {
625 var wifiIfaces = uci.sections('wireless', 'wifi-iface'),
629 for (var i = 0; i < wifiIfaces.length; i++) {
630 if (!wifiIfaces[i]['.anonymous'])
633 var new_name = next_free_sid(id_offset);
635 tasks.push(this.callUciRename('wireless', wifiIfaces[i]['.name'], new_name));
636 id_offset = +new_name.substring(7) + 1;
639 return Promise.all(tasks)
640 .then(L.bind(ui.changes.init, ui.changes))
641 .then(L.bind(ui.changes.apply, ui.changes));
644 renderMigration: function() {
645 ui.showModal(_('Wireless configuration migration'), [
646 E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')),
647 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.')),
648 E('div', { 'class': 'right' },
650 'class': 'btn cbi-button-action important',
651 'click': ui.createHandlerFn(this, 'handleMigration')
656 renderOverview: function() {
659 m = new form.Map('wireless');
663 s = m.section(form.GridSection, 'wifi-device', _('Wireless Overview'));
667 s.load = function() {
668 return network.getWifiDevices().then(L.bind(function(radios) {
669 this.radios = radios.sort(function(a, b) {
670 return a.getName() > b.getName();
675 for (var i = 0; i < radios.length; i++)
676 tasks.push(radios[i].getWifiNetworks());
678 return Promise.all(tasks);
679 }, this)).then(L.bind(function(data) {
682 for (var i = 0; i < data.length; i++)
683 this.wifis.push.apply(this.wifis, data[i]);
687 s.cfgsections = function() {
690 for (var i = 0; i < this.radios.length; i++) {
691 rv.push(this.radios[i].getName());
693 for (var j = 0; j < this.wifis.length; j++)
694 if (this.wifis[j].getWifiDeviceName() == this.radios[i].getName())
695 rv.push(this.wifis[j].getName());
701 s.modaltitle = function(section_id) {
702 var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id})[0];
703 return radioNet ? radioNet.getI18n() : _('Edit wireless network');
706 s.lookupRadioOrNetwork = function(section_id) {
707 var radioDev = this.radios.filter(function(r) { return r.getName() == section_id })[0];
711 var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id })[0];
718 s.renderRowActions = function(section_id) {
719 var inst = this.lookupRadioOrNetwork(section_id), btns;
721 if (inst.getWifiNetworks) {
724 'class': 'cbi-button cbi-button-neutral',
725 'title': _('Restart radio interface'),
726 'click': ui.createHandlerFn(this, radio_restart, section_id)
729 'class': 'cbi-button cbi-button-action important',
730 'title': _('Find and join network'),
731 'click': ui.createHandlerFn(this, 'handleScan', inst)
734 'class': 'cbi-button cbi-button-add',
735 'title': _('Provide new network'),
736 'click': ui.createHandlerFn(this, 'handleAdd', inst)
741 var isDisabled = (inst.get('disabled') == '1' ||
742 uci.get('wireless', inst.getWifiDeviceName(), 'disabled') == '1');
746 'class': 'cbi-button cbi-button-neutral enable-disable',
747 'title': isDisabled ? _('Enable this network') : _('Disable this network'),
748 'click': ui.createHandlerFn(this, network_updown, section_id, this.map)
749 }, isDisabled ? _('Enable') : _('Disable')),
751 'class': 'cbi-button cbi-button-action important',
752 'title': _('Edit this network'),
753 'click': ui.createHandlerFn(this, 'renderMoreOptionsModal', section_id)
756 'class': 'cbi-button cbi-button-negative remove',
757 'title': _('Delete this network'),
758 'click': ui.createHandlerFn(this, 'handleRemove', section_id)
763 return E('div', { 'class': 'td middle cbi-section-actions' }, E('div', btns));
766 s.addModalOptions = function(s) {
767 return network.getWifiNetwork(s.section).then(function(radioNet) {
768 var hwtype = uci.get('wireless', radioNet.getWifiDeviceName(), 'type');
771 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
775 ss.tab('general', _('General Setup'));
776 ss.tab('advanced', _('Advanced Settings'));
778 var isDisabled = (radioNet.get('disabled') == '1' ||
779 uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == 1);
781 o = ss.taboption('general', form.DummyValue, '_wifistat_modal', _('Status'));
782 o.cfgvalue = L.bind(function(radioNet) {
783 return render_modal_status(null, radioNet);
785 o.write = function() {};
787 o = ss.taboption('general', form.Button, '_toggle', isDisabled ? _('Wireless network is disabled') : _('Wireless network is enabled'));
788 o.inputstyle = isDisabled ? 'apply' : 'reset';
789 o.inputtitle = isDisabled ? _('Enable') : _('Disable');
790 o.onclick = ui.createHandlerFn(s, network_updown, s.section, s.map);
792 o = ss.taboption('general', CBIWifiFrequencyValue, '_freq', '<br />' + _('Operating frequency'));
793 o.ucisection = s.section;
795 if (hwtype == 'mac80211') {
796 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.'));
797 o.wifiNetwork = radioNet;
799 o = ss.taboption('advanced', CBIWifiCountryValue, 'country', _('Country Code'));
800 o.wifiNetwork = radioNet;
802 o = ss.taboption('advanced', form.Flag, 'legacy_rates', _('Allow legacy 802.11b rates'));
803 o.default = o.enabled;
805 o = ss.taboption('advanced', form.Value, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
806 o.datatype = 'range(0,114750)';
807 o.placeholder = 'auto';
809 o = ss.taboption('advanced', form.Value, 'frag', _('Fragmentation Threshold'));
810 o.datatype = 'min(256)';
811 o.placeholder = _('off');
813 o = ss.taboption('advanced', form.Value, 'rts', _('RTS/CTS Threshold'));
814 o.datatype = 'uinteger';
815 o.placeholder = _('off');
817 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!'));
820 o = ss.taboption('advanced', form.Value, 'beacon_int', _('Beacon Interval'));
821 o.datatype = 'range(15,65535)';
827 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getName(), 'wifi-iface', _('Interface Configuration'));
831 ss.tab('general', _('General Setup'));
832 ss.tab('encryption', _('Wireless Security'));
833 ss.tab('macfilter', _('MAC-Filter'));
834 ss.tab('advanced', _('Advanced Settings'));
836 o = ss.taboption('general', form.ListValue, 'mode', _('Mode'));
837 o.value('ap', _('Access Point'));
838 o.value('sta', _('Client'));
839 o.value('adhoc', _('Ad-Hoc'));
841 o = ss.taboption('general', form.Value, 'mesh_id', _('Mesh Id'));
842 o.depends('mode', 'mesh');
844 o = ss.taboption('advanced', form.Flag, 'mesh_fwding', _('Forward mesh peer traffic'));
847 o.depends('mode', 'mesh');
849 o = ss.taboption('advanced', form.Value, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
852 o.datatype = 'range(-255,1)';
853 o.depends('mode', 'mesh');
855 o = ss.taboption('general', form.Value, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
856 o.datatype = 'maxlength(32)';
857 o.depends('mode', 'ap');
858 o.depends('mode', 'sta');
859 o.depends('mode', 'adhoc');
860 o.depends('mode', 'ahdemo');
861 o.depends('mode', 'monitor');
862 o.depends('mode', 'ap-wds');
863 o.depends('mode', 'sta-wds');
864 o.depends('mode', 'wds');
866 o = ss.taboption('general', form.Value, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
867 o.datatype = 'macaddr';
869 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.'));
873 o.write = function(section_id, value) {
874 return network.getDevice(section_id).then(L.bind(function(dev) {
875 var old_networks = dev.getNetworks().reduce(function(o, v) { o[v.getName()] = v; return o }, {}),
877 values = L.toArray(value),
880 for (var i = 0; i < values.length; i++) {
881 new_networks[values[i]] = true;
883 if (old_networks[values[i]])
886 tasks.push(network.getNetwork(values[i]).then(L.bind(function(name, net) {
887 return net || network.addNetwork(name, { proto: 'none' });
888 }, this, values[i])).then(L.bind(function(dev, net) {
891 net.set('type', 'bridge');
897 for (var name in old_networks)
898 if (!new_networks[name])
899 tasks.push(network.getNetwork(name).then(L.bind(function(dev, net) {
901 net.deleteDevice(dev);
904 return Promise.all(tasks);
908 if (hwtype == 'mac80211') {
909 var mode = ss.children[0],
910 bssid = ss.children[5],
913 mode.value('mesh', '802.11s');
914 mode.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
915 mode.value('monitor', _('Monitor'));
917 bssid.depends('mode', 'adhoc');
918 bssid.depends('mode', 'sta');
919 bssid.depends('mode', 'sta-wds');
921 o = ss.taboption('macfilter', form.ListValue, 'macfilter', _('MAC-Address Filter'));
922 o.depends('mode', 'ap');
923 o.depends('mode', 'ap-wds');
924 o.value('', _('disable'));
925 o.value('allow', _('Allow listed only'));
926 o.value('deny', _('Allow all except listed'));
928 o = ss.taboption('macfilter', form.DynamicList, 'maclist', _('MAC-List'));
929 o.datatype = 'macaddr';
930 o.depends('macfilter', 'allow');
931 o.depends('macfilter', 'deny');
932 o.load = function(section_id) {
933 return network.getHostHints().then(L.bind(function(hints) {
934 hints.getMACHints().map(L.bind(function(hint) {
935 this.value(hint[0], hint[1] ? '%s (%s)'.format(hint[0], hint[1]) : hint[0]);
938 return form.DynamicList.prototype.load.apply(this, [section_id]);
942 mode.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
943 mode.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
945 mode.write = function(section_id, value) {
948 uci.set('wireless', section_id, 'mode', 'ap');
949 uci.set('wireless', section_id, 'wds', '1');
953 uci.set('wireless', section_id, 'mode', 'sta');
954 uci.set('wireless', section_id, 'wds', '1');
958 uci.set('wireless', section_id, 'mode', value);
959 uci.unset('wireless', section_id, 'wds');
964 mode.cfgvalue = function(section_id) {
965 var mode = uci.get('wireless', section_id, 'mode'),
966 wds = uci.get('wireless', section_id, 'wds');
968 if (mode == 'ap' && wds)
970 else if (mode == 'sta' && wds)
976 o = ss.taboption('general', form.Flag, 'hidden', _('Hide <abbr title="Extended Service Set Identifier">ESSID</abbr>'));
977 o.depends('mode', 'ap');
978 o.depends('mode', 'ap-wds');
980 o = ss.taboption('general', form.Flag, 'wmm', _('WMM Mode'));
981 o.depends('mode', 'ap');
982 o.depends('mode', 'ap-wds');
983 o.default = o.enabled;
985 o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
986 o.depends('mode', 'ap');
987 o.depends('mode', 'ap-wds');
989 o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name'));
991 o.placeholder = radioNet.getIfname();
992 if (/^radio\d+\.network/.test(o.placeholder))
995 o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble'));
996 o.default = o.enabled;
998 o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
1001 o.datatype = 'range(1,255)';
1003 o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
1005 o.placeholder = 600;
1006 o.datatype = 'uinteger';
1008 o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling'));
1010 o.datatype = 'uinteger';
1012 o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('sec'));
1014 o.placeholder = 300;
1015 o.datatype = 'uinteger';
1017 o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval'));
1019 o.placeholder = 65535;
1020 o.datatype = 'uinteger';
1022 o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
1023 o.default = o.enabled;
1027 encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption'));
1028 o.depends('mode', 'ap');
1029 o.depends('mode', 'sta');
1030 o.depends('mode', 'adhoc');
1031 o.depends('mode', 'ahdemo');
1032 o.depends('mode', 'ap-wds');
1033 o.depends('mode', 'sta-wds');
1034 o.depends('mode', 'mesh');
1036 o.cfgvalue = function(section_id) {
1037 var v = String(uci.get('wireless', section_id, 'encryption'));
1040 else if (v.match(/\+/))
1041 return v.replace(/\+.+$/, '');
1045 o.write = function(section_id, value) {
1046 var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id),
1047 co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id);
1049 if (value == 'wpa' || value == 'wpa2')
1050 uci.unset('wireless', section_id, 'key');
1052 if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp'))
1055 uci.set('wireless', section_id, 'encryption', e);
1058 o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher'));
1059 o.depends('encryption', 'wpa');
1060 o.depends('encryption', 'wpa2');
1061 o.depends('encryption', 'psk');
1062 o.depends('encryption', 'psk2');
1063 o.depends('encryption', 'wpa-mixed');
1064 o.depends('encryption', 'psk-mixed');
1065 o.value('auto', _('auto'));
1066 o.value('ccmp', _('Force CCMP (AES)'));
1067 o.value('tkip', _('Force TKIP'));
1068 o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1069 o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write;
1071 o.cfgvalue = function(section_id) {
1072 var v = String(uci.get('wireless', section_id, 'encryption'));
1073 if (v.match(/\+/)) {
1074 v = v.replace(/^[^+]+\+/, '');
1077 else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip')
1084 var crypto_modes = [];
1086 if (hwtype == 'mac80211') {
1087 var has_supplicant = L.hasSystemFeature('wpasupplicant'),
1088 has_hostapd = L.hasSystemFeature('hostapd');
1090 // Probe EAP support
1091 var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'),
1092 has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap');
1094 // Probe SAE support
1095 var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'),
1096 has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae');
1098 // Probe OWE support
1099 var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
1100 has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
1103 if (has_hostapd || has_supplicant) {
1104 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1105 crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1106 crypto_modes.push(['psk', 'WPA-PSK', 21]);
1109 encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1112 if (has_ap_sae || has_sta_sae) {
1113 crypto_modes.push(['sae', 'WPA3-SAE', 31]);
1114 crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1117 if (has_ap_eap || has_sta_eap) {
1118 crypto_modes.push(['wpa2', 'WPA2-EAP', 32]);
1119 crypto_modes.push(['wpa', 'WPA-EAP', 20]);
1122 if (has_ap_owe || has_sta_owe) {
1123 crypto_modes.push(['owe', 'OWE', 1]);
1126 encr.crypto_support = {
1130 'psk': has_hostapd || _('Requires hostapd'),
1131 'psk2': has_hostapd || _('Requires hostapd'),
1132 'psk-mixed': has_hostapd || _('Requires hostapd'),
1133 'sae': has_ap_sae || _('Requires hostapd with SAE support'),
1134 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
1135 'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
1136 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
1137 'owe': has_ap_owe || _('Requires hostapd with OWE support')
1142 'psk': has_supplicant || _('Requires wpa-supplicant'),
1143 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1144 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1145 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1146 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1147 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1148 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1149 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
1154 'psk': has_supplicant || _('Requires wpa-supplicant'),
1155 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1156 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1159 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support')
1171 encr.crypto_support['ap-wds'] = encr.crypto_support['ap'];
1172 encr.crypto_support['sta-wds'] = encr.crypto_support['sta'];
1174 encr.validate = function(section_id, value) {
1175 var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
1176 modeval = modeopt.formvalue(section_id),
1177 modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)],
1178 enctitle = this.vallist[this.keylist.indexOf(value)];
1180 if (value == 'none')
1183 if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value))
1184 return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle);
1186 return this.crypto_support[modeval][value];
1189 else if (hwtype == 'broadcom') {
1190 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1191 crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1192 crypto_modes.push(['psk', 'WPA-PSK', 21]);
1195 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1196 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1197 crypto_modes.push(['none', _('No Encryption'), 0]);
1199 crypto_modes.sort(function(a, b) { return b[2] - a[2] });
1201 for (var i = 0; i < crypto_modes.length; i++) {
1202 var security_level = (crypto_modes[i][2] >= 30) ? _('strong security')
1203 : (crypto_modes[i][2] >= 20) ? _('medium security')
1204 : (crypto_modes[i][2] >= 10) ? _('weak security') : _('open network');
1206 encr.value(crypto_modes[i][0], '%s (%s)'.format(crypto_modes[i][1], security_level));
1210 o = ss.taboption('encryption', form.Value, 'auth_server', _('Radius-Authentication-Server'));
1211 o.depends({ mode: 'ap', encryption: 'wpa' });
1212 o.depends({ mode: 'ap', encryption: 'wpa2' });
1213 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1214 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1216 o.datatype = 'host(0)';
1218 o = ss.taboption('encryption', form.Value, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
1219 o.depends({ mode: 'ap', encryption: 'wpa' });
1220 o.depends({ mode: 'ap', encryption: 'wpa2' });
1221 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1222 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1224 o.datatype = 'port';
1226 o = ss.taboption('encryption', form.Value, 'auth_secret', _('Radius-Authentication-Secret'));
1227 o.depends({ mode: 'ap', encryption: 'wpa' });
1228 o.depends({ mode: 'ap', encryption: 'wpa2' });
1229 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1230 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1234 o = ss.taboption('encryption', form.Value, 'acct_server', _('Radius-Accounting-Server'));
1235 o.depends({ mode: 'ap', encryption: 'wpa' });
1236 o.depends({ mode: 'ap', encryption: 'wpa2' });
1237 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1238 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1240 o.datatype = 'host(0)';
1242 o = ss.taboption('encryption', form.Value, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
1243 o.depends({ mode: 'ap', encryption: 'wpa' });
1244 o.depends({ mode: 'ap', encryption: 'wpa2' });
1245 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1246 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1248 o.datatype = 'port';
1250 o = ss.taboption('encryption', form.Value, 'acct_secret', _('Radius-Accounting-Secret'));
1251 o.depends({ mode: 'ap', encryption: 'wpa' });
1252 o.depends({ mode: 'ap', encryption: 'wpa2' });
1253 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1254 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1258 o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client'));
1259 o.depends({ mode: 'ap', encryption: 'wpa' });
1260 o.depends({ mode: 'ap', encryption: 'wpa2' });
1261 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1262 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1264 o.datatype = 'host(0)';
1266 o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
1267 o.depends({ mode: 'ap', encryption: 'wpa' });
1268 o.depends({ mode: 'ap', encryption: 'wpa2' });
1269 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1270 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1272 o.datatype = 'port';
1274 o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret'));
1275 o.depends({ mode: 'ap', encryption: 'wpa' });
1276 o.depends({ mode: 'ap', encryption: 'wpa2' });
1277 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1278 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1283 o = ss.taboption('encryption', form.Value, '_wpa_key', _('Key'));
1284 o.depends('encryption', 'psk');
1285 o.depends('encryption', 'psk2');
1286 o.depends('encryption', 'psk+psk2');
1287 o.depends('encryption', 'psk-mixed');
1288 o.depends('encryption', 'sae');
1289 o.depends('encryption', 'sae-mixed');
1290 o.datatype = 'wpakey';
1294 o.cfgvalue = function(section_id) {
1295 var key = uci.get('wireless', section_id, 'key');
1296 return /^[1234]$/.test(key) ? null : key;
1299 o.write = function(section_id, value) {
1300 uci.set('wireless', section_id, 'key', value);
1301 uci.unset('wireless', section_id, 'key1');
1302 uci.unset('wireless', section_id, 'key2');
1303 uci.unset('wireless', section_id, 'key3');
1304 uci.unset('wireless', section_id, 'key4');
1308 o = ss.taboption('encryption', form.ListValue, '_wep_key', _('Used Key Slot'));
1309 o.depends('encryption', 'wep-open');
1310 o.depends('encryption', 'wep-shared');
1311 o.value('1', _('Key #%d').format(1));
1312 o.value('2', _('Key #%d').format(2));
1313 o.value('3', _('Key #%d').format(3));
1314 o.value('4', _('Key #%d').format(4));
1316 o.cfgvalue = function(section_id) {
1317 var slot = +uci.get('wireless', section_id, 'key');
1318 return (slot >= 1 && slot <= 4) ? String(slot) : '';
1321 o.write = function(section_id, value) {
1322 uci.set('wireless', section_id, 'key', value);
1325 for (var slot = 1; slot <= 4; slot++) {
1326 o = ss.taboption('encryption', form.Value, 'key%d'.format(slot), _('Key #%d').format(slot));
1327 o.depends('encryption', 'wep-open');
1328 o.depends('encryption', 'wep-shared');
1329 o.datatype = 'wepkey';
1333 o.write = function(section_id, value) {
1334 if (value != null && (value.length == 5 || value.length == 13))
1335 value = 's:%s'.format(value);
1336 uci.set('wireless', section_id, this.option, value);
1341 if (hwtype == 'mac80211') {
1342 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1343 var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap');
1345 o = ss.taboption('encryption', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1346 o.depends({ mode: 'ap', encryption: 'wpa' });
1347 o.depends({ mode: 'ap', encryption: 'wpa2' });
1348 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1349 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1351 o.depends({ mode: 'ap', encryption: 'psk' });
1352 o.depends({ mode: 'ap', encryption: 'psk2' });
1353 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1354 o.depends({ mode: 'ap', encryption: 'sae' });
1355 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1356 o.depends({ mode: 'ap-wds', encryption: 'psk' });
1357 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1358 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1359 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1360 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1364 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.'));
1365 o.depends({ mode: 'ap', encryption: 'wpa' });
1366 o.depends({ mode: 'ap', encryption: 'wpa2' });
1367 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1368 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1369 o.depends({ ieee80211r: '1' });
1372 o = ss.taboption('encryption', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1373 o.depends({ ieee80211r: '1' });
1374 o.placeholder = '4f57';
1375 o.datatype = 'and(hexstring,length(4))';
1378 o = ss.taboption('encryption', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1379 o.depends({ ieee80211r: '1' });
1380 o.placeholder = '1000';
1381 o.datatype = 'range(1000,65535)';
1384 o = ss.taboption('encryption', form.ListValue, 'ft_over_ds', _('FT protocol'));
1385 o.depends({ ieee80211r: '1' });
1386 o.value('1', _('FT over DS'));
1387 o.value('0', _('FT over the Air'));
1390 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.'));
1391 o.depends({ ieee80211r: '1' });
1392 o.default = o.enabled;
1395 o = ss.taboption('encryption', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1396 o.depends({ ieee80211r: '1' });
1397 o.placeholder = '10000';
1398 o.datatype = 'uinteger';
1401 o = ss.taboption('encryption', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1402 o.depends({ ieee80211r: '1' });
1403 o.placeholder = '00004f577274';
1404 o.datatype = 'and(hexstring,length(12))';
1407 o = ss.taboption('encryption', form.Flag, 'pmk_r1_push', _('PMK R1 Push'));
1408 o.depends({ ieee80211r: '1' });
1409 o.placeholder = '0';
1412 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.'));
1413 o.depends({ ieee80211r: '1' });
1416 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.'));
1417 o.depends({ ieee80211r: '1' });
1419 // End of 802.11r options
1421 o = ss.taboption('encryption', form.ListValue, 'eap_type', _('EAP-Method'));
1422 o.value('tls', 'TLS');
1423 o.value('ttls', 'TTLS');
1424 o.value('peap', 'PEAP');
1425 o.value('fast', 'FAST');
1426 o.depends({ mode: 'sta', encryption: 'wpa' });
1427 o.depends({ mode: 'sta', encryption: 'wpa2' });
1428 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1429 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1431 o = ss.taboption('encryption', form.FileUpload, 'ca_cert', _('Path to CA-Certificate'));
1432 o.depends({ mode: 'sta', encryption: 'wpa' });
1433 o.depends({ mode: 'sta', encryption: 'wpa2' });
1434 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1435 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1437 o = ss.taboption('encryption', form.FileUpload, 'client_cert', _('Path to Client-Certificate'));
1438 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1439 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1440 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1441 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1443 o = ss.taboption('encryption', form.FileUpload, 'priv_key', _('Path to Private Key'));
1444 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1445 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1446 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1447 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1449 o = ss.taboption('encryption', form.Value, 'priv_key_pwd', _('Password of Private Key'));
1450 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1451 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1452 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1453 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1456 o = ss.taboption('encryption', form.ListValue, 'auth', _('Authentication'));
1457 o.value('PAP', 'PAP');
1458 o.value('CHAP', 'CHAP');
1459 o.value('MSCHAP', 'MSCHAP');
1460 o.value('MSCHAPV2', 'MSCHAPv2');
1463 o.value('EAP-MSCHAPV2');
1465 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1466 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1467 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1468 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1469 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1470 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1471 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1472 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1473 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1474 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1475 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1476 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1478 o.validate = function(section_id, value) {
1479 var eo = this.section.children.filter(function(o) { return o.option == 'eap_type' })[0],
1480 ev = eo.formvalue(section_id);
1482 if (ev != 'ttls' && (value == 'PAP' || value == 'CHAP' || value == 'MSCHAP' || value == 'MSCHAPV2'))
1483 return _('This authentication type is not applicable to the selected EAP method.');
1488 o = ss.taboption('encryption', form.FileUpload, 'ca_cert2', _('Path to inner CA-Certificate'));
1489 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1490 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1491 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1492 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1494 o = ss.taboption('encryption', form.FileUpload, 'client_cert2', _('Path to inner Client-Certificate'));
1495 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1496 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1497 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1498 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1500 o = ss.taboption('encryption', form.FileUpload, 'priv_key2', _('Path to inner Private Key'));
1501 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1502 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1503 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1504 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1506 o = ss.taboption('encryption', form.Value, 'priv_key2_pwd', _('Password of inner Private Key'));
1507 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1508 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1509 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1510 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1513 o = ss.taboption('encryption', form.Value, 'identity', _('Identity'));
1514 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1515 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1516 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1517 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1518 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1519 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1520 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1521 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1522 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1523 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1524 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1525 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1526 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1527 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1528 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1529 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1531 o = ss.taboption('encryption', form.Value, 'anonymous_identity', _('Anonymous Identity'));
1532 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1533 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1534 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1535 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1536 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1537 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1538 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1539 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1540 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1541 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1542 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1543 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1544 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1545 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1546 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1547 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1549 o = ss.taboption('encryption', form.Value, 'password', _('Password'));
1550 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1551 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1552 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1553 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1554 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1555 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1556 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1557 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1558 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1559 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1560 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1561 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1565 if (hwtype == 'mac80211') {
1566 // ieee802.11w options
1567 if (L.hasSystemFeature('hostapd', '11w')) {
1568 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)"));
1569 o.value('', _('Disabled'));
1570 o.value('1', _('Optional'));
1571 o.value('2', _('Required'));
1572 o.depends({ mode: 'ap', encryption: 'wpa2' });
1573 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1574 o.depends({ mode: 'ap', encryption: 'psk2' });
1575 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1576 o.depends({ mode: 'ap', encryption: 'sae' });
1577 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1578 o.depends({ mode: 'ap', encryption: 'owe' });
1579 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1580 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1581 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1582 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1583 o.depends({ mode: 'ap-wds', encryption: 'owe' });
1584 o.depends({ mode: 'sta', encryption: 'wpa2' });
1585 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1586 o.depends({ mode: 'sta', encryption: 'psk2' });
1587 o.depends({ mode: 'sta', encryption: 'psk-mixed' });
1588 o.depends({ mode: 'sta', encryption: 'sae' });
1589 o.depends({ mode: 'sta', encryption: 'sae-mixed' });
1590 o.depends({ mode: 'sta', encryption: 'owe' });
1591 o.depends({ mode: 'sta-wds', encryption: 'psk2' });
1592 o.depends({ mode: 'sta-wds', encryption: 'psk-mixed' });
1593 o.depends({ mode: 'sta-wds', encryption: 'sae' });
1594 o.depends({ mode: 'sta-wds', encryption: 'sae-mixed' });
1595 o.depends({ mode: 'sta-wds', encryption: 'owe' });
1597 '2': [{ encryption: 'sae' }, { encryption: 'owe' }],
1598 '1': [{ encryption: 'sae-mixed'}],
1602 o = ss.taboption('encryption', form.Value, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1603 o.depends('ieee80211w', '1');
1604 o.depends('ieee80211w', '2');
1605 o.datatype = 'uinteger';
1606 o.placeholder = '1000';
1609 o = ss.taboption('encryption', form.Value, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1610 o.depends('ieee80211w', '1');
1611 o.depends('ieee80211w', '2');
1612 o.datatype = 'uinteger';
1613 o.placeholder = '201';
1617 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.'));
1618 o.depends({ mode: 'ap', encryption: 'wpa2' });
1619 o.depends({ mode: 'ap', encryption: 'psk2' });
1620 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1621 o.depends({ mode: 'ap', encryption: 'sae' });
1622 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1623 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1624 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1625 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1626 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1627 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1629 if (L.hasSystemFeature('hostapd', 'cli') && L.hasSystemFeature('wpasupplicant')) {
1630 o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1633 o.default = o.disabled;
1634 o.depends('encryption', 'psk');
1635 o.depends('encryption', 'psk2');
1636 o.depends('encryption', 'psk-mixed');
1637 o.depends('encryption', 'sae');
1638 o.depends('encryption', 'sae-mixed');
1645 s.handleRemove = function(section_id, ev) {
1646 document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id)).style.opacity = 0.5;
1647 return form.TypedSection.prototype.handleRemove.apply(this, [section_id, ev]);
1650 s.handleScan = function(radioDev, ev) {
1651 var table = E('div', { 'class': 'table' }, [
1652 E('div', { 'class': 'tr table-titles' }, [
1653 E('div', { 'class': 'th col-2 middle center' }, _('Signal')),
1654 E('div', { 'class': 'th col-4 middle left' }, _('SSID')),
1655 E('div', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1656 E('div', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1657 E('div', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1658 E('div', { 'class': 'th col-3 middle left' }, _('Encryption')),
1659 E('div', { 'class': 'th cbi-section-actions right' }, ' '),
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' },
1670 'click': L.bind(this.handleScanAbort, this)
1674 md.style.maxWidth = '90%';
1675 md.style.maxHeight = 'none';
1677 this.pollFn = L.bind(this.handleScanRefresh, this, radioDev, {}, table);
1679 L.Poll.add(this.pollFn);
1683 s.handleScanRefresh = function(radioDev, scanCache, table) {
1684 return radioDev.getScanList().then(L.bind(function(results) {
1687 for (var i = 0; i < results.length; i++)
1688 scanCache[results[i].bssid] = results[i];
1690 for (var k in scanCache)
1691 if (scanCache[k].stale)
1692 results.push(scanCache[k]);
1694 results.sort(function(a, b) {
1695 var diff = (b.quality - a.quality) || (a.channel - b.channel);
1700 if (a.ssid < b.ssid)
1702 else if (a.ssid > b.ssid)
1705 if (a.bssid < b.bssid)
1707 else if (a.bssid > b.bssid)
1711 for (var i = 0; i < results.length; i++) {
1712 var res = results[i],
1713 qv = res.quality || 0,
1714 qm = res.quality_max || 0,
1715 q = (qv > 0 && qm > 0) ? Math.floor((100 / qm) * qv) : 0,
1716 s = res.stale ? 'opacity:0.5' : '';
1719 E('span', { 'style': s }, render_signal_badge(q, res.signal, res.noise)),
1720 E('span', { 'style': s }, '%h'.format(res.ssid)),
1721 E('span', { 'style': s }, '%d'.format(res.channel)),
1722 E('span', { 'style': s }, '%h'.format(res.mode)),
1723 E('span', { 'style': s }, '%h'.format(res.bssid)),
1724 E('span', { 'style': s }, '%h'.format(network.formatWifiEncryption(res.encryption))),
1725 E('div', { 'class': 'right' }, E('button', {
1726 'class': 'cbi-button cbi-button-action important',
1727 'click': L.bind(this.handleJoin, this, radioDev, res)
1728 }, _('Join Network')))
1734 cbi_update_table(table, rows);
1738 s.handleScanAbort = function(ev) {
1739 var md = L.dom.parent(ev.target, 'div[aria-modal="true"]');
1741 md.style.maxWidth = '';
1742 md.style.maxHeight = '';
1746 L.Poll.remove(this.pollFn);
1751 s.handleJoinConfirm = function(radioDev, bss, form, ev) {
1752 var nameopt = L.toArray(form.lookupOption('name', '_new_'))[0],
1753 passopt = L.toArray(form.lookupOption('password', '_new_'))[0],
1754 zoneopt = L.toArray(form.lookupOption('zone', '_new_'))[0],
1755 replopt = L.toArray(form.lookupOption('replace', '_new_'))[0],
1756 nameval = (nameopt && nameopt.isValid('_new_')) ? nameopt.formvalue('_new_') : null,
1757 passval = (passopt && passopt.isValid('_new_')) ? passopt.formvalue('_new_') : null,
1758 zoneval = zoneopt ? zoneopt.formvalue('_new_') : null,
1759 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1760 is_wep = (enc && Array.isArray(enc.wep)),
1761 is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' })),
1762 is_sae = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'sae' }));
1764 if (nameval == null || (passopt && passval == null))
1767 var section_id = null;
1769 return this.map.save(function() {
1770 var wifi_sections = uci.sections('wireless', 'wifi-iface');
1772 if (replopt.formvalue('_new_') == '1') {
1773 for (var i = 0; i < wifi_sections.length; i++)
1774 if (wifi_sections[i].device == radioDev.getName())
1775 uci.remove('wireless', wifi_sections[i]['.name']);
1778 if (uci.get('wireless', radioDev.getName(), 'disabled') == '1') {
1779 for (var i = 0; i < wifi_sections.length; i++)
1780 if (wifi_sections[i].device == radioDev.getName())
1781 uci.set('wireless', wifi_sections[i]['.name'], 'disabled', '1');
1783 uci.unset('wireless', radioDev.getName(), 'disabled');
1786 section_id = next_free_sid(wifi_sections.length);
1788 uci.add('wireless', 'wifi-iface', section_id);
1789 uci.set('wireless', section_id, 'device', radioDev.getName());
1790 uci.set('wireless', section_id, 'mode', (bss.mode == 'Ad-Hoc') ? 'adhoc' : 'sta');
1791 uci.set('wireless', section_id, 'network', nameval);
1793 if (bss.ssid != null)
1794 uci.set('wireless', section_id, 'ssid', bss.ssid);
1795 else if (bss.bssid != null)
1796 uci.set('wireless', section_id, 'bssid', bss.bssid);
1799 uci.set('wireless', section_id, 'encryption', 'sae');
1800 uci.set('wireless', section_id, 'key', passval);
1803 for (var i = enc.wpa.length - 1; i >= 0; i--) {
1804 if (enc.wpa[i] == 2) {
1805 uci.set('wireless', section_id, 'encryption', 'psk2');
1808 else if (enc.wpa[i] == 1) {
1809 uci.set('wireless', section_id, 'encryption', 'psk');
1814 uci.set('wireless', section_id, 'key', passval);
1817 uci.set('wireless', section_id, 'encryption', 'wep-open');
1818 uci.set('wireless', section_id, 'key', '1');
1819 uci.set('wireless', section_id, 'key1', passval);
1822 return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) {
1823 firewall.deleteNetwork(net.getName());
1825 var zonePromise = zoneval
1826 ? firewall.getZone(zoneval).then(function(zone) { return zone || firewall.addZone(zoneval) })
1827 : Promise.resolve();
1829 return zonePromise.then(function(zone) {
1831 zone.addNetwork(net.getName());
1834 }).then(L.bind(function() {
1835 return this.renderMoreOptionsModal(section_id);
1839 s.handleJoin = function(radioDev, bss, ev) {
1840 this.handleScanAbort(ev);
1842 var m2 = new form.Map('wireless'),
1843 s2 = m2.section(form.NamedSection, '_new_'),
1844 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1845 is_wep = (enc && Array.isArray(enc.wep)),
1846 is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' || a == 'sae' })),
1847 replace, passphrase, name, zone;
1849 s2.render = function() {
1850 return Promise.all([
1852 this.renderUCISection('_new_')
1853 ]).then(this.renderContents.bind(this));
1856 replace = s2.option(form.Flag, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
1858 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>'));
1859 name.datatype = 'uciname';
1860 name.default = 'wwan';
1861 name.rmempty = false;
1862 name.validate = function(section_id, value) {
1863 if (uci.get('network', value))
1864 return _('The network name is already used');
1869 for (var i = 2; uci.get('network', name.default); i++)
1870 name.default = 'wwan%d'.format(i);
1872 if (is_wep || is_psk) {
1873 passphrase = s2.option(form.Value, 'password', is_wep ? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
1874 passphrase.datatype = is_wep ? 'wepkey' : 'wpakey';
1875 passphrase.password = true;
1876 passphrase.rmempty = false;
1879 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.'));
1880 zone.default = 'wan';
1882 return m2.render().then(L.bind(function(nodes) {
1883 ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [
1885 E('div', { 'class': 'right' }, [
1888 'click': ui.hideModal
1889 }, _('Cancel')), ' ',
1891 'class': 'cbi-button cbi-button-positive important',
1892 'click': ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2)
1895 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase || name).cbid('_new_'))).focus();
1899 s.handleAdd = function(radioDev, ev) {
1900 var section_id = next_free_sid(uci.sections('wireless', 'wifi-iface').length);
1902 uci.unset('wireless', radioDev.getName(), 'disabled');
1904 uci.add('wireless', 'wifi-iface', section_id);
1905 uci.set('wireless', section_id, 'device', radioDev.getName());
1906 uci.set('wireless', section_id, 'mode', 'ap');
1907 uci.set('wireless', section_id, 'ssid', 'OpenWrt');
1908 uci.set('wireless', section_id, 'encryption', 'none');
1910 this.addedSection = section_id;
1911 return this.renderMoreOptionsModal(section_id);
1914 o = s.option(form.DummyValue, '_badge');
1915 o.modalonly = false;
1916 o.textvalue = function(section_id) {
1917 var inst = this.section.lookupRadioOrNetwork(section_id),
1918 node = E('div', { 'class': 'center' });
1920 if (inst.getWifiNetworks)
1921 node.appendChild(render_radio_badge(inst));
1923 node.appendChild(render_network_badge(inst));
1928 o = s.option(form.DummyValue, '_stat');
1929 o.modalonly = false;
1930 o.textvalue = function(section_id) {
1931 var inst = this.section.lookupRadioOrNetwork(section_id);
1933 if (inst.getWifiNetworks)
1934 return render_radio_status(inst, this.section.wifis.filter(function(e) {
1935 return (e.getWifiDeviceName() == inst.getName());
1938 return render_network_status(inst);
1941 return m.render().then(L.bind(function(m, nodes) {
1942 L.Poll.add(L.bind(function() {
1943 var section_ids = m.children[0].cfgsections(),
1944 tasks = [ network.getHostHints(), network.getWifiDevices() ];
1946 for (var i = 0; i < section_ids.length; i++) {
1947 var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])),
1948 dsc = row.querySelector('[data-name="_stat"] > div'),
1949 btns = row.querySelectorAll('.cbi-section-actions button');
1951 if (dsc.getAttribute('restart') == '') {
1952 dsc.setAttribute('restart', '1');
1953 tasks.push(fs.exec('/sbin/wifi', ['up', section_ids[i]]).catch(function(e) {
1954 ui.addNotification(null, E('p', e.message));
1957 else if (dsc.getAttribute('restart') == '1') {
1958 dsc.removeAttribute('restart');
1959 btns[0].classList.remove('spinning');
1960 btns[0].disabled = false;
1964 return Promise.all(tasks)
1965 .then(L.bind(function(hosts_radios) {
1968 for (var i = 0; i < hosts_radios[1].length; i++)
1969 tasks.push(hosts_radios[1][i].getWifiNetworks());
1971 return Promise.all(tasks).then(function(data) {
1972 hosts_radios[2] = [];
1974 for (var i = 0; i < data.length; i++)
1975 hosts_radios[2].push.apply(hosts_radios[2], data[i]);
1977 return hosts_radios;
1980 .then(L.bind(function(hosts_radios_wifis) {
1983 for (var i = 0; i < hosts_radios_wifis[2].length; i++)
1984 tasks.push(hosts_radios_wifis[2][i].getAssocList());
1986 return Promise.all(tasks).then(function(data) {
1987 hosts_radios_wifis[3] = [];
1989 for (var i = 0; i < data.length; i++) {
1990 var wifiNetwork = hosts_radios_wifis[2][i],
1991 radioDev = hosts_radios_wifis[1].filter(function(d) { return d.getName() == wifiNetwork.getWifiDeviceName() })[0];
1993 for (var j = 0; j < data[i].length; j++)
1994 hosts_radios_wifis[3].push(Object.assign({ radio: radioDev, network: wifiNetwork }, data[i][j]));
1997 return hosts_radios_wifis;
2000 .then(L.bind(this.poll_status, this, nodes));
2003 var table = E('div', { 'class': 'table', 'id': 'wifi_assoclist_table' }, [
2004 E('div', { 'class': 'tr table-titles' }, [
2005 E('div', { 'class': 'th nowrap' }, _('Network')),
2006 E('div', { 'class': 'th hide-xs' }, _('MAC-Address')),
2007 E('div', { 'class': 'th' }, _('Host')),
2008 E('div', { 'class': 'th nowrap' }, _('Signal / Noise')),
2009 E('div', { 'class': 'th nowrap' }, _('RX Rate / TX Rate'))
2013 cbi_update_table(table, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
2015 return E([ nodes, E('h3', _('Associated Stations')), table ]);