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