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