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