3c6f51afeb2afce0a1695c416cebaf0de99cdbad
12 'require tools.widgets as widgets';
14 var isReadonlyView
= !L
.hasViewPermission();
16 function count_changes(section_id
) {
17 var changes
= ui
.changes
.changes
, n
= 0;
19 if (!L
.isObject(changes
))
22 if (Array
.isArray(changes
.wireless
))
23 for (var i
= 0; i
< changes
.wireless
.length
; i
++)
24 n
+= (changes
.wireless
[i
][1] == section_id
);
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') }),
37 function render_signal_badge(signalPercent
, signalValue
, noiseValue
, wrap
, mode
) {
38 var icon
, title
, value
;
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');
51 icon
= L
.resource('icons/signal-75-100.png');
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
);
62 value
= '%d\xa0%s'.format(signalValue
, _('dBm'));
63 title
= '%s: %d %s'.format(_('Signal'), signalValue
, _('dBm'));
66 else if (signalPercent
> -1) {
69 title
= _('No client associated');
75 title
= _('Not associated');
79 title
= _('No RX signal');
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'));
87 value
= '---\xa0%s'.format(_('dBm'));
91 value
= E('em', {}, E('small', {}, [ _('disabled') ]));
92 title
= _('Interface is disabled');
96 'class': wrap
? 'center' : 'ifacebadge',
98 'data-signal': signalValue
,
99 'data-noise': noiseValue
101 E('img', { 'src': icon
}),
103 wrap
? E('br') : ' ',
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());
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
;
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();
127 L
.itemlist(node
.lastElementChild
, [
128 _('Channel'), '%s (%s %s)'.format(channel
|| '?', frequency
|| '?', _('GHz')),
129 _('Bitrate'), '%s %s'.format(bitrate
|| '?', _('Mbit/s'))
132 node
.lastElementChild
.appendChild(E('em', _('Device is not active')));
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()),
148 status_text
= E('a', {
150 click
: L
.bind(ui
.changes
.displayChanges
, ui
.changes
)
151 }, _('Interface has %d pending changes').format(changecount
));
153 status_text
= E('em', disabled
? _('Wireless is disabled') : _('Wireless is not associated'));
155 return L
.itemlist(E('div'), [
156 is_mesh
? _('Mesh ID') : _('SSID'), (is_mesh
? radioNet
.getMeshID() : radioNet
.getSSID()) || '?',
158 _('BSSID'), (!changecount
&& is_assoc
) ? bssid
: null,
159 _('Encryption'), (!changecount
&& is_assoc
) ? radioNet
.getActiveEncryption() || _('None') : null,
161 ], [ ' | ', E('br') ]);
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
);
173 node
= E('span', { 'class': 'ifacebadge large', 'data-network': radioNet
.getName() }, [ E('small'), E('span') ]);
175 dom
.content(node
.firstElementChild
, render_signal_badge(
176 disabled
? -1 : radioNet
.getSignalPercent(),
177 radioNet
.getSignal(), noise
, true, radioNet
.getMode()));
179 L
.itemlist(node
.lastElementChild
, [
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'), ' | ' ]);
193 dom
.append(node
.lastElementChild
, E('em', disabled
? _('Wireless is disabled') : _('Wireless is not associated')));
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
;
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');
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
);
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');
229 btn
.classList
.add('spinning');
232 dsc
.setAttribute('restart', '');
233 dom
.content(dsc
, E('em', _('Device is restarting…')));
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');
242 uci
.unset('wireless', id
, 'disabled');
243 uci
.unset('wireless', radio
, 'disabled');
246 uci
.set('wireless', id
, 'disabled', '1');
248 var all_networks_disabled
= true,
249 wifi_ifaces
= uci
.sections('wireless', 'wifi-iface');
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;
258 if (all_networks_disabled
)
259 uci
.set('wireless', radio
, 'disabled', '1');
262 return map
.save().then(function() {
267 function next_free_sid(offset
) {
268 var sid
= 'wifinet' + offset
;
270 while (uci
.get('wireless', sid
))
271 sid
= 'wifinet' + (++offset
);
276 function add_dependency_permutations(o
, deps
) {
279 for (var key
in deps
) {
280 if (!deps
.hasOwnProperty(key
) || !Array
.isArray(deps
[key
]))
283 var list
= deps
[key
],
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
]) : {});
297 for (var i
= 0; i
< (res
? res
.length
: 0); i
++)
301 var CBIWifiFrequencyValue
= form
.Value
.extend({
302 callFrequencyList
: rpc
.declare({
305 params
: [ 'device' ],
306 expect
: { results
: [] }
309 load: function(section_id
) {
311 network
.getWifiDevice(section_id
),
312 this.callFrequencyList(section_id
)
313 ]).then(L
.bind(function(data
) {
315 '2g': L
.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
316 '5g': L
.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
317 '6g': L
.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
321 for (var i
= 0; i
< data
[1].length
; i
++) {
324 if (data
[1][i
].mhz
>= 2412 && data
[1][i
].mhz
<= 2484)
326 else if (data
[1][i
].mhz
>= 5160 && data
[1][i
].mhz
<= 5885)
328 else if (data
[1][i
].mhz
>= 5925 && data
[1][i
].mhz
<= 7125)
330 else if (data
[1][i
].mhz
>= 58320 && data
[1][i
].mhz
<= 69120)
335 this.channels
[band
].push(
337 '%d (%d Mhz)'.format(data
[1][i
].channel
, data
[1][i
].mhz
),
338 !data
[1][i
].restricted
342 var hwmodelist
= L
.toArray(data
[0] ? data
[0].getHWModes() : null)
343 .reduce(function(o
, v
) { o
[v
] = true; return o
}, {});
346 '', 'Legacy', hwmodelist
.a
|| hwmodelist
.b
|| hwmodelist
.g
,
347 'n', 'N', hwmodelist
.n
,
348 'ac', 'AC', L
.hasSystemFeature('hostapd', '11ac') && hwmodelist
.ac
,
349 'ax', 'AX', L
.hasSystemFeature('hostapd', '11ax') && hwmodelist
.ax
352 var htmodelist
= L
.toArray(data
[0] ? data
[0].getHTModes() : null)
353 .reduce(function(o
, v
) { o
[v
] = true; return o
}, {});
356 '': [ '', '-', true ],
358 'HT20', '20 MHz', htmodelist
.HT20
,
359 'HT40', '40 MHz', htmodelist
.HT40
362 'VHT20', '20 MHz', htmodelist
.VHT20
,
363 'VHT40', '40 MHz', htmodelist
.VHT40
,
364 'VHT80', '80 MHz', htmodelist
.VHT80
,
365 'VHT160', '160 MHz', htmodelist
.VHT160
368 'HE20', '20 MHz', htmodelist
.HE20
,
369 'HE40', '40 MHz', htmodelist
.HE40
,
370 'HE80', '80 MHz', htmodelist
.HE80
,
371 'HE160', '160 MHz', htmodelist
.HE160
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
382 '2g', '2.4 GHz', this.channels
['2g'].length
> 3,
383 '5g', '5 GHz', this.channels
['5g'].length
> 3
389 '2g', '2.4 GHz', this.channels
['2g'].length
> 3,
390 '5g', '5 GHz', this.channels
['5g'].length
> 3,
391 '6g', '6 GHz', this.channels
['6g'].length
> 3
397 setValues: function(sel
, vals
) {
399 sel
.vals
.selected
= sel
.selectedIndex
;
401 while (sel
.options
[0])
404 for (var i
= 0; vals
&& i
< vals
.length
; i
+= 3)
406 sel
.add(E('option', { value
: vals
[i
+0] }, [ vals
[i
+1] ]));
408 if (vals
&& !isNaN(vals
.selected
))
409 sel
.selectedIndex
= vals
.selected
;
411 sel
.parentNode
.style
.display
= (sel
.options
.length
<= 1) ? 'none' : '';
415 toggleWifiMode: function(elem
) {
416 this.toggleWifiHTMode(elem
);
417 this.toggleWifiBand(elem
);
420 toggleWifiHTMode: function(elem
) {
421 var mode
= elem
.querySelector('.mode');
422 var bwdt
= elem
.querySelector('.htmode');
424 this.setValues(bwdt
, this.htmodes
[mode
.value
]);
427 toggleWifiBand: function(elem
) {
428 var mode
= elem
.querySelector('.mode');
429 var band
= elem
.querySelector('.band');
431 this.setValues(band
, this.bands
[mode
.value
]);
432 this.toggleWifiChannel(elem
);
434 this.map
.checkDepends();
437 toggleWifiChannel: function(elem
) {
438 var band
= elem
.querySelector('.band');
439 var chan
= elem
.querySelector('.channel');
441 this.setValues(chan
, this.channels
[band
.value
]);
444 setInitialValues: function(section_id
, elem
) {
445 var mode
= elem
.querySelector('.mode'),
446 band
= elem
.querySelector('.band'),
447 chan
= elem
.querySelector('.channel'),
448 bwdt
= elem
.querySelector('.htmode'),
449 htval
= uci
.get('wireless', section_id
, 'htmode'),
450 hwval
= uci
.get('wireless', section_id
, 'hwmode'),
451 chval
= uci
.get('wireless', section_id
, 'channel'),
452 bandval
= uci
.get('wireless', section_id
, 'band');
454 this.setValues(mode
, this.modes
);
456 if (/HE20|HE40|HE80|HE160/.test(htval
))
458 else if (/VHT20|VHT40|VHT80|VHT160/.test(htval
))
460 else if (/HT20|HT40/.test(htval
))
465 this.toggleWifiMode(elem
);
468 this.useBandOption
= false;
476 this.useBandOption
= true;
478 band
.value
= bandval
;
481 this.toggleWifiBand(elem
);
484 chan
.value
= chval
|| (chan
.options
[0] ? chan
.options
[0].value
: 'auto');
489 renderWidget: function(section_id
, option_index
, cfgvalue
) {
493 E('label', { 'style': 'float:left; margin-right:3px' }, [
497 'style': 'width:auto',
498 'change': L
.bind(this.toggleWifiMode
, this, elem
),
499 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
502 E('label', { 'style': 'float:left; margin-right:3px' }, [
506 'style': 'width:auto',
507 'change': L
.bind(this.toggleWifiBand
, this, elem
),
508 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
511 E('label', { 'style': 'float:left; margin-right:3px' }, [
512 _('Channel'), E('br'),
515 'style': 'width:auto',
516 'change': L
.bind(this.map
.checkDepends
, this.map
),
517 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
520 E('label', { 'style': 'float:left; margin-right:3px' }, [
524 'style': 'width:auto',
525 'change': L
.bind(this.map
.checkDepends
, this.map
),
526 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
529 E('br', { 'style': 'clear:left' })
532 return this.setInitialValues(section_id
, elem
);
535 cfgvalue: function(section_id
) {
537 uci
.get('wireless', section_id
, 'htmode'),
538 uci
.get('wireless', section_id
, 'hwmode') || uci
.get('wireless', section_id
, 'band'),
539 uci
.get('wireless', section_id
, 'channel')
543 formvalue: function(section_id
) {
544 var node
= this.map
.findElement('data-field', this.cbid(section_id
));
547 node
.querySelector('.htmode').value
,
548 node
.querySelector('.band').value
,
549 node
.querySelector('.channel').value
553 write: function(section_id
, value
) {
554 uci
.set('wireless', section_id
, 'htmode', value
[0] || null);
556 if (this.useBandOption
)
557 uci
.set('wireless', section_id
, 'band', value
[1]);
559 uci
.set('wireless', section_id
, 'hwmode', (value
[1] == '2g') ? '11g' : '11a');
561 uci
.set('wireless', section_id
, 'channel', value
[2]);
565 var CBIWifiTxPowerValue
= form
.ListValue
.extend({
566 callTxPowerList
: rpc
.declare({
568 method
: 'txpowerlist',
569 params
: [ 'device' ],
570 expect
: { results
: [] }
573 load: function(section_id
) {
574 return this.callTxPowerList(section_id
).then(L
.bind(function(pwrlist
) {
575 this.powerval
= this.wifiNetwork
? this.wifiNetwork
.getTXPower() : null;
576 this.poweroff
= this.wifiNetwork
? this.wifiNetwork
.getTXPowerOffset() : null;
578 this.value('', _('driver default'));
580 for (var i
= 0; i
< pwrlist
.length
; i
++)
581 this.value(pwrlist
[i
].dbm
, '%d dBm (%d mW)'.format(pwrlist
[i
].dbm
, pwrlist
[i
].mw
));
583 return form
.ListValue
.prototype.load
.apply(this, [section_id
]);
587 renderWidget: function(section_id
, option_index
, cfgvalue
) {
588 var widget
= form
.ListValue
.prototype.renderWidget
.apply(this, [section_id
, option_index
, cfgvalue
]);
589 widget
.firstElementChild
.style
.width
= 'auto';
591 dom
.append(widget
, E('span', [
592 ' - ', _('Current power'), ': ',
593 E('span', [ this.powerval
!= null ? '%d dBm'.format(this.powerval
) : E('em', _('unknown')) ]),
594 this.poweroff
? ' + %d dB offset = %s dBm'.format(this.poweroff
, this.powerval
!= null ? this.powerval
+ this.poweroff
: '?') : ''
601 var CBIWifiCountryValue
= form
.Value
.extend({
602 callCountryList
: rpc
.declare({
604 method
: 'countrylist',
605 params
: [ 'device' ],
606 expect
: { results
: [] }
609 load: function(section_id
) {
610 return this.callCountryList(section_id
).then(L
.bind(function(countrylist
) {
611 if (Array
.isArray(countrylist
) && countrylist
.length
> 0) {
612 this.value('', _('driver default'));
614 for (var i
= 0; i
< countrylist
.length
; i
++)
615 this.value(countrylist
[i
].iso3166
, '%s - %s'.format(countrylist
[i
].iso3166
, countrylist
[i
].country
));
618 return form
.Value
.prototype.load
.apply(this, [section_id
]);
622 validate: function(section_id
, formvalue
) {
623 if (formvalue
!= null && formvalue
!= '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue
))
624 return _('Use ISO/IEC 3166 alpha2 country codes.');
629 renderWidget: function(section_id
, option_index
, cfgvalue
) {
630 var typeClass
= (this.keylist
&& this.keylist
.length
) ? form
.ListValue
: form
.Value
;
631 return typeClass
.prototype.renderWidget
.apply(this, [section_id
, option_index
, cfgvalue
]);
636 poll_status: function(map
, data
) {
637 var rows
= map
.querySelectorAll('.cbi-section-table-row[data-sid]');
639 for (var i
= 0; i
< rows
.length
; i
++) {
640 var section_id
= rows
[i
].getAttribute('data-sid'),
641 radioDev
= data
[1].filter(function(d
) { return d
.getName() == section_id
})[0],
642 radioNet
= data
[2].filter(function(n
) { return n
.getName() == section_id
})[0],
643 badge
= rows
[i
].querySelector('[data-name="_badge"] > div'),
644 stat
= rows
[i
].querySelector('[data-name="_stat"]'),
645 btns
= rows
[i
].querySelectorAll('.cbi-section-actions button'),
646 busy
= btns
[0].classList
.contains('spinning') || btns
[1].classList
.contains('spinning') || btns
[2].classList
.contains('spinning');
649 dom
.content(badge
, render_radio_badge(radioDev
));
650 dom
.content(stat
, render_radio_status(radioDev
, data
[2].filter(function(n
) { return n
.getWifiDeviceName() == radioDev
.getName() })));
653 dom
.content(badge
, render_network_badge(radioNet
));
654 dom
.content(stat
, render_network_status(radioNet
));
657 if (stat
.hasAttribute('restart'))
658 dom
.content(stat
, E('em', _('Device is restarting…')));
660 btns
[0].disabled
= isReadonlyView
|| busy
;
661 btns
[1].disabled
= (isReadonlyView
&& radioDev
) || busy
;
662 btns
[2].disabled
= isReadonlyView
|| busy
;
665 var table
= document
.querySelector('#wifi_assoclist_table'),
669 for (var i
= 0; i
< data
[3].length
; i
++) {
670 var bss
= data
[3][i
],
671 name
= hosts
.getHostnameByMACAddr(bss
.mac
),
672 ipv4
= hosts
.getIPAddrByMACAddr(bss
.mac
),
673 ipv6
= hosts
.getIP6AddrByMACAddr(bss
.mac
);
677 if (name
&& ipv4
&& ipv6
)
678 hint
= '%s <span class="hide-xs">(%s, %s)</span>'.format(name
, ipv4
, ipv6
);
679 else if (name
&& (ipv4
|| ipv6
))
680 hint
= '%s <span class="hide-xs">(%s)</span>'.format(name
, ipv4
|| ipv6
);
682 hint
= name
|| ipv4
|| ipv6
|| '?';
686 'class': 'ifacebadge',
687 'data-ifname': bss
.network
.getIfname(),
688 'data-ssid': bss
.network
.getSSID()
691 'src': L
.resource('icons/wifi%s.png').format(bss
.network
.isUp() ? '' : '_disabled'),
692 'title': bss
.radio
.getI18n()
695 ' %s '.format(bss
.network
.getShortName()),
696 E('small', '(%s)'.format(bss
.network
.getIfname()))
701 render_signal_badge(Math
.min((bss
.signal
+ 110) / 70 * 100, 100), bss
.signal
, bss
.noise
),
703 E('span', format_wifirate(bss
.rx
)),
705 E('span', format_wifirate(bss
.tx
))
709 if (bss
.network
.isClientDisconnectSupported()) {
710 if (table
.firstElementChild
.childNodes
.length
< 6)
711 table
.firstElementChild
.appendChild(E('th', { 'class': 'th cbi-section-actions'}));
713 row
.push(E('button', {
714 'class': 'cbi-button cbi-button-remove',
715 'click': L
.bind(function(net
, mac
, ev
) {
716 dom
.parent(ev
.currentTarget
, '.tr').style
.opacity
= 0.5;
717 ev
.currentTarget
.classList
.add('spinning');
718 ev
.currentTarget
.disabled
= true;
719 ev
.currentTarget
.blur();
721 net
.disconnectClient(mac
, true, 5, 60000);
722 }, this, bss
.network
, bss
.mac
),
723 'disabled': isReadonlyView
|| null
724 }, [ _('Disconnect') ]));
733 cbi_update_table(table
, trows
, E('em', _('No information available')));
735 var stat
= document
.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large');
738 render_modal_status(stat
, data
[2].filter(function(n
) { return n
.getName() == stat
.getAttribute('data-network') })[0]);
740 return network
.flushCache();
746 uci
.load('wireless'),
751 checkAnonymousSections: function() {
752 var wifiIfaces
= uci
.sections('wireless', 'wifi-iface');
754 for (var i
= 0; i
< wifiIfaces
.length
; i
++)
755 if (wifiIfaces
[i
]['.anonymous'])
761 callUciRename
: rpc
.declare({
764 params
: [ 'config', 'section', 'name' ]
768 if (this.checkAnonymousSections())
769 return this.renderMigration();
771 return this.renderOverview();
774 handleMigration: function(ev
) {
775 var wifiIfaces
= uci
.sections('wireless', 'wifi-iface'),
779 for (var i
= 0; i
< wifiIfaces
.length
; i
++) {
780 if (!wifiIfaces
[i
]['.anonymous'])
783 var new_name
= next_free_sid(id_offset
);
785 tasks
.push(this.callUciRename('wireless', wifiIfaces
[i
]['.name'], new_name
));
786 id_offset
= +new_name
.substring(7) + 1;
789 return Promise
.all(tasks
)
790 .then(L
.bind(ui
.changes
.init
, ui
.changes
))
791 .then(L
.bind(ui
.changes
.apply
, ui
.changes
));
794 renderMigration: function() {
795 ui
.showModal(_('Wireless configuration migration'), [
796 E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')),
797 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.')),
798 E('div', { 'class': 'right' },
800 'class': 'btn cbi-button-action important',
801 'click': ui
.createHandlerFn(this, 'handleMigration')
806 renderOverview: function() {
809 m
= new form
.Map('wireless');
813 s
= m
.section(form
.GridSection
, 'wifi-device', _('Wireless Overview'));
817 s
.load = function() {
818 return network
.getWifiDevices().then(L
.bind(function(radios
) {
819 this.radios
= radios
.sort(function(a
, b
) {
820 return a
.getName() > b
.getName();
825 for (var i
= 0; i
< radios
.length
; i
++)
826 tasks
.push(radios
[i
].getWifiNetworks());
828 return Promise
.all(tasks
);
829 }, this)).then(L
.bind(function(data
) {
832 for (var i
= 0; i
< data
.length
; i
++)
833 this.wifis
.push
.apply(this.wifis
, data
[i
]);
837 s
.cfgsections = function() {
840 for (var i
= 0; i
< this.radios
.length
; i
++) {
841 rv
.push(this.radios
[i
].getName());
843 for (var j
= 0; j
< this.wifis
.length
; j
++)
844 if (this.wifis
[j
].getWifiDeviceName() == this.radios
[i
].getName())
845 rv
.push(this.wifis
[j
].getName());
851 s
.modaltitle = function(section_id
) {
852 var radioNet
= this.wifis
.filter(function(w
) { return w
.getName() == section_id
})[0];
853 return radioNet
? radioNet
.getI18n() : _('Edit wireless network');
856 s
.lookupRadioOrNetwork = function(section_id
) {
857 var radioDev
= this.radios
.filter(function(r
) { return r
.getName() == section_id
})[0];
861 var radioNet
= this.wifis
.filter(function(w
) { return w
.getName() == section_id
})[0];
868 s
.renderRowActions = function(section_id
) {
869 var inst
= this.lookupRadioOrNetwork(section_id
), btns
;
871 if (inst
.getWifiNetworks
) {
874 'class': 'cbi-button cbi-button-neutral',
875 'title': _('Restart radio interface'),
876 'click': ui
.createHandlerFn(this, radio_restart
, section_id
)
879 'class': 'cbi-button cbi-button-action important',
880 'title': _('Find and join network'),
881 'click': ui
.createHandlerFn(this, 'handleScan', inst
)
884 'class': 'cbi-button cbi-button-add',
885 'title': _('Provide new network'),
886 'click': ui
.createHandlerFn(this, 'handleAdd', inst
)
891 var isDisabled
= (inst
.get('disabled') == '1' ||
892 uci
.get('wireless', inst
.getWifiDeviceName(), 'disabled') == '1');
896 'class': 'cbi-button cbi-button-neutral enable-disable',
897 'title': isDisabled
? _('Enable this network') : _('Disable this network'),
898 'click': ui
.createHandlerFn(this, network_updown
, section_id
, this.map
)
899 }, isDisabled
? _('Enable') : _('Disable')),
901 'class': 'cbi-button cbi-button-action important',
902 'title': _('Edit this network'),
903 'click': ui
.createHandlerFn(this, 'renderMoreOptionsModal', section_id
)
906 'class': 'cbi-button cbi-button-negative remove',
907 'title': _('Delete this network'),
908 'click': ui
.createHandlerFn(this, 'handleRemove', section_id
)
913 return E('td', { 'class': 'td middle cbi-section-actions' }, E('div', btns
));
916 s
.addModalOptions = function(s
) {
917 return network
.getWifiNetwork(s
.section
).then(function(radioNet
) {
918 var hwtype
= uci
.get('wireless', radioNet
.getWifiDeviceName(), 'type');
921 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
925 ss
.tab('general', _('General Setup'));
926 ss
.tab('advanced', _('Advanced Settings'));
928 var isDisabled
= (radioNet
.get('disabled') == '1' ||
929 uci
.get('wireless', radioNet
.getWifiDeviceName(), 'disabled') == 1);
931 o
= ss
.taboption('general', form
.DummyValue
, '_wifistat_modal', _('Status'));
932 o
.cfgvalue
= L
.bind(function(radioNet
) {
933 return render_modal_status(null, radioNet
);
935 o
.write = function() {};
937 o
= ss
.taboption('general', form
.Button
, '_toggle', isDisabled
? _('Wireless network is disabled') : _('Wireless network is enabled'));
938 o
.inputstyle
= isDisabled
? 'apply' : 'reset';
939 o
.inputtitle
= isDisabled
? _('Enable') : _('Disable');
940 o
.onclick
= ui
.createHandlerFn(s
, network_updown
, s
.section
, s
.map
);
942 o
= ss
.taboption('general', CBIWifiFrequencyValue
, '_freq', '<br />' + _('Operating frequency'));
943 o
.ucisection
= s
.section
;
945 if (hwtype
== 'mac80211') {
946 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.'));
947 o
.depends({'_freq': '2g', '!contains': true});
949 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.'));
950 o
.wifiNetwork
= radioNet
;
952 o
= ss
.taboption('advanced', CBIWifiCountryValue
, 'country', _('Country Code'));
953 o
.wifiNetwork
= radioNet
;
955 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.'));
956 o
.value('0', _('Disabled'));
957 o
.value('1', _('Normal'));
958 o
.value('2', _('High'));
959 o
.value('3', _('Very High'));
961 o
= ss
.taboption('advanced', form
.Value
, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
962 o
.datatype
= 'or(range(0,114750),"auto")';
963 o
.placeholder
= 'auto';
965 o
= ss
.taboption('advanced', form
.Value
, 'frag', _('Fragmentation Threshold'));
966 o
.datatype
= 'min(256)';
967 o
.placeholder
= _('off');
969 o
= ss
.taboption('advanced', form
.Value
, 'rts', _('RTS/CTS Threshold'));
970 o
.datatype
= 'uinteger';
971 o
.placeholder
= _('off');
973 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!'));
976 o
= ss
.taboption('advanced', form
.Value
, 'beacon_int', _('Beacon Interval'));
977 o
.datatype
= 'range(15,65535)';
983 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getName(), 'wifi-iface', _('Interface Configuration'));
987 ss
.tab('general', _('General Setup'));
988 ss
.tab('encryption', _('Wireless Security'));
989 ss
.tab('macfilter', _('MAC-Filter'));
990 ss
.tab('advanced', _('Advanced Settings'));
991 ss
.tab('roaming', _('WLAN roaming'), _('Settings for assisting wireless clients in roaming between multiple APs: 802.11r, 802.11k and 802.11v'));
993 o
= ss
.taboption('general', form
.ListValue
, 'mode', _('Mode'));
994 o
.value('ap', _('Access Point'));
995 o
.value('sta', _('Client'));
996 o
.value('adhoc', _('Ad-Hoc'));
998 o
= ss
.taboption('general', form
.Value
, 'mesh_id', _('Mesh Id'));
999 o
.depends('mode', 'mesh');
1001 o
= ss
.taboption('advanced', form
.Flag
, 'mesh_fwding', _('Forward mesh peer traffic'));
1004 o
.depends('mode', 'mesh');
1006 o
= ss
.taboption('advanced', form
.Value
, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
1009 o
.datatype
= 'range(-255,1)';
1010 o
.depends('mode', 'mesh');
1012 o
= ss
.taboption('general', form
.Value
, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
1013 o
.datatype
= 'maxlength(32)';
1014 o
.depends('mode', 'ap');
1015 o
.depends('mode', 'sta');
1016 o
.depends('mode', 'adhoc');
1017 o
.depends('mode', 'ahdemo');
1018 o
.depends('mode', 'monitor');
1019 o
.depends('mode', 'ap-wds');
1020 o
.depends('mode', 'sta-wds');
1021 o
.depends('mode', 'wds');
1023 o
= ss
.taboption('general', form
.Value
, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
1024 o
.datatype
= 'macaddr';
1026 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.'));
1030 o
.write = function(section_id
, value
) {
1031 return network
.getDevice(section_id
).then(L
.bind(function(dev
) {
1032 var old_networks
= dev
.getNetworks().reduce(function(o
, v
) { o
[v
.getName()] = v
; return o
}, {}),
1034 values
= L
.toArray(value
),
1037 for (var i
= 0; i
< values
.length
; i
++) {
1038 new_networks
[values
[i
]] = true;
1040 if (old_networks
[values
[i
]])
1043 tasks
.push(network
.getNetwork(values
[i
]).then(L
.bind(function(name
, net
) {
1044 return net
|| network
.addNetwork(name
, { proto
: 'none' });
1045 }, this, values
[i
])).then(L
.bind(function(dev
, net
) {
1047 if (!net
.isEmpty()) {
1048 var target_dev
= net
.getDevice();
1050 /* Resolve parent interface of vlan */
1051 while (target_dev
&& target_dev
.getType() == 'vlan')
1052 target_dev
= target_dev
.getParent();
1054 if (!target_dev
|| target_dev
.getType() != 'bridge')
1055 net
.set('type', 'bridge');
1063 for (var name
in old_networks
)
1064 if (!new_networks
[name
])
1065 tasks
.push(network
.getNetwork(name
).then(L
.bind(function(dev
, net
) {
1067 net
.deleteDevice(dev
);
1070 return Promise
.all(tasks
);
1074 if (hwtype
== 'mac80211') {
1075 var mode
= ss
.children
[0],
1076 bssid
= ss
.children
[5],
1079 mode
.value('mesh', '802.11s');
1080 mode
.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
1081 mode
.value('monitor', _('Monitor'));
1083 bssid
.depends('mode', 'adhoc');
1084 bssid
.depends('mode', 'sta');
1085 bssid
.depends('mode', 'sta-wds');
1087 o
= ss
.taboption('macfilter', form
.ListValue
, 'macfilter', _('MAC Address Filter'));
1088 o
.depends('mode', 'ap');
1089 o
.depends('mode', 'ap-wds');
1090 o
.value('', _('disable'));
1091 o
.value('allow', _('Allow listed only'));
1092 o
.value('deny', _('Allow all except listed'));
1094 o
= ss
.taboption('macfilter', form
.DynamicList
, 'maclist', _('MAC-List'));
1095 o
.datatype
= 'macaddr';
1097 o
.depends('macfilter', 'allow');
1098 o
.depends('macfilter', 'deny');
1099 o
.load = function(section_id
) {
1100 return network
.getHostHints().then(L
.bind(function(hints
) {
1101 hints
.getMACHints().map(L
.bind(function(hint
) {
1102 this.value(hint
[0], hint
[1] ? '%s (%s)'.format(hint
[0], hint
[1]) : hint
[0]);
1105 return form
.DynamicList
.prototype.load
.apply(this, [section_id
]);
1109 mode
.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
1110 mode
.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
1112 mode
.write = function(section_id
, value
) {
1115 uci
.set('wireless', section_id
, 'mode', 'ap');
1116 uci
.set('wireless', section_id
, 'wds', '1');
1120 uci
.set('wireless', section_id
, 'mode', 'sta');
1121 uci
.set('wireless', section_id
, 'wds', '1');
1125 uci
.set('wireless', section_id
, 'mode', value
);
1126 uci
.unset('wireless', section_id
, 'wds');
1131 mode
.cfgvalue = function(section_id
) {
1132 var mode
= uci
.get('wireless', section_id
, 'mode'),
1133 wds
= uci
.get('wireless', section_id
, 'wds');
1135 if (mode
== 'ap' && wds
)
1137 else if (mode
== 'sta' && wds
)
1143 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.'));
1144 o
.depends('mode', 'ap');
1145 o
.depends('mode', 'ap-wds');
1147 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.'));
1148 o
.depends('mode', 'ap');
1149 o
.depends('mode', 'ap-wds');
1150 o
.default = o
.enabled
;
1152 /* https://w1.fi/cgit/hostap/commit/?id=34f7c699a6bcb5c45f82ceb6743354ad79296078 */
1153 /* multicast_to_unicast https://github.com/openwrt/openwrt/commit/7babb978ad9d7fc29acb1ff86afb1eb343af303a */
1154 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.'));
1157 o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
1158 o.depends('mode', 'ap');
1159 o.depends('mode', 'ap-wds');
1161 o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name'));
1163 o.datatype = 'maxlength(15)';
1164 o.placeholder = radioNet.getIfname();
1165 if (/^radio\d+\.network/.test(o.placeholder))
1168 o = ss.taboption('advanced', form.Value, 'macaddr', _('MAC address'), _('Override default MAC address - the range of usable addresses might be limited by the driver'));
1170 o.placeholder = radioNet.getActiveBSSID();
1171 o.datatype = 'macaddr';
1173 o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble'));
1174 o.default = o.enabled;
1176 o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
1179 o.datatype = 'range(1,255)';
1181 o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
1183 o.placeholder = 600;
1184 o.datatype = 'uinteger';
1186 o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling'));
1188 o.datatype = 'uinteger';
1190 o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('802.11v: BSS Max Idle. Units: seconds.'));
1192 o.placeholder = 300;
1193 o.datatype = 'uinteger';
1195 o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval'));
1197 o.placeholder = 65535;
1198 o.datatype = 'uinteger';
1200 o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
1201 o.default = o.enabled;
1205 encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption'));
1206 o.depends('mode', 'ap');
1207 o.depends('mode', 'sta');
1208 o.depends('mode', 'adhoc');
1209 o.depends('mode', 'ahdemo');
1210 o.depends('mode', 'ap-wds');
1211 o.depends('mode', 'sta-wds');
1212 o.depends('mode', 'mesh');
1214 o.cfgvalue = function(section_id) {
1215 var v = String(uci.get('wireless', section_id, 'encryption'));
1218 else if (v.match(/\+/))
1219 return v.replace(/\+.+$/, '');
1223 o.write = function(section_id, value) {
1224 var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id),
1225 co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id);
1227 if (value == 'wpa' || value == 'wpa2' || value == 'wpa3' || value == 'wpa3-mixed')
1228 uci.unset('wireless', section_id, 'key');
1230 if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp'))
1233 uci.set('wireless', section_id, 'encryption', e);
1236 o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher'));
1237 o.depends('encryption', 'wpa');
1238 o.depends('encryption', 'wpa2');
1239 o.depends('encryption', 'wpa3');
1240 o.depends('encryption', 'wpa3-mixed');
1241 o.depends('encryption', 'psk');
1242 o.depends('encryption', 'psk2');
1243 o.depends('encryption', 'wpa-mixed');
1244 o.depends('encryption', 'psk-mixed');
1245 o.value('auto', _('auto'));
1246 o.value('ccmp', _('Force CCMP (AES)'));
1247 o.value('tkip', _('Force TKIP'));
1248 o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1249 o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write;
1251 o.cfgvalue = function(section_id) {
1252 var v = String(uci.get('wireless', section_id, 'encryption'));
1253 if (v.match(/\+/)) {
1254 v = v.replace(/^[^+]+\+/, '');
1257 else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip')
1264 var crypto_modes = [];
1266 if (hwtype == 'mac80211') {
1267 var has_supplicant = L.hasSystemFeature('wpasupplicant'),
1268 has_hostapd = L.hasSystemFeature('hostapd');
1270 // Probe EAP support
1271 var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'),
1272 has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap');
1274 // Probe SAE support
1275 var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'),
1276 has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae');
1278 // Probe OWE support
1279 var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
1280 has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
1282 // Probe Suite-B support
1283 var has_ap_eap192 = L.hasSystemFeature('hostapd', 'suiteb192'),
1284 has_sta_eap192 = L.hasSystemFeature('wpasupplicant', 'suiteb192');
1286 // Probe WEP support
1287 var has_ap_wep = L.hasSystemFeature('hostapd', 'wep'),
1288 has_sta_wep = L.hasSystemFeature('wpasupplicant', 'wep');
1290 if (has_hostapd || has_supplicant) {
1291 crypto_modes.push(['psk2', 'WPA2-PSK', 35]);
1292 crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1293 crypto_modes.push(['psk', 'WPA-PSK', 12]);
1296 encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1299 if (has_ap_sae || has_sta_sae) {
1300 crypto_modes.push(['sae', 'WPA3-SAE', 31]);
1301 crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1304 if (has_ap_wep || has_sta_wep) {
1305 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1306 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1309 if (has_ap_eap || has_sta_eap) {
1310 if (has_ap_eap192 || has_sta_eap192) {
1311 crypto_modes.push(['wpa3', 'WPA3-EAP', 33]);
1312 crypto_modes.push(['wpa3-mixed', 'WPA2-EAP/WPA3-EAP Mixed Mode', 32]);
1315 crypto_modes.push(['wpa2', 'WPA2-EAP', 34]);
1316 crypto_modes.push(['wpa', 'WPA-EAP', 20]);
1319 if (has_ap_owe || has_sta_owe) {
1320 crypto_modes.push(['owe', 'OWE', 1]);
1323 encr.crypto_support = {
1325 'wep-open': has_ap_wep || _('Requires hostapd with WEP support'),
1326 'wep-shared': has_ap_wep || _('Requires hostapd with WEP support'),
1327 'psk': has_hostapd || _('Requires hostapd'),
1328 'psk2': has_hostapd || _('Requires hostapd'),
1329 'psk-mixed': has_hostapd || _('Requires hostapd'),
1330 'sae': has_ap_sae || _('Requires hostapd with SAE support'),
1331 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
1332 'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
1333 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
1334 'wpa3': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
1335 'wpa3-mixed': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
1336 'owe': has_ap_owe || _('Requires hostapd with OWE support')
1339 'wep-open': has_sta_wep || _('Requires wpa-supplicant with WEP support'),
1340 'wep-shared': has_sta_wep || _('Requires wpa-supplicant with WEP support'),
1341 'psk': has_supplicant || _('Requires wpa-supplicant'),
1342 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1343 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1344 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1345 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1346 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1347 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1348 'wpa3': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
1349 'wpa3-mixed': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
1350 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
1355 'psk': has_supplicant || _('Requires wpa-supplicant'),
1356 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1357 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1360 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support')
1372 encr.crypto_support['ap-wds'] = encr.crypto_support['ap'];
1373 encr.crypto_support['sta-wds'] = encr.crypto_support['sta'];
1375 encr.validate = function(section_id, value) {
1376 var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
1377 modeval = modeopt.formvalue(section_id),
1378 modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)],
1379 enctitle = this.vallist[this.keylist.indexOf(value)];
1381 if (value == 'none')
1384 if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value))
1385 return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle);
1387 return this.crypto_support[modeval][value];
1390 else if (hwtype == 'broadcom') {
1391 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1392 crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1393 crypto_modes.push(['psk', 'WPA-PSK', 12]);
1394 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1395 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1398 crypto_modes.push(['none', _('No Encryption'), 0]);
1400 crypto_modes.sort(function(a, b) { return b[2] - a[2] });
1402 for (var i = 0; i < crypto_modes.length; i++) {
1403 var security_level = (crypto_modes[i][2] >= 30) ? _('strong security')
1404 : (crypto_modes[i][2] >= 20) ? _('medium security')
1405 : (crypto_modes[i][2] >= 10) ? _('weak security') : _('open network');
1407 encr.value(crypto_modes[i][0], '%s (%s)'.format(crypto_modes[i][1], security_level));
1411 o = ss.taboption('encryption', form.Value, 'auth_server', _('RADIUS Authentication Server'));
1412 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1414 o.datatype = 'host(0)';
1416 o = ss.taboption('encryption', form.Value, 'auth_port', _('RADIUS Authentication Port'));
1417 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1419 o.datatype = 'port';
1420 o.placeholder = '1812';
1422 o = ss.taboption('encryption', form.Value, 'auth_secret', _('RADIUS Authentication Secret'));
1423 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1427 o = ss.taboption('encryption', form.Value, 'acct_server', _('RADIUS Accounting Server'));
1428 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1430 o.datatype = 'host(0)';
1432 o = ss.taboption('encryption', form.Value, 'acct_port', _('RADIUS Accounting Port'));
1433 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1435 o.datatype = 'port';
1436 o.placeholder = '1813';
1438 o = ss.taboption('encryption', form.Value, 'acct_secret', _('RADIUS Accounting Secret'));
1439 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1443 /* extra RADIUS settings start */
1444 o
= ss
.taboption('encryption', form
.ListValue
, 'dynamic_vlan', _('RADIUS Dynamic VLAN Assignment'), _('Required: Rejects auth if RADIUS server does not provide appropriate VLAN attributes.'));
1445 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1446 o
.value('0', _('Disabled'));
1447 o
.value('1', _('Optional'));
1448 o
.value('2', _('Required'));
1449 o
.write = function (section_id
, value
) {
1450 return this.super('write', [section_id
, (value
== 0) ? null: value
]);
1453 o
= ss
.taboption('encryption', form
.Flag
, 'per_sta_vif', _('RADIUS Per STA VLAN'), _('Each STA is assigned its own AP_VLAN interface.'));
1454 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1456 //hostapd internally defaults to vlan_naming=1 even with dynamic VLAN off
1457 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>.'));
1458 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1460 o
= ss
.taboption('encryption', widgets
.DeviceSelect
, 'vlan_tagged_interface', _('RADIUS VLAN Tagged Interface'), _('E.g. eth0, eth1'));
1461 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1468 o
.noinactive
= true;
1470 o
= ss
.taboption('encryption', form
.Value
, 'vlan_bridge', _('RADIUS VLAN Bridge Naming Scheme'), _('E.g. <code>br-vlan</code> or <code>brvlan</code>.'));
1471 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1473 /* extra RADIUS settings end */
1475 o
= ss
.taboption('encryption', form
.Value
, 'dae_client', _('DAE-Client'), _('Dynamic Authorization Extension client.'));
1476 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1478 o
.datatype
= 'host(0)';
1480 o
= ss
.taboption('encryption', form
.Value
, 'dae_port', _('DAE-Port'), _('Dynamic Authorization Extension port.'));
1481 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1483 o
.datatype
= 'port';
1484 o
.placeholder
= '3799';
1486 o
= ss
.taboption('encryption', form
.Value
, 'dae_secret', _('DAE-Secret'), _('Dynamic Authorization Extension secret.'));
1487 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1491 //WPA(1) has only WPA IE. Only >= WPA2 has RSN IE Preauth frames.
1492 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.'));
1493 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa2', 'wpa3', 'wpa3-mixed'] });
1496 o
= ss
.taboption('encryption', form
.Value
, '_wpa_key', _('Key'));
1497 o
.depends('encryption', 'psk');
1498 o
.depends('encryption', 'psk2');
1499 o
.depends('encryption', 'psk+psk2');
1500 o
.depends('encryption', 'psk-mixed');
1501 o
.depends('encryption', 'sae');
1502 o
.depends('encryption', 'sae-mixed');
1503 o
.datatype
= 'wpakey';
1507 o
.cfgvalue = function(section_id
) {
1508 var key
= uci
.get('wireless', section_id
, 'key');
1509 return /^[1234]$/.test(key
) ? null : key
;
1512 o
.write = function(section_id
, value
) {
1513 uci
.set('wireless', section_id
, 'key', value
);
1514 uci
.unset('wireless', section_id
, 'key1');
1515 uci
.unset('wireless', section_id
, 'key2');
1516 uci
.unset('wireless', section_id
, 'key3');
1517 uci
.unset('wireless', section_id
, 'key4');
1521 o
= ss
.taboption('encryption', form
.ListValue
, '_wep_key', _('Used Key Slot'));
1522 o
.depends('encryption', 'wep-open');
1523 o
.depends('encryption', 'wep-shared');
1524 o
.value('1', _('Key #%d').format(1));
1525 o
.value('2', _('Key #%d').format(2));
1526 o
.value('3', _('Key #%d').format(3));
1527 o
.value('4', _('Key #%d').format(4));
1529 o
.cfgvalue = function(section_id
) {
1530 var slot
= +uci
.get('wireless', section_id
, 'key');
1531 return (slot
>= 1 && slot
<= 4) ? String(slot
) : '';
1534 o
.write = function(section_id
, value
) {
1535 uci
.set('wireless', section_id
, 'key', value
);
1538 for (var slot
= 1; slot
<= 4; slot
++) {
1539 o
= ss
.taboption('encryption', form
.Value
, 'key%d'.format(slot
), _('Key #%d').format(slot
));
1540 o
.depends('encryption', 'wep-open');
1541 o
.depends('encryption', 'wep-shared');
1542 o
.datatype
= 'wepkey';
1546 o
.write = function(section_id
, value
) {
1547 if (value
!= null && (value
.length
== 5 || value
.length
== 13))
1548 value
= 's:%s'.format(value
);
1549 uci
.set('wireless', section_id
, this.option
, value
);
1554 if (hwtype
== 'mac80211') {
1555 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1556 var has_80211r
= L
.hasSystemFeature('hostapd', '11r') || L
.hasSystemFeature('hostapd', 'eap');
1558 o
= ss
.taboption('roaming', form
.Flag
, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1559 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1561 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1564 o
= ss
.taboption('roaming', form
.Value
, 'nasid', _('NAS ID'), _('Used for two different purposes: RADIUS NAS ID and 802.11r R0KH-ID. Not needed with normal WPA(2)-PSK.'));
1565 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1566 o
.depends({ ieee80211r
: '1' });
1569 o
= ss
.taboption('roaming', form
.Value
, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1570 o
.depends({ ieee80211r
: '1' });
1571 o
.placeholder
= '4f57';
1572 o
.datatype
= 'and(hexstring,length(4))';
1575 o
= ss
.taboption('roaming', form
.Value
, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1576 o
.depends({ ieee80211r
: '1' });
1577 o
.placeholder
= '1000';
1578 o
.datatype
= 'range(1000,65535)';
1581 o
= ss
.taboption('roaming', form
.ListValue
, 'ft_over_ds', _('FT protocol'));
1582 o
.depends({ ieee80211r
: '1' });
1583 o
.value('0', _('FT over the Air'));
1584 o
.value('1', _('FT over DS'));
1587 o
= ss
.taboption('roaming', form
.Flag
, 'ft_psk_generate_local', _('Generate PMK locally'), _('When using a PSK, the PMK can be automatically generated. When enabled, the R0/R1 key options below are not applied. Disable this to use the R0 and R1 key options.'));
1588 o
.depends({ ieee80211r
: '1' });
1589 o
.default = o
.enabled
;
1592 o
= ss
.taboption('roaming', form
.Value
, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1593 o
.depends({ ieee80211r
: '1' });
1594 o
.placeholder
= '10000';
1595 o
.datatype
= 'uinteger';
1598 o
= ss
.taboption('roaming', form
.Value
, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1599 o
.depends({ ieee80211r
: '1' });
1600 o
.placeholder
= '00004f577274';
1601 o
.datatype
= 'and(hexstring,length(12))';
1604 o
= ss
.taboption('roaming', form
.Flag
, 'pmk_r1_push', _('PMK R1 Push'));
1605 o
.depends({ ieee80211r
: '1' });
1606 o
.placeholder
= '0';
1609 o
= ss
.taboption('roaming', form
.DynamicList
, 'r0kh', _('External R0 Key Holder List'), _('List of R0KHs in the same Mobility Domain. <br />Format: MAC-address,NAS-Identifier,128-bit key as hex string. <br />This list is used to map R0KH-ID (NAS Identifier) to a destination MAC address when requesting PMK-R1 key from the R0KH that the STA used during the Initial Mobility Domain Association.'));
1610 o
.depends({ ieee80211r
: '1' });
1613 o
= ss
.taboption('roaming', form
.DynamicList
, 'r1kh', _('External R1 Key Holder List'), _ ('List of R1KHs in the same Mobility Domain. <br />Format: MAC-address,R1KH-ID as 6 octets with colons,128-bit key as hex string. <br />This list is used to map R1KH-ID to a destination MAC address when sending PMK-R1 key from the R0KH. This is also the list of authorized R1KHs in the MD that can request PMK-R1 keys.'));
1614 o
.depends({ ieee80211r
: '1' });
1616 // End of 802.11r options
1618 // Probe 802.11k and 802.11v support via EAP support (full hostapd has EAP)
1619 if (L
.hasSystemFeature('hostapd', 'eap')) {
1620 /* 802.11k settings start */ o
=
1621 ss
.taboption('roaming', form
.Flag
, 'ieee80211k', _('802.11k RRM'), _('Radio Resource Measurement - Sends beacons to assist roaming. Not all clients support this.'));
1622 // add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1623 o
.depends('mode', 'ap');
1624 o
.depends('mode', 'ap-wds');
1626 o
= ss
.taboption('roaming', form
.Flag
, 'rrm_neighbor_report', _('Neighbour Report'), _('802.11k: Enable neighbor report via radio measurements.'));
1627 o
.depends({ ieee80211k
: '1' });
1628 o
.default = o
.enabled
;
1630 o
= ss
.taboption('roaming', form
.Flag
, 'rrm_beacon_report', _('Beacon Report'), _('802.11k: Enable beacon report via radio measurements.'));
1631 o
.depends({ ieee80211k
: '1' });
1632 o
.default = o
.enabled
;
1633 /* 802.11k settings end */
1635 /* 802.11v settings start */
1636 o
= ss
.taboption('roaming', form
.ListValue
, 'time_advertisement', _('Time advertisement'), _('802.11v: Time Advertisement in management frames.'));
1637 o
.value('0', _('Disabled'));
1638 o
.value('2', _('Enabled'));
1639 o
.write = function (section_id
, value
) {
1640 return this.super('write', [section_id
, (value
== 2) ? value
: null]);
1643 //Pull current System TZ setting
1644 var tz
= uci
.get('system', '@system[0]', 'timezone');
1645 o
= ss
.taboption('roaming', form
.Value
, 'time_zone', _('Time zone'), _('802.11v: Local Time Zone Advertisement in management frames.'));
1649 o
= ss
.taboption('roaming', form
.Flag
, 'wnm_sleep_mode', _('WNM Sleep Mode'), _('802.11v: Wireless Network Management (WNM) Sleep Mode (extended sleep mode for stations).'));
1652 /* wnm_sleep_mode_no_keys: https://git.openwrt.org/?p=openwrt/openwrt.git;a=commitdiff;h=bf98faaac8ed24cf7d3d93dd4fcd7304d109363b */
1653 o = ss.taboption('roaming', form.Flag, 'wnm_sleep_mode_no_keys', _('WNM Sleep Mode Fixes'), _('802.11v: Wireless Network Management (WNM) Sleep Mode Fixes: Prevents reinstallation attacks.'));
1656 o = ss.taboption('roaming', form.Flag, 'bss_transition', _('BSS Transition'), _('802.11v: Basic Service Set (BSS) transition management.'));
1659 /* in master, but not 21.02.1: proxy_arp */
1660 o
= ss
.taboption('roaming', form
.Flag
, 'proxy_arp', _('ProxyARP'), _('802.11v: Proxy ARP enables non-AP STA to remain in power-save for longer.'));
1663 /* TODO: na_mcast_to_ucast is missing: needs adding to hostapd.sh - nice to have */
1665 /* 802.11v settings end */
1668 if (hwtype
== 'mac80211') {
1669 o
= ss
.taboption('encryption', form
.ListValue
, 'eap_type', _('EAP-Method'));
1670 o
.value('tls', 'TLS');
1671 o
.value('ttls', 'TTLS');
1672 o
.value('peap', 'PEAP');
1673 o
.value('fast', 'FAST');
1674 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1676 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"));
1679 o
.default = o
.disabled
;
1680 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1681 o
.validate = function(section_id
, value
) {
1682 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1683 return _("This option cannot be used because the ca-bundle package is not installed.");
1688 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert', _('Path to CA-Certificate'));
1689 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], ca_cert_usesystem
: ['0'] });
1691 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"));
1692 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1694 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"));
1695 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1697 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)"));
1698 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1700 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)"));
1701 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1703 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert', _('Path to Client-Certificate'));
1704 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1706 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key', _('Path to Private Key'));
1707 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1709 o
= ss
.taboption('encryption', form
.Value
, 'priv_key_pwd', _('Password of Private Key'));
1710 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1713 o
= ss
.taboption('encryption', form
.ListValue
, 'auth', _('Authentication'));
1714 o
.value('PAP', 'PAP');
1715 o
.value('CHAP', 'CHAP');
1716 o
.value('MSCHAP', 'MSCHAP');
1717 o
.value('MSCHAPV2', 'MSCHAPv2');
1718 o
.value('EAP-GTC', 'EAP-GTC');
1719 o
.value('EAP-MD5', 'EAP-MD5');
1720 o
.value('EAP-MSCHAPV2', 'EAP-MSCHAPv2');
1721 o
.value('EAP-TLS', 'EAP-TLS');
1722 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1724 o
.validate = function(section_id
, value
) {
1725 var eo
= this.section
.children
.filter(function(o
) { return o
.option
== 'eap_type' })[0],
1726 ev
= eo
.formvalue(section_id
);
1728 if (ev
!= 'ttls' && (value
== 'PAP' || value
== 'CHAP' || value
== 'MSCHAP' || value
== 'MSCHAPV2'))
1729 return _('This authentication type is not applicable to the selected EAP method.');
1734 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"));
1737 o
.default = o
.disabled
;
1738 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1739 o
.validate = function(section_id
, value
) {
1740 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1741 return _("This option cannot be used because the ca-bundle package is not installed.");
1746 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert2', _('Path to inner CA-Certificate'));
1747 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'], ca_cert2_usesystem
: ['0'] });
1749 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"));
1750 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1752 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"));
1753 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1755 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)"));
1756 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1758 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)"));
1759 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1761 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert2', _('Path to inner Client-Certificate'));
1762 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1764 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key2', _('Path to inner Private Key'));
1765 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1767 o
= ss
.taboption('encryption', form
.Value
, 'priv_key2_pwd', _('Password of inner Private Key'));
1768 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1771 o
= ss
.taboption('encryption', form
.Value
, 'identity', _('Identity'));
1772 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1774 o
= ss
.taboption('encryption', form
.Value
, 'anonymous_identity', _('Anonymous Identity'));
1775 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1777 o
= ss
.taboption('encryption', form
.Value
, 'password', _('Password'));
1778 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1782 if (hwtype
== 'mac80211') {
1783 // ieee802.11w options
1784 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"));
1785 o
.value('0', _('Disabled'));
1786 o
.value('1', _('Optional'));
1787 o
.value('2', _('Required'));
1788 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds', 'sta', 'sta-wds'], encryption
: ['owe', 'psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1791 '2': [{ encryption
: 'sae' }, { encryption
: 'owe' }, { encryption
: 'wpa3' }, { encryption
: 'wpa3-mixed' }],
1792 '1': [{ encryption
: 'sae-mixed'}],
1796 o
.write = function(section_id
, value
) {
1797 if (value
!= this.default)
1798 return form
.ListValue
.prototype.write
.call(this, section_id
, value
);
1800 return form
.ListValue
.prototype.remove
.call(this, section_id
);
1803 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1804 o
.depends('ieee80211w', '1');
1805 o
.depends('ieee80211w', '2');
1806 o
.datatype
= 'uinteger';
1807 o
.placeholder
= '1000';
1810 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1811 o
.depends('ieee80211w', '1');
1812 o
.depends('ieee80211w', '2');
1813 o
.datatype
= 'uinteger';
1814 o
.placeholder
= '201';
1817 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.'));
1818 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1820 if (L
.hasSystemFeature('hostapd', 'wps') && L
.hasSystemFeature('wpasupplicant')) {
1821 o
= ss
.taboption('encryption', form
.Flag
, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1824 o
.default = o
.disabled
;
1825 o
.depends('encryption', 'psk');
1826 o
.depends('encryption', 'psk2');
1827 o
.depends('encryption', 'psk-mixed');
1828 o
.depends('encryption', 'sae');
1829 o
.depends('encryption', 'sae-mixed');
1836 s
.handleRemove = function(section_id
, ev
) {
1837 document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id
)).style
.opacity
= 0.5;
1838 return form
.TypedSection
.prototype.handleRemove
.apply(this, [section_id
, ev
]);
1841 s
.handleScan = function(radioDev
, ev
) {
1842 var table
= E('table', { 'class': 'table' }, [
1843 E('tr', { 'class': 'tr table-titles' }, [
1844 E('th', { 'class': 'th col-2 middle center' }, _('Signal')),
1845 E('th', { 'class': 'th col-4 middle left' }, _('SSID')),
1846 E('th', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1847 E('th', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1848 E('th', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1849 E('th', { 'class': 'th col-3 middle left' }, _('Encryption')),
1850 E('th', { 'class': 'th cbi-section-actions right' }, ' '),
1854 var stop
= E('button', {
1856 'click': L
.bind(this.handleScanStartStop
, this),
1857 'style': 'display:none',
1858 'data-state': 'stop'
1859 }, _('Stop refresh'));
1861 cbi_update_table(table
, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1863 var md
= ui
.showModal(_('Join Network: Wireless Scan'), [
1865 E('div', { 'class': 'right' }, [
1870 'click': L
.bind(this.handleScanAbort
, this)
1875 md
.style
.maxWidth
= '90%';
1876 md
.style
.maxHeight
= 'none';
1878 this.pollFn
= L
.bind(this.handleScanRefresh
, this, radioDev
, {}, table
, stop
);
1880 poll
.add(this.pollFn
);
1884 s
.handleScanRefresh = function(radioDev
, scanCache
, table
, stop
) {
1885 return radioDev
.getScanList().then(L
.bind(function(results
) {
1888 for (var i
= 0; i
< results
.length
; i
++)
1889 scanCache
[results
[i
].bssid
] = results
[i
];
1891 for (var k
in scanCache
)
1892 if (scanCache
[k
].stale
)
1893 results
.push(scanCache
[k
]);
1895 results
.sort(function(a
, b
) {
1896 var diff
= (b
.quality
- a
.quality
) || (a
.channel
- b
.channel
);
1901 if (a
.ssid
< b
.ssid
)
1903 else if (a
.ssid
> b
.ssid
)
1906 if (a
.bssid
< b
.bssid
)
1908 else if (a
.bssid
> b
.bssid
)
1912 for (var i
= 0; i
< results
.length
; i
++) {
1913 var res
= results
[i
],
1914 qv
= res
.quality
|| 0,
1915 qm
= res
.quality_max
|| 0,
1916 q
= (qv
> 0 && qm
> 0) ? Math
.floor((100 / qm
) * qv
) : 0,
1917 s
= res
.stale
? 'opacity:0.5' : '';
1920 E('span', { 'style': s
}, render_signal_badge(q
, res
.signal
, res
.noise
)),
1921 E('span', { 'style': s
}, (res
.ssid
!= null) ? '%h'.format(res
.ssid
) : E('em', _('hidden'))),
1922 E('span', { 'style': s
}, '%d'.format(res
.channel
)),
1923 E('span', { 'style': s
}, '%h'.format(res
.mode
)),
1924 E('span', { 'style': s
}, '%h'.format(res
.bssid
)),
1925 E('span', { 'style': s
}, '%h'.format(network
.formatWifiEncryption(res
.encryption
))),
1926 E('div', { 'class': 'right' }, E('button', {
1927 'class': 'cbi-button cbi-button-action important',
1928 'click': ui
.createHandlerFn(this, 'handleJoin', radioDev
, res
)
1929 }, _('Join Network')))
1935 cbi_update_table(table
, rows
);
1937 stop
.disabled
= false;
1938 stop
.style
.display
= '';
1939 stop
.classList
.remove('spinning');
1943 s
.handleScanStartStop = function(ev
) {
1944 var btn
= ev
.currentTarget
;
1946 if (btn
.getAttribute('data-state') == 'stop') {
1947 poll
.remove(this.pollFn
);
1948 btn
.firstChild
.data
= _('Start refresh');
1949 btn
.setAttribute('data-state', 'start');
1952 poll
.add(this.pollFn
);
1953 btn
.firstChild
.data
= _('Stop refresh');
1954 btn
.setAttribute('data-state', 'stop');
1955 btn
.classList
.add('spinning');
1956 btn
.disabled
= true;
1960 s
.handleScanAbort = function(ev
) {
1961 var md
= dom
.parent(ev
.target
, 'div[aria-modal="true"]');
1963 md
.style
.maxWidth
= '';
1964 md
.style
.maxHeight
= '';
1968 poll
.remove(this.pollFn
);
1973 s
.handleJoinConfirm = function(radioDev
, bss
, form
, ev
) {
1974 var nameopt
= L
.toArray(form
.lookupOption('name', '_new_'))[0],
1975 passopt
= L
.toArray(form
.lookupOption('password', '_new_'))[0],
1976 ssidopt
= L
.toArray(form
.lookupOption('ssid', '_new_'))[0],
1977 bssidopt
= L
.toArray(form
.lookupOption('bssid', '_new_'))[0],
1978 zoneopt
= L
.toArray(form
.lookupOption('zone', '_new_'))[0],
1979 replopt
= L
.toArray(form
.lookupOption('replace', '_new_'))[0],
1980 nameval
= (nameopt
&& nameopt
.isValid('_new_')) ? nameopt
.formvalue('_new_') : null,
1981 passval
= (passopt
&& passopt
.isValid('_new_')) ? passopt
.formvalue('_new_') : null,
1982 ssidval
= (ssidopt
&& ssidopt
.isValid('_new_')) ? ssidopt
.formvalue('_new_') : null,
1983 bssidval
= (bssidopt
&& bssidopt
.isValid('_new_')) ? bssidopt
.formvalue('_new_') : null,
1984 zoneval
= zoneopt
? zoneopt
.formvalue('_new_') : null,
1985 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
1986 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
1987 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' }).length
> 0),
1988 is_sae
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'sae' }).length
> 0);
1990 if (nameval
== null || (passopt
&& passval
== null))
1993 var section_id
= null;
1995 return this.map
.save(function() {
1996 var wifi_sections
= uci
.sections('wireless', 'wifi-iface');
1998 if (replopt
.formvalue('_new_') == '1') {
1999 for (var i
= 0; i
< wifi_sections
.length
; i
++)
2000 if (wifi_sections
[i
].device
== radioDev
.getName())
2001 uci
.remove('wireless', wifi_sections
[i
]['.name']);
2004 if (uci
.get('wireless', radioDev
.getName(), 'disabled') == '1') {
2005 for (var i
= 0; i
< wifi_sections
.length
; i
++)
2006 if (wifi_sections
[i
].device
== radioDev
.getName())
2007 uci
.set('wireless', wifi_sections
[i
]['.name'], 'disabled', '1');
2009 uci
.unset('wireless', radioDev
.getName(), 'disabled');
2012 section_id
= next_free_sid(wifi_sections
.length
);
2014 uci
.add('wireless', 'wifi-iface', section_id
);
2015 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
2016 uci
.set('wireless', section_id
, 'mode', (bss
.mode
== 'Ad-Hoc') ? 'adhoc' : 'sta');
2017 uci
.set('wireless', section_id
, 'network', nameval
);
2019 if (bss
.ssid
!= null) {
2020 uci
.set('wireless', section_id
, 'ssid', bss
.ssid
);
2022 if (bssidval
== '1')
2023 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
2025 else if (bss
.bssid
!= null) {
2026 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
2029 if (ssidval
!= null)
2030 uci
.set('wireless', section_id
, 'ssid', ssidval
);
2033 uci
.set('wireless', section_id
, 'encryption', 'sae');
2034 uci
.set('wireless', section_id
, 'key', passval
);
2037 for (var i
= enc
.wpa
.length
- 1; i
>= 0; i
--) {
2038 if (enc
.wpa
[i
] == 2) {
2039 uci
.set('wireless', section_id
, 'encryption', 'psk2');
2042 else if (enc
.wpa
[i
] == 1) {
2043 uci
.set('wireless', section_id
, 'encryption', 'psk');
2048 uci
.set('wireless', section_id
, 'key', passval
);
2051 uci
.set('wireless', section_id
, 'encryption', 'wep-open');
2052 uci
.set('wireless', section_id
, 'key', '1');
2053 uci
.set('wireless', section_id
, 'key1', passval
);
2056 uci
.set('wireless', section_id
, 'encryption', 'none');
2059 return network
.addNetwork(nameval
, { proto
: 'dhcp' }).then(function(net
) {
2060 firewall
.deleteNetwork(net
.getName());
2062 var zonePromise
= zoneval
2063 ? firewall
.getZone(zoneval
).then(function(zone
) { return zone
|| firewall
.addZone(zoneval
) })
2064 : Promise
.resolve();
2066 return zonePromise
.then(function(zone
) {
2068 zone
.addNetwork(net
.getName());
2071 }).then(L
.bind(function() {
2072 ui
.showModal(null, E('p', { 'class': 'spinning' }, [ _('Loading data…') ]));
2074 return this.renderMoreOptionsModal(section_id
);
2078 s
.handleJoin = function(radioDev
, bss
, ev
) {
2079 poll
.remove(this.pollFn
);
2081 var m2
= new form
.Map('wireless'),
2082 s2
= m2
.section(form
.NamedSection
, '_new_'),
2083 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
2084 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
2085 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' || a
== 'sae' })),
2086 replace
, passphrase
, name
, bssid
, zone
;
2088 var nameUsed = function(name
) {
2089 var s
= uci
.get('network', name
);
2090 if (s
!= null && s
['.type'] != 'interface')
2093 var net
= (s
!= null) ? network
.instantiateNetwork(name
) : null;
2094 return (net
!= null && !net
.isEmpty());
2097 s2
.render = function() {
2098 return Promise
.all([
2100 this.renderUCISection('_new_')
2101 ]).then(this.renderContents
.bind(this));
2104 if (bss
.ssid
== null) {
2105 name
= s2
.option(form
.Value
, 'ssid', _('Network SSID'), _('The correct SSID must be manually specified when joining a hidden wireless network'));
2106 name
.rmempty
= false;
2109 replace
= s2
.option(form
.Flag
, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
2111 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>'));
2112 name
.datatype
= 'uciname';
2113 name
.default = 'wwan';
2114 name
.rmempty
= false;
2115 name
.validate = function(section_id
, value
) {
2116 if (nameUsed(value
))
2117 return _('The network name is already used');
2122 for (var i
= 2; nameUsed(name
.default); i
++)
2123 name
.default = 'wwan%d'.format(i
);
2125 if (is_wep
|| is_psk
) {
2126 passphrase
= s2
.option(form
.Value
, 'password', is_wep
? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
2127 passphrase
.datatype
= is_wep
? 'wepkey' : 'wpakey';
2128 passphrase
.password
= true;
2129 passphrase
.rmempty
= false;
2132 if (bss
.ssid
!= null) {
2133 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
));
2134 bssid
.default = '0';
2137 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.'));
2138 zone
.default = 'wan';
2140 return m2
.render().then(L
.bind(function(nodes
) {
2141 ui
.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss
.ssid
)), [
2143 E('div', { 'class': 'right' }, [
2146 'click': ui
.hideModal
2147 }, _('Cancel')), ' ',
2149 'class': 'cbi-button cbi-button-positive important',
2150 'click': ui
.createHandlerFn(this, 'handleJoinConfirm', radioDev
, bss
, m2
)
2153 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase
|| name
).cbid('_new_'))).focus();
2157 s
.handleAdd = function(radioDev
, ev
) {
2158 var section_id
= next_free_sid(uci
.sections('wireless', 'wifi-iface').length
);
2160 uci
.unset('wireless', radioDev
.getName(), 'disabled');
2162 uci
.add('wireless', 'wifi-iface', section_id
);
2163 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
2164 uci
.set('wireless', section_id
, 'mode', 'ap');
2165 uci
.set('wireless', section_id
, 'ssid', 'OpenWrt');
2166 uci
.set('wireless', section_id
, 'encryption', 'none');
2168 this.addedSection
= section_id
;
2169 return this.renderMoreOptionsModal(section_id
);
2172 o
= s
.option(form
.DummyValue
, '_badge');
2173 o
.modalonly
= false;
2174 o
.textvalue = function(section_id
) {
2175 var inst
= this.section
.lookupRadioOrNetwork(section_id
),
2176 node
= E('div', { 'class': 'center' });
2178 if (inst
.getWifiNetworks
)
2179 node
.appendChild(render_radio_badge(inst
));
2181 node
.appendChild(render_network_badge(inst
));
2186 o
= s
.option(form
.DummyValue
, '_stat');
2187 o
.modalonly
= false;
2188 o
.textvalue = function(section_id
) {
2189 var inst
= this.section
.lookupRadioOrNetwork(section_id
);
2191 if (inst
.getWifiNetworks
)
2192 return render_radio_status(inst
, this.section
.wifis
.filter(function(e
) {
2193 return (e
.getWifiDeviceName() == inst
.getName());
2196 return render_network_status(inst
);
2199 return m
.render().then(L
.bind(function(m
, nodes
) {
2200 poll
.add(L
.bind(function() {
2201 var section_ids
= m
.children
[0].cfgsections(),
2202 tasks
= [ network
.getHostHints(), network
.getWifiDevices() ];
2204 for (var i
= 0; i
< section_ids
.length
; i
++) {
2205 var row
= nodes
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids
[i
])),
2206 dsc
= row
.querySelector('[data-name="_stat"] > div'),
2207 btns
= row
.querySelectorAll('.cbi-section-actions button');
2209 if (dsc
.getAttribute('restart') == '') {
2210 dsc
.setAttribute('restart', '1');
2211 tasks
.push(fs
.exec('/sbin/wifi', ['up', section_ids
[i
]]).catch(function(e
) {
2212 ui
.addNotification(null, E('p', e
.message
));
2215 else if (dsc
.getAttribute('restart') == '1') {
2216 dsc
.removeAttribute('restart');
2217 btns
[0].classList
.remove('spinning');
2218 btns
[0].disabled
= false;
2222 return Promise
.all(tasks
)
2223 .then(L
.bind(function(hosts_radios
) {
2226 for (var i
= 0; i
< hosts_radios
[1].length
; i
++)
2227 tasks
.push(hosts_radios
[1][i
].getWifiNetworks());
2229 return Promise
.all(tasks
).then(function(data
) {
2230 hosts_radios
[2] = [];
2232 for (var i
= 0; i
< data
.length
; i
++)
2233 hosts_radios
[2].push
.apply(hosts_radios
[2], data
[i
]);
2235 return hosts_radios
;
2238 .then(L
.bind(function(hosts_radios_wifis
) {
2241 for (var i
= 0; i
< hosts_radios_wifis
[2].length
; i
++)
2242 tasks
.push(hosts_radios_wifis
[2][i
].getAssocList());
2244 return Promise
.all(tasks
).then(function(data
) {
2245 hosts_radios_wifis
[3] = [];
2247 for (var i
= 0; i
< data
.length
; i
++) {
2248 var wifiNetwork
= hosts_radios_wifis
[2][i
],
2249 radioDev
= hosts_radios_wifis
[1].filter(function(d
) { return d
.getName() == wifiNetwork
.getWifiDeviceName() })[0];
2251 for (var j
= 0; j
< data
[i
].length
; j
++)
2252 hosts_radios_wifis
[3].push(Object
.assign({ radio
: radioDev
, network
: wifiNetwork
}, data
[i
][j
]));
2255 return hosts_radios_wifis
;
2258 .then(L
.bind(this.poll_status
, this, nodes
));
2261 var table
= E('table', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [
2262 E('tr', { 'class': 'tr table-titles' }, [
2263 E('th', { 'class': 'th nowrap' }, _('Network')),
2264 E('th', { 'class': 'th hide-xs' }, _('MAC address')),
2265 E('th', { 'class': 'th' }, _('Host')),
2266 E('th', { 'class': 'th' }, _('Signal / Noise')),
2267 E('th', { 'class': 'th' }, _('RX Rate / TX Rate'))
2271 cbi_update_table(table
, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
2273 return E([ nodes
, E('h3', _('Associated Stations')), table
]);