Merge pull request #6138 from ysc3839/natmap
[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 uci.load('system')
747 ]);
748 },
749
750 checkAnonymousSections: function() {
751 var wifiIfaces = uci.sections('wireless', 'wifi-iface');
752
753 for (var i = 0; i < wifiIfaces.length; i++)
754 if (wifiIfaces[i]['.anonymous'])
755 return true;
756
757 return false;
758 },
759
760 callUciRename: rpc.declare({
761 object: 'uci',
762 method: 'rename',
763 params: [ 'config', 'section', 'name' ]
764 }),
765
766 render: function() {
767 if (this.checkAnonymousSections())
768 return this.renderMigration();
769 else
770 return this.renderOverview();
771 },
772
773 handleMigration: function(ev) {
774 var wifiIfaces = uci.sections('wireless', 'wifi-iface'),
775 id_offset = 0,
776 tasks = [];
777
778 for (var i = 0; i < wifiIfaces.length; i++) {
779 if (!wifiIfaces[i]['.anonymous'])
780 continue;
781
782 var new_name = next_free_sid(id_offset);
783
784 tasks.push(this.callUciRename('wireless', wifiIfaces[i]['.name'], new_name));
785 id_offset = +new_name.substring(7) + 1;
786 }
787
788 return Promise.all(tasks)
789 .then(L.bind(ui.changes.init, ui.changes))
790 .then(L.bind(ui.changes.apply, ui.changes));
791 },
792
793 renderMigration: function() {
794 ui.showModal(_('Wireless configuration migration'), [
795 E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')),
796 E('p', _('Upon pressing "Continue", anonymous "wifi-iface" sections will be assigned with a name in the form <em>wifinet#</em> and the network will be restarted to apply the updated configuration.')),
797 E('div', { 'class': 'right' },
798 E('button', {
799 'class': 'btn cbi-button-action important',
800 'click': ui.createHandlerFn(this, 'handleMigration')
801 }, _('Continue')))
802 ]);
803 },
804
805 renderOverview: function() {
806 var m, s, o;
807
808 m = new form.Map('wireless');
809 m.chain('network');
810 m.chain('firewall');
811
812 s = m.section(form.GridSection, 'wifi-device', _('Wireless Overview'));
813 s.anonymous = true;
814 s.addremove = false;
815
816 s.load = function() {
817 return network.getWifiDevices().then(L.bind(function(radios) {
818 this.radios = radios.sort(function(a, b) {
819 return a.getName() > b.getName();
820 });
821
822 var tasks = [];
823
824 for (var i = 0; i < radios.length; i++)
825 tasks.push(radios[i].getWifiNetworks());
826
827 return Promise.all(tasks);
828 }, this)).then(L.bind(function(data) {
829 this.wifis = [];
830
831 for (var i = 0; i < data.length; i++)
832 this.wifis.push.apply(this.wifis, data[i]);
833 }, this));
834 };
835
836 s.cfgsections = function() {
837 var rv = [];
838
839 for (var i = 0; i < this.radios.length; i++) {
840 rv.push(this.radios[i].getName());
841
842 for (var j = 0; j < this.wifis.length; j++)
843 if (this.wifis[j].getWifiDeviceName() == this.radios[i].getName())
844 rv.push(this.wifis[j].getName());
845 }
846
847 return rv;
848 };
849
850 s.modaltitle = function(section_id) {
851 var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id})[0];
852 return radioNet ? radioNet.getI18n() : _('Edit wireless network');
853 };
854
855 s.lookupRadioOrNetwork = function(section_id) {
856 var radioDev = this.radios.filter(function(r) { return r.getName() == section_id })[0];
857 if (radioDev)
858 return radioDev;
859
860 var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id })[0];
861 if (radioNet)
862 return radioNet;
863
864 return null;
865 };
866
867 s.renderRowActions = function(section_id) {
868 var inst = this.lookupRadioOrNetwork(section_id), btns;
869
870 if (inst.getWifiNetworks) {
871 btns = [
872 E('button', {
873 'class': 'cbi-button cbi-button-neutral',
874 'title': _('Restart radio interface'),
875 'click': ui.createHandlerFn(this, radio_restart, section_id)
876 }, _('Restart')),
877 E('button', {
878 'class': 'cbi-button cbi-button-action important',
879 'title': _('Find and join network'),
880 'click': ui.createHandlerFn(this, 'handleScan', inst)
881 }, _('Scan')),
882 E('button', {
883 'class': 'cbi-button cbi-button-add',
884 'title': _('Provide new network'),
885 'click': ui.createHandlerFn(this, 'handleAdd', inst)
886 }, _('Add'))
887 ];
888 }
889 else {
890 var isDisabled = (inst.get('disabled') == '1' ||
891 uci.get('wireless', inst.getWifiDeviceName(), 'disabled') == '1');
892
893 btns = [
894 E('button', {
895 'class': 'cbi-button cbi-button-neutral enable-disable',
896 'title': isDisabled ? _('Enable this network') : _('Disable this network'),
897 'click': ui.createHandlerFn(this, network_updown, section_id, this.map)
898 }, isDisabled ? _('Enable') : _('Disable')),
899 E('button', {
900 'class': 'cbi-button cbi-button-action important',
901 'title': _('Edit this network'),
902 'click': ui.createHandlerFn(this, 'renderMoreOptionsModal', section_id)
903 }, _('Edit')),
904 E('button', {
905 'class': 'cbi-button cbi-button-negative remove',
906 'title': _('Delete this network'),
907 'click': ui.createHandlerFn(this, 'handleRemove', section_id)
908 }, _('Remove'))
909 ];
910 }
911
912 return E('td', { 'class': 'td middle cbi-section-actions' }, E('div', btns));
913 };
914
915 s.addModalOptions = function(s) {
916 return network.getWifiNetwork(s.section).then(function(radioNet) {
917 var hwtype = uci.get('wireless', radioNet.getWifiDeviceName(), 'type');
918 var o, ss;
919
920 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
921 o.modalonly = true;
922
923 ss = o.subsection;
924 ss.tab('general', _('General Setup'));
925 ss.tab('advanced', _('Advanced Settings'));
926
927 var isDisabled = (radioNet.get('disabled') == '1' ||
928 uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == 1);
929
930 o = ss.taboption('general', form.DummyValue, '_wifistat_modal', _('Status'));
931 o.cfgvalue = L.bind(function(radioNet) {
932 return render_modal_status(null, radioNet);
933 }, this, radioNet);
934 o.write = function() {};
935
936 o = ss.taboption('general', form.Button, '_toggle', isDisabled ? _('Wireless network is disabled') : _('Wireless network is enabled'));
937 o.inputstyle = isDisabled ? 'apply' : 'reset';
938 o.inputtitle = isDisabled ? _('Enable') : _('Disable');
939 o.onclick = ui.createHandlerFn(s, network_updown, s.section, s.map);
940
941 o = ss.taboption('general', CBIWifiFrequencyValue, '_freq', '<br />' + _('Operating frequency'));
942 o.ucisection = s.section;
943
944 if (hwtype == 'mac80211') {
945 o = ss.taboption('general', form.Flag, 'legacy_rates', _('Allow legacy 802.11b rates'), _('Legacy or badly behaving devices may require legacy 802.11b rates to interoperate. Airtime efficiency may be significantly reduced where these are used. It is recommended to not allow 802.11b rates where possible.'));
946 o.depends({'_freq': '2g', '!contains': true});
947
948 o = ss.taboption('general', CBIWifiTxPowerValue, 'txpower', _('Maximum transmit power'), _('Specifies the maximum transmit power the wireless radio may use. Depending on regulatory requirements and wireless usage, the actual transmit power may be reduced by the driver.'));
949 o.wifiNetwork = radioNet;
950
951 o = ss.taboption('advanced', CBIWifiCountryValue, 'country', _('Country Code'));
952 o.wifiNetwork = radioNet;
953
954 o = ss.taboption('advanced', form.ListValue, 'cell_density', _('Coverage cell density'), _('Configures data rates based on the coverage cell density. Normal configures basic rates to 6, 12, 24 Mbps if legacy 802.11b rates are not used else to 5.5, 11 Mbps. High configures basic rates to 12, 24 Mbps if legacy 802.11b rates are not used else to the 11 Mbps rate. Very High configures 24 Mbps as the basic rate. Supported rates lower than the minimum basic rate are not offered.'));
955 o.value('0', _('Disabled'));
956 o.value('1', _('Normal'));
957 o.value('2', _('High'));
958 o.value('3', _('Very High'));
959
960 o = ss.taboption('advanced', form.Value, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
961 o.datatype = 'or(range(0,114750),"auto")';
962 o.placeholder = 'auto';
963
964 o = ss.taboption('advanced', form.Value, 'frag', _('Fragmentation Threshold'));
965 o.datatype = 'min(256)';
966 o.placeholder = _('off');
967
968 o = ss.taboption('advanced', form.Value, 'rts', _('RTS/CTS Threshold'));
969 o.datatype = 'uinteger';
970 o.placeholder = _('off');
971
972 o = ss.taboption('advanced', form.Flag, 'noscan', _('Force 40MHz mode'), _('Always use 40MHz channels even if the secondary channel overlaps. Using this option does not comply with IEEE 802.11n-2009!'));
973 o.rmempty = true;
974
975 o = ss.taboption('advanced', form.Value, 'beacon_int', _('Beacon Interval'));
976 o.datatype = 'range(15,65535)';
977 o.placeholder = 100;
978 o.rmempty = true;
979 }
980
981
982 o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getName(), 'wifi-iface', _('Interface Configuration'));
983 o.modalonly = true;
984
985 ss = o.subsection;
986 ss.tab('general', _('General Setup'));
987 ss.tab('encryption', _('Wireless Security'));
988 ss.tab('macfilter', _('MAC-Filter'));
989 ss.tab('advanced', _('Advanced Settings'));
990
991 o = ss.taboption('general', form.ListValue, 'mode', _('Mode'));
992 o.value('ap', _('Access Point'));
993 o.value('sta', _('Client'));
994 o.value('adhoc', _('Ad-Hoc'));
995
996 o = ss.taboption('general', form.Value, 'mesh_id', _('Mesh Id'));
997 o.depends('mode', 'mesh');
998
999 o = ss.taboption('advanced', form.Flag, 'mesh_fwding', _('Forward mesh peer traffic'));
1000 o.rmempty = false;
1001 o.default = '1';
1002 o.depends('mode', 'mesh');
1003
1004 o = ss.taboption('advanced', form.Value, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
1005 o.rmempty = false;
1006 o.default = '0';
1007 o.datatype = 'range(-255,1)';
1008 o.depends('mode', 'mesh');
1009
1010 o = ss.taboption('general', form.Value, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
1011 o.datatype = 'maxlength(32)';
1012 o.depends('mode', 'ap');
1013 o.depends('mode', 'sta');
1014 o.depends('mode', 'adhoc');
1015 o.depends('mode', 'ahdemo');
1016 o.depends('mode', 'monitor');
1017 o.depends('mode', 'ap-wds');
1018 o.depends('mode', 'sta-wds');
1019 o.depends('mode', 'wds');
1020
1021 o = ss.taboption('general', form.Value, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
1022 o.datatype = 'macaddr';
1023
1024 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.'));
1025 o.rmempty = true;
1026 o.multiple = true;
1027 o.novirtual = true;
1028 o.write = function(section_id, value) {
1029 return network.getDevice(section_id).then(L.bind(function(dev) {
1030 var old_networks = dev.getNetworks().reduce(function(o, v) { o[v.getName()] = v; return o }, {}),
1031 new_networks = {},
1032 values = L.toArray(value),
1033 tasks = [];
1034
1035 for (var i = 0; i < values.length; i++) {
1036 new_networks[values[i]] = true;
1037
1038 if (old_networks[values[i]])
1039 continue;
1040
1041 tasks.push(network.getNetwork(values[i]).then(L.bind(function(name, net) {
1042 return net || network.addNetwork(name, { proto: 'none' });
1043 }, this, values[i])).then(L.bind(function(dev, net) {
1044 if (net) {
1045 if (!net.isEmpty()) {
1046 var target_dev = net.getDevice();
1047
1048 /* Resolve parent interface of vlan */
1049 while (target_dev && target_dev.getType() == 'vlan')
1050 target_dev = target_dev.getParent();
1051
1052 if (!target_dev || target_dev.getType() != 'bridge')
1053 net.set('type', 'bridge');
1054 }
1055
1056 net.addDevice(dev);
1057 }
1058 }, this, dev)));
1059 }
1060
1061 for (var name in old_networks)
1062 if (!new_networks[name])
1063 tasks.push(network.getNetwork(name).then(L.bind(function(dev, net) {
1064 if (net)
1065 net.deleteDevice(dev);
1066 }, this, dev)));
1067
1068 return Promise.all(tasks);
1069 }, this));
1070 };
1071
1072 if (hwtype == 'mac80211') {
1073 var mode = ss.children[0],
1074 bssid = ss.children[5],
1075 encr;
1076
1077 /* 802.11v settings start */
1078 // Probe 802.11v support (needs full hostapd/wpad) via EAP support (full hostapd has EAP)
1079 if (L.hasSystemFeature('hostapd', 'eap'))
1080 {
1081 o = ss.taboption('advanced', form.ListValue, 'time_advertisement', _('Time advertisement'), _('802.11v: Time Advertisement in management frames.'));
1082 o.value('0', _('Disabled'));
1083 o.value('2', _('Enabled'));
1084 o.write = function (section_id, value) {
1085 return this.super('write', [section_id, (value == 2) ? value: null]);
1086 }
1087
1088 //Pull current System TZ setting
1089 var tz = uci.get('system', '@system[0]', 'timezone');
1090 o = ss.taboption('advanced', form.Value, 'time_zone', _('Time zone'), _('802.11v: Local Time Zone Advertisement in management frames.'));
1091 o.value(tz);
1092 o.rmempty = true;
1093
1094 o = ss.taboption('advanced', form.Flag, 'wnm_sleep_mode', _('WNM Sleep Mode'), _('802.11v: Wireless Network Management (WNM) Sleep Mode (extended sleep mode for stations).'));
1095 o.rmempty = true;
1096
1097 /* wnm_sleep_mode_no_keys: https://git.openwrt.org/?p=openwrt/openwrt.git;a=commitdiff;h=bf98faaac8ed24cf7d3d93dd4fcd7304d109363b */
1098 o = ss.taboption('advanced', form.Flag, 'wnm_sleep_mode_no_keys', _('WNM Sleep Mode Fixes'), _('802.11v: Wireless Network Management (WNM) Sleep Mode Fixes: Prevents reinstallation attacks.'));
1099 o.rmempty = true;
1100
1101 o = ss.taboption('advanced', form.Flag, 'bss_transition', _('BSS Transition'), _('802.11v: Basic Service Set (BSS) transition management.'));
1102 o.rmempty = true;
1103
1104 /* in master, but not 21.02.1: proxy_arp */
1105 o = ss.taboption('advanced', form.Flag, 'proxy_arp', _('ProxyARP'), _('802.11v: Proxy ARP enables non-AP STA to remain in power-save for longer.'));
1106 o.rmempty = true;
1107
1108 /* TODO: na_mcast_to_ucast is missing: needs adding to hostapd.sh - nice to have */
1109 }
1110 /* 802.11v settings end */
1111
1112 mode.value('mesh', '802.11s');
1113 mode.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
1114 mode.value('monitor', _('Monitor'));
1115
1116 bssid.depends('mode', 'adhoc');
1117 bssid.depends('mode', 'sta');
1118 bssid.depends('mode', 'sta-wds');
1119
1120 o = ss.taboption('macfilter', form.ListValue, 'macfilter', _('MAC Address Filter'));
1121 o.depends('mode', 'ap');
1122 o.depends('mode', 'ap-wds');
1123 o.value('', _('disable'));
1124 o.value('allow', _('Allow listed only'));
1125 o.value('deny', _('Allow all except listed'));
1126
1127 o = ss.taboption('macfilter', form.DynamicList, 'maclist', _('MAC-List'));
1128 o.datatype = 'macaddr';
1129 o.retain = true;
1130 o.depends('macfilter', 'allow');
1131 o.depends('macfilter', 'deny');
1132 o.load = function(section_id) {
1133 return network.getHostHints().then(L.bind(function(hints) {
1134 hints.getMACHints().map(L.bind(function(hint) {
1135 this.value(hint[0], hint[1] ? '%s (%s)'.format(hint[0], hint[1]) : hint[0]);
1136 }, this));
1137
1138 return form.DynamicList.prototype.load.apply(this, [section_id]);
1139 }, this));
1140 };
1141
1142 mode.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
1143 mode.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
1144
1145 mode.write = function(section_id, value) {
1146 switch (value) {
1147 case 'ap-wds':
1148 uci.set('wireless', section_id, 'mode', 'ap');
1149 uci.set('wireless', section_id, 'wds', '1');
1150 break;
1151
1152 case 'sta-wds':
1153 uci.set('wireless', section_id, 'mode', 'sta');
1154 uci.set('wireless', section_id, 'wds', '1');
1155 break;
1156
1157 default:
1158 uci.set('wireless', section_id, 'mode', value);
1159 uci.unset('wireless', section_id, 'wds');
1160 break;
1161 }
1162 };
1163
1164 mode.cfgvalue = function(section_id) {
1165 var mode = uci.get('wireless', section_id, 'mode'),
1166 wds = uci.get('wireless', section_id, 'wds');
1167
1168 if (mode == 'ap' && wds)
1169 return 'ap-wds';
1170 else if (mode == 'sta' && wds)
1171 return 'sta-wds';
1172
1173 return mode;
1174 };
1175
1176 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.'));
1177 o.depends('mode', 'ap');
1178 o.depends('mode', 'ap-wds');
1179
1180 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.'));
1181 o.depends('mode', 'ap');
1182 o.depends('mode', 'ap-wds');
1183 o.default = o.enabled;
1184
1185 /* https://w1.fi/cgit/hostap/commit/?id=34f7c699a6bcb5c45f82ceb6743354ad79296078 */
1186 /* multicast_to_unicast https://github.com/openwrt/openwrt/commit/7babb978ad9d7fc29acb1ff86afb1eb343af303a */
1187 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.'));
1188 o.rmempty = true;
1189
1190 /* 802.11k settings start */
1191 // Probe 802.11k support via EAP support (full hostapd has EAP)
1192 if (L.hasSystemFeature('hostapd', 'eap')) {
1193 o = ss.taboption('advanced', form.Flag, 'ieee80211k', _('802.11k RRM'), _('Radio Resource Measurement - Sends beacons to assist roaming. Not all clients support this.'));
1194 // add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1195 o.depends('mode', 'ap');
1196 o.depends('mode', 'ap-wds');
1197
1198 o = ss.taboption('advanced', form.Flag, 'rrm_neighbor_report', _('Neighbour Report'), _('802.11k: Enable neighbor report via radio measurements.'));
1199 o.depends({ ieee80211k: '1' });
1200 o.default = o.enabled;
1201
1202 o = ss.taboption('advanced', form.Flag, 'rrm_beacon_report', _('Beacon Report'), _('802.11k: Enable beacon report via radio measurements.'));
1203 o.depends({ ieee80211k: '1' });
1204 o.default = o.enabled;
1205 }
1206 /* 802.11k settings end */
1207
1208 o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
1209 o.depends('mode', 'ap');
1210 o.depends('mode', 'ap-wds');
1211
1212 o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name'));
1213 o.optional = true;
1214 o.placeholder = radioNet.getIfname();
1215 if (/^radio\d+\.network/.test(o.placeholder))
1216 o.placeholder = '';
1217
1218 o = ss.taboption('advanced', form.Value, 'macaddr', _('MAC address'), _('Override default MAC address - the range of usable addresses might be limited by the driver'));
1219 o.optional = true;
1220 o.placeholder = radioNet.getActiveBSSID();
1221 o.datatype = 'macaddr';
1222
1223 o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble'));
1224 o.default = o.enabled;
1225
1226 o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
1227 o.optional = true;
1228 o.placeholder = 2;
1229 o.datatype = 'range(1,255)';
1230
1231 o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
1232 o.optional = true;
1233 o.placeholder = 600;
1234 o.datatype = 'uinteger';
1235
1236 o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling'));
1237 o.optional = true;
1238 o.datatype = 'uinteger';
1239
1240 o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('802.11v: BSS Max Idle. Units: seconds.'));
1241 o.optional = true;
1242 o.placeholder = 300;
1243 o.datatype = 'uinteger';
1244
1245 o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval'));
1246 o.optional = true;
1247 o.placeholder = 65535;
1248 o.datatype = 'uinteger';
1249
1250 o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
1251 o.default = o.enabled;
1252 }
1253
1254
1255 encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption'));
1256 o.depends('mode', 'ap');
1257 o.depends('mode', 'sta');
1258 o.depends('mode', 'adhoc');
1259 o.depends('mode', 'ahdemo');
1260 o.depends('mode', 'ap-wds');
1261 o.depends('mode', 'sta-wds');
1262 o.depends('mode', 'mesh');
1263
1264 o.cfgvalue = function(section_id) {
1265 var v = String(uci.get('wireless', section_id, 'encryption'));
1266 if (v == 'wep')
1267 return 'wep-open';
1268 else if (v.match(/\+/))
1269 return v.replace(/\+.+$/, '');
1270 return v;
1271 };
1272
1273 o.write = function(section_id, value) {
1274 var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id),
1275 co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id);
1276
1277 if (value == 'wpa' || value == 'wpa2' || value == 'wpa3' || value == 'wpa3-mixed')
1278 uci.unset('wireless', section_id, 'key');
1279
1280 if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp'))
1281 e += '+' + c;
1282
1283 uci.set('wireless', section_id, 'encryption', e);
1284 };
1285
1286 o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher'));
1287 o.depends('encryption', 'wpa');
1288 o.depends('encryption', 'wpa2');
1289 o.depends('encryption', 'wpa3');
1290 o.depends('encryption', 'wpa3-mixed');
1291 o.depends('encryption', 'psk');
1292 o.depends('encryption', 'psk2');
1293 o.depends('encryption', 'wpa-mixed');
1294 o.depends('encryption', 'psk-mixed');
1295 o.value('auto', _('auto'));
1296 o.value('ccmp', _('Force CCMP (AES)'));
1297 o.value('tkip', _('Force TKIP'));
1298 o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1299 o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write;
1300
1301 o.cfgvalue = function(section_id) {
1302 var v = String(uci.get('wireless', section_id, 'encryption'));
1303 if (v.match(/\+/)) {
1304 v = v.replace(/^[^+]+\+/, '');
1305 if (v == 'aes')
1306 v = 'ccmp';
1307 else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip')
1308 v = 'tkip+ccmp';
1309 }
1310 return v;
1311 };
1312
1313
1314 var crypto_modes = [];
1315
1316 if (hwtype == 'mac80211') {
1317 var has_supplicant = L.hasSystemFeature('wpasupplicant'),
1318 has_hostapd = L.hasSystemFeature('hostapd');
1319
1320 // Probe EAP support
1321 var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'),
1322 has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap');
1323
1324 // Probe SAE support
1325 var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'),
1326 has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae');
1327
1328 // Probe OWE support
1329 var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
1330 has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
1331
1332 // Probe Suite-B support
1333 var has_ap_eap192 = L.hasSystemFeature('hostapd', 'suiteb192'),
1334 has_sta_eap192 = L.hasSystemFeature('wpasupplicant', 'suiteb192');
1335
1336 // Probe WEP support
1337 var has_ap_wep = L.hasSystemFeature('hostapd', 'wep'),
1338 has_sta_wep = L.hasSystemFeature('wpasupplicant', 'wep');
1339
1340 if (has_hostapd || has_supplicant) {
1341 crypto_modes.push(['psk2', 'WPA2-PSK', 35]);
1342 crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1343 crypto_modes.push(['psk', 'WPA-PSK', 12]);
1344 }
1345 else {
1346 encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1347 }
1348
1349 if (has_ap_sae || has_sta_sae) {
1350 crypto_modes.push(['sae', 'WPA3-SAE', 31]);
1351 crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1352 }
1353
1354 if (has_ap_wep || has_sta_wep) {
1355 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1356 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1357 }
1358
1359 if (has_ap_eap || has_sta_eap) {
1360 if (has_ap_eap192 || has_sta_eap192) {
1361 crypto_modes.push(['wpa3', 'WPA3-EAP', 33]);
1362 crypto_modes.push(['wpa3-mixed', 'WPA2-EAP/WPA3-EAP Mixed Mode', 32]);
1363 }
1364
1365 crypto_modes.push(['wpa2', 'WPA2-EAP', 34]);
1366 crypto_modes.push(['wpa', 'WPA-EAP', 20]);
1367 }
1368
1369 if (has_ap_owe || has_sta_owe) {
1370 crypto_modes.push(['owe', 'OWE', 1]);
1371 }
1372
1373 encr.crypto_support = {
1374 'ap': {
1375 'wep-open': has_ap_wep || _('Requires hostapd with WEP support'),
1376 'wep-shared': has_ap_wep || _('Requires hostapd with WEP support'),
1377 'psk': has_hostapd || _('Requires hostapd'),
1378 'psk2': has_hostapd || _('Requires hostapd'),
1379 'psk-mixed': has_hostapd || _('Requires hostapd'),
1380 'sae': has_ap_sae || _('Requires hostapd with SAE support'),
1381 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
1382 'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
1383 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
1384 'wpa3': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
1385 'wpa3-mixed': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
1386 'owe': has_ap_owe || _('Requires hostapd with OWE support')
1387 },
1388 'sta': {
1389 'wep-open': has_sta_wep || _('Requires wpa-supplicant with WEP support'),
1390 'wep-shared': has_sta_wep || _('Requires wpa-supplicant with WEP support'),
1391 'psk': has_supplicant || _('Requires wpa-supplicant'),
1392 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1393 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1394 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1395 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1396 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1397 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1398 'wpa3': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
1399 'wpa3-mixed': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
1400 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
1401 },
1402 'adhoc': {
1403 'wep-open': true,
1404 'wep-shared': true,
1405 'psk': has_supplicant || _('Requires wpa-supplicant'),
1406 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1407 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1408 },
1409 'mesh': {
1410 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support')
1411 },
1412 'ahdemo': {
1413 'wep-open': true,
1414 'wep-shared': true
1415 },
1416 'wds': {
1417 'wep-open': true,
1418 'wep-shared': true
1419 }
1420 };
1421
1422 encr.crypto_support['ap-wds'] = encr.crypto_support['ap'];
1423 encr.crypto_support['sta-wds'] = encr.crypto_support['sta'];
1424
1425 encr.validate = function(section_id, value) {
1426 var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
1427 modeval = modeopt.formvalue(section_id),
1428 modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)],
1429 enctitle = this.vallist[this.keylist.indexOf(value)];
1430
1431 if (value == 'none')
1432 return true;
1433
1434 if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value))
1435 return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle);
1436
1437 return this.crypto_support[modeval][value];
1438 };
1439 }
1440 else if (hwtype == 'broadcom') {
1441 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1442 crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1443 crypto_modes.push(['psk', 'WPA-PSK', 12]);
1444 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1445 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1446 }
1447
1448 crypto_modes.push(['none', _('No Encryption'), 0]);
1449
1450 crypto_modes.sort(function(a, b) { return b[2] - a[2] });
1451
1452 for (var i = 0; i < crypto_modes.length; i++) {
1453 var security_level = (crypto_modes[i][2] >= 30) ? _('strong security')
1454 : (crypto_modes[i][2] >= 20) ? _('medium security')
1455 : (crypto_modes[i][2] >= 10) ? _('weak security') : _('open network');
1456
1457 encr.value(crypto_modes[i][0], '%s (%s)'.format(crypto_modes[i][1], security_level));
1458 }
1459
1460
1461 o = ss.taboption('encryption', form.Value, 'auth_server', _('RADIUS Authentication Server'));
1462 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1463 o.rmempty = true;
1464 o.datatype = 'host(0)';
1465
1466 o = ss.taboption('encryption', form.Value, 'auth_port', _('RADIUS Authentication Port'));
1467 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1468 o.rmempty = true;
1469 o.datatype = 'port';
1470 o.placeholder = '1812';
1471
1472 o = ss.taboption('encryption', form.Value, 'auth_secret', _('RADIUS Authentication Secret'));
1473 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1474 o.rmempty = true;
1475 o.password = true;
1476
1477 o = ss.taboption('encryption', form.Value, 'acct_server', _('RADIUS Accounting Server'));
1478 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1479 o.rmempty = true;
1480 o.datatype = 'host(0)';
1481
1482 o = ss.taboption('encryption', form.Value, 'acct_port', _('RADIUS Accounting Port'));
1483 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1484 o.rmempty = true;
1485 o.datatype = 'port';
1486 o.placeholder = '1813';
1487
1488 o = ss.taboption('encryption', form.Value, 'acct_secret', _('RADIUS Accounting Secret'));
1489 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1490 o.rmempty = true;
1491 o.password = true;
1492
1493 o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client'), _('Dynamic Authorization Extension client.'));
1494 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1495 o.rmempty = true;
1496 o.datatype = 'host(0)';
1497
1498 o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Dynamic Authorization Extension port.'));
1499 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1500 o.rmempty = true;
1501 o.datatype = 'port';
1502 o.placeholder = '3799';
1503
1504 o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret'), _('Dynamic Authorization Extension secret.'));
1505 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1506 o.rmempty = true;
1507 o.password = true;
1508
1509 //WPA(1) has only WPA IE. Only >= WPA2 has RSN IE Preauth frames.
1510 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.'));
1511 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa2', 'wpa3', 'wpa3-mixed'] });
1512
1513
1514 o = ss.taboption('encryption', form.Value, '_wpa_key', _('Key'));
1515 o.depends('encryption', 'psk');
1516 o.depends('encryption', 'psk2');
1517 o.depends('encryption', 'psk+psk2');
1518 o.depends('encryption', 'psk-mixed');
1519 o.depends('encryption', 'sae');
1520 o.depends('encryption', 'sae-mixed');
1521 o.datatype = 'wpakey';
1522 o.rmempty = true;
1523 o.password = true;
1524
1525 o.cfgvalue = function(section_id) {
1526 var key = uci.get('wireless', section_id, 'key');
1527 return /^[1234]$/.test(key) ? null : key;
1528 };
1529
1530 o.write = function(section_id, value) {
1531 uci.set('wireless', section_id, 'key', value);
1532 uci.unset('wireless', section_id, 'key1');
1533 uci.unset('wireless', section_id, 'key2');
1534 uci.unset('wireless', section_id, 'key3');
1535 uci.unset('wireless', section_id, 'key4');
1536 };
1537
1538
1539 o = ss.taboption('encryption', form.ListValue, '_wep_key', _('Used Key Slot'));
1540 o.depends('encryption', 'wep-open');
1541 o.depends('encryption', 'wep-shared');
1542 o.value('1', _('Key #%d').format(1));
1543 o.value('2', _('Key #%d').format(2));
1544 o.value('3', _('Key #%d').format(3));
1545 o.value('4', _('Key #%d').format(4));
1546
1547 o.cfgvalue = function(section_id) {
1548 var slot = +uci.get('wireless', section_id, 'key');
1549 return (slot >= 1 && slot <= 4) ? String(slot) : '';
1550 };
1551
1552 o.write = function(section_id, value) {
1553 uci.set('wireless', section_id, 'key', value);
1554 };
1555
1556 for (var slot = 1; slot <= 4; slot++) {
1557 o = ss.taboption('encryption', form.Value, 'key%d'.format(slot), _('Key #%d').format(slot));
1558 o.depends('encryption', 'wep-open');
1559 o.depends('encryption', 'wep-shared');
1560 o.datatype = 'wepkey';
1561 o.rmempty = true;
1562 o.password = true;
1563
1564 o.write = function(section_id, value) {
1565 if (value != null && (value.length == 5 || value.length == 13))
1566 value = 's:%s'.format(value);
1567 uci.set('wireless', section_id, this.option, value);
1568 };
1569 }
1570
1571
1572 if (hwtype == 'mac80211') {
1573 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1574 var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap');
1575
1576 o = ss.taboption('encryption', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1577 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1578 if (has_80211r)
1579 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1580 o.rmempty = true;
1581
1582 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.'));
1583 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1584 o.depends({ ieee80211r: '1' });
1585 o.rmempty = true;
1586
1587 o = ss.taboption('encryption', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1588 o.depends({ ieee80211r: '1' });
1589 o.placeholder = '4f57';
1590 o.datatype = 'and(hexstring,length(4))';
1591 o.rmempty = true;
1592
1593 o = ss.taboption('encryption', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1594 o.depends({ ieee80211r: '1' });
1595 o.placeholder = '1000';
1596 o.datatype = 'range(1000,65535)';
1597 o.rmempty = true;
1598
1599 o = ss.taboption('encryption', form.ListValue, 'ft_over_ds', _('FT protocol'));
1600 o.depends({ ieee80211r: '1' });
1601 o.value('0', _('FT over the Air'));
1602 o.value('1', _('FT over DS'));
1603 o.rmempty = true;
1604
1605 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.'));
1606 o.depends({ ieee80211r: '1' });
1607 o.default = o.enabled;
1608 o.rmempty = false;
1609
1610 o = ss.taboption('encryption', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1611 o.depends({ ieee80211r: '1' });
1612 o.placeholder = '10000';
1613 o.datatype = 'uinteger';
1614 o.rmempty = true;
1615
1616 o = ss.taboption('encryption', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1617 o.depends({ ieee80211r: '1' });
1618 o.placeholder = '00004f577274';
1619 o.datatype = 'and(hexstring,length(12))';
1620 o.rmempty = true;
1621
1622 o = ss.taboption('encryption', form.Flag, 'pmk_r1_push', _('PMK R1 Push'));
1623 o.depends({ ieee80211r: '1' });
1624 o.placeholder = '0';
1625 o.rmempty = true;
1626
1627 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.'));
1628 o.depends({ ieee80211r: '1' });
1629 o.rmempty = true;
1630
1631 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.'));
1632 o.depends({ ieee80211r: '1' });
1633 o.rmempty = true;
1634 // End of 802.11r options
1635
1636 o = ss.taboption('encryption', form.ListValue, 'eap_type', _('EAP-Method'));
1637 o.value('tls', 'TLS');
1638 o.value('ttls', 'TTLS');
1639 o.value('peap', 'PEAP');
1640 o.value('fast', 'FAST');
1641 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1642
1643 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"));
1644 o.enabled = '1';
1645 o.disabled = '0';
1646 o.default = o.disabled;
1647 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1648 o.validate = function(section_id, value) {
1649 if (value == '1' && !L.hasSystemFeature('cabundle')) {
1650 return _("This option cannot be used because the ca-bundle package is not installed.");
1651 }
1652 return true;
1653 };
1654
1655 o = ss.taboption('encryption', form.FileUpload, 'ca_cert', _('Path to CA-Certificate'));
1656 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], ca_cert_usesystem: ['0'] });
1657
1658 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"));
1659 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1660
1661 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"));
1662 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1663
1664 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)"));
1665 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1666
1667 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)"));
1668 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1669
1670 o = ss.taboption('encryption', form.FileUpload, 'client_cert', _('Path to Client-Certificate'));
1671 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['tls'] });
1672
1673 o = ss.taboption('encryption', form.FileUpload, 'priv_key', _('Path to Private Key'));
1674 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['tls'] });
1675
1676 o = ss.taboption('encryption', form.Value, 'priv_key_pwd', _('Password of Private Key'));
1677 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['tls'] });
1678 o.password = true;
1679
1680 o = ss.taboption('encryption', form.ListValue, 'auth', _('Authentication'));
1681 o.value('PAP', 'PAP');
1682 o.value('CHAP', 'CHAP');
1683 o.value('MSCHAP', 'MSCHAP');
1684 o.value('MSCHAPV2', 'MSCHAPv2');
1685 o.value('EAP-GTC', 'EAP-GTC');
1686 o.value('EAP-MD5', 'EAP-MD5');
1687 o.value('EAP-MSCHAPV2', 'EAP-MSCHAPv2');
1688 o.value('EAP-TLS', 'EAP-TLS');
1689 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'ttls'] });
1690
1691 o.validate = function(section_id, value) {
1692 var eo = this.section.children.filter(function(o) { return o.option == 'eap_type' })[0],
1693 ev = eo.formvalue(section_id);
1694
1695 if (ev != 'ttls' && (value == 'PAP' || value == 'CHAP' || value == 'MSCHAP' || value == 'MSCHAPV2'))
1696 return _('This authentication type is not applicable to the selected EAP method.');
1697
1698 return true;
1699 };
1700
1701 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"));
1702 o.enabled = '1';
1703 o.disabled = '0';
1704 o.default = o.disabled;
1705 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
1706 o.validate = function(section_id, value) {
1707 if (value == '1' && !L.hasSystemFeature('cabundle')) {
1708 return _("This option cannot be used because the ca-bundle package is not installed.");
1709 }
1710 return true;
1711 };
1712
1713 o = ss.taboption('encryption', form.FileUpload, 'ca_cert2', _('Path to inner CA-Certificate'));
1714 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'], ca_cert2_usesystem: ['0'] });
1715
1716 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"));
1717 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
1718
1719 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"));
1720 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
1721
1722 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)"));
1723 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
1724
1725 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)"));
1726 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
1727
1728 o = ss.taboption('encryption', form.FileUpload, 'client_cert2', _('Path to inner Client-Certificate'));
1729 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
1730
1731 o = ss.taboption('encryption', form.FileUpload, 'priv_key2', _('Path to inner Private Key'));
1732 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
1733
1734 o = ss.taboption('encryption', form.Value, 'priv_key2_pwd', _('Password of inner Private Key'));
1735 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
1736 o.password = true;
1737
1738 o = ss.taboption('encryption', form.Value, 'identity', _('Identity'));
1739 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'tls', 'ttls'] });
1740
1741 o = ss.taboption('encryption', form.Value, 'anonymous_identity', _('Anonymous Identity'));
1742 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'tls', 'ttls'] });
1743
1744 o = ss.taboption('encryption', form.Value, 'password', _('Password'));
1745 add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'ttls'] });
1746 o.password = true;
1747
1748
1749 if (hwtype == 'mac80211') {
1750 // ieee802.11w options
1751 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"));
1752 o.value('0', _('Disabled'));
1753 o.value('1', _('Optional'));
1754 o.value('2', _('Required'));
1755 add_dependency_permutations(o, { mode: ['ap', 'ap-wds', 'sta', 'sta-wds'], encryption: ['owe', 'psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1756
1757 o.defaults = {
1758 '2': [{ encryption: 'sae' }, { encryption: 'owe' }, { encryption: 'wpa3' }, { encryption: 'wpa3-mixed' }],
1759 '1': [{ encryption: 'sae-mixed'}],
1760 '0': []
1761 };
1762
1763 o.write = function(section_id, value) {
1764 if (value != this.default)
1765 return form.ListValue.prototype.write.call(this, section_id, value);
1766 else
1767 return form.ListValue.prototype.remove.call(this, section_id);
1768 };
1769
1770 o = ss.taboption('encryption', form.Value, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1771 o.depends('ieee80211w', '1');
1772 o.depends('ieee80211w', '2');
1773 o.datatype = 'uinteger';
1774 o.placeholder = '1000';
1775 o.rmempty = true;
1776
1777 o = ss.taboption('encryption', form.Value, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1778 o.depends('ieee80211w', '1');
1779 o.depends('ieee80211w', '2');
1780 o.datatype = 'uinteger';
1781 o.placeholder = '201';
1782 o.rmempty = true;
1783
1784 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.'));
1785 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1786
1787 if (L.hasSystemFeature('hostapd', 'wps') && L.hasSystemFeature('wpasupplicant')) {
1788 o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1789 o.enabled = '1';
1790 o.disabled = '0';
1791 o.default = o.disabled;
1792 o.depends('encryption', 'psk');
1793 o.depends('encryption', 'psk2');
1794 o.depends('encryption', 'psk-mixed');
1795 o.depends('encryption', 'sae');
1796 o.depends('encryption', 'sae-mixed');
1797 }
1798 }
1799 }
1800 });
1801 };
1802
1803 s.handleRemove = function(section_id, ev) {
1804 document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id)).style.opacity = 0.5;
1805 return form.TypedSection.prototype.handleRemove.apply(this, [section_id, ev]);
1806 };
1807
1808 s.handleScan = function(radioDev, ev) {
1809 var table = E('table', { 'class': 'table' }, [
1810 E('tr', { 'class': 'tr table-titles' }, [
1811 E('th', { 'class': 'th col-2 middle center' }, _('Signal')),
1812 E('th', { 'class': 'th col-4 middle left' }, _('SSID')),
1813 E('th', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1814 E('th', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1815 E('th', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1816 E('th', { 'class': 'th col-3 middle left' }, _('Encryption')),
1817 E('th', { 'class': 'th cbi-section-actions right' }, ' '),
1818 ])
1819 ]);
1820
1821 var stop = E('button', {
1822 'class': 'btn',
1823 'click': L.bind(this.handleScanStartStop, this),
1824 'style': 'display:none',
1825 'data-state': 'stop'
1826 }, _('Stop refresh'));
1827
1828 cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1829
1830 var md = ui.showModal(_('Join Network: Wireless Scan'), [
1831 table,
1832 E('div', { 'class': 'right' }, [
1833 stop,
1834 ' ',
1835 E('button', {
1836 'class': 'btn',
1837 'click': L.bind(this.handleScanAbort, this)
1838 }, _('Dismiss'))
1839 ])
1840 ]);
1841
1842 md.style.maxWidth = '90%';
1843 md.style.maxHeight = 'none';
1844
1845 this.pollFn = L.bind(this.handleScanRefresh, this, radioDev, {}, table, stop);
1846
1847 poll.add(this.pollFn);
1848 poll.start();
1849 };
1850
1851 s.handleScanRefresh = function(radioDev, scanCache, table, stop) {
1852 return radioDev.getScanList().then(L.bind(function(results) {
1853 var rows = [];
1854
1855 for (var i = 0; i < results.length; i++)
1856 scanCache[results[i].bssid] = results[i];
1857
1858 for (var k in scanCache)
1859 if (scanCache[k].stale)
1860 results.push(scanCache[k]);
1861
1862 results.sort(function(a, b) {
1863 var diff = (b.quality - a.quality) || (a.channel - b.channel);
1864
1865 if (diff)
1866 return diff;
1867
1868 if (a.ssid < b.ssid)
1869 return -1;
1870 else if (a.ssid > b.ssid)
1871 return 1;
1872
1873 if (a.bssid < b.bssid)
1874 return -1;
1875 else if (a.bssid > b.bssid)
1876 return 1;
1877 });
1878
1879 for (var i = 0; i < results.length; i++) {
1880 var res = results[i],
1881 qv = res.quality || 0,
1882 qm = res.quality_max || 0,
1883 q = (qv > 0 && qm > 0) ? Math.floor((100 / qm) * qv) : 0,
1884 s = res.stale ? 'opacity:0.5' : '';
1885
1886 rows.push([
1887 E('span', { 'style': s }, render_signal_badge(q, res.signal, res.noise)),
1888 E('span', { 'style': s }, (res.ssid != null) ? '%h'.format(res.ssid) : E('em', _('hidden'))),
1889 E('span', { 'style': s }, '%d'.format(res.channel)),
1890 E('span', { 'style': s }, '%h'.format(res.mode)),
1891 E('span', { 'style': s }, '%h'.format(res.bssid)),
1892 E('span', { 'style': s }, '%h'.format(network.formatWifiEncryption(res.encryption))),
1893 E('div', { 'class': 'right' }, E('button', {
1894 'class': 'cbi-button cbi-button-action important',
1895 'click': ui.createHandlerFn(this, 'handleJoin', radioDev, res)
1896 }, _('Join Network')))
1897 ]);
1898
1899 res.stale = true;
1900 }
1901
1902 cbi_update_table(table, rows);
1903
1904 stop.disabled = false;
1905 stop.style.display = '';
1906 stop.classList.remove('spinning');
1907 }, this));
1908 };
1909
1910 s.handleScanStartStop = function(ev) {
1911 var btn = ev.currentTarget;
1912
1913 if (btn.getAttribute('data-state') == 'stop') {
1914 poll.remove(this.pollFn);
1915 btn.firstChild.data = _('Start refresh');
1916 btn.setAttribute('data-state', 'start');
1917 }
1918 else {
1919 poll.add(this.pollFn);
1920 btn.firstChild.data = _('Stop refresh');
1921 btn.setAttribute('data-state', 'stop');
1922 btn.classList.add('spinning');
1923 btn.disabled = true;
1924 }
1925 };
1926
1927 s.handleScanAbort = function(ev) {
1928 var md = dom.parent(ev.target, 'div[aria-modal="true"]');
1929 if (md) {
1930 md.style.maxWidth = '';
1931 md.style.maxHeight = '';
1932 }
1933
1934 ui.hideModal();
1935 poll.remove(this.pollFn);
1936
1937 this.pollFn = null;
1938 };
1939
1940 s.handleJoinConfirm = function(radioDev, bss, form, ev) {
1941 var nameopt = L.toArray(form.lookupOption('name', '_new_'))[0],
1942 passopt = L.toArray(form.lookupOption('password', '_new_'))[0],
1943 ssidopt = L.toArray(form.lookupOption('ssid', '_new_'))[0],
1944 bssidopt = L.toArray(form.lookupOption('bssid', '_new_'))[0],
1945 zoneopt = L.toArray(form.lookupOption('zone', '_new_'))[0],
1946 replopt = L.toArray(form.lookupOption('replace', '_new_'))[0],
1947 nameval = (nameopt && nameopt.isValid('_new_')) ? nameopt.formvalue('_new_') : null,
1948 passval = (passopt && passopt.isValid('_new_')) ? passopt.formvalue('_new_') : null,
1949 ssidval = (ssidopt && ssidopt.isValid('_new_')) ? ssidopt.formvalue('_new_') : null,
1950 bssidval = (bssidopt && bssidopt.isValid('_new_')) ? bssidopt.formvalue('_new_') : null,
1951 zoneval = zoneopt ? zoneopt.formvalue('_new_') : null,
1952 enc = L.isObject(bss.encryption) ? bss.encryption : null,
1953 is_wep = (enc && Array.isArray(enc.wep)),
1954 is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' }).length > 0),
1955 is_sae = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'sae' }).length > 0);
1956
1957 if (nameval == null || (passopt && passval == null))
1958 return;
1959
1960 var section_id = null;
1961
1962 return this.map.save(function() {
1963 var wifi_sections = uci.sections('wireless', 'wifi-iface');
1964
1965 if (replopt.formvalue('_new_') == '1') {
1966 for (var i = 0; i < wifi_sections.length; i++)
1967 if (wifi_sections[i].device == radioDev.getName())
1968 uci.remove('wireless', wifi_sections[i]['.name']);
1969 }
1970
1971 if (uci.get('wireless', radioDev.getName(), 'disabled') == '1') {
1972 for (var i = 0; i < wifi_sections.length; i++)
1973 if (wifi_sections[i].device == radioDev.getName())
1974 uci.set('wireless', wifi_sections[i]['.name'], 'disabled', '1');
1975
1976 uci.unset('wireless', radioDev.getName(), 'disabled');
1977 }
1978
1979 section_id = next_free_sid(wifi_sections.length);
1980
1981 uci.add('wireless', 'wifi-iface', section_id);
1982 uci.set('wireless', section_id, 'device', radioDev.getName());
1983 uci.set('wireless', section_id, 'mode', (bss.mode == 'Ad-Hoc') ? 'adhoc' : 'sta');
1984 uci.set('wireless', section_id, 'network', nameval);
1985
1986 if (bss.ssid != null) {
1987 uci.set('wireless', section_id, 'ssid', bss.ssid);
1988
1989 if (bssidval == '1')
1990 uci.set('wireless', section_id, 'bssid', bss.bssid);
1991 }
1992 else if (bss.bssid != null) {
1993 uci.set('wireless', section_id, 'bssid', bss.bssid);
1994 }
1995
1996 if (ssidval != null)
1997 uci.set('wireless', section_id, 'ssid', ssidval);
1998
1999 if (is_sae) {
2000 uci.set('wireless', section_id, 'encryption', 'sae');
2001 uci.set('wireless', section_id, 'key', passval);
2002 }
2003 else if (is_psk) {
2004 for (var i = enc.wpa.length - 1; i >= 0; i--) {
2005 if (enc.wpa[i] == 2) {
2006 uci.set('wireless', section_id, 'encryption', 'psk2');
2007 break;
2008 }
2009 else if (enc.wpa[i] == 1) {
2010 uci.set('wireless', section_id, 'encryption', 'psk');
2011 break;
2012 }
2013 }
2014
2015 uci.set('wireless', section_id, 'key', passval);
2016 }
2017 else if (is_wep) {
2018 uci.set('wireless', section_id, 'encryption', 'wep-open');
2019 uci.set('wireless', section_id, 'key', '1');
2020 uci.set('wireless', section_id, 'key1', passval);
2021 }
2022 else {
2023 uci.set('wireless', section_id, 'encryption', 'none');
2024 }
2025
2026 return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) {
2027 firewall.deleteNetwork(net.getName());
2028
2029 var zonePromise = zoneval
2030 ? firewall.getZone(zoneval).then(function(zone) { return zone || firewall.addZone(zoneval) })
2031 : Promise.resolve();
2032
2033 return zonePromise.then(function(zone) {
2034 if (zone)
2035 zone.addNetwork(net.getName());
2036 });
2037 });
2038 }).then(L.bind(function() {
2039 ui.showModal(null, E('p', { 'class': 'spinning' }, [ _('Loading data…') ]));
2040
2041 return this.renderMoreOptionsModal(section_id);
2042 }, this));
2043 };
2044
2045 s.handleJoin = function(radioDev, bss, ev) {
2046 poll.remove(this.pollFn);
2047
2048 var m2 = new form.Map('wireless'),
2049 s2 = m2.section(form.NamedSection, '_new_'),
2050 enc = L.isObject(bss.encryption) ? bss.encryption : null,
2051 is_wep = (enc && Array.isArray(enc.wep)),
2052 is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' || a == 'sae' })),
2053 replace, passphrase, name, bssid, zone;
2054
2055 var nameUsed = function(name) {
2056 var s = uci.get('network', name);
2057 if (s != null && s['.type'] != 'interface')
2058 return true;
2059
2060 var net = (s != null) ? network.instantiateNetwork(name) : null;
2061 return (net != null && !net.isEmpty());
2062 };
2063
2064 s2.render = function() {
2065 return Promise.all([
2066 {},
2067 this.renderUCISection('_new_')
2068 ]).then(this.renderContents.bind(this));
2069 };
2070
2071 if (bss.ssid == null) {
2072 name = s2.option(form.Value, 'ssid', _('Network SSID'), _('The correct SSID must be manually specified when joining a hidden wireless network'));
2073 name.rmempty = false;
2074 };
2075
2076 replace = s2.option(form.Flag, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
2077
2078 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>'));
2079 name.datatype = 'uciname';
2080 name.default = 'wwan';
2081 name.rmempty = false;
2082 name.validate = function(section_id, value) {
2083 if (nameUsed(value))
2084 return _('The network name is already used');
2085
2086 return true;
2087 };
2088
2089 for (var i = 2; nameUsed(name.default); i++)
2090 name.default = 'wwan%d'.format(i);
2091
2092 if (is_wep || is_psk) {
2093 passphrase = s2.option(form.Value, 'password', is_wep ? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
2094 passphrase.datatype = is_wep ? 'wepkey' : 'wpakey';
2095 passphrase.password = true;
2096 passphrase.rmempty = false;
2097 }
2098
2099 if (bss.ssid != null) {
2100 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));
2101 bssid.default = '0';
2102 }
2103
2104 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.'));
2105 zone.default = 'wan';
2106
2107 return m2.render().then(L.bind(function(nodes) {
2108 ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [
2109 nodes,
2110 E('div', { 'class': 'right' }, [
2111 E('button', {
2112 'class': 'btn',
2113 'click': ui.hideModal
2114 }, _('Cancel')), ' ',
2115 E('button', {
2116 'class': 'cbi-button cbi-button-positive important',
2117 'click': ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2)
2118 }, _('Submit'))
2119 ])
2120 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase || name).cbid('_new_'))).focus();
2121 }, this));
2122 };
2123
2124 s.handleAdd = function(radioDev, ev) {
2125 var section_id = next_free_sid(uci.sections('wireless', 'wifi-iface').length);
2126
2127 uci.unset('wireless', radioDev.getName(), 'disabled');
2128
2129 uci.add('wireless', 'wifi-iface', section_id);
2130 uci.set('wireless', section_id, 'device', radioDev.getName());
2131 uci.set('wireless', section_id, 'mode', 'ap');
2132 uci.set('wireless', section_id, 'ssid', 'OpenWrt');
2133 uci.set('wireless', section_id, 'encryption', 'none');
2134
2135 this.addedSection = section_id;
2136 return this.renderMoreOptionsModal(section_id);
2137 };
2138
2139 o = s.option(form.DummyValue, '_badge');
2140 o.modalonly = false;
2141 o.textvalue = function(section_id) {
2142 var inst = this.section.lookupRadioOrNetwork(section_id),
2143 node = E('div', { 'class': 'center' });
2144
2145 if (inst.getWifiNetworks)
2146 node.appendChild(render_radio_badge(inst));
2147 else
2148 node.appendChild(render_network_badge(inst));
2149
2150 return node;
2151 };
2152
2153 o = s.option(form.DummyValue, '_stat');
2154 o.modalonly = false;
2155 o.textvalue = function(section_id) {
2156 var inst = this.section.lookupRadioOrNetwork(section_id);
2157
2158 if (inst.getWifiNetworks)
2159 return render_radio_status(inst, this.section.wifis.filter(function(e) {
2160 return (e.getWifiDeviceName() == inst.getName());
2161 }));
2162 else
2163 return render_network_status(inst);
2164 };
2165
2166 return m.render().then(L.bind(function(m, nodes) {
2167 poll.add(L.bind(function() {
2168 var section_ids = m.children[0].cfgsections(),
2169 tasks = [ network.getHostHints(), network.getWifiDevices() ];
2170
2171 for (var i = 0; i < section_ids.length; i++) {
2172 var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])),
2173 dsc = row.querySelector('[data-name="_stat"] > div'),
2174 btns = row.querySelectorAll('.cbi-section-actions button');
2175
2176 if (dsc.getAttribute('restart') == '') {
2177 dsc.setAttribute('restart', '1');
2178 tasks.push(fs.exec('/sbin/wifi', ['up', section_ids[i]]).catch(function(e) {
2179 ui.addNotification(null, E('p', e.message));
2180 }));
2181 }
2182 else if (dsc.getAttribute('restart') == '1') {
2183 dsc.removeAttribute('restart');
2184 btns[0].classList.remove('spinning');
2185 btns[0].disabled = false;
2186 }
2187 }
2188
2189 return Promise.all(tasks)
2190 .then(L.bind(function(hosts_radios) {
2191 var tasks = [];
2192
2193 for (var i = 0; i < hosts_radios[1].length; i++)
2194 tasks.push(hosts_radios[1][i].getWifiNetworks());
2195
2196 return Promise.all(tasks).then(function(data) {
2197 hosts_radios[2] = [];
2198
2199 for (var i = 0; i < data.length; i++)
2200 hosts_radios[2].push.apply(hosts_radios[2], data[i]);
2201
2202 return hosts_radios;
2203 });
2204 }, network))
2205 .then(L.bind(function(hosts_radios_wifis) {
2206 var tasks = [];
2207
2208 for (var i = 0; i < hosts_radios_wifis[2].length; i++)
2209 tasks.push(hosts_radios_wifis[2][i].getAssocList());
2210
2211 return Promise.all(tasks).then(function(data) {
2212 hosts_radios_wifis[3] = [];
2213
2214 for (var i = 0; i < data.length; i++) {
2215 var wifiNetwork = hosts_radios_wifis[2][i],
2216 radioDev = hosts_radios_wifis[1].filter(function(d) { return d.getName() == wifiNetwork.getWifiDeviceName() })[0];
2217
2218 for (var j = 0; j < data[i].length; j++)
2219 hosts_radios_wifis[3].push(Object.assign({ radio: radioDev, network: wifiNetwork }, data[i][j]));
2220 }
2221
2222 return hosts_radios_wifis;
2223 });
2224 }, network))
2225 .then(L.bind(this.poll_status, this, nodes));
2226 }, this), 5);
2227
2228 var table = E('table', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [
2229 E('tr', { 'class': 'tr table-titles' }, [
2230 E('th', { 'class': 'th nowrap' }, _('Network')),
2231 E('th', { 'class': 'th hide-xs' }, _('MAC address')),
2232 E('th', { 'class': 'th' }, _('Host')),
2233 E('th', { 'class': 'th' }, _('Signal / Noise')),
2234 E('th', { 'class': 'th' }, _('RX Rate / TX Rate'))
2235 ])
2236 ]);
2237
2238 cbi_update_table(table, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
2239
2240 return E([ nodes, E('h3', _('Associated Stations')), table ]);
2241 }, this, m));
2242 }
2243 });