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