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