luci-mod-network: wireless.js: restore mac hints in mac list widget
[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 o.load = function(section_id) {
897 return network.getHostHints().then(L.bind(function(hints) {
898 hints.getMACHints().map(L.bind(function(hint) {
899 this.value(hint[0], hint[1] ? '%s (%s)'.format(hint[0], hint[1]) : hint[0]);
900 }, this));
901
902 return form.DynamicList.prototype.load.apply(this, [section_id]);
903 }, this));
904 };
905
906 mode.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
907 mode.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
908
909 mode.write = function(section_id, value) {
910 switch (value) {
911 case 'ap-wds':
912 uci.set('wireless', section_id, 'mode', 'ap');
913 uci.set('wireless', section_id, 'wds', '1');
914 break;
915
916 case 'sta-wds':
917 uci.set('wireless', section_id, 'mode', 'sta');
918 uci.set('wireless', section_id, 'wds', '1');
919 break;
920
921 default:
922 uci.set('wireless', section_id, 'mode', value);
923 uci.unset('wireless', section_id, 'wds');
924 break;
925 }
926 };
927
928 mode.cfgvalue = function(section_id) {
929 var mode = uci.get('wireless', section_id, 'mode'),
930 wds = uci.get('wireless', section_id, 'wds');
931
932 if (mode == 'ap' && wds)
933 return 'ap-wds';
934 else if (mode == 'sta' && wds)
935 return 'sta-wds';
936
937 return mode;
938 };
939
940 o = ss.taboption('general', form.Flag, 'hidden', _('Hide <abbr title="Extended Service Set Identifier">ESSID</abbr>'));
941 o.depends('mode', 'ap');
942 o.depends('mode', 'ap-wds');
943
944 o = ss.taboption('general', form.Flag, 'wmm', _('WMM Mode'));
945 o.depends('mode', 'ap');
946 o.depends('mode', 'ap-wds');
947 o.default = o.enabled;
948
949 o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
950 o.depends('mode', 'ap');
951 o.depends('mode', 'ap-wds');
952
953 o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name'));
954 o.optional = true;
955 o.placeholder = radioNet.getIfname();
956 if (/^radio\d+\.network/.test(o.placeholder))
957 o.placeholder = '';
958
959 o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble'));
960 o.default = o.enabled;
961
962 o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
963 o.optional = true;
964 o.placeholder = 2;
965 o.datatype = 'range(1,255)';
966
967 o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
968 o.optional = true;
969 o.placeholder = 600;
970 o.datatype = 'uinteger';
971
972 o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling'));
973 o.optional = true;
974 o.datatype = 'uinteger';
975
976 o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('sec'));
977 o.optional = true;
978 o.placeholder = 300;
979 o.datatype = 'uinteger';
980
981 o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval'));
982 o.optional = true;
983 o.placeholder = 65535;
984 o.datatype = 'uinteger';
985
986 o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
987 o.default = o.enabled;
988 }
989
990
991 encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption'));
992 o.depends('mode', 'ap');
993 o.depends('mode', 'sta');
994 o.depends('mode', 'adhoc');
995 o.depends('mode', 'ahdemo');
996 o.depends('mode', 'ap-wds');
997 o.depends('mode', 'sta-wds');
998 o.depends('mode', 'mesh');
999
1000 o.cfgvalue = function(section_id) {
1001 var v = String(uci.get('wireless', section_id, 'encryption'));
1002 if (v == 'wep')
1003 return 'wep-open';
1004 else if (v.match(/\+/))
1005 return v.replace(/\+.+$/, '');
1006 return v;
1007 };
1008
1009 o.write = function(section_id, value) {
1010 var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id),
1011 co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id);
1012
1013 if (value == 'wpa' || value == 'wpa2')
1014 uci.unset('wireless', section_id, 'key');
1015
1016 if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp'))
1017 e += '+' + c;
1018
1019 uci.set('wireless', section_id, 'encryption', e);
1020 };
1021
1022 o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher'));
1023 o.depends('encryption', 'wpa');
1024 o.depends('encryption', 'wpa2');
1025 o.depends('encryption', 'psk');
1026 o.depends('encryption', 'psk2');
1027 o.depends('encryption', 'wpa-mixed');
1028 o.depends('encryption', 'psk-mixed');
1029 o.value('auto', _('auto'));
1030 o.value('ccmp', _('Force CCMP (AES)'));
1031 o.value('tkip', _('Force TKIP'));
1032 o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1033 o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write;
1034
1035 o.cfgvalue = function(section_id) {
1036 var v = String(uci.get('wireless', section_id, 'encryption'));
1037 if (v.match(/\+/)) {
1038 v = v.replace(/^[^+]+\+/, '');
1039 if (v == 'aes')
1040 v = 'ccmp';
1041 else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip')
1042 v = 'tkip+ccmp';
1043 }
1044 return v;
1045 };
1046
1047
1048 encr.value('none', _('No Encryption'));
1049 encr.value('wep-open', _('WEP Open System'));
1050 encr.value('wep-shared', _('WEP Shared Key'));
1051
1052 if (hwtype == 'mac80211') {
1053 var has_supplicant = L.hasSystemFeature('wpasupplicant'),
1054 has_hostapd = L.hasSystemFeature('hostapd');
1055
1056 // Probe EAP support
1057 var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'),
1058 has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap');
1059
1060 // Probe SAE support
1061 var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'),
1062 has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae');
1063
1064 // Probe OWE support
1065 var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
1066 has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
1067
1068
1069 if (has_hostapd || has_supplicant) {
1070 encr.value('psk', 'WPA-PSK');
1071 encr.value('psk2', 'WPA2-PSK');
1072 encr.value('psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode');
1073 }
1074 else {
1075 encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1076 }
1077
1078 if (has_ap_sae || has_sta_sae) {
1079 encr.value('sae', 'WPA3-SAE');
1080 encr.value('sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode');
1081 }
1082
1083 if (has_ap_eap || has_sta_eap) {
1084 encr.value('wpa', 'WPA-EAP');
1085 encr.value('wpa2', 'WPA2-EAP');
1086 }
1087
1088 if (has_ap_owe || has_sta_owe) {
1089 encr.value('owe', 'OWE');
1090 }
1091
1092 encr.crypto_support = {
1093 'ap': {
1094 'wep-open': true,
1095 'wep-shared': true,
1096 'psk': has_hostapd || _('Requires hostapd'),
1097 'psk2': has_hostapd || _('Requires hostapd'),
1098 'psk-mixed': has_hostapd || _('Requires hostapd'),
1099 'sae': has_ap_sae || _('Requires hostapd with SAE support'),
1100 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
1101 'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
1102 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
1103 'owe': has_ap_owe || _('Requires hostapd with OWE support')
1104 },
1105 'sta': {
1106 'wep-open': true,
1107 'wep-shared': true,
1108 'psk': has_supplicant || _('Requires wpa-supplicant'),
1109 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1110 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1111 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1112 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1113 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1114 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1115 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
1116 },
1117 'adhoc': {
1118 'wep-open': true,
1119 'wep-shared': true,
1120 'psk': has_supplicant || _('Requires wpa-supplicant'),
1121 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1122 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1123 },
1124 'mesh': {
1125 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support')
1126 },
1127 'ahdemo': {
1128 'wep-open': true,
1129 'wep-shared': true
1130 },
1131 'wds': {
1132 'wep-open': true,
1133 'wep-shared': true
1134 }
1135 };
1136
1137 encr.crypto_support['ap-wds'] = encr.crypto_support['ap'];
1138 encr.crypto_support['sta-wds'] = encr.crypto_support['sta'];
1139
1140 encr.validate = function(section_id, value) {
1141 var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
1142 modeval = modeopt.formvalue(section_id),
1143 modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)],
1144 enctitle = this.vallist[this.keylist.indexOf(value)];
1145
1146 if (value == 'none')
1147 return true;
1148
1149 if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value))
1150 return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle);
1151
1152 return this.crypto_support[modeval][value];
1153 };
1154 }
1155 else if (hwtype == 'broadcom') {
1156 encr.value('psk', 'WPA-PSK');
1157 encr.value('psk2', 'WPA2-PSK');
1158 encr.value('psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode');
1159 }
1160
1161
1162 o = ss.taboption('encryption', form.Value, 'auth_server', _('Radius-Authentication-Server'));
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 = 'host(0)';
1169
1170 o = ss.taboption('encryption', form.Value, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
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.datatype = 'port';
1177
1178 o = ss.taboption('encryption', form.Value, 'auth_secret', _('Radius-Authentication-Secret'));
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.password = true;
1185
1186 o = ss.taboption('encryption', form.Value, 'acct_server', _('Radius-Accounting-Server'));
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 = 'host(0)';
1193
1194 o = ss.taboption('encryption', form.Value, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
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.datatype = 'port';
1201
1202 o = ss.taboption('encryption', form.Value, 'acct_secret', _('Radius-Accounting-Secret'));
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.password = true;
1209
1210 o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client'));
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 = 'host(0)';
1217
1218 o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
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.datatype = 'port';
1225
1226 o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret'));
1227 o.depends({ mode: 'ap', encryption: 'wpa' });
1228 o.depends({ mode: 'ap', encryption: 'wpa2' });
1229 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1230 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1231 o.rmempty = true;
1232 o.password = true;
1233
1234
1235 o = ss.taboption('encryption', form.Value, '_wpa_key', _('Key'));
1236 o.depends('encryption', 'psk');
1237 o.depends('encryption', 'psk2');
1238 o.depends('encryption', 'psk+psk2');
1239 o.depends('encryption', 'psk-mixed');
1240 o.depends('encryption', 'sae');
1241 o.depends('encryption', 'sae-mixed');
1242 o.datatype = 'wpakey';
1243 o.rmempty = true;
1244 o.password = true;
1245
1246 o.cfgvalue = function(section_id) {
1247 var key = uci.get('wireless', section_id, 'key');
1248 return /^[1234]$/.test(key) ? null : key;
1249 };
1250
1251 o.write = function(section_id, value) {
1252 uci.set('wireless', section_id, 'key', value);
1253 uci.unset('wireless', section_id, 'key1');
1254 };
1255
1256
1257 o = ss.taboption('encryption', form.ListValue, '_wep_key', _('Used Key Slot'));
1258 o.depends('encryption', 'wep-open');
1259 o.depends('encryption', 'wep-shared');
1260 o.value('1', _('Key #%d').format(1));
1261 o.value('2', _('Key #%d').format(2));
1262 o.value('3', _('Key #%d').format(3));
1263 o.value('4', _('Key #%d').format(4));
1264
1265 o.cfgvalue = function(section_id) {
1266 var slot = +uci.get('wireless', section_id, 'key');
1267 return (slot >= 1 && slot <= 4) ? slot : 1;
1268 };
1269
1270 o.write = function(section_id, value) {
1271 uci.set('wireless', section_id, 'key', value);
1272 };
1273
1274 for (var slot = 1; slot <= 4; slot++) {
1275 o = ss.taboption('encryption', form.Value, 'key%d'.format(slot), _('Key #%d').format(slot));
1276 o.depends('encryption', 'wep-open');
1277 o.depends('encryption', 'wep-shared');
1278 o.datatype = 'wepkey';
1279 o.rmempty = true;
1280 o.password = true;
1281
1282 o.write = function(section_id, value) {
1283 if (value != null && (value.length == 5 || value.length == 13))
1284 value = 's:%s'.format(value);
1285 uci.set('wireless', section_id, this.option, value);
1286 };
1287 }
1288
1289
1290 if (hwtype == 'mac80211') {
1291 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1292 var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap');
1293
1294 o = ss.taboption('encryption', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1295 o.depends({ mode: 'ap', encryption: 'wpa' });
1296 o.depends({ mode: 'ap', encryption: 'wpa2' });
1297 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1298 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1299 if (has_80211r) {
1300 o.depends({ mode: 'ap', encryption: 'psk' });
1301 o.depends({ mode: 'ap', encryption: 'psk2' });
1302 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1303 o.depends({ mode: 'ap', encryption: 'sae' });
1304 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1305 o.depends({ mode: 'ap-wds', encryption: 'psk' });
1306 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1307 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1308 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1309 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1310 }
1311 o.rmempty = true;
1312
1313 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.'));
1314 o.depends({ mode: 'ap', encryption: 'wpa' });
1315 o.depends({ mode: 'ap', encryption: 'wpa2' });
1316 o.depends({ mode: 'ap-wds', encryption: 'wpa' });
1317 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1318 o.depends({ ieee80211r: '1' });
1319 o.rmempty = true;
1320
1321 o = ss.taboption('encryption', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1322 o.depends({ ieee80211r: '1' });
1323 o.placeholder = '4f57';
1324 o.datatype = 'and(hexstring,length(4))';
1325 o.rmempty = true;
1326
1327 o = ss.taboption('encryption', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1328 o.depends({ ieee80211r: '1' });
1329 o.placeholder = '1000';
1330 o.datatype = 'range(1000,65535)';
1331 o.rmempty = true;
1332
1333 o = ss.taboption('encryption', form.ListValue, 'ft_over_ds', _('FT protocol'));
1334 o.depends({ ieee80211r: '1' });
1335 o.value('1', _('FT over DS'));
1336 o.value('0', _('FT over the Air'));
1337 o.rmempty = true;
1338
1339 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.'));
1340 o.depends({ ieee80211r: '1' });
1341 o.default = o.enabled;
1342 o.rmempty = false;
1343
1344 o = ss.taboption('encryption', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1345 o.depends({ ieee80211r: '1' });
1346 o.placeholder = '10000';
1347 o.datatype = 'uinteger';
1348 o.rmempty = true;
1349
1350 o = ss.taboption('encryption', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1351 o.depends({ ieee80211r: '1' });
1352 o.placeholder = '00004f577274';
1353 o.datatype = 'and(hexstring,length(12))';
1354 o.rmempty = true;
1355
1356 o = ss.taboption('encryption', form.Flag, 'pmk_r1_push', _('PMK R1 Push'));
1357 o.depends({ ieee80211r: '1' });
1358 o.placeholder = '0';
1359 o.rmempty = true;
1360
1361 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.'));
1362 o.depends({ ieee80211r: '1' });
1363 o.rmempty = true;
1364
1365 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.'));
1366 o.depends({ ieee80211r: '1' });
1367 o.rmempty = true;
1368 // End of 802.11r options
1369
1370 o = ss.taboption('encryption', form.ListValue, 'eap_type', _('EAP-Method'));
1371 o.value('tls', 'TLS');
1372 o.value('ttls', 'TTLS');
1373 o.value('peap', 'PEAP');
1374 o.value('fast', 'FAST');
1375 o.depends({ mode: 'sta', encryption: 'wpa' });
1376 o.depends({ mode: 'sta', encryption: 'wpa2' });
1377 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1378 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1379
1380 o = ss.taboption('encryption', form.FileUpload, 'ca_cert', _('Path to CA-Certificate'));
1381 o.depends({ mode: 'sta', encryption: 'wpa' });
1382 o.depends({ mode: 'sta', encryption: 'wpa2' });
1383 o.depends({ mode: 'sta-wds', encryption: 'wpa' });
1384 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1385
1386 o = ss.taboption('encryption', form.FileUpload, 'client_cert', _('Path to Client-Certificate'));
1387 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1388 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1389 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1390 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1391
1392 o = ss.taboption('encryption', form.FileUpload, 'priv_key', _('Path to Private Key'));
1393 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1394 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1395 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1396 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1397
1398 o = ss.taboption('encryption', form.Value, 'priv_key_pwd', _('Password of Private Key'));
1399 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1400 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1401 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1402 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1403 o.password = true;
1404
1405 o = ss.taboption('encryption', form.ListValue, 'auth', _('Authentication'));
1406 o.value('PAP', 'PAP');
1407 o.value('CHAP', 'CHAP');
1408 o.value('MSCHAP', 'MSCHAP');
1409 o.value('MSCHAPV2', 'MSCHAPv2');
1410 o.value('EAP-GTC');
1411 o.value('EAP-MD5');
1412 o.value('EAP-MSCHAPV2');
1413 o.value('EAP-TLS');
1414 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1415 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1416 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1417 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1418 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1419 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1420 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1421 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1422 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1423 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1424 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1425 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1426
1427 o.validate = function(section_id, value) {
1428 var eo = this.section.children.filter(function(o) { return o.option == 'eap_type' })[0],
1429 ev = eo.formvalue(section_id);
1430
1431 if (ev != 'ttls' && (value == 'PAP' || value == 'CHAP' || value == 'MSCHAP' || value == 'MSCHAPV2'))
1432 return _('This authentication type is not applicable to the selected EAP method.');
1433
1434 return true;
1435 };
1436
1437 o = ss.taboption('encryption', form.FileUpload, 'ca_cert2', _('Path to inner CA-Certificate'));
1438 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1439 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1440 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1441 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1442
1443 o = ss.taboption('encryption', form.FileUpload, 'client_cert2', _('Path to inner Client-Certificate'));
1444 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1445 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1446 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1447 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1448
1449 o = ss.taboption('encryption', form.FileUpload, 'priv_key2', _('Path to inner Private Key'));
1450 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1451 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1452 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1453 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1454
1455 o = ss.taboption('encryption', form.Value, 'priv_key2_pwd', _('Password of inner Private Key'));
1456 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
1457 o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
1458 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
1459 o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
1460 o.password = true;
1461
1462 o = ss.taboption('encryption', form.Value, 'identity', _('Identity'));
1463 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1464 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1465 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1466 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1467 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1468 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1469 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1470 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1471 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1472 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1473 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1474 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1475 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1476 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1477 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1478 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1479
1480 o = ss.taboption('encryption', form.Value, 'anonymous_identity', _('Anonymous Identity'));
1481 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1482 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1483 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1484 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1485 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1486 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1487 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1488 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1489 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1490 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1491 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1492 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1493 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
1494 o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
1495 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
1496 o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
1497
1498 o = ss.taboption('encryption', form.Value, 'password', _('Password'));
1499 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
1500 o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
1501 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
1502 o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
1503 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
1504 o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
1505 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
1506 o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
1507 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
1508 o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
1509 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
1510 o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
1511 o.password = true;
1512
1513
1514 if (hwtype == 'mac80211') {
1515 // ieee802.11w options
1516 if (L.hasSystemFeature('hostapd', '11w')) {
1517 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)"));
1518 o.default = '';
1519 o.value('', _('Disabled (default)'));
1520 o.value('1', _('Optional'));
1521 o.value('2', _('Required'));
1522 o.depends({ mode: 'ap', encryption: 'wpa2' });
1523 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1524 o.depends({ mode: 'ap', encryption: 'psk2' });
1525 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1526 o.depends({ mode: 'ap', encryption: 'sae' });
1527 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1528 o.depends({ mode: 'ap', encryption: 'owe' });
1529 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1530 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1531 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1532 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1533 o.depends({ mode: 'ap-wds', encryption: 'owe' });
1534 o.depends({ mode: 'sta', encryption: 'wpa2' });
1535 o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
1536 o.depends({ mode: 'sta', encryption: 'psk2' });
1537 o.depends({ mode: 'sta', encryption: 'psk-mixed' });
1538 o.depends({ mode: 'sta', encryption: 'sae' });
1539 o.depends({ mode: 'sta', encryption: 'sae-mixed' });
1540 o.depends({ mode: 'sta', encryption: 'owe' });
1541 o.depends({ mode: 'sta-wds', encryption: 'psk2' });
1542 o.depends({ mode: 'sta-wds', encryption: 'psk-mixed' });
1543 o.depends({ mode: 'sta-wds', encryption: 'sae' });
1544 o.depends({ mode: 'sta-wds', encryption: 'sae-mixed' });
1545 o.depends({ mode: 'sta-wds', encryption: 'owe' });
1546
1547 o = ss.taboption('encryption', form.Value, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1548 o.depends('ieee80211w', '1');
1549 o.depends('ieee80211w', '2');
1550 o.datatype = 'uinteger';
1551 o.placeholder = '1000';
1552 o.rmempty = true;
1553
1554 o = ss.taboption('encryption', form.Value, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1555 o.depends('ieee80211w', '1');
1556 o.depends('ieee80211w', '2');
1557 o.datatype = 'uinteger';
1558 o.placeholder = '201';
1559 o.rmempty = true;
1560 };
1561
1562 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.'));
1563 o.depends({ mode: 'ap', encryption: 'wpa2' });
1564 o.depends({ mode: 'ap', encryption: 'psk2' });
1565 o.depends({ mode: 'ap', encryption: 'psk-mixed' });
1566 o.depends({ mode: 'ap', encryption: 'sae' });
1567 o.depends({ mode: 'ap', encryption: 'sae-mixed' });
1568 o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
1569 o.depends({ mode: 'ap-wds', encryption: 'psk2' });
1570 o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
1571 o.depends({ mode: 'ap-wds', encryption: 'sae' });
1572 o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
1573
1574 if (L.hasSystemFeature('hostapd', 'cli') && L.hasSystemFeature('wpasupplicant')) {
1575 o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK'))
1576 o.enabled = '1';
1577 o.disabled = '0';
1578 o.default = o.disabled;
1579 o.depends('encryption', 'psk');
1580 o.depends('encryption', 'psk2');
1581 o.depends('encryption', 'psk-mixed');
1582 }
1583 }
1584 }
1585 });
1586 };
1587
1588 s.handleRemove = function(section_id, ev) {
1589 document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id)).style.opacity = 0.5;
1590 return form.TypedSection.prototype.handleRemove.apply(this, [section_id, ev]);
1591 };
1592
1593 s.handleScan = function(radioDev, ev) {
1594 var table = E('div', { 'class': 'table' }, [
1595 E('div', { 'class': 'tr table-titles' }, [
1596 E('div', { 'class': 'th col-2 middle center' }, _('Signal')),
1597 E('div', { 'class': 'th col-4 middle left' }, _('SSID')),
1598 E('div', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1599 E('div', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1600 E('div', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1601 E('div', { 'class': 'th col-3 middle left' }, _('Encryption')),
1602 E('div', { 'class': 'th cbi-section-actions right' }, ' '),
1603 ])
1604 ]);
1605
1606 cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1607
1608 var md = L.ui.showModal(_('Join Network: Wireless Scan'), [
1609 table,
1610 E('div', { 'class': 'right' },
1611 E('button', {
1612 'class': 'btn',
1613 'click': L.bind(this.handleScanAbort, this)
1614 }, _('Dismiss')))
1615 ]);
1616
1617 md.style.maxWidth = '90%';
1618 md.style.maxHeight = 'none';
1619
1620 this.pollFn = L.bind(this.handleScanRefresh, this, radioDev, {}, table);
1621
1622 L.Poll.add(this.pollFn);
1623 L.Poll.start();
1624 };
1625
1626 s.handleScanRefresh = function(radioDev, scanCache, table) {
1627 return radioDev.getScanList().then(L.bind(function(results) {
1628 var rows = [];
1629
1630 for (var i = 0; i < results.length; i++)
1631 scanCache[results[i].bssid] = results[i];
1632
1633 for (var k in scanCache)
1634 if (scanCache[k].stale)
1635 results.push(scanCache[k]);
1636
1637 results.sort(function(a, b) {
1638 var diff = (b.quality - a.quality) || (a.channel - b.channel);
1639
1640 if (diff)
1641 return diff;
1642
1643 if (a.ssid < b.ssid)
1644 return -1;
1645 else if (a.ssid > b.ssid)
1646 return 1;
1647
1648 if (a.bssid < b.bssid)
1649 return -1;
1650 else if (a.bssid > b.bssid)
1651 return 1;
1652 });
1653
1654 for (var i = 0; i < results.length; i++) {
1655 var res = results[i],
1656 qv = res.quality || 0,
1657 qm = res.quality_max || 0,
1658 q = (qv > 0 && qm > 0) ? Math.floor((100 / qm) * qv) : 0,
1659 s = res.stale ? 'opacity:0.5' : '';
1660
1661 rows.push([
1662 E('span', { 'style': s }, render_signal_badge(q, res.signal, res.noise)),
1663 E('span', { 'style': s }, '%h'.format(res.ssid)),
1664 E('span', { 'style': s }, '%d'.format(res.channel)),
1665 E('span', { 'style': s }, '%h'.format(res.mode)),
1666 E('span', { 'style': s }, '%h'.format(res.bssid)),
1667 E('span', { 'style': s }, '%h'.format(network.formatWifiEncryption(res.encryption))),
1668 E('div', { 'class': 'right' }, E('button', {
1669 'class': 'cbi-button cbi-button-action important',
1670 'click': L.bind(this.handleJoin, this, radioDev, res)
1671 }, _('Join Network')))
1672 ]);
1673
1674 res.stale = true;
1675 }
1676
1677 cbi_update_table(table, rows);
1678 }, this));
1679 };
1680
1681 s.handleScanAbort = function(ev) {
1682 var md = L.dom.parent(ev.target, 'div[aria-modal="true"]');
1683 if (md) {
1684 md.style.maxWidth = '';
1685 md.style.maxHeight = '';
1686 }
1687
1688 L.ui.hideModal();
1689 L.Poll.remove(this.pollFn);
1690
1691 this.pollFn = null;
1692 };
1693
1694 s.handleJoinConfirm = function(radioDev, bss, form, ev) {
1695 var nameopt = L.toArray(form.lookupOption('name', '_new_'))[0],
1696 passopt = L.toArray(form.lookupOption('password', '_new_'))[0],
1697 zoneopt = L.toArray(form.lookupOption('zone', '_new_'))[0],
1698 replopt = L.toArray(form.lookupOption('replace', '_new_'))[0],
1699 nameval = (nameopt && nameopt.isValid('_new_')) ? nameopt.formvalue('_new_') : null,
1700 passval = (passopt && passopt.isValid('_new_')) ? passopt.formvalue('_new_') : null,
1701 zoneval = zoneopt ? zoneopt.formvalue('_new_') : null,
1702 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1703 is_wep = (enc && Array.isArray(enc.wep)),
1704 is_psk = (enc && Array.isArray(enc.wpa) && Array.isArray(enc.authentication) && enc.authentication[0] == 'psk');
1705
1706 if (nameval == null || (passopt && passval == null))
1707 return;
1708
1709 var section_id = null;
1710
1711 return this.map.save(function() {
1712 if (replopt.formvalue('_new_') == '1') {
1713 var sections = uci.sections('wireless', 'wifi-iface');
1714
1715 for (var i = 0; i < sections.length; i++)
1716 if (sections[i].device == radioDev.getName())
1717 uci.remove('wireless', sections[i]['.name']);
1718 }
1719
1720 section_id = next_free_sid(uci.sections('wifi-iface').length);
1721
1722 uci.add('wireless', 'wifi-iface', section_id);
1723 uci.set('wireless', section_id, 'device', radioDev.getName());
1724 uci.set('wireless', section_id, 'mode', (bss.mode == 'Ad-Hoc') ? 'adhoc' : 'sta');
1725 uci.set('wireless', section_id, 'network', nameval);
1726
1727 if (bss.ssid != null)
1728 uci.set('wireless', section_id, 'ssid', bss.ssid);
1729 else if (bss.bssid != null)
1730 uci.set('wireless', section_id, 'bssid', bss.bssid);
1731
1732 if (is_psk) {
1733 for (var i = enc.wpa.length - 1; i >= 0; i--) {
1734 if (enc.wpa[i] == 2) {
1735 uci.set('wireless', section_id, 'encryption', 'psk2');
1736 break;
1737 }
1738 else if (enc.wpa[i] == 1) {
1739 uci.set('wireless', section_id, 'encryption', 'psk');
1740 break;
1741 }
1742 }
1743
1744 uci.set('wireless', section_id, 'key', passval);
1745 }
1746 else if (is_wep) {
1747 uci.set('wireless', section_id, 'encryption', 'wep-open');
1748 uci.set('wireless', section_id, 'key', '1');
1749 uci.set('wireless', section_id, 'key1', passval);
1750 }
1751
1752 var zonePromise = zoneval
1753 ? firewall.getZone(zoneval).then(function(zone) { return zone || firewall.addZone(zoneval) })
1754 : Promise.resolve();
1755
1756 return zonePromise.then(function(zone) {
1757 return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) {
1758 firewall.deleteNetwork(net.getName());
1759
1760 if (zone)
1761 zone.addNetwork(net.getName());
1762 });
1763 });
1764 }).then(L.bind(function() {
1765 return this.renderMoreOptionsModal(section_id);
1766 }, this));
1767 };
1768
1769 s.handleJoin = function(radioDev, bss, ev) {
1770 this.handleScanAbort(ev);
1771
1772 var m2 = new form.Map('wireless'),
1773 s2 = m2.section(form.NamedSection, '_new_'),
1774 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1775 is_wep = (enc && Array.isArray(enc.wep)),
1776 is_psk = (enc && Array.isArray(enc.wpa) && Array.isArray(enc.authentication) && enc.authentication[0] == 'psk'),
1777 replace, passphrase, name, zone;
1778
1779 s2.render = function() {
1780 return Promise.all([
1781 {},
1782 this.renderUCISection('_new_')
1783 ]).then(this.renderContents.bind(this));
1784 };
1785
1786 replace = s2.option(form.Flag, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
1787
1788 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>'));
1789 name.datatype = 'uciname';
1790 name.default = 'wwan';
1791 name.rmempty = false;
1792 name.validate = function(section_id, value) {
1793 if (uci.get('network', value))
1794 return _('The network name is already used');
1795
1796 return true;
1797 };
1798
1799 for (var i = 2; uci.get('network', name.default); i++)
1800 name.default = 'wwan%d'.format(i);
1801
1802 if (is_wep || is_psk) {
1803 passphrase = s2.option(form.Value, 'password', is_wep ? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
1804 passphrase.datatype = is_wep ? 'wepkey' : 'wpakey';
1805 passphrase.password = true;
1806 passphrase.rmempty = false;
1807 }
1808
1809 zone = s2.option(widgets.ZoneSelect, 'zone', _('Create / Assign firewall-zone'), _('Choose the firewall zone you want to assign to this interface. Select <em>unspecified</em> to remove the interface from the associated zone or fill out the <em>create</em> field to define a new zone and attach the interface to it.'));
1810 zone.default = 'wan';
1811
1812 return m2.render().then(L.bind(function(nodes) {
1813 L.ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [
1814 nodes,
1815 E('div', { 'class': 'right' }, [
1816 E('button', {
1817 'class': 'btn',
1818 'click': L.ui.hideModal
1819 }, _('Cancel')), ' ',
1820 E('button', {
1821 'class': 'cbi-button cbi-button-positive important',
1822 'click': L.ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2)
1823 }, _('Submit'))
1824 ])
1825 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase || name).cbid('_new_'))).focus();
1826 }, this));
1827 };
1828
1829 s.handleAdd = function(radioDev, ev) {
1830 var section_id = next_free_sid(uci.sections('wireless', 'wifi-iface').length);
1831
1832 uci.unset('wireless', radioDev.getName(), 'disabled');
1833
1834 uci.add('wireless', 'wifi-iface', section_id);
1835 uci.set('wireless', section_id, 'device', radioDev.getName());
1836 uci.set('wireless', section_id, 'mode', 'ap');
1837 uci.set('wireless', section_id, 'ssid', 'OpenWrt');
1838 uci.set('wireless', section_id, 'encryption', 'none');
1839
1840 this.addedSection = section_id;
1841 return this.renderMoreOptionsModal(section_id);
1842 };
1843
1844 o = s.option(form.DummyValue, '_badge');
1845 o.modalonly = false;
1846 o.textvalue = function(section_id) {
1847 var inst = this.section.lookupRadioOrNetwork(section_id),
1848 node = E('div', { 'class': 'center' });
1849
1850 if (inst.getWifiNetworks)
1851 node.appendChild(render_radio_badge(inst));
1852 else
1853 node.appendChild(render_network_badge(inst));
1854
1855 return node;
1856 };
1857
1858 o = s.option(form.DummyValue, '_stat');
1859 o.modalonly = false;
1860 o.textvalue = function(section_id) {
1861 var inst = this.section.lookupRadioOrNetwork(section_id);
1862
1863 if (inst.getWifiNetworks)
1864 return render_radio_status(inst, this.section.wifis.filter(function(e) {
1865 return (e.getWifiDeviceName() == inst.getName());
1866 }));
1867 else
1868 return render_network_status(inst);
1869 };
1870
1871 return m.render().then(L.bind(function(m, nodes) {
1872 L.Poll.add(L.bind(function() {
1873 var section_ids = m.children[0].cfgsections(),
1874 tasks = [ network.getHostHints(), network.getWifiDevices() ];
1875
1876 for (var i = 0; i < section_ids.length; i++) {
1877 var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])),
1878 dsc = row.querySelector('[data-name="_stat"] > div'),
1879 btns = row.querySelectorAll('.cbi-section-actions button');
1880
1881 if (dsc.getAttribute('restart') == '') {
1882 dsc.setAttribute('restart', '1');
1883 tasks.push(L.Request.post(
1884 L.url('admin/network/wireless_reconnect', section_ids[i]),
1885 'token=' + L.env.token,
1886 { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
1887 ).catch(function() {}));
1888 }
1889 else if (dsc.getAttribute('restart') == '1') {
1890 dsc.removeAttribute('restart');
1891 btns[0].classList.remove('spinning');
1892 btns[0].disabled = false;
1893 }
1894 }
1895
1896 return Promise.all(tasks)
1897 .then(L.bind(function(hosts_radios) {
1898 var tasks = [];
1899
1900 for (var i = 0; i < hosts_radios[1].length; i++)
1901 tasks.push(hosts_radios[1][i].getWifiNetworks());
1902
1903 return Promise.all(tasks).then(function(data) {
1904 hosts_radios[2] = [];
1905
1906 for (var i = 0; i < data.length; i++)
1907 hosts_radios[2].push.apply(hosts_radios[2], data[i]);
1908
1909 return hosts_radios;
1910 });
1911 }, network))
1912 .then(L.bind(function(hosts_radios_wifis) {
1913 var tasks = [];
1914
1915 for (var i = 0; i < hosts_radios_wifis[2].length; i++)
1916 tasks.push(hosts_radios_wifis[2][i].getAssocList());
1917
1918 return Promise.all(tasks).then(function(data) {
1919 hosts_radios_wifis[3] = [];
1920
1921 for (var i = 0; i < data.length; i++) {
1922 var wifiNetwork = hosts_radios_wifis[2][i],
1923 radioDev = hosts_radios_wifis[1].filter(function(d) { return d.getName() == wifiNetwork.getWifiDeviceName() })[0];
1924
1925 for (var j = 0; j < data[i].length; j++)
1926 hosts_radios_wifis[3].push(Object.assign({ radio: radioDev, network: wifiNetwork }, data[i][j]));
1927 }
1928
1929 return hosts_radios_wifis;
1930 });
1931 }, network))
1932 .then(L.bind(this.poll_status, this, nodes));
1933 }, this), 5);
1934
1935 var table = E('div', { 'class': 'table', 'id': 'wifi_assoclist_table' }, [
1936 E('div', { 'class': 'tr table-titles' }, [
1937 E('div', { 'class': 'th nowrap' }, _('Network')),
1938 E('div', { 'class': 'th hide-xs' }, _('MAC-Address')),
1939 E('div', { 'class': 'th nowrap' }, _('Host')),
1940 E('div', { 'class': 'th nowrap' }, _('Signal / Noise')),
1941 E('div', { 'class': 'th nowrap' }, _('RX Rate / TX Rate'))
1942 ])
1943 ]);
1944
1945 cbi_update_table(table, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
1946
1947 return E([ nodes, E('h3', _('Associated Stations')), table ]);
1948 }, this, m));
1949 }
1950 });