Merge pull request #7292 from stangri/master-luci-app-https-dns-proxy
[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\xa0%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' || value == 'wpa3-192')
1221 uci.unset('wireless', section_id, 'key');
1222
1223 if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'ccmp256' || c == 'gcmp' || c == 'gcmp256' || 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', 'wpa3-192');
1235 o.depends('encryption', 'psk');
1236 o.depends('encryption', 'psk2');
1237 o.depends('encryption', 'wpa-mixed');
1238 o.depends('encryption', 'psk-mixed');
1239 o.value('auto', _('auto'));
1240 o.value('ccmp', _('Force CCMP (AES)'));
1241 o.value('ccmp256', _('Force CCMP-256 (AES)'));
1242 o.value('gcmp', _('Force GCMP (AES)'));
1243 o.value('gcmp256', _('Force GCMP-256 (AES)'));
1244 o.value('tkip', _('Force TKIP'));
1245 o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1246 o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write;
1247
1248 o.cfgvalue = function(section_id) {
1249 var v = String(uci.get('wireless', section_id, 'encryption'));
1250 if (v.match(/\+/)) {
1251 v = v.replace(/^[^+]+\+/, '');
1252 if (v == 'aes')
1253 v = 'ccmp';
1254 else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip')
1255 v = 'tkip+ccmp';
1256 }
1257 return v;
1258 };
1259
1260
1261 var crypto_modes = [];
1262
1263 if (hwtype == 'mac80211') {
1264 var has_supplicant = L.hasSystemFeature('wpasupplicant'),
1265 has_hostapd = L.hasSystemFeature('hostapd');
1266
1267 // Probe EAP support
1268 var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'),
1269 has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap');
1270
1271 // Probe SAE support
1272 var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'),
1273 has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae');
1274
1275 // Probe OWE support
1276 var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
1277 has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
1278
1279 // Probe Suite-B support
1280 var has_ap_eap192 = L.hasSystemFeature('hostapd', 'suiteb192'),
1281 has_sta_eap192 = L.hasSystemFeature('wpasupplicant', 'suiteb192');
1282
1283 // Probe WEP support
1284 var has_ap_wep = L.hasSystemFeature('hostapd', 'wep'),
1285 has_sta_wep = L.hasSystemFeature('wpasupplicant', 'wep');
1286
1287 if (has_hostapd || has_supplicant) {
1288 crypto_modes.push(['psk2', 'WPA2-PSK', 35]);
1289 crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1290 crypto_modes.push(['psk', 'WPA-PSK', 12]);
1291 }
1292 else {
1293 encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1294 }
1295
1296 if (has_ap_sae || has_sta_sae) {
1297 crypto_modes.push(['sae', 'WPA3-SAE', 31]);
1298 crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1299 }
1300
1301 if (has_ap_wep || has_sta_wep) {
1302 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1303 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1304 }
1305
1306 if (has_ap_eap || has_sta_eap) {
1307 if (has_ap_eap192 || has_sta_eap192) {
1308 crypto_modes.push(['wpa3', 'WPA3-EAP', 33]);
1309 crypto_modes.push(['wpa3-mixed', 'WPA2-EAP/WPA3-EAP Mixed Mode', 32]);
1310 crypto_modes.push(['wpa3-192', 'WPA3-EAP 192-bit Mode', 36]);
1311 }
1312
1313 crypto_modes.push(['wpa2', 'WPA2-EAP', 34]);
1314 crypto_modes.push(['wpa', 'WPA-EAP', 20]);
1315 }
1316
1317 if (has_ap_owe || has_sta_owe) {
1318 crypto_modes.push(['owe', 'OWE', 1]);
1319 }
1320
1321 encr.crypto_support = {
1322 'ap': {
1323 'wep-open': has_ap_wep || _('Requires hostapd with WEP support'),
1324 'wep-shared': has_ap_wep || _('Requires hostapd with WEP support'),
1325 'psk': has_hostapd || _('Requires hostapd'),
1326 'psk2': has_hostapd || _('Requires hostapd'),
1327 'psk-mixed': has_hostapd || _('Requires hostapd'),
1328 'sae': has_ap_sae || _('Requires hostapd with SAE support'),
1329 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
1330 'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
1331 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
1332 'wpa3': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
1333 'wpa3-mixed': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
1334 'wpa3-192': 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 'wpa3-192': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
1350 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
1351 },
1352 'adhoc': {
1353 'wep-open': true,
1354 'wep-shared': true,
1355 'psk': has_supplicant || _('Requires wpa-supplicant'),
1356 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1357 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1358 },
1359 'mesh': {
1360 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support')
1361 },
1362 'ahdemo': {
1363 'wep-open': true,
1364 'wep-shared': true
1365 },
1366 'wds': {
1367 'wep-open': true,
1368 'wep-shared': true
1369 }
1370 };
1371
1372 encr.crypto_support['ap-wds'] = encr.crypto_support['ap'];
1373 encr.crypto_support['sta-wds'] = encr.crypto_support['sta'];
1374
1375 encr.validate = function(section_id, value) {
1376 var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
1377 modeval = modeopt.formvalue(section_id),
1378 modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)],
1379 enctitle = this.vallist[this.keylist.indexOf(value)];
1380
1381 if (value == 'none')
1382 return true;
1383
1384 if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value))
1385 return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle);
1386
1387 return this.crypto_support[modeval][value];
1388 };
1389 }
1390 else if (hwtype == 'broadcom') {
1391 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1392 crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1393 crypto_modes.push(['psk', 'WPA-PSK', 12]);
1394 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1395 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1396 }
1397
1398 crypto_modes.push(['none', _('No Encryption'), 0]);
1399
1400 crypto_modes.sort(function(a, b) { return b[2] - a[2] });
1401
1402 for (var i = 0; i < crypto_modes.length; i++) {
1403 var security_level = (crypto_modes[i][2] >= 30) ? _('strong security')
1404 : (crypto_modes[i][2] >= 20) ? _('medium security')
1405 : (crypto_modes[i][2] >= 10) ? _('weak security') : _('open network');
1406
1407 encr.value(crypto_modes[i][0], '%s (%s)'.format(crypto_modes[i][1], security_level));
1408 }
1409
1410
1411 o = ss.taboption('encryption', form.Value, 'auth_server', _('RADIUS Authentication Server'));
1412 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1413 o.rmempty = true;
1414 o.datatype = 'host(0)';
1415
1416 o = ss.taboption('encryption', form.Value, 'auth_port', _('RADIUS Authentication Port'));
1417 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1418 o.rmempty = true;
1419 o.datatype = 'port';
1420 o.placeholder = '1812';
1421
1422 o = ss.taboption('encryption', form.Value, 'auth_secret', _('RADIUS Authentication Secret'));
1423 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1424 o.rmempty = true;
1425 o.password = true;
1426
1427 o = ss.taboption('encryption', form.Value, 'acct_server', _('RADIUS Accounting Server'));
1428 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1429 o.rmempty = true;
1430 o.datatype = 'host(0)';
1431
1432 o = ss.taboption('encryption', form.Value, 'acct_port', _('RADIUS Accounting Port'));
1433 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1434 o.rmempty = true;
1435 o.datatype = 'port';
1436 o.placeholder = '1813';
1437
1438 o = ss.taboption('encryption', form.Value, 'acct_secret', _('RADIUS Accounting Secret'));
1439 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1440 o.rmempty = true;
1441 o.password = true;
1442
1443 /* extra RADIUS settings start */
1444 var attr_validate = function(section_id, value) {
1445 if (!value)
1446 return true;
1447
1448 if (!/^[0-9]+(:s:.+|:d:[0-9]+|:x:([0-9a-zA-Z]{2})+)?$/.test(value) )
1449 return _('Must be in %s format.').format('<attr_id>[:<syntax:value>]');
1450
1451 return true;
1452 };
1453
1454 var req_attr_syntax = _('Format:') + '<code>&lt;attr_id&gt;[:&lt;syntax:value&gt;]</code>' + '<br />' +
1455 '<code>syntax: s = %s; '.format(_('string (UTF-8)')) + 'd = %s; '.format(_('integer')) + 'x = %s</code>'.format(_('octet string'))
1456
1457 /* https://w1.fi/cgit/hostap/commit/?id=af35e7af7f8bb1ca9f0905b4074fb56a264aa12b */
1458 o = ss.taboption('encryption', form.DynamicList, 'radius_auth_req_attr', _('RADIUS Access-Request attributes'),
1459 _('Attributes to add/replace in each request.') + '<br />' + req_attr_syntax );
1460 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1461 o.rmempty = true;
1462 o.validate = attr_validate;
1463 o.placeholder = '126:s:Operator';
1464
1465 o = ss.taboption('encryption', form.DynamicList, 'radius_acct_req_attr', _('RADIUS Accounting-Request attributes'),
1466 _('Attributes to add/replace in each request.') + '<br />' + req_attr_syntax );
1467 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1468 o.rmempty = true;
1469 o.validate = attr_validate;
1470 o.placeholder = '77:x:74657374696e67';
1471
1472 o = ss.taboption('encryption', form.ListValue, 'dynamic_vlan', _('RADIUS Dynamic VLAN Assignment'), _('Required: Rejects auth if RADIUS server does not provide appropriate VLAN attributes.'));
1473 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1474 o.value('0', _('Disabled'));
1475 o.value('1', _('Optional'));
1476 o.value('2', _('Required'));
1477 o.write = function (section_id, value) {
1478 return this.super('write', [section_id, (value == 0) ? null: value]);
1479 }
1480
1481 o = ss.taboption('encryption', form.Flag, 'per_sta_vif', _('RADIUS Per STA VLAN'), _('Each STA is assigned its own AP_VLAN interface.'));
1482 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1483
1484 //hostapd internally defaults to vlan_naming=1 even with dynamic VLAN off
1485 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>.'));
1486 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1487
1488 o = ss.taboption('encryption', widgets.DeviceSelect, 'vlan_tagged_interface', _('RADIUS VLAN Tagged Interface'), _('E.g. eth0, eth1'));
1489 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1490 o.size = 1;
1491 o.rmempty = true;
1492 o.multiple = false;
1493 o.noaliases = true;
1494 o.nocreate = true;
1495 o.noinactive = true;
1496
1497 o = ss.taboption('encryption', form.Value, 'vlan_bridge', _('RADIUS VLAN Bridge Naming Scheme'), _('E.g. <code>br-vlan</code> or <code>brvlan</code>.'));
1498 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1499 o.rmempty = true;
1500
1501 /* extra RADIUS settings end */
1502
1503 o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client'), _('Dynamic Authorization Extension client.'));
1504 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1505 o.rmempty = true;
1506 o.datatype = 'host(0)';
1507
1508 o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Dynamic Authorization Extension port.'));
1509 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1510 o.rmempty = true;
1511 o.datatype = 'port';
1512 o.placeholder = '3799';
1513
1514 o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret'), _('Dynamic Authorization Extension secret.'));
1515 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1516 o.rmempty = true;
1517 o.password = true;
1518
1519 //WPA(1) has only WPA IE. Only >= WPA2 has RSN IE Preauth frames.
1520 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.'));
1521 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa2', 'wpa3', 'wpa3-mixed'] });
1522
1523
1524 o = ss.taboption('encryption', form.Value, '_wpa_key', _('Key'));
1525 o.depends('encryption', 'psk');
1526 o.depends('encryption', 'psk2');
1527 o.depends('encryption', 'psk+psk2');
1528 o.depends('encryption', 'psk-mixed');
1529 o.depends('encryption', 'sae');
1530 o.depends('encryption', 'sae-mixed');
1531 o.datatype = 'wpakey';
1532 o.rmempty = true;
1533 o.password = true;
1534
1535 o.cfgvalue = function(section_id) {
1536 var key = uci.get('wireless', section_id, 'key');
1537 return /^[1234]$/.test(key) ? null : key;
1538 };
1539
1540 o.write = function(section_id, value) {
1541 uci.set('wireless', section_id, 'key', value);
1542 uci.unset('wireless', section_id, 'key1');
1543 uci.unset('wireless', section_id, 'key2');
1544 uci.unset('wireless', section_id, 'key3');
1545 uci.unset('wireless', section_id, 'key4');
1546 };
1547
1548
1549 o = ss.taboption('encryption', form.ListValue, '_wep_key', _('Used Key Slot'));
1550 o.depends('encryption', 'wep-open');
1551 o.depends('encryption', 'wep-shared');
1552 o.value('1', _('Key #%d').format(1));
1553 o.value('2', _('Key #%d').format(2));
1554 o.value('3', _('Key #%d').format(3));
1555 o.value('4', _('Key #%d').format(4));
1556
1557 o.cfgvalue = function(section_id) {
1558 var slot = +uci.get('wireless', section_id, 'key');
1559 return (slot >= 1 && slot <= 4) ? String(slot) : '';
1560 };
1561
1562 o.write = function(section_id, value) {
1563 uci.set('wireless', section_id, 'key', value);
1564 };
1565
1566 for (var slot = 1; slot <= 4; slot++) {
1567 o = ss.taboption('encryption', form.Value, 'key%d'.format(slot), _('Key #%d').format(slot));
1568 o.depends('encryption', 'wep-open');
1569 o.depends('encryption', 'wep-shared');
1570 o.datatype = 'wepkey';
1571 o.rmempty = true;
1572 o.password = true;
1573
1574 o.write = function(section_id, value) {
1575 if (value != null && (value.length == 5 || value.length == 13))
1576 value = 's:%s'.format(value);
1577 uci.set('wireless', section_id, this.option, value);
1578 };
1579 }
1580
1581
1582 if (hwtype == 'mac80211') {
1583 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1584 var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap');
1585
1586 o = ss.taboption('roaming', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1587 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa2', 'wpa3', 'wpa3-mixed', , 'wpa3-192'] });
1588 if (has_80211r)
1589 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1590 o.rmempty = true;
1591
1592 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.'));
1593 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1594 o.depends({ ieee80211r: '1' });
1595 o.rmempty = true;
1596
1597 o = ss.taboption('roaming', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1598 o.depends({ ieee80211r: '1' });
1599 o.placeholder = '4f57';
1600 o.datatype = 'and(hexstring,length(4))';
1601 o.rmempty = true;
1602
1603 o = ss.taboption('roaming', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1604 o.depends({ ieee80211r: '1' });
1605 o.placeholder = '1000';
1606 o.datatype = 'range(1000,65535)';
1607 o.rmempty = true;
1608
1609 o = ss.taboption('roaming', form.ListValue, 'ft_over_ds', _('FT protocol'));
1610 o.depends({ ieee80211r: '1' });
1611 o.value('0', _('FT over the Air'));
1612 o.value('1', _('FT over DS'));
1613 o.rmempty = true;
1614
1615 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.'));
1616 add_dependency_permutations(o, { ieee80211r: ['1'], mode: ['ap', 'ap-wds'], encryption: ['psk2', 'psk-mixed'] });
1617 o.default = o.enabled;
1618 o.rmempty = false;
1619
1620 o = ss.taboption('roaming', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1621 o.depends({ ieee80211r: '1' });
1622 o.placeholder = '10000';
1623 o.datatype = 'uinteger';
1624 o.rmempty = true;
1625
1626 o = ss.taboption('roaming', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1627 o.depends({ ieee80211r: '1' });
1628 o.placeholder = '00004f577274';
1629 o.datatype = 'and(hexstring,length(12))';
1630 o.rmempty = true;
1631
1632 o = ss.taboption('roaming', form.Flag, 'pmk_r1_push', _('PMK R1 Push'));
1633 o.depends({ ieee80211r: '1' });
1634 o.placeholder = '0';
1635 o.rmempty = true;
1636
1637 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,256-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.'));
1638 o.depends({ ieee80211r: '1' });
1639 o.rmempty = true;
1640
1641 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,256-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.'));
1642 o.depends({ ieee80211r: '1' });
1643 o.rmempty = true;
1644 // End of 802.11r options
1645
1646 // Probe 802.11k and 802.11v support via EAP support (full hostapd has EAP)
1647 if (L.hasSystemFeature('hostapd', 'eap')) {
1648 /* 802.11k settings start */
1649 o = ss.taboption('roaming', form.Flag, 'ieee80211k', _('802.11k RRM'), _('Radio Resource Measurement - Sends beacons to assist roaming. Not all clients support this.'));
1650 // add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1651 o.depends('mode', 'ap');
1652 o.depends('mode', 'ap-wds');
1653
1654 o = ss.taboption('roaming', form.Flag, 'rrm_neighbor_report', _('Neighbour Report'), _('802.11k: Enable neighbor report via radio measurements.'));
1655 o.depends({ ieee80211k: '1' });
1656 o.default = o.enabled;
1657
1658 o = ss.taboption('roaming', form.Flag, 'rrm_beacon_report', _('Beacon Report'), _('802.11k: Enable beacon report via radio measurements.'));
1659 o.depends({ ieee80211k: '1' });
1660 o.default = o.enabled;
1661 /* 802.11k settings end */
1662
1663 /* 802.11v settings start */
1664 o = ss.taboption('roaming', form.ListValue, 'time_advertisement', _('Time advertisement'), _('802.11v: Time Advertisement in management frames.'));
1665 o.value('0', _('Disabled'));
1666 o.value('2', _('Enabled'));
1667 o.write = function (section_id, value) {
1668 return this.super('write', [section_id, (value == 2) ? value: null]);
1669 }
1670
1671 //Pull current System TZ setting
1672 var tz = uci.get('system', '@system[0]', 'timezone');
1673 o = ss.taboption('roaming', form.Value, 'time_zone', _('Time zone'), _('802.11v: Local Time Zone Advertisement in management frames.'));
1674 o.value(tz);
1675 o.rmempty = true;
1676
1677 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).'));
1678 o.rmempty = true;
1679
1680 /* wnm_sleep_mode_no_keys: https://git.openwrt.org/?p=openwrt/openwrt.git;a=commitdiff;h=bf98faaac8ed24cf7d3d93dd4fcd7304d109363b */
1681 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.'));
1682 o.rmempty = true;
1683
1684 o = ss.taboption('roaming', form.Flag, 'bss_transition', _('BSS Transition'), _('802.11v: Basic Service Set (BSS) transition management.'));
1685 o.rmempty = true;
1686
1687 /* in master, but not 21.02.1: proxy_arp */
1688 o = ss.taboption('roaming', form.Flag, 'proxy_arp', _('ProxyARP'), _('802.11v: Proxy ARP enables non-AP STA to remain in power-save for longer.'));
1689 o.rmempty = true;
1690
1691 /* TODO: na_mcast_to_ucast is missing: needs adding to hostapd.sh - nice to have */
1692 }
1693 /* 802.11v settings end */
1694 }
1695
1696 if (hwtype == 'mac80211') {
1697 o = ss.taboption('encryption', form.ListValue, 'eap_type', _('EAP-Method'));
1698 o.value('tls', 'TLS');
1699 o.value('ttls', 'TTLS');
1700 o.value('peap', 'PEAP');
1701 o.value('fast', 'FAST');
1702 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1703
1704 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"));
1705 o.enabled = '1';
1706 o.disabled = '0';
1707 o.default = o.disabled;
1708 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1709 o.validate = function(section_id, value) {
1710 if (value == '1' && !L.hasSystemFeature('cabundle')) {
1711 return _("This option cannot be used because the ca-bundle package is not installed.");
1712 }
1713 return true;
1714 };
1715
1716 o = ss.taboption('encryption', form.FileUpload, 'ca_cert', _('Path to CA-Certificate'));
1717 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], ca_cert_usesystem: ['0'] });
1718
1719 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"));
1720 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1721
1722 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"));
1723 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1724
1725 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)"));
1726 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1727
1728 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)"));
1729 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1730
1731 o = ss.taboption('encryption', form.FileUpload, 'client_cert', _('Path to Client-Certificate'));
1732 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], eap_type: ['tls'] });
1733
1734 o = ss.taboption('encryption', form.FileUpload, 'priv_key', _('Path to Private Key'));
1735 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], eap_type: ['tls'] });
1736
1737 o = ss.taboption('encryption', form.Value, 'priv_key_pwd', _('Password of Private Key'));
1738 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], eap_type: ['tls'] });
1739 o.password = true;
1740
1741 o = ss.taboption('encryption', form.ListValue, 'auth', _('Authentication'));
1742 o.value('PAP', 'PAP');
1743 o.value('CHAP', 'CHAP');
1744 o.value('MSCHAP', 'MSCHAP');
1745 o.value('MSCHAPV2', 'MSCHAPv2');
1746 o.value('EAP-GTC', 'EAP-GTC');
1747 o.value('EAP-MD5', 'EAP-MD5');
1748 o.value('EAP-MSCHAPV2', 'EAP-MSCHAPv2');
1749 o.value('EAP-TLS', 'EAP-TLS');
1750 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], eap_type: ['fast', 'peap', 'ttls'] });
1751
1752 o.validate = function(section_id, value) {
1753 var eo = this.section.children.filter(function(o) { return o.option == 'eap_type' })[0],
1754 ev = eo.formvalue(section_id);
1755
1756 if (ev != 'ttls' && (value == 'PAP' || value == 'CHAP' || value == 'MSCHAP' || value == 'MSCHAPV2'))
1757 return _('This authentication type is not applicable to the selected EAP method.');
1758
1759 return true;
1760 };
1761
1762 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"));
1763 o.enabled = '1';
1764 o.disabled = '0';
1765 o.default = o.disabled;
1766 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth: ['EAP-TLS'] });
1767 o.validate = function(section_id, value) {
1768 if (value == '1' && !L.hasSystemFeature('cabundle')) {
1769 return _("This option cannot be used because the ca-bundle package is not installed.");
1770 }
1771 return true;
1772 };
1773
1774 o = ss.taboption('encryption', form.FileUpload, 'ca_cert2', _('Path to inner CA-Certificate'));
1775 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth: ['EAP-TLS'], ca_cert2_usesystem: ['0'] });
1776
1777 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"));
1778 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth: ['EAP-TLS'] });
1779
1780 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"));
1781 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth: ['EAP-TLS'] });
1782
1783 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)"));
1784 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth: ['EAP-TLS'] });
1785
1786 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)"));
1787 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth: ['EAP-TLS'] });
1788
1789 o = ss.taboption('encryption', form.FileUpload, 'client_cert2', _('Path to inner Client-Certificate'));
1790 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth: ['EAP-TLS'] });
1791
1792 o = ss.taboption('encryption', form.FileUpload, 'priv_key2', _('Path to inner Private Key'));
1793 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth: ['EAP-TLS'] });
1794
1795 o = ss.taboption('encryption', form.Value, 'priv_key2_pwd', _('Password of inner Private Key'));
1796 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth: ['EAP-TLS'] });
1797 o.password = true;
1798
1799 o = ss.taboption('encryption', form.Value, 'identity', _('Identity'));
1800 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], eap_type: ['fast', 'peap', 'tls', 'ttls'] });
1801
1802 o = ss.taboption('encryption', form.Value, 'anonymous_identity', _('Anonymous Identity'));
1803 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], eap_type: ['fast', 'peap', 'tls', 'ttls'] });
1804
1805 o = ss.taboption('encryption', form.Value, 'password', _('Password'));
1806 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], eap_type: ['fast', 'peap', 'ttls'] });
1807 o.password = true;
1808
1809
1810 if (hwtype == 'mac80211') {
1811 // ieee802.11w options
1812 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"));
1813 o.value('0', _('Disabled'));
1814 o.value('1', _('Optional'));
1815 o.value('2', _('Required'));
1816 add_dependency_permutations(o, { mode: ['ap', 'ap-wds', 'sta', 'sta-wds'], encryption: ['owe', 'psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1817
1818 o.defaults = {
1819 '2': [{ encryption: 'sae' }, { encryption: 'owe' }, { encryption: 'wpa3' }, { encryption: 'wpa3-mixed' }],
1820 '1': [{ encryption: 'sae-mixed'}],
1821 '0': []
1822 };
1823
1824 o.write = function(section_id, value) {
1825 if (value != this.default)
1826 return form.ListValue.prototype.write.call(this, section_id, value);
1827 else
1828 return form.ListValue.prototype.remove.call(this, section_id);
1829 };
1830
1831 o = ss.taboption('encryption', form.Value, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1832 o.depends('ieee80211w', '1');
1833 o.depends('ieee80211w', '2');
1834 o.datatype = 'uinteger';
1835 o.placeholder = '1000';
1836 o.rmempty = true;
1837
1838 o = ss.taboption('encryption', form.Value, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1839 o.depends('ieee80211w', '1');
1840 o.depends('ieee80211w', '2');
1841 o.datatype = 'uinteger';
1842 o.placeholder = '201';
1843 o.rmempty = true;
1844
1845 if (L.hasSystemFeature('hostapd', 'ocv') || L.hasSystemFeature('wpasupplicant', 'ocv')) {
1846 o = ss.taboption('encryption', form.ListValue, 'ocv', _('Operating Channel Validation'), _("Note: Workaround mode allows a STA that claims OCV capability to connect even if the STA doesn't send OCI or negotiate PMF."));
1847 o.value('0', _('Disabled'));
1848 o.value('1', _('Enabled'));
1849 o.value('2', _('Enabled (workaround mode)'));
1850 o.default = '0';
1851 o.depends('ieee80211w', '1');
1852 o.depends('ieee80211w', '2');
1853
1854 o.validate = function(section_id, value) {
1855 var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
1856 modeval = modeopt.formvalue(section_id);
1857
1858 if ((value == '2') && ((modeval == 'sta') || (modeval == 'sta-wds'))) {
1859 return _('Workaround mode can only be used when acting as an access point.');
1860 }
1861
1862 return true;
1863 }
1864 }
1865
1866 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.'));
1867 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1868
1869 if (L.hasSystemFeature('hostapd', 'wps') && L.hasSystemFeature('wpasupplicant')) {
1870 o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1871 o.enabled = '1';
1872 o.disabled = '0';
1873 o.default = o.disabled;
1874 o.depends('encryption', 'psk');
1875 o.depends('encryption', 'psk2');
1876 o.depends('encryption', 'psk-mixed');
1877 o.depends('encryption', 'sae');
1878 o.depends('encryption', 'sae-mixed');
1879 }
1880 }
1881 }
1882 });
1883 };
1884
1885 s.handleRemove = function(section_id, ev) {
1886 document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id)).style.opacity = 0.5;
1887 return form.TypedSection.prototype.handleRemove.apply(this, [section_id, ev]);
1888 };
1889
1890 s.handleScan = function(radioDev, ev) {
1891 var table = E('table', { 'class': 'table' }, [
1892 E('tr', { 'class': 'tr table-titles' }, [
1893 E('th', { 'class': 'th col-2 middle center' }, _('Signal')),
1894 E('th', { 'class': 'th col-4 middle left' }, _('SSID')),
1895 E('th', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1896 E('th', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1897 E('th', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1898 E('th', { 'class': 'th col-3 middle left' }, _('Encryption')),
1899 E('th', { 'class': 'th cbi-section-actions right' }, ' '),
1900 ])
1901 ]);
1902
1903 var stop = E('button', {
1904 'class': 'btn',
1905 'click': L.bind(this.handleScanStartStop, this),
1906 'style': 'display:none',
1907 'data-state': 'stop'
1908 }, _('Stop refresh'));
1909
1910 cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1911
1912 var md = ui.showModal(_('Join Network: Wireless Scan'), [
1913 table,
1914 E('div', { 'class': 'right' }, [
1915 stop,
1916 ' ',
1917 E('button', {
1918 'class': 'btn',
1919 'click': L.bind(this.handleScanAbort, this)
1920 }, _('Dismiss'))
1921 ])
1922 ]);
1923
1924 md.style.maxWidth = '90%';
1925 md.style.maxHeight = 'none';
1926
1927 this.pollFn = L.bind(this.handleScanRefresh, this, radioDev, {}, table, stop);
1928
1929 poll.add(this.pollFn);
1930 poll.start();
1931 };
1932
1933 s.handleScanRefresh = function(radioDev, scanCache, table, stop) {
1934 return radioDev.getScanList().then(L.bind(function(results) {
1935 var rows = [];
1936
1937 for (var i = 0; i < results.length; i++)
1938 scanCache[results[i].bssid] = results[i];
1939
1940 for (var k in scanCache)
1941 if (scanCache[k].stale)
1942 results.push(scanCache[k]);
1943
1944 results.sort(function(a, b) {
1945 var diff = (b.quality - a.quality) || (a.channel - b.channel);
1946
1947 if (diff)
1948 return diff;
1949
1950 if (a.ssid < b.ssid)
1951 return -1;
1952 else if (a.ssid > b.ssid)
1953 return 1;
1954
1955 if (a.bssid < b.bssid)
1956 return -1;
1957 else if (a.bssid > b.bssid)
1958 return 1;
1959 });
1960
1961 for (var i = 0; i < results.length; i++) {
1962 var res = results[i],
1963 qv = res.quality || 0,
1964 qm = res.quality_max || 0,
1965 q = (qv > 0 && qm > 0) ? Math.floor((100 / qm) * qv) : 0,
1966 s = res.stale ? 'opacity:0.5' : '';
1967
1968 rows.push([
1969 E('span', { 'style': s }, render_signal_badge(q, res.signal, res.noise)),
1970 E('span', { 'style': s }, (res.ssid != null) ? '%h'.format(res.ssid) : E('em', _('hidden'))),
1971 E('span', { 'style': s }, '%d'.format(res.channel)),
1972 E('span', { 'style': s }, '%h'.format(res.mode)),
1973 E('span', { 'style': s }, '%h'.format(res.bssid)),
1974 E('span', { 'style': s }, '%h'.format(network.formatWifiEncryption(res.encryption))),
1975 E('div', { 'class': 'right' }, E('button', {
1976 'class': 'cbi-button cbi-button-action important',
1977 'click': ui.createHandlerFn(this, 'handleJoin', radioDev, res)
1978 }, _('Join Network')))
1979 ]);
1980
1981 res.stale = true;
1982 }
1983
1984 cbi_update_table(table, rows);
1985
1986 stop.disabled = false;
1987 stop.style.display = '';
1988 stop.classList.remove('spinning');
1989 }, this));
1990 };
1991
1992 s.handleScanStartStop = function(ev) {
1993 var btn = ev.currentTarget;
1994
1995 if (btn.getAttribute('data-state') == 'stop') {
1996 poll.remove(this.pollFn);
1997 btn.firstChild.data = _('Start refresh');
1998 btn.setAttribute('data-state', 'start');
1999 }
2000 else {
2001 poll.add(this.pollFn);
2002 btn.firstChild.data = _('Stop refresh');
2003 btn.setAttribute('data-state', 'stop');
2004 btn.classList.add('spinning');
2005 btn.disabled = true;
2006 }
2007 };
2008
2009 s.handleScanAbort = function(ev) {
2010 var md = dom.parent(ev.target, 'div[aria-modal="true"]');
2011 if (md) {
2012 md.style.maxWidth = '';
2013 md.style.maxHeight = '';
2014 }
2015
2016 ui.hideModal();
2017 poll.remove(this.pollFn);
2018
2019 this.pollFn = null;
2020 };
2021
2022 s.handleJoinConfirm = function(radioDev, bss, form, ev) {
2023 var nameopt = L.toArray(form.lookupOption('name', '_new_'))[0],
2024 passopt = L.toArray(form.lookupOption('password', '_new_'))[0],
2025 ssidopt = L.toArray(form.lookupOption('ssid', '_new_'))[0],
2026 bssidopt = L.toArray(form.lookupOption('bssid', '_new_'))[0],
2027 zoneopt = L.toArray(form.lookupOption('zone', '_new_'))[0],
2028 replopt = L.toArray(form.lookupOption('replace', '_new_'))[0],
2029 nameval = (nameopt && nameopt.isValid('_new_')) ? nameopt.formvalue('_new_') : null,
2030 passval = (passopt && passopt.isValid('_new_')) ? passopt.formvalue('_new_') : null,
2031 ssidval = (ssidopt && ssidopt.isValid('_new_')) ? ssidopt.formvalue('_new_') : null,
2032 bssidval = (bssidopt && bssidopt.isValid('_new_')) ? bssidopt.formvalue('_new_') : null,
2033 zoneval = zoneopt ? zoneopt.formvalue('_new_') : null,
2034 enc = L.isObject(bss.encryption) ? bss.encryption : null,
2035 is_wep = (enc && Array.isArray(enc.wep)),
2036 is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' }).length > 0),
2037 is_sae = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'sae' }).length > 0);
2038
2039 if (nameval == null || (passopt && passval == null))
2040 return;
2041
2042 var section_id = null;
2043
2044 return this.map.save(function() {
2045 var wifi_sections = uci.sections('wireless', 'wifi-iface');
2046
2047 if (replopt.formvalue('_new_') == '1') {
2048 for (var i = 0; i < wifi_sections.length; i++)
2049 if (wifi_sections[i].device == radioDev.getName())
2050 uci.remove('wireless', wifi_sections[i]['.name']);
2051 }
2052
2053 if (uci.get('wireless', radioDev.getName(), 'disabled') == '1') {
2054 for (var i = 0; i < wifi_sections.length; i++)
2055 if (wifi_sections[i].device == radioDev.getName())
2056 uci.set('wireless', wifi_sections[i]['.name'], 'disabled', '1');
2057
2058 uci.unset('wireless', radioDev.getName(), 'disabled');
2059 }
2060
2061 var htmodes = radioDev.getHTModes();
2062
2063 if (bss.vht_operation && htmodes && htmodes.indexOf('VHT20') !== -1) {
2064 for (var w = bss.vht_operation.channel_width; w >= 20; w /= 2) {
2065 if (htmodes.indexOf('VHT'+w) !== -1) {
2066 uci.set('wireless', radioDev.getName(), 'htmode', 'VHT'+w);
2067 break;
2068 }
2069 }
2070 }
2071 else if (bss.ht_operation && htmodes && htmodes.indexOf('HT20') !== -1) {
2072 var w = (bss.ht_operation.secondary_channel_offset == 'no secondary') ? 20 : 40;
2073 uci.set('wireless', radioDev.getName(), 'htmode', 'HT'+w);
2074 }
2075 else {
2076 uci.remove('wireless', radioDev.getName(), 'htmode');
2077 }
2078
2079 uci.set('wireless', radioDev.getName(), 'channel', bss.channel);
2080
2081 section_id = next_free_sid(wifi_sections.length);
2082
2083 uci.add('wireless', 'wifi-iface', section_id);
2084 uci.set('wireless', section_id, 'device', radioDev.getName());
2085 uci.set('wireless', section_id, 'mode', (bss.mode == 'Ad-Hoc') ? 'adhoc' : 'sta');
2086 uci.set('wireless', section_id, 'network', nameval);
2087
2088 if (bss.ssid != null) {
2089 uci.set('wireless', section_id, 'ssid', bss.ssid);
2090
2091 if (bssidval == '1')
2092 uci.set('wireless', section_id, 'bssid', bss.bssid);
2093 }
2094 else if (bss.bssid != null) {
2095 uci.set('wireless', section_id, 'bssid', bss.bssid);
2096 }
2097
2098 if (ssidval != null)
2099 uci.set('wireless', section_id, 'ssid', ssidval);
2100
2101 if (is_sae) {
2102 uci.set('wireless', section_id, 'encryption', 'sae');
2103 uci.set('wireless', section_id, 'key', passval);
2104 }
2105 else if (is_psk) {
2106 for (var i = enc.wpa.length - 1; i >= 0; i--) {
2107 if (enc.wpa[i] == 2) {
2108 uci.set('wireless', section_id, 'encryption', 'psk2');
2109 break;
2110 }
2111 else if (enc.wpa[i] == 1) {
2112 uci.set('wireless', section_id, 'encryption', 'psk');
2113 break;
2114 }
2115 }
2116
2117 uci.set('wireless', section_id, 'key', passval);
2118 }
2119 else if (is_wep) {
2120 uci.set('wireless', section_id, 'encryption', 'wep-open');
2121 uci.set('wireless', section_id, 'key', '1');
2122 uci.set('wireless', section_id, 'key1', passval);
2123 }
2124 else {
2125 uci.set('wireless', section_id, 'encryption', 'none');
2126 }
2127
2128 return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) {
2129 firewall.deleteNetwork(net.getName());
2130
2131 var zonePromise = zoneval
2132 ? firewall.getZone(zoneval).then(function(zone) { return zone || firewall.addZone(zoneval) })
2133 : Promise.resolve();
2134
2135 return zonePromise.then(function(zone) {
2136 if (zone)
2137 zone.addNetwork(net.getName());
2138 });
2139 });
2140 }).then(L.bind(function() {
2141 ui.showModal(null, E('p', { 'class': 'spinning' }, [ _('Loading data…') ]));
2142
2143 return this.renderMoreOptionsModal(section_id);
2144 }, this));
2145 };
2146
2147 s.handleJoin = function(radioDev, bss, ev) {
2148 poll.remove(this.pollFn);
2149
2150 var m2 = new form.Map('wireless'),
2151 s2 = m2.section(form.NamedSection, '_new_'),
2152 enc = L.isObject(bss.encryption) ? bss.encryption : null,
2153 is_wep = (enc && Array.isArray(enc.wep)),
2154 is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' || a == 'sae' })),
2155 replace, passphrase, name, bssid, zone;
2156
2157 var nameUsed = function(name) {
2158 var s = uci.get('network', name);
2159 if (s != null && s['.type'] != 'interface')
2160 return true;
2161
2162 var net = (s != null) ? network.instantiateNetwork(name) : null;
2163 return (net != null && !net.isEmpty());
2164 };
2165
2166 s2.render = function() {
2167 return Promise.all([
2168 {},
2169 this.renderUCISection('_new_')
2170 ]).then(this.renderContents.bind(this));
2171 };
2172
2173 if (bss.ssid == null) {
2174 name = s2.option(form.Value, 'ssid', _('Network SSID'), _('The correct SSID must be manually specified when joining a hidden wireless network'));
2175 name.rmempty = false;
2176 };
2177
2178 replace = s2.option(form.Flag, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
2179
2180 name = s2.option(form.Value, 'name', _('Name of the new network'),
2181 _('Name for OpenWrt network configuration. (No relation to wireless network name/SSID)') + '<br />' +
2182 _('The allowed characters are: <code>A-Z</code>, <code>a-z</code>, <code>0-9</code> and <code>_</code>'));
2183 name.datatype = 'uciname';
2184 name.default = 'wwan';
2185 name.rmempty = false;
2186 name.validate = function(section_id, value) {
2187 if (nameUsed(value))
2188 return _('The network name is already used');
2189
2190 return true;
2191 };
2192
2193 for (var i = 2; nameUsed(name.default); i++)
2194 name.default = 'wwan%d'.format(i);
2195
2196 if (is_wep || is_psk) {
2197 passphrase = s2.option(form.Value, 'password', is_wep ? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
2198 passphrase.datatype = is_wep ? 'wepkey' : 'wpakey';
2199 passphrase.password = true;
2200 passphrase.rmempty = false;
2201 }
2202
2203 if (bss.ssid != null) {
2204 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));
2205 bssid.default = '0';
2206 }
2207
2208 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.'));
2209 zone.default = 'wan';
2210
2211 return m2.render().then(L.bind(function(nodes) {
2212 ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [
2213 nodes,
2214 E('div', { 'class': 'right' }, [
2215 E('button', {
2216 'class': 'btn',
2217 'click': ui.hideModal
2218 }, _('Cancel')), ' ',
2219 E('button', {
2220 'class': 'cbi-button cbi-button-positive important',
2221 'click': ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2)
2222 }, _('Submit'))
2223 ])
2224 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase || name).cbid('_new_'))).focus();
2225 }, this));
2226 };
2227
2228 s.handleAdd = function(radioDev, ev) {
2229 var section_id = next_free_sid(uci.sections('wireless', 'wifi-iface').length);
2230
2231 uci.unset('wireless', radioDev.getName(), 'disabled');
2232
2233 uci.add('wireless', 'wifi-iface', section_id);
2234 uci.set('wireless', section_id, 'device', radioDev.getName());
2235 uci.set('wireless', section_id, 'mode', 'ap');
2236 uci.set('wireless', section_id, 'ssid', 'OpenWrt');
2237 uci.set('wireless', section_id, 'encryption', 'none');
2238
2239 this.addedSection = section_id;
2240 return this.renderMoreOptionsModal(section_id);
2241 };
2242
2243 o = s.option(form.DummyValue, '_badge');
2244 o.modalonly = false;
2245 o.textvalue = function(section_id) {
2246 var inst = this.section.lookupRadioOrNetwork(section_id),
2247 node = E('div', { 'class': 'center' });
2248
2249 if (inst.getWifiNetworks)
2250 node.appendChild(render_radio_badge(inst));
2251 else
2252 node.appendChild(render_network_badge(inst));
2253
2254 return node;
2255 };
2256
2257 o = s.option(form.DummyValue, '_stat');
2258 o.modalonly = false;
2259 o.textvalue = function(section_id) {
2260 var inst = this.section.lookupRadioOrNetwork(section_id);
2261
2262 if (inst.getWifiNetworks)
2263 return render_radio_status(inst, this.section.wifis.filter(function(e) {
2264 return (e.getWifiDeviceName() == inst.getName());
2265 }));
2266 else
2267 return render_network_status(inst);
2268 };
2269
2270 return m.render().then(L.bind(function(m, nodes) {
2271 poll.add(L.bind(function() {
2272 var section_ids = m.children[0].cfgsections(),
2273 tasks = [ network.getHostHints(), network.getWifiDevices() ];
2274
2275 for (var i = 0; i < section_ids.length; i++) {
2276 var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])),
2277 dsc = row.querySelector('[data-name="_stat"] > div'),
2278 btns = row.querySelectorAll('.cbi-section-actions button');
2279
2280 if (dsc.getAttribute('restart') == '') {
2281 dsc.setAttribute('restart', '1');
2282 tasks.push(fs.exec('/sbin/wifi', ['up', section_ids[i]]).catch(function(e) {
2283 ui.addNotification(null, E('p', e.message));
2284 }));
2285 }
2286 else if (dsc.getAttribute('restart') == '1') {
2287 dsc.removeAttribute('restart');
2288 btns[0].classList.remove('spinning');
2289 btns[0].disabled = false;
2290 }
2291 }
2292
2293 return Promise.all(tasks)
2294 .then(L.bind(function(hosts_radios) {
2295 var tasks = [];
2296
2297 for (var i = 0; i < hosts_radios[1].length; i++)
2298 tasks.push(hosts_radios[1][i].getWifiNetworks());
2299
2300 return Promise.all(tasks).then(function(data) {
2301 hosts_radios[2] = [];
2302
2303 for (var i = 0; i < data.length; i++)
2304 hosts_radios[2].push.apply(hosts_radios[2], data[i]);
2305
2306 return hosts_radios;
2307 });
2308 }, network))
2309 .then(L.bind(function(hosts_radios_wifis) {
2310 var tasks = [];
2311
2312 for (var i = 0; i < hosts_radios_wifis[2].length; i++)
2313 tasks.push(hosts_radios_wifis[2][i].getAssocList());
2314
2315 return Promise.all(tasks).then(function(data) {
2316 hosts_radios_wifis[3] = [];
2317
2318 for (var i = 0; i < data.length; i++) {
2319 var wifiNetwork = hosts_radios_wifis[2][i],
2320 radioDev = hosts_radios_wifis[1].filter(function(d) { return d.getName() == wifiNetwork.getWifiDeviceName() })[0];
2321
2322 for (var j = 0; j < data[i].length; j++)
2323 hosts_radios_wifis[3].push(Object.assign({ radio: radioDev, network: wifiNetwork }, data[i][j]));
2324 }
2325
2326 return hosts_radios_wifis;
2327 });
2328 }, network))
2329 .then(L.bind(this.poll_status, this, nodes));
2330 }, this), 5);
2331
2332 var table = E('table', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [
2333 E('tr', { 'class': 'tr table-titles' }, [
2334 E('th', { 'class': 'th nowrap' }, _('Network')),
2335 E('th', { 'class': 'th hide-xs' }, _('MAC address')),
2336 E('th', { 'class': 'th' }, _('Host')),
2337 E('th', { 'class': 'th' }, _('Signal / Noise')),
2338 E('th', { 'class': 'th' }, _('RX Rate / TX Rate'))
2339 ])
2340 ]);
2341
2342 cbi_update_table(table, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
2343
2344 return E([ nodes, E('h3', _('Associated Stations')), table ]);
2345 }, this, m));
2346 },
2347
2348 handleReset: null
2349 });