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