eaa08135483d8c551cf272b6da66d0e15bf49be9
[oweals/luci.git] / modules / luci-mod-network / htdocs / luci-static / resources / view / network / wireless.js
1 'use strict';
2 'require fs';
3 'require ui';
4 'require rpc';
5 'require uci';
6 'require form';
7 'require network';
8 'require firewall';
9 'require tools.widgets as widgets';
10
11 function count_changes(section_id) {
12         var changes = ui.changes.changes, n = 0;
13
14         if (!L.isObject(changes))
15                 return n;
16
17         if (Array.isArray(changes.wireless))
18                 for (var i = 0; i < changes.wireless.length; i++)
19                         n += (changes.wireless[i][1] == section_id);
20
21         return n;
22 }
23
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') }),
27                 ' ',
28                 radioDev.getName()
29         ]);
30 }
31
32 function render_signal_badge(signalPercent, signalValue, noiseValue, wrap) {
33         var icon, title, value;
34
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');
45         else
46                 icon = L.resource('icons/signal-75-100.png');
47
48         if (signalValue != null && signalValue != 0 && noiseValue != null && noiseValue != 0) {
49                 value = '%d / %d %s'.format(signalValue, noiseValue, _('dBm'));
50                 title = '%s: %d %s / %s: %d %s / %s %d'.format(
51                         _('Signal'), signalValue, _('dBm'),
52                         _('Noise'), noiseValue, _('dBm'),
53                         _('SNR'), signalValue - noiseValue);
54         }
55         else if (signalValue != null && signalValue != 0) {
56                 value = '%d %s'.format(signalValue, _('dBm'));
57                 title = '%s: %d %s'.format(_('Signal'), signalValue, _('dBm'));
58         }
59         else if (signalPercent > -1) {
60                 value = '\xa0---\xa0';
61                 title = _('No signal');
62         }
63         else {
64                 value = E('em', {}, E('small', {}, [ _('disabled') ]));
65                 title = _('Interface is disabled');
66         }
67
68         return E('div', { 'class': wrap ? 'center' : 'ifacebadge', 'title': title },
69                 [ E('img', { 'src': icon }), wrap ? E('br') : ' ', value ]);
70 }
71
72 function render_network_badge(radioNet) {
73         return render_signal_badge(radioNet.isUp() ? radioNet.getSignalPercent() : -1,  radioNet.getSignal(), radioNet.getNoise());
74 }
75
76 function render_radio_status(radioDev, wifiNets) {
77         var name = radioDev.getI18n().replace(/ Wireless Controller .+$/, ''),
78             node = E('div', [ E('big', {}, E('strong', {}, name)), E('div') ]),
79             channel, frequency, bitrate;
80
81         for (var i = 0; i < wifiNets.length; i++) {
82                 channel   = channel   || wifiNets[i].getChannel();
83                 frequency = frequency || wifiNets[i].getFrequency();
84                 bitrate   = bitrate   || wifiNets[i].getBitRate();
85         }
86
87         if (radioDev.isUp())
88                 L.itemlist(node.lastElementChild, [
89                         _('Channel'), '%s (%s %s)'.format(channel || '?', frequency || '?', _('GHz')),
90                         _('Bitrate'), '%s %s'.format(bitrate || '?', _('Mbit/s'))
91                 ], ' | ');
92         else
93                 node.lastElementChild.appendChild(E('em', _('Device is not active')));
94
95         return node;
96 }
97
98 function render_network_status(radioNet) {
99         var mode = radioNet.getActiveMode(),
100             bssid = radioNet.getActiveBSSID(),
101             channel = radioNet.getChannel(),
102             disabled = (radioNet.get('disabled') == '1' || uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == '1'),
103             is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled),
104             is_mesh = (radioNet.getMode() == 'mesh'),
105             changecount = count_changes(radioNet.getName()),
106             status_text = null;
107
108         if (changecount)
109                 status_text = E('a', {
110                         href: '#',
111                         click: L.bind(ui.changes.displayChanges, ui.changes)
112                 }, _('Interface has %d pending changes').format(changecount));
113         else if (!is_assoc)
114                 status_text = E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated'));
115
116         return L.itemlist(E('div'), [
117                 is_mesh ? _('Mesh ID') : _('SSID'), (is_mesh ? radioNet.getMeshID() : radioNet.getSSID()) || '?',
118                 _('Mode'),       mode,
119                 _('BSSID'),      (!changecount && is_assoc) ? bssid : null,
120                 _('Encryption'), (!changecount && is_assoc) ? radioNet.getActiveEncryption() || _('None') : null,
121                 null,            status_text
122         ], [ ' | ', E('br') ]);
123 }
124
125 function render_modal_status(node, radioNet) {
126         var mode = radioNet.getActiveMode(),
127             noise = radioNet.getNoise(),
128             bssid = radioNet.getActiveBSSID(),
129             channel = radioNet.getChannel(),
130             disabled = (radioNet.get('disabled') == '1'),
131             is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled);
132
133         if (node == null)
134                 node = E('span', { 'class': 'ifacebadge large', 'data-network': radioNet.getName() }, [ E('small'), E('span') ]);
135
136         L.dom.content(node.firstElementChild, render_signal_badge(disabled ? -1 : radioNet.getSignalPercent(), radioNet.getSignal(), noise, true));
137
138         L.itemlist(node.lastElementChild, [
139                 _('Mode'),       mode,
140                 _('SSID'),       radioNet.getSSID() || '?',
141                 _('BSSID'),      is_assoc ? bssid : null,
142                 _('Encryption'), is_assoc ? radioNet.getActiveEncryption() || _('None') : null,
143                 _('Channel'),    is_assoc ? '%d (%.3f %s)'.format(radioNet.getChannel(), radioNet.getFrequency() || 0, _('GHz')) : null,
144                 _('Tx-Power'),   is_assoc ? '%d %s'.format(radioNet.getTXPower(), _('dBm')) : null,
145                 _('Signal'),     is_assoc ? '%d %s'.format(radioNet.getSignal(), _('dBm')) : null,
146                 _('Noise'),      (is_assoc && noise != null) ? '%d %s'.format(noise, _('dBm')) : null,
147                 _('Bitrate'),    is_assoc ? '%.1f %s'.format(radioNet.getBitRate() || 0, _('Mbit/s')) : null,
148                 _('Country'),    is_assoc ? radioNet.getCountryCode() : null
149         ], [ ' | ', E('br'), E('br'), E('br'), E('br'), E('br'), ' | ', E('br'), ' | ' ]);
150
151         if (!is_assoc)
152                 L.dom.append(node.lastElementChild, E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated')));
153
154         return node;
155 }
156
157 function format_wifirate(rate) {
158         var s = '%.1f Mbit/s, %dMHz'.format(rate.rate / 1000, rate.mhz);
159
160         if (rate.ht || rate.vht) {
161                 if (rate.vht)      s += ', VHT-MCS %d'.format(rate.mcs);
162                 if (rate.nss)      s += ', VHT-NSS %d'.format(rate.nss);
163                 if (rate.ht)       s += ', MCS %s'.format(rate.mcs);
164                 if (rate.short_gi) s += ', Short GI';
165         }
166
167         return s;
168 }
169
170 function radio_restart(id, ev) {
171         var row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id)),
172             dsc = row.querySelector('[data-name="_stat"] > div'),
173             btn = row.querySelector('.cbi-section-actions button');
174
175         btn.blur();
176         btn.classList.add('spinning');
177         btn.disabled = true;
178
179         dsc.setAttribute('restart', '');
180         L.dom.content(dsc, E('em', _('Device is restarting…')));
181 }
182
183 function network_updown(id, map, ev) {
184         var radio = uci.get('wireless', id, 'device'),
185             disabled = (uci.get('wireless', id, 'disabled') == '1') ||
186                        (uci.get('wireless', radio, 'disabled') == '1');
187
188         if (disabled) {
189                 uci.unset('wireless', id, 'disabled');
190                 uci.unset('wireless', radio, 'disabled');
191         }
192         else {
193                 uci.set('wireless', id, 'disabled', '1');
194
195                 var all_networks_disabled = true,
196                     wifi_ifaces = uci.sections('wireless', 'wifi-iface');
197
198                 for (var i = 0; i < wifi_ifaces.length; i++) {
199                         if (wifi_ifaces[i].device == radio && wifi_ifaces[i].disabled != '1') {
200                                 all_networks_disabled = false;
201                                 break;
202                         }
203                 }
204
205                 if (all_networks_disabled)
206                         uci.set('wireless', radio, 'disabled', '1');
207         }
208
209         return map.save().then(function() {
210                 ui.changes.apply()
211         });
212 }
213
214 function next_free_sid(offset) {
215         var sid = 'wifinet' + offset;
216
217         while (uci.get('wireless', sid))
218                 sid = 'wifinet' + (++offset);
219
220         return sid;
221 }
222
223 var CBIWifiFrequencyValue = form.Value.extend({
224         callFrequencyList: rpc.declare({
225                 object: 'iwinfo',
226                 method: 'freqlist',
227                 params: [ 'device' ],
228                 expect: { results: [] }
229         }),
230
231         load: function(section_id) {
232                 return Promise.all([
233                         network.getWifiDevice(section_id),
234                         this.callFrequencyList(section_id)
235                 ]).then(L.bind(function(data) {
236                         this.channels = {
237                                 '11g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
238                                 '11a': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : []
239                         };
240
241                         for (var i = 0; i < data[1].length; i++)
242                                 this.channels[(data[1][i].mhz > 2484) ? '11a' : '11g'].push(
243                                         data[1][i].channel,
244                                         '%d (%d Mhz)'.format(data[1][i].channel, data[1][i].mhz),
245                                         !data[1][i].restricted
246                                 );
247
248                         var hwmodelist = L.toArray(data[0] ? data[0].getHWModes() : null)
249                                 .reduce(function(o, v) { o[v] = true; return o }, {});
250
251                         this.modes = [
252                                 '', 'Legacy', true,
253                                 'n', 'N', hwmodelist.n,
254                                 'ac', 'AC', hwmodelist.ac
255                         ];
256
257                         var htmodelist = L.toArray(data[0] ? data[0].getHTModes() : null)
258                                 .reduce(function(o, v) { o[v] = true; return o }, {});
259
260                         this.htmodes = {
261                                 '': [ '', '-', true ],
262                                 'n': [
263                                         'HT20', '20 MHz', htmodelist.HT20,
264                                         'HT40', '40 MHz', htmodelist.HT40
265                                 ],
266                                 'ac': [
267                                         'VHT20', '20 MHz', htmodelist.VHT20,
268                                         'VHT40', '40 MHz', htmodelist.VHT40,
269                                         'VHT80', '80 MHz', htmodelist.VHT80,
270                                         'VHT160', '160 MHz', htmodelist.VHT160
271                                 ]
272                         };
273
274                         this.bands = {
275                                 '': [
276                                         '11g', '2.4 GHz', this.channels['11g'].length > 3,
277                                         '11a', '5 GHz', this.channels['11a'].length > 3
278                                 ],
279                                 'n': [
280                                         '11g', '2.4 GHz', this.channels['11g'].length > 3,
281                                         '11a', '5 GHz', this.channels['11a'].length > 3
282                                 ],
283                                 'ac': [
284                                         '11a', '5 GHz', true
285                                 ]
286                         };
287                 }, this));
288         },
289
290         setValues: function(sel, vals) {
291                 if (sel.vals)
292                         sel.vals.selected = sel.selectedIndex;
293
294                 while (sel.options[0])
295                         sel.remove(0);
296
297                 for (var i = 0; vals && i < vals.length; i += 3)
298                         if (vals[i+2])
299                                 sel.add(E('option', { value: vals[i+0] }, [ vals[i+1] ]));
300
301                 if (vals && !isNaN(vals.selected))
302                         sel.selectedIndex = vals.selected;
303
304                 sel.parentNode.style.display = (sel.options.length <= 1) ? 'none' : '';
305                 sel.vals = vals;
306         },
307
308         toggleWifiMode: function(elem) {
309                 this.toggleWifiHTMode(elem);
310                 this.toggleWifiBand(elem);
311         },
312
313         toggleWifiHTMode: function(elem) {
314                 var mode = elem.querySelector('.mode');
315                 var bwdt = elem.querySelector('.htmode');
316
317                 this.setValues(bwdt, this.htmodes[mode.value]);
318         },
319
320         toggleWifiBand: function(elem) {
321                 var mode = elem.querySelector('.mode');
322                 var band = elem.querySelector('.band');
323
324                 this.setValues(band, this.bands[mode.value]);
325                 this.toggleWifiChannel(elem);
326         },
327
328         toggleWifiChannel: function(elem) {
329                 var band = elem.querySelector('.band');
330                 var chan = elem.querySelector('.channel');
331
332                 this.setValues(chan, this.channels[band.value]);
333         },
334
335         setInitialValues: function(section_id, elem) {
336                 var mode = elem.querySelector('.mode'),
337                     band = elem.querySelector('.band'),
338                     chan = elem.querySelector('.channel'),
339                     bwdt = elem.querySelector('.htmode'),
340                     htval = uci.get('wireless', section_id, 'htmode'),
341                     hwval = uci.get('wireless', section_id, 'hwmode'),
342                     chval = uci.get('wireless', section_id, 'channel');
343
344                 this.setValues(mode, this.modes);
345
346                 if (/VHT20|VHT40|VHT80|VHT160/.test(htval))
347                         mode.value = 'ac';
348                 else if (/HT20|HT40/.test(htval))
349                         mode.value = 'n';
350                 else
351                         mode.value = '';
352
353                 this.toggleWifiMode(elem);
354
355                 if (/a/.test(hwval))
356                         band.value = '11a';
357                 else
358                         band.value = '11g';
359
360                 this.toggleWifiBand(elem);
361
362                 bwdt.value = htval;
363                 chan.value = chval;
364
365                 return elem;
366         },
367
368         renderWidget: function(section_id, option_index, cfgvalue) {
369                 var elem = E('div');
370
371                 L.dom.content(elem, [
372                         E('label', { 'style': 'float:left; margin-right:3px' }, [
373                                 _('Mode'), E('br'),
374                                 E('select', {
375                                         'class': 'mode',
376                                         'style': 'width:auto',
377                                         'change': L.bind(this.toggleWifiMode, this, elem)
378                                 })
379                         ]),
380                         E('label', { 'style': 'float:left; margin-right:3px' }, [
381                                 _('Band'), E('br'),
382                                 E('select', {
383                                         'class': 'band',
384                                         'style': 'width:auto',
385                                         'change': L.bind(this.toggleWifiBand, this, elem)
386                                 })
387                         ]),
388                         E('label', { 'style': 'float:left; margin-right:3px' }, [
389                                 _('Channel'), E('br'),
390                                 E('select', {
391                                         'class': 'channel',
392                                         'style': 'width:auto'
393                                 })
394                         ]),
395                         E('label', { 'style': 'float:left; margin-right:3px' }, [
396                                 _('Width'), E('br'),
397                                 E('select', {
398                                         'class': 'htmode',
399                                         'style': 'width:auto'
400                                 })
401                         ]),
402                         E('br', { 'style': 'clear:left' })
403                 ]);
404
405                 return this.setInitialValues(section_id, elem);
406         },
407
408         cfgvalue: function(section_id) {
409                 return [
410                     uci.get('wireless', section_id, 'htmode'),
411                     uci.get('wireless', section_id, 'hwmode'),
412                     uci.get('wireless', section_id, 'channel')
413                 ];
414         },
415
416         formvalue: function(section_id) {
417                 var node = this.map.findElement('data-field', this.cbid(section_id));
418
419                 return [
420                     node.querySelector('.htmode').value,
421                     node.querySelector('.band').value,
422                         node.querySelector('.channel').value
423                 ];
424         },
425
426         write: function(section_id, value) {
427                 uci.set('wireless', section_id, 'htmode', value[0] || null);
428                 uci.set('wireless', section_id, 'hwmode', value[1]);
429                 uci.set('wireless', section_id, 'channel', value[2]);
430         }
431 });
432
433 var CBIWifiTxPowerValue = form.ListValue.extend({
434         callTxPowerList: rpc.declare({
435                 object: 'iwinfo',
436                 method: 'txpowerlist',
437                 params: [ 'device' ],
438                 expect: { results: [] }
439         }),
440
441         load: function(section_id) {
442                 return this.callTxPowerList(section_id).then(L.bind(function(pwrlist) {
443                         this.powerval = this.wifiNetwork ? this.wifiNetwork.getTXPower() : null;
444                         this.poweroff = this.wifiNetwork ? this.wifiNetwork.getTXPowerOffset() : null;
445
446                         this.value('', _('driver default'));
447
448                         for (var i = 0; i < pwrlist.length; i++)
449                                 this.value(pwrlist[i].dbm, '%d dBm (%d mW)'.format(pwrlist[i].dbm, pwrlist[i].mw));
450
451                         return form.ListValue.prototype.load.apply(this, [section_id]);
452                 }, this));
453         },
454
455         renderWidget: function(section_id, option_index, cfgvalue) {
456                 var widget = form.ListValue.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
457                     widget.firstElementChild.style.width = 'auto';
458
459                 L.dom.append(widget, E('span', [
460                         ' - ', _('Current power'), ': ',
461                         E('span', [ this.powerval != null ? '%d dBm'.format(this.powerval) : E('em', _('unknown')) ]),
462                         this.poweroff ? ' + %d dB offset = %s dBm'.format(this.poweroff, this.powerval != null ? this.powerval + this.poweroff : '?') : ''
463                 ]));
464
465                 return widget;
466         }
467 });
468
469 var CBIWifiCountryValue = form.Value.extend({
470         callCountryList: rpc.declare({
471                 object: 'iwinfo',
472                 method: 'countrylist',
473                 params: [ 'device' ],
474                 expect: { results: [] }
475         }),
476
477         load: function(section_id) {
478                 return this.callCountryList(section_id).then(L.bind(function(countrylist) {
479                         if (Array.isArray(countrylist) && countrylist.length > 0) {
480                                 this.value('', _('driver default'));
481
482                                 for (var i = 0; i < countrylist.length; i++)
483                                         this.value(countrylist[i].iso3166, '%s - %s'.format(countrylist[i].iso3166, countrylist[i].country));
484                         }
485
486                         return form.Value.prototype.load.apply(this, [section_id]);
487                 }, this));
488         },
489
490         validate: function(section_id, formvalue) {
491                 if (formvalue != null && formvalue != '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue))
492                         return _('Use ISO/IEC 3166 alpha2 country codes.');
493
494                 return true;
495         },
496
497         renderWidget: function(section_id, option_index, cfgvalue) {
498                 var typeClass = (this.keylist && this.keylist.length) ? form.ListValue : form.Value;
499                 return typeClass.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
500         }
501 });
502
503 return L.view.extend({
504         poll_status: function(map, data) {
505                 var rows = map.querySelectorAll('.cbi-section-table-row[data-sid]');
506
507                 for (var i = 0; i < rows.length; i++) {
508                         var section_id = rows[i].getAttribute('data-sid'),
509                             radioDev = data[1].filter(function(d) { return d.getName() == section_id })[0],
510                             radioNet = data[2].filter(function(n) { return n.getName() == section_id })[0],
511                             badge = rows[i].querySelector('[data-name="_badge"] > div'),
512                             stat = rows[i].querySelector('[data-name="_stat"]'),
513                             btns = rows[i].querySelectorAll('.cbi-section-actions button'),
514                             busy = btns[0].classList.contains('spinning') || btns[1].classList.contains('spinning') || btns[2].classList.contains('spinning');
515
516                         if (radioDev) {
517                                 L.dom.content(badge, render_radio_badge(radioDev));
518                                 L.dom.content(stat, render_radio_status(radioDev, data[2].filter(function(n) { return n.getWifiDeviceName() == radioDev.getName() })));
519                         }
520                         else {
521                                 L.dom.content(badge, render_network_badge(radioNet));
522                                 L.dom.content(stat, render_network_status(radioNet));
523                         }
524
525                         if (stat.hasAttribute('restart'))
526                                 L.dom.content(stat, E('em', _('Device is restarting…')));
527
528                         btns[0].disabled = busy;
529                         btns[1].disabled = busy;
530                         btns[2].disabled = busy;
531                 }
532
533                 var table = document.querySelector('#wifi_assoclist_table'),
534                     hosts = data[0],
535                     trows = [];
536
537                 for (var i = 0; i < data[3].length; i++) {
538                         var bss = data[3][i],
539                             name = hosts.getHostnameByMACAddr(bss.mac),
540                             ipv4 = hosts.getIPAddrByMACAddr(bss.mac),
541                             ipv6 = hosts.getIP6AddrByMACAddr(bss.mac);
542
543                         var hint;
544
545                         if (name && ipv4 && ipv6)
546                                 hint = '%s (%s, %s)'.format(name, ipv4, ipv6);
547                         else if (name && (ipv4 || ipv6))
548                                 hint = '%s (%s)'.format(name, ipv4 || ipv6);
549                         else
550                                 hint = name || ipv4 || ipv6 || '?';
551
552                         var row = [
553                                 E('span', { 'class': 'ifacebadge' }, [
554                                         E('img', {
555                                                 'src': L.resource('icons/wifi%s.png').format(bss.network.isUp() ? '' : '_disabled'),
556                                                 'title': bss.radio.getI18n()
557                                         }),
558                                         ' %s '.format(bss.network.getShortName()),
559                                         E('small', '(%s)'.format(bss.network.getIfname()))
560                                 ]),
561                                 bss.mac,
562                                 hint,
563                                 render_signal_badge(Math.min((bss.signal + 110) / 70 * 100, 100), bss.signal, bss.noise),
564                                 E('span', {}, [
565                                         E('span', format_wifirate(bss.rx)),
566                                         E('br'),
567                                         E('span', format_wifirate(bss.tx))
568                                 ])
569                         ];
570
571                         if (bss.network.isClientDisconnectSupported()) {
572                                 if (table.firstElementChild.childNodes.length < 6)
573                                         table.firstElementChild.appendChild(E('div', { 'class': 'th nowrap right'}, [ _('Disconnect') ]));
574
575                                 row.push(E('button', {
576                                         'class': 'cbi-button cbi-button-remove',
577                                         'click': L.bind(function(net, mac, ev) {
578                                                 L.dom.parent(ev.currentTarget, '.tr').style.opacity = 0.5;
579                                                 ev.currentTarget.classList.add('spinning');
580                                                 ev.currentTarget.disabled = true;
581                                                 ev.currentTarget.blur();
582
583                                                 net.disconnectClient(mac, true, 5, 60000);
584                                         }, this, bss.network, bss.mac)
585                                 }, [ _('Disconnect') ]));
586                         }
587                         else {
588                                 row.push('-');
589                         }
590
591                         trows.push(row);
592                 }
593
594                 cbi_update_table(table, trows, E('em', _('No information available')));
595
596                 var stat = document.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large');
597
598                 if (stat)
599                         render_modal_status(stat, data[2].filter(function(n) { return n.getName() == stat.getAttribute('data-network') })[0]);
600
601                 return network.flushCache();
602         },
603
604         load: function() {
605                 return Promise.all([
606                         uci.changes(),
607                         uci.load('wireless')
608                 ]);
609         },
610
611         checkAnonymousSections: function() {
612                 var wifiIfaces = uci.sections('wireless', 'wifi-iface');
613
614                 for (var i = 0; i < wifiIfaces.length; i++)
615                         if (wifiIfaces[i]['.anonymous'])
616                                 return true;
617
618                 return false;
619         },
620
621         callUciRename: rpc.declare({
622                 object: 'uci',
623                 method: 'rename',
624                 params: [ 'config', 'section', 'name' ]
625         }),
626
627         render: function() {
628                 if (this.checkAnonymousSections())
629                         return this.renderMigration();
630                 else
631                         return this.renderOverview();
632         },
633
634         handleMigration: function(ev) {
635                 var wifiIfaces = uci.sections('wireless', 'wifi-iface'),
636                     id_offset = 0,
637                     tasks = [];
638
639                 for (var i = 0; i < wifiIfaces.length; i++) {
640                         if (!wifiIfaces[i]['.anonymous'])
641                                 continue;
642
643                         var new_name = next_free_sid(id_offset);
644
645                         tasks.push(this.callUciRename('wireless', wifiIfaces[i]['.name'], new_name));
646                         id_offset = +new_name.substring(7) + 1;
647                 }
648
649                 return Promise.all(tasks)
650                         .then(L.bind(ui.changes.init, ui.changes))
651                         .then(L.bind(ui.changes.apply, ui.changes));
652         },
653
654         renderMigration: function() {
655                 ui.showModal(_('Wireless configuration migration'), [
656                         E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')),
657                         E('p', _('Upon pressing "Continue", anonymous "wifi-iface" sections will be assigned with a name in the form <em>wifinet#</em> and the network will be restarted to apply the updated configuration.')),
658                         E('div', { 'class': 'right' },
659                                 E('button', {
660                                         'class': 'btn cbi-button-action important',
661                                         'click': ui.createHandlerFn(this, 'handleMigration')
662                                 }, _('Continue')))
663                 ]);
664         },
665
666         renderOverview: function() {
667                 var m, s, o;
668
669                 m = new form.Map('wireless');
670                 m.chain('network');
671                 m.chain('firewall');
672
673                 s = m.section(form.GridSection, 'wifi-device', _('Wireless Overview'));
674                 s.anonymous = true;
675                 s.addremove = false;
676
677                 s.load = function() {
678                         return network.getWifiDevices().then(L.bind(function(radios) {
679                                 this.radios = radios.sort(function(a, b) {
680                                         return a.getName() > b.getName();
681                                 });
682
683                                 var tasks = [];
684
685                                 for (var i = 0; i < radios.length; i++)
686                                         tasks.push(radios[i].getWifiNetworks());
687
688                                 return Promise.all(tasks);
689                         }, this)).then(L.bind(function(data) {
690                                 this.wifis = [];
691
692                                 for (var i = 0; i < data.length; i++)
693                                         this.wifis.push.apply(this.wifis, data[i]);
694                         }, this));
695                 };
696
697                 s.cfgsections = function() {
698                         var rv = [];
699
700                         for (var i = 0; i < this.radios.length; i++) {
701                                 rv.push(this.radios[i].getName());
702
703                                 for (var j = 0; j < this.wifis.length; j++)
704                                         if (this.wifis[j].getWifiDeviceName() == this.radios[i].getName())
705                                                 rv.push(this.wifis[j].getName());
706                         }
707
708                         return rv;
709                 };
710
711                 s.modaltitle = function(section_id) {
712                         var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id})[0];
713                         return radioNet ? radioNet.getI18n() : _('Edit wireless network');
714                 };
715
716                 s.lookupRadioOrNetwork = function(section_id) {
717                         var radioDev = this.radios.filter(function(r) { return r.getName() == section_id })[0];
718                         if (radioDev)
719                                 return radioDev;
720
721                         var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id })[0];
722                         if (radioNet)
723                                 return radioNet;
724
725                         return null;
726                 };
727
728                 s.renderRowActions = function(section_id) {
729                         var inst = this.lookupRadioOrNetwork(section_id), btns;
730
731                         if (inst.getWifiNetworks) {
732                                 btns = [
733                                         E('button', {
734                                                 'class': 'cbi-button cbi-button-neutral',
735                                                 'title': _('Restart radio interface'),
736                                                 'click': ui.createHandlerFn(this, radio_restart, section_id)
737                                         }, _('Restart')),
738                                         E('button', {
739                                                 'class': 'cbi-button cbi-button-action important',
740                                                 'title': _('Find and join network'),
741                                                 'click': ui.createHandlerFn(this, 'handleScan', inst)
742                                         }, _('Scan')),
743                                         E('button', {
744                                                 'class': 'cbi-button cbi-button-add',
745                                                 'title': _('Provide new network'),
746                                                 'click': ui.createHandlerFn(this, 'handleAdd', inst)
747                                         }, _('Add'))
748                                 ];
749                         }
750                         else {
751                                 var isDisabled = (inst.get('disabled') == '1' ||
752                                         uci.get('wireless', inst.getWifiDeviceName(), 'disabled') == '1');
753
754                                 btns = [
755                                         E('button', {
756                                                 'class': 'cbi-button cbi-button-neutral enable-disable',
757                                                 'title': isDisabled ? _('Enable this network') : _('Disable this network'),
758                                                 'click': ui.createHandlerFn(this, network_updown, section_id, this.map)
759                                         }, isDisabled ? _('Enable') : _('Disable')),
760                                         E('button', {
761                                                 'class': 'cbi-button cbi-button-action important',
762                                                 'title': _('Edit this network'),
763                                                 'click': ui.createHandlerFn(this, 'renderMoreOptionsModal', section_id)
764                                         }, _('Edit')),
765                                         E('button', {
766                                                 'class': 'cbi-button cbi-button-negative remove',
767                                                 'title': _('Delete this network'),
768                                                 'click': ui.createHandlerFn(this, 'handleRemove', section_id)
769                                         }, _('Remove'))
770                                 ];
771                         }
772
773                         return E('div', { 'class': 'td middle cbi-section-actions' }, E('div', btns));
774                 };
775
776                 s.addModalOptions = function(s) {
777                         return network.getWifiNetwork(s.section).then(function(radioNet) {
778                                 var hwtype = uci.get('wireless', radioNet.getWifiDeviceName(), 'type');
779                                 var o, ss;
780
781                                 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
782                                 o.modalonly = true;
783
784                                 ss = o.subsection;
785                                 ss.tab('general', _('General Setup'));
786                                 ss.tab('advanced', _('Advanced Settings'));
787
788                                 var isDisabled = (radioNet.get('disabled') == '1' ||
789                                         uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == 1);
790
791                                 o = ss.taboption('general', form.DummyValue, '_wifistat_modal', _('Status'));
792                                 o.cfgvalue = L.bind(function(radioNet) {
793                                         return render_modal_status(null, radioNet);
794                                 }, this, radioNet);
795                                 o.write = function() {};
796
797                                 o = ss.taboption('general', form.Button, '_toggle', isDisabled ? _('Wireless network is disabled') : _('Wireless network is enabled'));
798                                 o.inputstyle = isDisabled ? 'apply' : 'reset';
799                                 o.inputtitle = isDisabled ? _('Enable') : _('Disable');
800                                 o.onclick = ui.createHandlerFn(s, network_updown, s.section, s.map);
801
802                                 o = ss.taboption('general', CBIWifiFrequencyValue, '_freq', '<br />' + _('Operating frequency'));
803                                 o.ucisection = s.section;
804
805                                 if (hwtype == 'mac80211') {
806                                         o = ss.taboption('general', CBIWifiTxPowerValue, 'txpower', _('Maximum transmit power'), _('Specifies the maximum transmit power the wireless radio may use. Depending on regulatory requirements and wireless usage, the actual transmit power may be reduced by the driver.'));
807                                         o.wifiNetwork = radioNet;
808
809                                         o = ss.taboption('advanced', CBIWifiCountryValue, 'country', _('Country Code'));
810                                         o.wifiNetwork = radioNet;
811
812                                         o = ss.taboption('advanced', form.Flag, 'legacy_rates', _('Allow legacy 802.11b rates'));
813                                         o.default = o.enabled;
814
815                                         o = ss.taboption('advanced', form.Value, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
816                                         o.datatype = 'range(0,114750)';
817                                         o.placeholder = 'auto';
818
819                                         o = ss.taboption('advanced', form.Value, 'frag', _('Fragmentation Threshold'));
820                                         o.datatype = 'min(256)';
821                                         o.placeholder = _('off');
822
823                                         o = ss.taboption('advanced', form.Value, 'rts', _('RTS/CTS Threshold'));
824                                         o.datatype = 'uinteger';
825                                         o.placeholder = _('off');
826
827                                         o = ss.taboption('advanced', form.Flag, 'noscan', _('Force 40MHz mode'), _('Always use 40MHz channels even if the secondary channel overlaps. Using this option does not comply with IEEE 802.11n-2009!'));
828                                         o.rmempty = true;
829
830                                         o = ss.taboption('advanced', form.Value, 'beacon_int', _('Beacon Interval'));
831                                         o.datatype = 'range(15,65535)';
832                                         o.placeholder = 100;
833                                         o.rmempty = true;
834                                 }
835
836
837                                 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getName(), 'wifi-iface', _('Interface Configuration'));
838                                 o.modalonly = true;
839
840                                 ss = o.subsection;
841                                 ss.tab('general', _('General Setup'));
842                                 ss.tab('encryption', _('Wireless Security'));
843                                 ss.tab('macfilter', _('MAC-Filter'));
844                                 ss.tab('advanced', _('Advanced Settings'));
845
846                                 o = ss.taboption('general', form.ListValue, 'mode', _('Mode'));
847                                 o.value('ap', _('Access Point'));
848                                 o.value('sta', _('Client'));
849                                 o.value('adhoc', _('Ad-Hoc'));
850
851                                 o = ss.taboption('general', form.Value, 'mesh_id', _('Mesh Id'));
852                                 o.depends('mode', 'mesh');
853
854                                 o = ss.taboption('advanced', form.Flag, 'mesh_fwding', _('Forward mesh peer traffic'));
855                                 o.rmempty = false;
856                                 o.default = '1';
857                                 o.depends('mode', 'mesh');
858
859                                 o = ss.taboption('advanced', form.Value, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
860                                 o.rmempty = false;
861                                 o.default = '0';
862                                 o.datatype = 'range(-255,1)';
863                                 o.depends('mode', 'mesh');
864
865                                 o = ss.taboption('general', form.Value, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
866                                 o.datatype = 'maxlength(32)';
867                                 o.depends('mode', 'ap');
868                                 o.depends('mode', 'sta');
869                                 o.depends('mode', 'adhoc');
870                                 o.depends('mode', 'ahdemo');
871                                 o.depends('mode', 'monitor');
872                                 o.depends('mode', 'ap-wds');
873                                 o.depends('mode', 'sta-wds');
874                                 o.depends('mode', 'wds');
875
876                                 o = ss.taboption('general', form.Value, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
877                                 o.datatype = 'macaddr';
878
879                                 o = ss.taboption('general', widgets.NetworkSelect, 'network', _('Network'), _('Choose the network(s) you want to attach to this wireless interface or fill out the <em>create</em> field to define a new network.'));
880                                 o.rmempty = true;
881                                 o.multiple = true;
882                                 o.novirtual = true;
883                                 o.write = function(section_id, value) {
884                                         return network.getDevice(section_id).then(L.bind(function(dev) {
885                                                 var old_networks = dev.getNetworks().reduce(function(o, v) { o[v.getName()] = v; return o }, {}),
886                                                     new_networks = {},
887                                                     values = L.toArray(value),
888                                                     tasks = [];
889
890                                                 for (var i = 0; i < values.length; i++) {
891                                                         new_networks[values[i]] = true;
892
893                                                         if (old_networks[values[i]])
894                                                                 continue;
895
896                                                         tasks.push(network.getNetwork(values[i]).then(L.bind(function(name, net) {
897                                                                 return net || network.addNetwork(name, { proto: 'none' });
898                                                         }, this, values[i])).then(L.bind(function(dev, net) {
899                                                                 if (net) {
900                                                                         if (!net.isEmpty())
901                                                                                 net.set('type', 'bridge');
902                                                                         net.addDevice(dev);
903                                                                 }
904                                                         }, this, dev)));
905                                                 }
906
907                                                 for (var name in old_networks)
908                                                         if (!new_networks[name])
909                                                                 tasks.push(network.getNetwork(name).then(L.bind(function(dev, net) {
910                                                                         if (net)
911                                                                                 net.deleteDevice(dev);
912                                                                 }, this, dev)));
913
914                                                 return Promise.all(tasks);
915                                         }, this));
916                                 };
917
918                                 if (hwtype == 'mac80211') {
919                                         var mode = ss.children[0],
920                                             bssid = ss.children[5],
921                                             encr;
922
923                                         mode.value('mesh', '802.11s');
924                                         mode.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
925                                         mode.value('monitor', _('Monitor'));
926
927                                         bssid.depends('mode', 'adhoc');
928                                         bssid.depends('mode', 'sta');
929                                         bssid.depends('mode', 'sta-wds');
930
931                                         o = ss.taboption('macfilter', form.ListValue, 'macfilter', _('MAC-Address Filter'));
932                                         o.depends('mode', 'ap');
933                                         o.depends('mode', 'ap-wds');
934                                         o.value('', _('disable'));
935                                         o.value('allow', _('Allow listed only'));
936                                         o.value('deny', _('Allow all except listed'));
937
938                                         o = ss.taboption('macfilter', form.DynamicList, 'maclist', _('MAC-List'));
939                                         o.datatype = 'macaddr';
940                                         o.depends('macfilter', 'allow');
941                                         o.depends('macfilter', 'deny');
942                                         o.load = function(section_id) {
943                                                 return network.getHostHints().then(L.bind(function(hints) {
944                                                         hints.getMACHints().map(L.bind(function(hint) {
945                                                                 this.value(hint[0], hint[1] ? '%s (%s)'.format(hint[0], hint[1]) : hint[0]);
946                                                         }, this));
947
948                                                         return form.DynamicList.prototype.load.apply(this, [section_id]);
949                                                 }, this));
950                                         };
951
952                                         mode.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
953                                         mode.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
954
955                                         mode.write = function(section_id, value) {
956                                                 switch (value) {
957                                                 case 'ap-wds':
958                                                         uci.set('wireless', section_id, 'mode', 'ap');
959                                                         uci.set('wireless', section_id, 'wds', '1');
960                                                         break;
961
962                                                 case 'sta-wds':
963                                                         uci.set('wireless', section_id, 'mode', 'sta');
964                                                         uci.set('wireless', section_id, 'wds', '1');
965                                                         break;
966
967                                                 default:
968                                                         uci.set('wireless', section_id, 'mode', value);
969                                                         uci.unset('wireless', section_id, 'wds');
970                                                         break;
971                                                 }
972                                         };
973
974                                         mode.cfgvalue = function(section_id) {
975                                                 var mode = uci.get('wireless', section_id, 'mode'),
976                                                     wds = uci.get('wireless', section_id, 'wds');
977
978                                                 if (mode == 'ap' && wds)
979                                                         return 'ap-wds';
980                                                 else if (mode == 'sta' && wds)
981                                                         return 'sta-wds';
982
983                                                 return mode;
984                                         };
985
986                                         o = ss.taboption('general', form.Flag, 'hidden', _('Hide <abbr title="Extended Service Set Identifier">ESSID</abbr>'));
987                                         o.depends('mode', 'ap');
988                                         o.depends('mode', 'ap-wds');
989
990                                         o = ss.taboption('general', form.Flag, 'wmm', _('WMM Mode'));
991                                         o.depends('mode', 'ap');
992                                         o.depends('mode', 'ap-wds');
993                                         o.default = o.enabled;
994
995                                         o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
996                                         o.depends('mode', 'ap');
997                                         o.depends('mode', 'ap-wds');
998
999                                         o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name'));
1000                                         o.optional = true;
1001                                         o.placeholder = radioNet.getIfname();
1002                                         if (/^radio\d+\.network/.test(o.placeholder))
1003                                                 o.placeholder = '';
1004
1005                                         o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble'));
1006                                         o.default = o.enabled;
1007
1008                                         o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
1009                                         o.optional = true;
1010                                         o.placeholder = 2;
1011                                         o.datatype = 'range(1,255)';
1012
1013                                         o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
1014                                         o.optional    = true;
1015                                         o.placeholder = 600;
1016                                         o.datatype    = 'uinteger';
1017
1018                                         o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling'));
1019                                         o.optional    = true;
1020                                         o.datatype    = 'uinteger';
1021
1022                                         o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('sec'));
1023                                         o.optional    = true;
1024                                         o.placeholder = 300;
1025                                         o.datatype    = 'uinteger';
1026
1027                                         o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval'));
1028                                         o.optional    = true;
1029                                         o.placeholder = 65535;
1030                                         o.datatype    = 'uinteger';
1031
1032                                         o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
1033                                         o.default = o.enabled;
1034                                 }
1035
1036
1037                                 encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption'));
1038                                 o.depends('mode', 'ap');
1039                                 o.depends('mode', 'sta');
1040                                 o.depends('mode', 'adhoc');
1041                                 o.depends('mode', 'ahdemo');
1042                                 o.depends('mode', 'ap-wds');
1043                                 o.depends('mode', 'sta-wds');
1044                                 o.depends('mode', 'mesh');
1045
1046                                 o.cfgvalue = function(section_id) {
1047                                         var v = String(uci.get('wireless', section_id, 'encryption'));
1048                                         if (v == 'wep')
1049                                                 return 'wep-open';
1050                                         else if (v.match(/\+/))
1051                                                 return v.replace(/\+.+$/, '');
1052                                         return v;
1053                                 };
1054
1055                                 o.write = function(section_id, value) {
1056                                         var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id),
1057                                             co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id);
1058
1059                                         if (value == 'wpa' || value == 'wpa2')
1060                                                 uci.unset('wireless', section_id, 'key');
1061
1062                                         if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp'))
1063                                                 e += '+' + c;
1064
1065                                         uci.set('wireless', section_id, 'encryption', e);
1066                                 };
1067
1068                                 o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher'));
1069                                 o.depends('encryption', 'wpa');
1070                                 o.depends('encryption', 'wpa2');
1071                                 o.depends('encryption', 'psk');
1072                                 o.depends('encryption', 'psk2');
1073                                 o.depends('encryption', 'wpa-mixed');
1074                                 o.depends('encryption', 'psk-mixed');
1075                                 o.value('auto', _('auto'));
1076                                 o.value('ccmp', _('Force CCMP (AES)'));
1077                                 o.value('tkip', _('Force TKIP'));
1078                                 o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1079                                 o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write;
1080
1081                                 o.cfgvalue = function(section_id) {
1082                                         var v = String(uci.get('wireless', section_id, 'encryption'));
1083                                         if (v.match(/\+/)) {
1084                                                 v = v.replace(/^[^+]+\+/, '');
1085                                                 if (v == 'aes')
1086                                                         v = 'ccmp';
1087                                                 else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip')
1088                                                         v = 'tkip+ccmp';
1089                                         }
1090                                         return v;
1091                                 };
1092
1093
1094                                 var crypto_modes = [];
1095
1096                                 if (hwtype == 'mac80211') {
1097                                         var has_supplicant = L.hasSystemFeature('wpasupplicant'),
1098                                             has_hostapd = L.hasSystemFeature('hostapd');
1099
1100                                         // Probe EAP support
1101                                         var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'),
1102                                             has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap');
1103
1104                                         // Probe SAE support
1105                                         var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'),
1106                                             has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae');
1107
1108                                         // Probe OWE support
1109                                         var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
1110                                             has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
1111
1112
1113                                         if (has_hostapd || has_supplicant) {
1114                                                 crypto_modes.push(['psk2',      'WPA2-PSK',                    33]);
1115                                                 crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1116                                                 crypto_modes.push(['psk',       'WPA-PSK',                     21]);
1117                                         }
1118                                         else {
1119                                                 encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1120                                         }
1121
1122                                         if (has_ap_sae || has_sta_sae) {
1123                                                 crypto_modes.push(['sae',       'WPA3-SAE',                     31]);
1124                                                 crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1125                                         }
1126
1127                                         if (has_ap_eap || has_sta_eap) {
1128                                                 crypto_modes.push(['wpa2', 'WPA2-EAP', 32]);
1129                                                 crypto_modes.push(['wpa',  'WPA-EAP',  20]);
1130                                         }
1131
1132                                         if (has_ap_owe || has_sta_owe) {
1133                                                 crypto_modes.push(['owe', 'OWE', 1]);
1134                                         }
1135
1136                                         encr.crypto_support = {
1137                                                 'ap': {
1138                                                         'wep-open': true,
1139                                                         'wep-shared': true,
1140                                                         'psk': has_hostapd || _('Requires hostapd'),
1141                                                         'psk2': has_hostapd || _('Requires hostapd'),
1142                                                         'psk-mixed': has_hostapd || _('Requires hostapd'),
1143                                                         'sae': has_ap_sae || _('Requires hostapd with SAE support'),
1144                                                         'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
1145                                                         'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
1146                                                         'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
1147                                                         'owe': has_ap_owe || _('Requires hostapd with OWE support')
1148                                                 },
1149                                                 'sta': {
1150                                                         'wep-open': true,
1151                                                         'wep-shared': true,
1152                                                         'psk': has_supplicant || _('Requires wpa-supplicant'),
1153                                                         'psk2': has_supplicant || _('Requires wpa-supplicant'),
1154                                                         'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1155                                                         'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1156                                                         'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1157                                                         'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1158                                                         'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1159                                                         'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
1160                                                 },
1161                                                 'adhoc': {
1162                                                         'wep-open': true,
1163                                                         'wep-shared': true,
1164                                                         'psk': has_supplicant || _('Requires wpa-supplicant'),
1165                                                         'psk2': has_supplicant || _('Requires wpa-supplicant'),
1166                                                         'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1167                                                 },
1168                                                 'mesh': {
1169                                                         'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support')
1170                                                 },
1171                                                 'ahdemo': {
1172                                                         'wep-open': true,
1173                                                         'wep-shared': true
1174                                                 },
1175                                                 'wds': {
1176                                                         'wep-open': true,
1177                                                         'wep-shared': true
1178                                                 }
1179                                         };
1180
1181                                         encr.crypto_support['ap-wds'] = encr.crypto_support['ap'];
1182                                         encr.crypto_support['sta-wds'] = encr.crypto_support['sta'];
1183
1184                                         encr.validate = function(section_id, value) {
1185                                                 var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
1186                                                     modeval = modeopt.formvalue(section_id),
1187                                                     modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)],
1188                                                     enctitle = this.vallist[this.keylist.indexOf(value)];
1189
1190                                                 if (value == 'none')
1191                                                         return true;
1192
1193                                                 if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value))
1194                                                         return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle);
1195
1196                                                 return this.crypto_support[modeval][value];
1197                                         };
1198                                 }
1199                                 else if (hwtype == 'broadcom') {
1200                                         crypto_modes.push(['psk2',     'WPA2-PSK',                    33]);
1201                                         crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1202                                         crypto_modes.push(['psk',      'WPA-PSK',                     21]);
1203                                 }
1204
1205                                 crypto_modes.push(['wep-open',   _('WEP Open System'), 11]);
1206                                 crypto_modes.push(['wep-shared', _('WEP Shared Key'),  10]);
1207                                 crypto_modes.push(['none',       _('No Encryption'),   0]);
1208
1209                                 crypto_modes.sort(function(a, b) { return b[2] - a[2] });
1210
1211                                 for (var i = 0; i < crypto_modes.length; i++) {
1212                                         var security_level = (crypto_modes[i][2] >= 30) ? _('strong security')
1213                                                 : (crypto_modes[i][2] >= 20) ? _('medium security')
1214                                                         : (crypto_modes[i][2] >= 10) ? _('weak security') : _('open network');
1215
1216                                         encr.value(crypto_modes[i][0], '%s (%s)'.format(crypto_modes[i][1], security_level));
1217                                 }
1218
1219
1220                                 o = ss.taboption('encryption', form.Value, 'auth_server', _('Radius-Authentication-Server'));
1221                                 o.depends({ mode: 'ap', encryption: 'wpa' });
1222                                 o.depends({ mode: 'ap', encryption: 'wpa2' });
1223                                 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1224                                 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1225                                 o.rmempty = true;
1226                                 o.datatype = 'host(0)';
1227
1228                                 o = ss.taboption('encryption', form.Value, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
1229                                 o.depends({ mode: 'ap', encryption: 'wpa' });
1230                                 o.depends({ mode: 'ap', encryption: 'wpa2' });
1231                                 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1232                                 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1233                                 o.rmempty = true;
1234                                 o.datatype = 'port';
1235
1236                                 o = ss.taboption('encryption', form.Value, 'auth_secret', _('Radius-Authentication-Secret'));
1237                                 o.depends({ mode: 'ap', encryption: 'wpa' });
1238                                 o.depends({ mode: 'ap', encryption: 'wpa2' });
1239                                 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1240                                 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1241                                 o.rmempty = true;
1242                                 o.password = true;
1243
1244                                 o = ss.taboption('encryption', form.Value, 'acct_server', _('Radius-Accounting-Server'));
1245                                 o.depends({ mode: 'ap', encryption: 'wpa' });
1246                                 o.depends({ mode: 'ap', encryption: 'wpa2' });
1247                                 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1248                                 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1249                                 o.rmempty = true;
1250                                 o.datatype = 'host(0)';
1251
1252                                 o = ss.taboption('encryption', form.Value, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
1253                                 o.depends({ mode: 'ap', encryption: 'wpa' });
1254                                 o.depends({ mode: 'ap', encryption: 'wpa2' });
1255                                 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1256                                 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1257                                 o.rmempty = true;
1258                                 o.datatype = 'port';
1259
1260                                 o = ss.taboption('encryption', form.Value, 'acct_secret', _('Radius-Accounting-Secret'));
1261                                 o.depends({ mode: 'ap', encryption: 'wpa' });
1262                                 o.depends({ mode: 'ap', encryption: 'wpa2' });
1263                                 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1264                                 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1265                                 o.rmempty = true;
1266                                 o.password = true;
1267
1268                                 o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client'));
1269                                 o.depends({ mode: 'ap', encryption: 'wpa' });
1270                                 o.depends({ mode: 'ap', encryption: 'wpa2' });
1271                                 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1272                                 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1273                                 o.rmempty = true;
1274                                 o.datatype = 'host(0)';
1275
1276                                 o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
1277                                 o.depends({ mode: 'ap', encryption: 'wpa' });
1278                                 o.depends({ mode: 'ap', encryption: 'wpa2' });
1279                                 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1280                                 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1281                                 o.rmempty = true;
1282                                 o.datatype = 'port';
1283
1284                                 o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret'));
1285                                 o.depends({ mode: 'ap', encryption: 'wpa' });
1286                                 o.depends({ mode: 'ap', encryption: 'wpa2' });
1287                                 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1288                                 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1289                                 o.rmempty = true;
1290                                 o.password = true;
1291
1292
1293                                 o = ss.taboption('encryption', form.Value, '_wpa_key', _('Key'));
1294                                 o.depends('encryption', 'psk');
1295                                 o.depends('encryption', 'psk2');
1296                                 o.depends('encryption', 'psk+psk2');
1297                                 o.depends('encryption', 'psk-mixed');
1298                                 o.depends('encryption', 'sae');
1299                                 o.depends('encryption', 'sae-mixed');
1300                                 o.datatype = 'wpakey';
1301                                 o.rmempty = true;
1302                                 o.password = true;
1303
1304                                 o.cfgvalue = function(section_id) {
1305                                         var key = uci.get('wireless', section_id, 'key');
1306                                         return /^[1234]$/.test(key) ? null : key;
1307                                 };
1308
1309                                 o.write = function(section_id, value) {
1310                                         uci.set('wireless', section_id, 'key', value);
1311                                         uci.unset('wireless', section_id, 'key1');
1312                                         uci.unset('wireless', section_id, 'key2');
1313                                         uci.unset('wireless', section_id, 'key3');
1314                                         uci.unset('wireless', section_id, 'key4');
1315                                 };
1316
1317
1318                                 o = ss.taboption('encryption', form.ListValue, '_wep_key', _('Used Key Slot'));
1319                                 o.depends('encryption', 'wep-open');
1320                                 o.depends('encryption', 'wep-shared');
1321                                 o.value('1', _('Key #%d').format(1));
1322                                 o.value('2', _('Key #%d').format(2));
1323                                 o.value('3', _('Key #%d').format(3));
1324                                 o.value('4', _('Key #%d').format(4));
1325
1326                                 o.cfgvalue = function(section_id) {
1327                                         var slot = +uci.get('wireless', section_id, 'key');
1328                                         return (slot >= 1 && slot <= 4) ? String(slot) : '';
1329                                 };
1330
1331                                 o.write = function(section_id, value) {
1332                                         uci.set('wireless', section_id, 'key', value);
1333                                 };
1334
1335                                 for (var slot = 1; slot <= 4; slot++) {
1336                                         o = ss.taboption('encryption', form.Value, 'key%d'.format(slot), _('Key #%d').format(slot));
1337                                         o.depends('encryption', 'wep-open');
1338                                         o.depends('encryption', 'wep-shared');
1339                                         o.datatype = 'wepkey';
1340                                         o.rmempty = true;
1341                                         o.password = true;
1342
1343                                         o.write = function(section_id, value) {
1344                                                 if (value != null && (value.length == 5 || value.length == 13))
1345                                                         value = 's:%s'.format(value);
1346                                                 uci.set('wireless', section_id, this.option, value);
1347                                         };
1348                                 }
1349
1350
1351                                 if (hwtype == 'mac80211') {
1352                                         // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1353                                         var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap');
1354
1355                                         o = ss.taboption('encryption', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1356                                         o.depends({ mode: 'ap', encryption: 'wpa' });
1357                                         o.depends({ mode: 'ap', encryption: 'wpa2' });
1358                                         o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1359                                         o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1360                                         if (has_80211r) {
1361                                                 o.depends({ mode: 'ap', encryption: 'psk' });
1362                                                 o.depends({ mode: 'ap', encryption: 'psk2' });
1363                                                 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1364                                                 o.depends({ mode: 'ap', encryption: 'sae' });
1365                                                 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1366                                                 o.depends({ mode: 'ap-wds', encryption: 'psk' });
1367                                                 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1368                                                 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1369                                                 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1370                                                 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1371                                         }
1372                                         o.rmempty = true;
1373
1374                                         o = ss.taboption('encryption', form.Value, 'nasid', _('NAS ID'), _('Used for two different purposes: RADIUS NAS ID and 802.11r R0KH-ID. Not needed with normal WPA(2)-PSK.'));
1375                                         o.depends({ mode: 'ap', encryption: 'wpa' });
1376                                         o.depends({ mode: 'ap', encryption: 'wpa2' });
1377                                         o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1378                                         o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1379                                         o.depends({ ieee80211r: '1' });
1380                                         o.rmempty = true;
1381
1382                                         o = ss.taboption('encryption', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1383                                         o.depends({ ieee80211r: '1' });
1384                                         o.placeholder = '4f57';
1385                                         o.datatype = 'and(hexstring,length(4))';
1386                                         o.rmempty = true;
1387
1388                                         o = ss.taboption('encryption', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1389                                         o.depends({ ieee80211r: '1' });
1390                                         o.placeholder = '1000';
1391                                         o.datatype = 'range(1000,65535)';
1392                                         o.rmempty = true;
1393
1394                                         o = ss.taboption('encryption', form.ListValue, 'ft_over_ds', _('FT protocol'));
1395                                         o.depends({ ieee80211r: '1' });
1396                                         o.value('1', _('FT over DS'));
1397                                         o.value('0', _('FT over the Air'));
1398                                         o.rmempty = true;
1399
1400                                         o = ss.taboption('encryption', form.Flag, 'ft_psk_generate_local', _('Generate PMK locally'), _('When using a PSK, the PMK can be automatically generated. When enabled, the R0/R1 key options below are not applied. Disable this to use the R0 and R1 key options.'));
1401                                         o.depends({ ieee80211r: '1' });
1402                                         o.default = o.enabled;
1403                                         o.rmempty = false;
1404
1405                                         o = ss.taboption('encryption', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1406                                         o.depends({ ieee80211r: '1' });
1407                                         o.placeholder = '10000';
1408                                         o.datatype = 'uinteger';
1409                                         o.rmempty = true;
1410
1411                                         o = ss.taboption('encryption', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1412                                         o.depends({ ieee80211r: '1' });
1413                                         o.placeholder = '00004f577274';
1414                                         o.datatype = 'and(hexstring,length(12))';
1415                                         o.rmempty = true;
1416
1417                                         o = ss.taboption('encryption', form.Flag, 'pmk_r1_push', _('PMK R1 Push'));
1418                                         o.depends({ ieee80211r: '1' });
1419                                         o.placeholder = '0';
1420                                         o.rmempty = true;
1421
1422                                         o = ss.taboption('encryption', form.DynamicList, 'r0kh', _('External R0 Key Holder List'), _('List of R0KHs in the same Mobility Domain. <br />Format: MAC-address,NAS-Identifier,128-bit key as hex string. <br />This list is used to map R0KH-ID (NAS Identifier) to a destination MAC address when requesting PMK-R1 key from the R0KH that the STA used during the Initial Mobility Domain Association.'));
1423                                         o.depends({ ieee80211r: '1' });
1424                                         o.rmempty = true;
1425
1426                                         o = ss.taboption('encryption', form.DynamicList, 'r1kh', _('External R1 Key Holder List'), _ ('List of R1KHs in the same Mobility Domain. <br />Format: MAC-address,R1KH-ID as 6 octets with colons,128-bit key as hex string. <br />This list is used to map R1KH-ID to a destination MAC address when sending PMK-R1 key from the R0KH. This is also the list of authorized R1KHs in the MD that can request PMK-R1 keys.'));
1427                                         o.depends({ ieee80211r: '1' });
1428                                         o.rmempty = true;
1429                                         // End of 802.11r options
1430
1431                                         o = ss.taboption('encryption', form.ListValue, 'eap_type', _('EAP-Method'));
1432                                         o.value('tls',  'TLS');
1433                                         o.value('ttls', 'TTLS');
1434                                         o.value('peap', 'PEAP');
1435                                         o.value('fast', 'FAST');
1436                                         o.depends({ mode: 'sta', encryption: 'wpa' });
1437                                         o.depends({ mode: 'sta', encryption: 'wpa2' });
1438                                         o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1439                                         o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1440
1441                                         o = ss.taboption('encryption', form.Flag, 'ca_cert_usesystem', _('Use system certificates'), _("Validate server certificate using built-in system CA bundle,<br />requires the \"ca-bundle\" package"));
1442                                         o.enabled = '1';
1443                                         o.disabled = '0';
1444                                         o.default = o.disabled;
1445                                         o.depends({ mode: 'sta', encryption: 'wpa' });
1446                                         o.depends({ mode: 'sta', encryption: 'wpa2' });
1447                                         o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1448                                         o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1449                                         o.validate = function(section_id, value) {
1450                                                 if (value == '1' && !L.hasSystemFeature('cabundle')) {
1451                                                         return _("This option cannot be used because the ca-bundle package is not installed.");
1452                                                 }
1453                                                 return true;
1454                                         };
1455
1456                                         o = ss.taboption('encryption', form.FileUpload, 'ca_cert', _('Path to CA-Certificate'));
1457                                         o.depends({ mode: 'sta', encryption: 'wpa', ca_cert_usesystem: '0' });
1458                                         o.depends({ mode: 'sta', encryption: 'wpa2', ca_cert_usesystem: '0' });
1459                                         o.depends({ mode: 'sta-wds', encryption: 'wpa', ca_cert_usesystem: '0' });
1460                                         o.depends({ mode: 'sta-wds', encryption: 'wpa2', ca_cert_usesystem: '0' });
1461
1462                                         o = ss.taboption('encryption', form.Value, 'subject_match', _('Certificate constraint (Subject)'), _("Certificate constraint substring - e.g. /CN=wifi.mycompany.com<br />See `logread -f` during handshake for actual values"));
1463                                         o.depends({ mode: 'sta', encryption: 'wpa' });
1464                                         o.depends({ mode: 'sta', encryption: 'wpa2' });
1465                                         o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1466                                         o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1467
1468                                         o = ss.taboption('encryption', form.DynamicList, 'altsubject_match', _('Certificate constraint (SAN)'), _("Certificate constraint(s) via Subject Alternate Name values<br />(supported attributes: EMAIL, DNS, URI) - e.g. DNS:wifi.mycompany.com"));
1469                                         o.depends({ mode: 'sta', encryption: 'wpa' });
1470                                         o.depends({ mode: 'sta', encryption: 'wpa2' });
1471                                         o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1472                                         o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1473
1474                                         o = ss.taboption('encryption', form.DynamicList, 'domain_match', _('Certificate constraint (Domain)'), _("Certificate constraint(s) against DNS SAN values (if available)<br />or Subject CN (exact match)"));
1475                                         o.depends({ mode: 'sta', encryption: 'wpa' });
1476                                         o.depends({ mode: 'sta', encryption: 'wpa2' });
1477                                         o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1478                                         o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1479
1480                                         o = ss.taboption('encryption', form.DynamicList, 'domain_suffix_match', _('Certificate constraint (Wildcard)'), _("Certificate constraint(s) against DNS SAN values (if available)<br />or Subject CN (suffix match)"));
1481                                         o.depends({ mode: 'sta', encryption: 'wpa' });
1482                                         o.depends({ mode: 'sta', encryption: 'wpa2' });
1483                                         o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1484                                         o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1485
1486                                         o = ss.taboption('encryption', form.FileUpload, 'client_cert', _('Path to Client-Certificate'));
1487                                         o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1488                                         o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1489                                         o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1490                                         o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1491
1492                                         o = ss.taboption('encryption', form.FileUpload, 'priv_key', _('Path to Private Key'));
1493                                         o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1494                                         o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1495                                         o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1496                                         o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1497
1498                                         o = ss.taboption('encryption', form.Value, 'priv_key_pwd', _('Password of Private Key'));
1499                                         o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1500                                         o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1501                                         o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1502                                         o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1503                                         o.password = true;
1504
1505                                         o = ss.taboption('encryption', form.ListValue, 'auth', _('Authentication'));
1506                                         o.value('PAP', 'PAP');
1507                                         o.value('CHAP', 'CHAP');
1508                                         o.value('MSCHAP', 'MSCHAP');
1509                                         o.value('MSCHAPV2', 'MSCHAPv2');
1510                                         o.value('EAP-GTC');
1511                                         o.value('EAP-MD5');
1512                                         o.value('EAP-MSCHAPV2');
1513                                         o.value('EAP-TLS');
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
1527                                         o.validate = function(section_id, value) {
1528                                                 var eo = this.section.children.filter(function(o) { return o.option == 'eap_type' })[0],
1529                                                     ev = eo.formvalue(section_id);
1530
1531                                                 if (ev != 'ttls' && (value == 'PAP' || value == 'CHAP' || value == 'MSCHAP' || value == 'MSCHAPV2'))
1532                                                         return _('This authentication type is not applicable to the selected EAP method.');
1533
1534                                                 return true;
1535                                         };
1536
1537                                         o = ss.taboption('encryption', form.Flag, 'ca_cert2_usesystem', _('Use system certificates for inner-tunnel'), _("Validate server certificate using built-in system CA bundle,<br />requires the \"ca-bundle\" package"));
1538                                         o.enabled = '1';
1539                                         o.disabled = '0';
1540                                         o.default = o.disabled;
1541                                         o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1542                                         o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1543                                         o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1544                                         o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1545                                         o.validate = function(section_id, value) {
1546                                                 if (value == '1' && !L.hasSystemFeature('cabundle')) {
1547                                                         return _("This option cannot be used because the ca-bundle package is not installed.");
1548                                                 }
1549                                                 return true;
1550                                         };
1551
1552                                         o = ss.taboption('encryption', form.FileUpload, 'ca_cert2', _('Path to inner CA-Certificate'));
1553                                         o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa', ca_cert2_usesystem: '0' });
1554                                         o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2', ca_cert2_usesystem: '0' });
1555                                         o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa', ca_cert2_usesystem: '0' });
1556                                         o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2', ca_cert2_usesystem: '0' });
1557
1558                                         o = ss.taboption('encryption', form.Value, 'subject_match2', _('Inner certificate constraint (Subject)'), _("Certificate constraint substring - e.g. /CN=wifi.mycompany.com<br />See `logread -f` during handshake for actual values"));
1559                                         o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1560                                         o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1561                                         o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1562                                         o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1563
1564                                         o = ss.taboption('encryption', form.DynamicList, 'altsubject_match2', _('Inner certificate constraint (SAN)'), _("Certificate constraint(s) via Subject Alternate Name values<br />(supported attributes: EMAIL, DNS, URI) - e.g. DNS:wifi.mycompany.com"));
1565                                         o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1566                                         o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1567                                         o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1568                                         o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1569
1570                                         o = ss.taboption('encryption', form.DynamicList, 'domain_match2', _('Inner certificate constraint (Domain)'), _("Certificate constraint(s) against DNS SAN values (if available)<br />or Subject CN (exact match)"));
1571                                         o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1572                                         o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1573                                         o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1574                                         o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1575
1576                                         o = ss.taboption('encryption', form.DynamicList, 'domain_suffix_match2', _('Inner certificate constraint (Wildcard)'), _("Certificate constraint(s) against DNS SAN values (if available)<br />or Subject CN (suffix match)"));
1577                                         o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1578                                         o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1579                                         o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1580                                         o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1581
1582                                         o = ss.taboption('encryption', form.FileUpload, 'client_cert2', _('Path to inner Client-Certificate'));
1583                                         o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1584                                         o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1585                                         o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1586                                         o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1587
1588                                         o = ss.taboption('encryption', form.FileUpload, 'priv_key2', _('Path to inner Private Key'));
1589                                         o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1590                                         o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1591                                         o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1592                                         o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1593
1594                                         o = ss.taboption('encryption', form.Value, 'priv_key2_pwd', _('Password of inner Private Key'));
1595                                         o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1596                                         o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1597                                         o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1598                                         o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1599                                         o.password = true;
1600
1601                                         o = ss.taboption('encryption', form.Value, 'identity', _('Identity'));
1602                                         o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1603                                         o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1604                                         o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1605                                         o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1606                                         o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1607                                         o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1608                                         o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1609                                         o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1610                                         o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1611                                         o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1612                                         o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1613                                         o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1614                                         o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1615                                         o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1616                                         o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1617                                         o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1618
1619                                         o = ss.taboption('encryption', form.Value, 'anonymous_identity', _('Anonymous Identity'));
1620                                         o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1621                                         o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1622                                         o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1623                                         o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1624                                         o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1625                                         o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1626                                         o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1627                                         o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1628                                         o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1629                                         o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1630                                         o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1631                                         o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1632                                         o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1633                                         o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1634                                         o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1635                                         o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1636
1637                                         o = ss.taboption('encryption', form.Value, 'password', _('Password'));
1638                                         o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1639                                         o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1640                                         o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1641                                         o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1642                                         o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1643                                         o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1644                                         o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1645                                         o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1646                                         o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1647                                         o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1648                                         o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1649                                         o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1650                                         o.password = true;
1651
1652
1653                                         if (hwtype == 'mac80211') {
1654                                                 // ieee802.11w options
1655                                                 if (L.hasSystemFeature('hostapd', '11w')) {
1656                                                         o = ss.taboption('encryption', form.ListValue, 'ieee80211w', _('802.11w Management Frame Protection'), _("Requires the 'full' version of wpad/hostapd and support from the wifi driver <br />(as of Jan 2019: ath9k, ath10k, mwlwifi and mt76)"));
1657                                                         o.value('', _('Disabled'));
1658                                                         o.value('1', _('Optional'));
1659                                                         o.value('2', _('Required'));
1660                                                         o.depends({ mode: 'ap', encryption: 'wpa2' });
1661                                                         o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1662                                                         o.depends({ mode: 'ap', encryption: 'psk2' });
1663                                                         o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1664                                                         o.depends({ mode: 'ap', encryption: 'sae' });
1665                                                         o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1666                                                         o.depends({ mode: 'ap', encryption: 'owe' });
1667                                                         o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1668                                                         o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1669                                                         o.depends({ mode: 'ap-wds', encryption: 'sae' });
1670                                                         o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1671                                                         o.depends({ mode: 'ap-wds', encryption: 'owe' });
1672                                                         o.depends({ mode: 'sta', encryption: 'wpa2' });
1673                                                         o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1674                                                         o.depends({ mode: 'sta', encryption: 'psk2' });
1675                                                         o.depends({ mode: 'sta', encryption: 'psk-mixed' });
1676                                                         o.depends({ mode: 'sta', encryption: 'sae' });
1677                                                         o.depends({ mode: 'sta', encryption: 'sae-mixed' });
1678                                                         o.depends({ mode: 'sta', encryption: 'owe' });
1679                                                         o.depends({ mode: 'sta-wds', encryption: 'psk2' });
1680                                                         o.depends({ mode: 'sta-wds', encryption: 'psk-mixed' });
1681                                                         o.depends({ mode: 'sta-wds', encryption: 'sae' });
1682                                                         o.depends({ mode: 'sta-wds', encryption: 'sae-mixed' });
1683                                                         o.depends({ mode: 'sta-wds', encryption: 'owe' });
1684                                                         o.defaults = {
1685                                                                 '2': [{ encryption: 'sae' }, { encryption: 'owe' }],
1686                                                                 '1': [{ encryption: 'sae-mixed'}],
1687                                                                 '':  []
1688                                                         };
1689
1690                                                         o = ss.taboption('encryption', form.Value, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1691                                                         o.depends('ieee80211w', '1');
1692                                                         o.depends('ieee80211w', '2');
1693                                                         o.datatype = 'uinteger';
1694                                                         o.placeholder = '1000';
1695                                                         o.rmempty = true;
1696
1697                                                         o = ss.taboption('encryption', form.Value, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1698                                                         o.depends('ieee80211w', '1');
1699                                                         o.depends('ieee80211w', '2');
1700                                                         o.datatype = 'uinteger';
1701                                                         o.placeholder = '201';
1702                                                         o.rmempty = true;
1703                                                 };
1704
1705                                                 o = ss.taboption('encryption', form.Flag, 'wpa_disable_eapol_key_retries', _('Enable key reinstallation (KRACK) countermeasures'), _('Complicates key reinstallation attacks on the client side by disabling retransmission of EAPOL-Key frames that are used to install keys. This workaround might cause interoperability issues and reduced robustness of key negotiation especially in environments with heavy traffic load.'));
1706                                                 o.depends({ mode: 'ap', encryption: 'wpa2' });
1707                                                 o.depends({ mode: 'ap', encryption: 'psk2' });
1708                                                 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1709                                                 o.depends({ mode: 'ap', encryption: 'sae' });
1710                                                 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1711                                                 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1712                                                 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1713                                                 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1714                                                 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1715                                                 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1716
1717                                                 if (L.hasSystemFeature('hostapd', 'cli') && L.hasSystemFeature('wpasupplicant')) {
1718                                                         o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1719                                                         o.enabled = '1';
1720                                                         o.disabled = '0';
1721                                                         o.default = o.disabled;
1722                                                         o.depends('encryption', 'psk');
1723                                                         o.depends('encryption', 'psk2');
1724                                                         o.depends('encryption', 'psk-mixed');
1725                                                         o.depends('encryption', 'sae');
1726                                                         o.depends('encryption', 'sae-mixed');
1727                                                 }
1728                                         }
1729                                 }
1730                         });
1731                 };
1732
1733                 s.handleRemove = function(section_id, ev) {
1734                         document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id)).style.opacity = 0.5;
1735                         return form.TypedSection.prototype.handleRemove.apply(this, [section_id, ev]);
1736                 };
1737
1738                 s.handleScan = function(radioDev, ev) {
1739                         var table = E('div', { 'class': 'table' }, [
1740                                 E('div', { 'class': 'tr table-titles' }, [
1741                                         E('div', { 'class': 'th col-2 middle center' }, _('Signal')),
1742                                         E('div', { 'class': 'th col-4 middle left' }, _('SSID')),
1743                                         E('div', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1744                                         E('div', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1745                                         E('div', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1746                                         E('div', { 'class': 'th col-3 middle left' }, _('Encryption')),
1747                                         E('div', { 'class': 'th cbi-section-actions right' }, ' '),
1748                                 ])
1749                         ]);
1750
1751                         var stop = E('button', {
1752                                 'class': 'btn',
1753                                 'click': L.bind(this.handleScanStartStop, this),
1754                                 'style': 'display:none',
1755                                 'data-state': 'stop'
1756                         }, _('Stop refresh'));
1757
1758                         cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1759
1760                         var md = ui.showModal(_('Join Network: Wireless Scan'), [
1761                                 table,
1762                                 E('div', { 'class': 'right' }, [
1763                                         stop,
1764                                         ' ',
1765                                         E('button', {
1766                                                 'class': 'btn',
1767                                                 'click': L.bind(this.handleScanAbort, this)
1768                                         }, _('Dismiss'))
1769                                 ])
1770                         ]);
1771
1772                         md.style.maxWidth = '90%';
1773                         md.style.maxHeight = 'none';
1774
1775                         this.pollFn = L.bind(this.handleScanRefresh, this, radioDev, {}, table, stop);
1776
1777                         L.Poll.add(this.pollFn);
1778                         L.Poll.start();
1779                 };
1780
1781                 s.handleScanRefresh = function(radioDev, scanCache, table, stop) {
1782                         return radioDev.getScanList().then(L.bind(function(results) {
1783                                 var rows = [];
1784
1785                                 for (var i = 0; i < results.length; i++)
1786                                         scanCache[results[i].bssid] = results[i];
1787
1788                                 for (var k in scanCache)
1789                                         if (scanCache[k].stale)
1790                                                 results.push(scanCache[k]);
1791
1792                                 results.sort(function(a, b) {
1793                                         var diff = (b.quality - a.quality) || (a.channel - b.channel);
1794
1795                                         if (diff)
1796                                                 return diff;
1797
1798                                         if (a.ssid < b.ssid)
1799                                                 return -1;
1800                                         else if (a.ssid > b.ssid)
1801                                                 return 1;
1802
1803                                         if (a.bssid < b.bssid)
1804                                                 return -1;
1805                                         else if (a.bssid > b.bssid)
1806                                                 return 1;
1807                                 });
1808
1809                                 for (var i = 0; i < results.length; i++) {
1810                                         var res = results[i],
1811                                             qv = res.quality || 0,
1812                                             qm = res.quality_max || 0,
1813                                             q = (qv > 0 && qm > 0) ? Math.floor((100 / qm) * qv) : 0,
1814                                             s = res.stale ? 'opacity:0.5' : '';
1815
1816                                         rows.push([
1817                                                 E('span', { 'style': s }, render_signal_badge(q, res.signal, res.noise)),
1818                                                 E('span', { 'style': s }, (res.ssid != null) ? '%h'.format(res.ssid) : E('em', _('hidden'))),
1819                                                 E('span', { 'style': s }, '%d'.format(res.channel)),
1820                                                 E('span', { 'style': s }, '%h'.format(res.mode)),
1821                                                 E('span', { 'style': s }, '%h'.format(res.bssid)),
1822                                                 E('span', { 'style': s }, '%h'.format(network.formatWifiEncryption(res.encryption))),
1823                                                 E('div', { 'class': 'right' }, E('button', {
1824                                                         'class': 'cbi-button cbi-button-action important',
1825                                                         'click': L.bind(this.handleJoin, this, radioDev, res)
1826                                                 }, _('Join Network')))
1827                                         ]);
1828
1829                                         res.stale = true;
1830                                 }
1831
1832                                 cbi_update_table(table, rows);
1833
1834                                 stop.disabled = false;
1835                                 stop.style.display = '';
1836                                 stop.classList.remove('spinning');
1837                         }, this));
1838                 };
1839
1840                 s.handleScanStartStop = function(ev) {
1841                         var btn = ev.currentTarget;
1842
1843                         if (btn.getAttribute('data-state') == 'stop') {
1844                                 L.Poll.remove(this.pollFn);
1845                                 btn.firstChild.data = _('Start refresh');
1846                                 btn.setAttribute('data-state', 'start');
1847                         }
1848                         else {
1849                                 L.Poll.add(this.pollFn);
1850                                 btn.firstChild.data = _('Stop refresh');
1851                                 btn.setAttribute('data-state', 'stop');
1852                                 btn.classList.add('spinning');
1853                                 btn.disabled = true;
1854                         }
1855                 };
1856
1857                 s.handleScanAbort = function(ev) {
1858                         var md = L.dom.parent(ev.target, 'div[aria-modal="true"]');
1859                         if (md) {
1860                                 md.style.maxWidth = '';
1861                                 md.style.maxHeight = '';
1862                         }
1863
1864                         ui.hideModal();
1865                         L.Poll.remove(this.pollFn);
1866
1867                         this.pollFn = null;
1868                 };
1869
1870                 s.handleJoinConfirm = function(radioDev, bss, form, ev) {
1871                         var nameopt = L.toArray(form.lookupOption('name', '_new_'))[0],
1872                             passopt = L.toArray(form.lookupOption('password', '_new_'))[0],
1873                             bssidopt = L.toArray(form.lookupOption('bssid', '_new_'))[0],
1874                             zoneopt = L.toArray(form.lookupOption('zone', '_new_'))[0],
1875                             replopt = L.toArray(form.lookupOption('replace', '_new_'))[0],
1876                             nameval = (nameopt && nameopt.isValid('_new_')) ? nameopt.formvalue('_new_') : null,
1877                             passval = (passopt && passopt.isValid('_new_')) ? passopt.formvalue('_new_') : null,
1878                             bssidval = (bssidopt && bssidopt.isValid('_new_')) ? bssidopt.formvalue('_new_') : null,
1879                             zoneval = zoneopt ? zoneopt.formvalue('_new_') : null,
1880                             enc = L.isObject(bss.encryption) ? bss.encryption : null,
1881                             is_wep = (enc && Array.isArray(enc.wep)),
1882                             is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' })),
1883                             is_sae = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'sae' }));
1884
1885                         if (nameval == null || (passopt && passval == null))
1886                                 return;
1887
1888                         var section_id = null;
1889
1890                         return this.map.save(function() {
1891                                 var wifi_sections = uci.sections('wireless', 'wifi-iface');
1892
1893                                 if (replopt.formvalue('_new_') == '1') {
1894                                         for (var i = 0; i < wifi_sections.length; i++)
1895                                                 if (wifi_sections[i].device == radioDev.getName())
1896                                                         uci.remove('wireless', wifi_sections[i]['.name']);
1897                                 }
1898
1899                                 if (uci.get('wireless', radioDev.getName(), 'disabled') == '1') {
1900                                         for (var i = 0; i < wifi_sections.length; i++)
1901                                                 if (wifi_sections[i].device == radioDev.getName())
1902                                                         uci.set('wireless', wifi_sections[i]['.name'], 'disabled', '1');
1903
1904                                         uci.unset('wireless', radioDev.getName(), 'disabled');
1905                                 }
1906
1907                                 section_id = next_free_sid(wifi_sections.length);
1908
1909                                 uci.add('wireless', 'wifi-iface', section_id);
1910                                 uci.set('wireless', section_id, 'device', radioDev.getName());
1911                                 uci.set('wireless', section_id, 'mode', (bss.mode == 'Ad-Hoc') ? 'adhoc' : 'sta');
1912                                 uci.set('wireless', section_id, 'network', nameval);
1913
1914                                 if (bss.ssid != null) {
1915                                         uci.set('wireless', section_id, 'ssid', bss.ssid);
1916
1917                                         if (bssidval == '1')
1918                                                 uci.set('wireless', section_id, 'bssid', bss.bssid);
1919                                 }
1920                                 else if (bss.bssid != null) {
1921                                         uci.set('wireless', section_id, 'bssid', bss.bssid);
1922                                 }
1923
1924                                 if (is_sae) {
1925                                         uci.set('wireless', section_id, 'encryption', 'sae');
1926                                         uci.set('wireless', section_id, 'key', passval);
1927                                 }
1928                                 else if (is_psk) {
1929                                         for (var i = enc.wpa.length - 1; i >= 0; i--) {
1930                                                 if (enc.wpa[i] == 2) {
1931                                                         uci.set('wireless', section_id, 'encryption', 'psk2');
1932                                                         break;
1933                                                 }
1934                                                 else if (enc.wpa[i] == 1) {
1935                                                         uci.set('wireless', section_id, 'encryption', 'psk');
1936                                                         break;
1937                                                 }
1938                                         }
1939
1940                                         uci.set('wireless', section_id, 'key', passval);
1941                                 }
1942                                 else if (is_wep) {
1943                                         uci.set('wireless', section_id, 'encryption', 'wep-open');
1944                                         uci.set('wireless', section_id, 'key', '1');
1945                                         uci.set('wireless', section_id, 'key1', passval);
1946                                 }
1947                                 else {
1948                                         uci.set('wireless', section_id, 'encryption', 'none');
1949                                 }
1950
1951                                 return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) {
1952                                         firewall.deleteNetwork(net.getName());
1953
1954                                         var zonePromise = zoneval
1955                                                 ? firewall.getZone(zoneval).then(function(zone) { return zone || firewall.addZone(zoneval) })
1956                                                 : Promise.resolve();
1957
1958                                         return zonePromise.then(function(zone) {
1959                                                 if (zone)
1960                                                         zone.addNetwork(net.getName());
1961                                         });
1962                                 });
1963                         }).then(L.bind(function() {
1964                                 return this.renderMoreOptionsModal(section_id);
1965                         }, this));
1966                 };
1967
1968                 s.handleJoin = function(radioDev, bss, ev) {
1969                         this.handleScanAbort(ev);
1970
1971                         var m2 = new form.Map('wireless'),
1972                             s2 = m2.section(form.NamedSection, '_new_'),
1973                             enc = L.isObject(bss.encryption) ? bss.encryption : null,
1974                             is_wep = (enc && Array.isArray(enc.wep)),
1975                             is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' || a == 'sae' })),
1976                             replace, passphrase, name, bssid, zone;
1977
1978                         var nameUsed = function(name) {
1979                                 var s = uci.get('network', name);
1980                                 if (s != null && s['.type'] != 'interface')
1981                                         return true;
1982
1983                                 var net = (s != null) ? network.instantiateNetwork(name) : null;
1984                                 return (net != null && !net.isEmpty());
1985                         };
1986
1987                         s2.render = function() {
1988                                 return Promise.all([
1989                                         {},
1990                                         this.renderUCISection('_new_')
1991                                 ]).then(this.renderContents.bind(this));
1992                         };
1993
1994                         replace = s2.option(form.Flag, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
1995
1996                         name = s2.option(form.Value, 'name', _('Name of the new network'), _('The allowed characters are: <code>A-Z</code>, <code>a-z</code>, <code>0-9</code> and <code>_</code>'));
1997                         name.datatype = 'uciname';
1998                         name.default = 'wwan';
1999                         name.rmempty = false;
2000                         name.validate = function(section_id, value) {
2001                                 if (nameUsed(value))
2002                                         return _('The network name is already used');
2003
2004                                 return true;
2005                         };
2006
2007                         for (var i = 2; nameUsed(name.default); i++)
2008                                 name.default = 'wwan%d'.format(i);
2009
2010                         if (is_wep || is_psk) {
2011                                 passphrase = s2.option(form.Value, 'password', is_wep ? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
2012                                 passphrase.datatype = is_wep ? 'wepkey' : 'wpakey';
2013                                 passphrase.password = true;
2014                                 passphrase.rmempty = false;
2015                         }
2016
2017                         if (bss.ssid != null) {
2018                                 bssid = s2.option(form.Flag, 'bssid', _('Lock to BSSID'), _('Instead of joining any network with a matching SSID, only connect to the BSSID <code>%h</code>.').format(bss.bssid));
2019                                 bssid.default = '0';
2020                         }
2021
2022                         zone = s2.option(widgets.ZoneSelect, 'zone', _('Create / Assign firewall-zone'), _('Choose the firewall zone you want to assign to this interface. Select <em>unspecified</em> to remove the interface from the associated zone or fill out the <em>create</em> field to define a new zone and attach the interface to it.'));
2023                         zone.default = 'wan';
2024
2025                         return m2.render().then(L.bind(function(nodes) {
2026                                 ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [
2027                                         nodes,
2028                                         E('div', { 'class': 'right' }, [
2029                                                 E('button', {
2030                                                         'class': 'btn',
2031                                                         'click': ui.hideModal
2032                                                 }, _('Cancel')), ' ',
2033                                                 E('button', {
2034                                                         'class': 'cbi-button cbi-button-positive important',
2035                                                         'click': ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2)
2036                                                 }, _('Submit'))
2037                                         ])
2038                                 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase || name).cbid('_new_'))).focus();
2039                         }, this));
2040                 };
2041
2042                 s.handleAdd = function(radioDev, ev) {
2043                         var section_id = next_free_sid(uci.sections('wireless', 'wifi-iface').length);
2044
2045                         uci.unset('wireless', radioDev.getName(), 'disabled');
2046
2047                         uci.add('wireless', 'wifi-iface', section_id);
2048                         uci.set('wireless', section_id, 'device', radioDev.getName());
2049                         uci.set('wireless', section_id, 'mode', 'ap');
2050                         uci.set('wireless', section_id, 'ssid', 'OpenWrt');
2051                         uci.set('wireless', section_id, 'encryption', 'none');
2052
2053                         this.addedSection = section_id;
2054                         return this.renderMoreOptionsModal(section_id);
2055                 };
2056
2057                 o = s.option(form.DummyValue, '_badge');
2058                 o.modalonly = false;
2059                 o.textvalue = function(section_id) {
2060                         var inst = this.section.lookupRadioOrNetwork(section_id),
2061                             node = E('div', { 'class': 'center' });
2062
2063                         if (inst.getWifiNetworks)
2064                                 node.appendChild(render_radio_badge(inst));
2065                         else
2066                                 node.appendChild(render_network_badge(inst));
2067
2068                         return node;
2069                 };
2070
2071                 o = s.option(form.DummyValue, '_stat');
2072                 o.modalonly = false;
2073                 o.textvalue = function(section_id) {
2074                         var inst = this.section.lookupRadioOrNetwork(section_id);
2075
2076                         if (inst.getWifiNetworks)
2077                                 return render_radio_status(inst, this.section.wifis.filter(function(e) {
2078                                         return (e.getWifiDeviceName() == inst.getName());
2079                                 }));
2080                         else
2081                                 return render_network_status(inst);
2082                 };
2083
2084                 return m.render().then(L.bind(function(m, nodes) {
2085                         L.Poll.add(L.bind(function() {
2086                                 var section_ids = m.children[0].cfgsections(),
2087                                     tasks = [ network.getHostHints(), network.getWifiDevices() ];
2088
2089                                 for (var i = 0; i < section_ids.length; i++) {
2090                                         var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])),
2091                                             dsc = row.querySelector('[data-name="_stat"] > div'),
2092                                             btns = row.querySelectorAll('.cbi-section-actions button');
2093
2094                                         if (dsc.getAttribute('restart') == '') {
2095                                                 dsc.setAttribute('restart', '1');
2096                                                 tasks.push(fs.exec('/sbin/wifi', ['up', section_ids[i]]).catch(function(e) {
2097                                                         ui.addNotification(null, E('p', e.message));
2098                                                 }));
2099                                         }
2100                                         else if (dsc.getAttribute('restart') == '1') {
2101                                                 dsc.removeAttribute('restart');
2102                                                 btns[0].classList.remove('spinning');
2103                                                 btns[0].disabled = false;
2104                                         }
2105                                 }
2106
2107                                 return Promise.all(tasks)
2108                                         .then(L.bind(function(hosts_radios) {
2109                                                 var tasks = [];
2110
2111                                                 for (var i = 0; i < hosts_radios[1].length; i++)
2112                                                         tasks.push(hosts_radios[1][i].getWifiNetworks());
2113
2114                                                 return Promise.all(tasks).then(function(data) {
2115                                                         hosts_radios[2] = [];
2116
2117                                                         for (var i = 0; i < data.length; i++)
2118                                                                 hosts_radios[2].push.apply(hosts_radios[2], data[i]);
2119
2120                                                         return hosts_radios;
2121                                                 });
2122                                         }, network))
2123                                         .then(L.bind(function(hosts_radios_wifis) {
2124                                                 var tasks = [];
2125
2126                                                 for (var i = 0; i < hosts_radios_wifis[2].length; i++)
2127                                                         tasks.push(hosts_radios_wifis[2][i].getAssocList());
2128
2129                                                 return Promise.all(tasks).then(function(data) {
2130                                                         hosts_radios_wifis[3] = [];
2131
2132                                                         for (var i = 0; i < data.length; i++) {
2133                                                                 var wifiNetwork = hosts_radios_wifis[2][i],
2134                                                                     radioDev = hosts_radios_wifis[1].filter(function(d) { return d.getName() == wifiNetwork.getWifiDeviceName() })[0];
2135
2136                                                                 for (var j = 0; j < data[i].length; j++)
2137                                                                         hosts_radios_wifis[3].push(Object.assign({ radio: radioDev, network: wifiNetwork }, data[i][j]));
2138                                                         }
2139
2140                                                         return hosts_radios_wifis;
2141                                                 });
2142                                         }, network))
2143                                         .then(L.bind(this.poll_status, this, nodes));
2144                         }, this), 5);
2145
2146                         var table = E('div', { 'class': 'table', 'id': 'wifi_assoclist_table' }, [
2147                                 E('div', { 'class': 'tr table-titles' }, [
2148                                         E('div', { 'class': 'th nowrap' }, _('Network')),
2149                                         E('div', { 'class': 'th hide-xs' }, _('MAC-Address')),
2150                                         E('div', { 'class': 'th' }, _('Host')),
2151                                         E('div', { 'class': 'th nowrap' }, _('Signal / Noise')),
2152                                         E('div', { 'class': 'th nowrap' }, _('RX Rate / TX Rate'))
2153                                 ])
2154                         ]);
2155
2156                         cbi_update_table(table, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
2157
2158                         return E([ nodes, E('h3', _('Associated Stations')), table ]);
2159                 }, this, m));
2160         }
2161 });