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