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 ] : [],
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
}, {});
347 'n', 'N', hwmodelist
.n
,
348 'ac', 'AC', hwmodelist
.ac
,
349 'ax', 'AX', 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
396 setValues: function(sel
, vals
) {
398 sel
.vals
.selected
= sel
.selectedIndex
;
400 while (sel
.options
[0])
403 for (var i
= 0; vals
&& i
< vals
.length
; i
+= 3)
405 sel
.add(E('option', { value
: vals
[i
+0] }, [ vals
[i
+1] ]));
407 if (vals
&& !isNaN(vals
.selected
))
408 sel
.selectedIndex
= vals
.selected
;
410 sel
.parentNode
.style
.display
= (sel
.options
.length
<= 1) ? 'none' : '';
414 toggleWifiMode: function(elem
) {
415 this.toggleWifiHTMode(elem
);
416 this.toggleWifiBand(elem
);
419 toggleWifiHTMode: function(elem
) {
420 var mode
= elem
.querySelector('.mode');
421 var bwdt
= elem
.querySelector('.htmode');
423 this.setValues(bwdt
, this.htmodes
[mode
.value
]);
426 toggleWifiBand: function(elem
) {
427 var mode
= elem
.querySelector('.mode');
428 var band
= elem
.querySelector('.band');
430 this.setValues(band
, this.bands
[mode
.value
]);
431 this.toggleWifiChannel(elem
);
433 this.map
.checkDepends();
436 toggleWifiChannel: function(elem
) {
437 var band
= elem
.querySelector('.band');
438 var chan
= elem
.querySelector('.channel');
440 this.setValues(chan
, this.channels
[band
.value
]);
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');
453 this.setValues(mode
, this.modes
);
455 if (/HE20|HE40|HE80|HE160/.test(htval
))
457 else if (/VHT20|VHT40|VHT80|VHT160/.test(htval
))
459 else if (/HT20|HT40/.test(htval
))
464 this.toggleWifiMode(elem
);
467 this.useBandOption
= false;
475 this.useBandOption
= true;
477 band
.value
= bandval
;
480 this.toggleWifiBand(elem
);
483 chan
.value
= chval
|| (chan
.options
[0] ? chan
.options
[0].value
: 'auto');
488 renderWidget: function(section_id
, option_index
, cfgvalue
) {
492 E('label', { 'style': 'float:left; margin-right:3px' }, [
496 'style': 'width:auto',
497 'change': L
.bind(this.toggleWifiMode
, this, elem
),
498 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
501 E('label', { 'style': 'float:left; margin-right:3px' }, [
505 'style': 'width:auto',
506 'change': L
.bind(this.toggleWifiBand
, this, elem
),
507 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
510 E('label', { 'style': 'float:left; margin-right:3px' }, [
511 _('Channel'), E('br'),
514 'style': 'width:auto',
515 'change': L
.bind(this.map
.checkDepends
, this.map
),
516 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
519 E('label', { 'style': 'float:left; margin-right:3px' }, [
523 'style': 'width:auto',
524 'change': L
.bind(this.map
.checkDepends
, this.map
),
525 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
528 E('br', { 'style': 'clear:left' })
531 return this.setInitialValues(section_id
, elem
);
534 cfgvalue: function(section_id
) {
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')
542 formvalue: function(section_id
) {
543 var node
= this.map
.findElement('data-field', this.cbid(section_id
));
546 node
.querySelector('.htmode').value
,
547 node
.querySelector('.band').value
,
548 node
.querySelector('.channel').value
552 write: function(section_id
, value
) {
553 uci
.set('wireless', section_id
, 'htmode', value
[0] || null);
555 if (this.useBandOption
)
556 uci
.set('wireless', section_id
, 'band', value
[1]);
558 uci
.set('wireless', section_id
, 'hwmode', (value
[1] == '2g') ? '11g' : '11a');
560 uci
.set('wireless', section_id
, 'channel', value
[2]);
564 var CBIWifiTxPowerValue
= form
.ListValue
.extend({
565 callTxPowerList
: rpc
.declare({
567 method
: 'txpowerlist',
568 params
: [ 'device' ],
569 expect
: { results
: [] }
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;
577 this.value('', _('driver default'));
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
));
582 return form
.ListValue
.prototype.load
.apply(this, [section_id
]);
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';
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
: '?') : ''
600 var CBIWifiCountryValue
= form
.Value
.extend({
601 callCountryList
: rpc
.declare({
603 method
: 'countrylist',
604 params
: [ 'device' ],
605 expect
: { results
: [] }
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'));
613 for (var i
= 0; i
< countrylist
.length
; i
++)
614 this.value(countrylist
[i
].iso3166
, '%s - %s'.format(countrylist
[i
].iso3166
, countrylist
[i
].country
));
617 return form
.Value
.prototype.load
.apply(this, [section_id
]);
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.');
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
]);
635 poll_status: function(map
, data
) {
636 var rows
= map
.querySelectorAll('.cbi-section-table-row[data-sid]');
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');
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() })));
652 dom
.content(badge
, render_network_badge(radioNet
));
653 dom
.content(stat
, render_network_status(radioNet
));
656 if (stat
.hasAttribute('restart'))
657 dom
.content(stat
, E('em', _('Device is restarting…')));
659 btns
[0].disabled
= isReadonlyView
|| busy
;
660 btns
[1].disabled
= (isReadonlyView
&& radioDev
) || busy
;
661 btns
[2].disabled
= isReadonlyView
|| busy
;
664 var table
= document
.querySelector('#wifi_assoclist_table'),
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
);
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
);
681 hint
= name
|| ipv4
|| ipv6
|| '?';
685 'class': 'ifacebadge',
686 'data-ifname': bss
.network
.getIfname(),
687 'data-ssid': bss
.network
.getSSID()
690 'src': L
.resource('icons/wifi%s.png').format(bss
.network
.isUp() ? '' : '_disabled'),
691 'title': bss
.radio
.getI18n()
694 ' %s '.format(bss
.network
.getShortName()),
695 E('small', '(%s)'.format(bss
.network
.getIfname()))
700 render_signal_badge(Math
.min((bss
.signal
+ 110) / 70 * 100, 100), bss
.signal
, bss
.noise
),
702 E('span', format_wifirate(bss
.rx
)),
704 E('span', format_wifirate(bss
.tx
))
708 if (bss
.network
.isClientDisconnectSupported()) {
709 if (table
.firstElementChild
.childNodes
.length
< 6)
710 table
.firstElementChild
.appendChild(E('th', { 'class': 'th cbi-section-actions'}));
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();
720 net
.disconnectClient(mac
, true, 5, 60000);
721 }, this, bss
.network
, bss
.mac
),
722 'disabled': isReadonlyView
|| null
723 }, [ _('Disconnect') ]));
732 cbi_update_table(table
, trows
, E('em', _('No information available')));
734 var stat
= document
.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large');
737 render_modal_status(stat
, data
[2].filter(function(n
) { return n
.getName() == stat
.getAttribute('data-network') })[0]);
739 return network
.flushCache();
745 uci
.load('wireless'),
750 checkAnonymousSections: function() {
751 var wifiIfaces
= uci
.sections('wireless', 'wifi-iface');
753 for (var i
= 0; i
< wifiIfaces
.length
; i
++)
754 if (wifiIfaces
[i
]['.anonymous'])
760 callUciRename
: rpc
.declare({
763 params
: [ 'config', 'section', 'name' ]
767 if (this.checkAnonymousSections())
768 return this.renderMigration();
770 return this.renderOverview();
773 handleMigration: function(ev
) {
774 var wifiIfaces
= uci
.sections('wireless', 'wifi-iface'),
778 for (var i
= 0; i
< wifiIfaces
.length
; i
++) {
779 if (!wifiIfaces
[i
]['.anonymous'])
782 var new_name
= next_free_sid(id_offset
);
784 tasks
.push(this.callUciRename('wireless', wifiIfaces
[i
]['.name'], new_name
));
785 id_offset
= +new_name
.substring(7) + 1;
788 return Promise
.all(tasks
)
789 .then(L
.bind(ui
.changes
.init
, ui
.changes
))
790 .then(L
.bind(ui
.changes
.apply
, ui
.changes
));
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' },
799 'class': 'btn cbi-button-action important',
800 'click': ui
.createHandlerFn(this, 'handleMigration')
805 renderOverview: function() {
808 m
= new form
.Map('wireless');
812 s
= m
.section(form
.GridSection
, 'wifi-device', _('Wireless Overview'));
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();
824 for (var i
= 0; i
< radios
.length
; i
++)
825 tasks
.push(radios
[i
].getWifiNetworks());
827 return Promise
.all(tasks
);
828 }, this)).then(L
.bind(function(data
) {
831 for (var i
= 0; i
< data
.length
; i
++)
832 this.wifis
.push
.apply(this.wifis
, data
[i
]);
836 s
.cfgsections = function() {
839 for (var i
= 0; i
< this.radios
.length
; i
++) {
840 rv
.push(this.radios
[i
].getName());
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());
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');
855 s
.lookupRadioOrNetwork = function(section_id
) {
856 var radioDev
= this.radios
.filter(function(r
) { return r
.getName() == section_id
})[0];
860 var radioNet
= this.wifis
.filter(function(w
) { return w
.getName() == section_id
})[0];
867 s
.renderRowActions = function(section_id
) {
868 var inst
= this.lookupRadioOrNetwork(section_id
), btns
;
870 if (inst
.getWifiNetworks
) {
873 'class': 'cbi-button cbi-button-neutral',
874 'title': _('Restart radio interface'),
875 'click': ui
.createHandlerFn(this, radio_restart
, section_id
)
878 'class': 'cbi-button cbi-button-action important',
879 'title': _('Find and join network'),
880 'click': ui
.createHandlerFn(this, 'handleScan', inst
)
883 'class': 'cbi-button cbi-button-add',
884 'title': _('Provide new network'),
885 'click': ui
.createHandlerFn(this, 'handleAdd', inst
)
890 var isDisabled
= (inst
.get('disabled') == '1' ||
891 uci
.get('wireless', inst
.getWifiDeviceName(), 'disabled') == '1');
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')),
900 'class': 'cbi-button cbi-button-action important',
901 'title': _('Edit this network'),
902 'click': ui
.createHandlerFn(this, 'renderMoreOptionsModal', section_id
)
905 'class': 'cbi-button cbi-button-negative remove',
906 'title': _('Delete this network'),
907 'click': ui
.createHandlerFn(this, 'handleRemove', section_id
)
912 return E('td', { 'class': 'td middle cbi-section-actions' }, E('div', btns
));
915 s
.addModalOptions = function(s
) {
916 return network
.getWifiNetwork(s
.section
).then(function(radioNet
) {
917 var hwtype
= uci
.get('wireless', radioNet
.getWifiDeviceName(), 'type');
920 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
924 ss
.tab('general', _('General Setup'));
925 ss
.tab('advanced', _('Advanced Settings'));
927 var isDisabled
= (radioNet
.get('disabled') == '1' ||
928 uci
.get('wireless', radioNet
.getWifiDeviceName(), 'disabled') == 1);
930 o
= ss
.taboption('general', form
.DummyValue
, '_wifistat_modal', _('Status'));
931 o
.cfgvalue
= L
.bind(function(radioNet
) {
932 return render_modal_status(null, radioNet
);
934 o
.write = function() {};
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
);
941 o
= ss
.taboption('general', CBIWifiFrequencyValue
, '_freq', '<br />' + _('Operating frequency'));
942 o
.ucisection
= s
.section
;
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});
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
;
951 o
= ss
.taboption('advanced', CBIWifiCountryValue
, 'country', _('Country Code'));
952 o
.wifiNetwork
= radioNet
;
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'));
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';
964 o
= ss
.taboption('advanced', form
.Value
, 'frag', _('Fragmentation Threshold'));
965 o
.datatype
= 'min(256)';
966 o
.placeholder
= _('off');
968 o
= ss
.taboption('advanced', form
.Value
, 'rts', _('RTS/CTS Threshold'));
969 o
.datatype
= 'uinteger';
970 o
.placeholder
= _('off');
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!'));
975 o
= ss
.taboption('advanced', form
.Value
, 'beacon_int', _('Beacon Interval'));
976 o
.datatype
= 'range(15,65535)';
982 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getName(), 'wifi-iface', _('Interface Configuration'));
986 ss
.tab('general', _('General Setup'));
987 ss
.tab('encryption', _('Wireless Security'));
988 ss
.tab('macfilter', _('MAC-Filter'));
989 ss
.tab('advanced', _('Advanced Settings'));
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'));
996 o
= ss
.taboption('general', form
.Value
, 'mesh_id', _('Mesh Id'));
997 o
.depends('mode', 'mesh');
999 o
= ss
.taboption('advanced', form
.Flag
, 'mesh_fwding', _('Forward mesh peer traffic'));
1002 o
.depends('mode', 'mesh');
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'));
1007 o
.datatype
= 'range(-255,1)';
1008 o
.depends('mode', 'mesh');
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');
1021 o
= ss
.taboption('general', form
.Value
, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
1022 o
.datatype
= 'macaddr';
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.'));
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
}, {}),
1032 values
= L
.toArray(value
),
1035 for (var i
= 0; i
< values
.length
; i
++) {
1036 new_networks
[values
[i
]] = true;
1038 if (old_networks
[values
[i
]])
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
) {
1045 if (!net
.isEmpty()) {
1046 var target_dev
= net
.getDevice();
1048 /* Resolve parent interface of vlan */
1049 while (target_dev
&& target_dev
.getType() == 'vlan')
1050 target_dev
= target_dev
.getParent();
1052 if (!target_dev
|| target_dev
.getType() != 'bridge')
1053 net
.set('type', 'bridge');
1061 for (var name
in old_networks
)
1062 if (!new_networks
[name
])
1063 tasks
.push(network
.getNetwork(name
).then(L
.bind(function(dev
, net
) {
1065 net
.deleteDevice(dev
);
1068 return Promise
.all(tasks
);
1072 if (hwtype
== 'mac80211') {
1073 var mode
= ss
.children
[0],
1074 bssid
= ss
.children
[5],
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'))
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]);
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.'));
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).'));
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.'));
1101 o = ss.taboption('advanced', form.Flag, 'bss_transition', _('BSS Transition'), _('802.11v: Basic Service Set (BSS) transition management.'));
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.'));
1108 /* TODO: na_mcast_to_ucast is missing: needs adding to hostapd.sh - nice to have */
1110 /* 802.11v settings end */
1112 mode
.value('mesh', '802.11s');
1113 mode
.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
1114 mode
.value('monitor', _('Monitor'));
1116 bssid
.depends('mode', 'adhoc');
1117 bssid
.depends('mode', 'sta');
1118 bssid
.depends('mode', 'sta-wds');
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'));
1127 o
= ss
.taboption('macfilter', form
.DynamicList
, 'maclist', _('MAC-List'));
1128 o
.datatype
= 'macaddr';
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]);
1138 return form
.DynamicList
.prototype.load
.apply(this, [section_id
]);
1142 mode
.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
1143 mode
.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
1145 mode
.write = function(section_id
, value
) {
1148 uci
.set('wireless', section_id
, 'mode', 'ap');
1149 uci
.set('wireless', section_id
, 'wds', '1');
1153 uci
.set('wireless', section_id
, 'mode', 'sta');
1154 uci
.set('wireless', section_id
, 'wds', '1');
1158 uci
.set('wireless', section_id
, 'mode', value
);
1159 uci
.unset('wireless', section_id
, 'wds');
1164 mode
.cfgvalue = function(section_id
) {
1165 var mode
= uci
.get('wireless', section_id
, 'mode'),
1166 wds
= uci
.get('wireless', section_id
, 'wds');
1168 if (mode
== 'ap' && wds
)
1170 else if (mode
== 'sta' && wds
)
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');
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
;
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.'));
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');
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
;
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
;
1206 /* 802.11k settings end */
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');
1212 o
= ss
.taboption('advanced', form
.Value
, 'ifname', _('Interface name'), _('Override default interface name'));
1214 o
.placeholder
= radioNet
.getIfname();
1215 if (/^radio\d+\.network/.test(o
.placeholder
))
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'));
1220 o
.placeholder
= radioNet
.getActiveBSSID();
1221 o
.datatype
= 'macaddr';
1223 o
= ss
.taboption('advanced', form
.Flag
, 'short_preamble', _('Short Preamble'));
1224 o
.default = o
.enabled
;
1226 o
= ss
.taboption('advanced', form
.Value
, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
1229 o
.datatype
= 'range(1,255)';
1231 o
= ss
.taboption('advanced', form
.Value
, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
1233 o
.placeholder
= 600;
1234 o
.datatype
= 'uinteger';
1236 o
= ss
.taboption('advanced', form
.Flag
, 'skip_inactivity_poll', _('Disable Inactivity Polling'));
1238 o
.datatype
= 'uinteger';
1240 o
= ss
.taboption('advanced', form
.Value
, 'max_inactivity', _('Station inactivity limit'), _('802.11v: BSS Max Idle. Units: seconds.'));
1242 o
.placeholder
= 300;
1243 o
.datatype
= 'uinteger';
1245 o
= ss
.taboption('advanced', form
.Value
, 'max_listen_interval', _('Maximum allowed Listen Interval'));
1247 o
.placeholder
= 65535;
1248 o
.datatype
= 'uinteger';
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
;
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');
1264 o
.cfgvalue = function(section_id
) {
1265 var v
= String(uci
.get('wireless', section_id
, 'encryption'));
1268 else if (v
.match(/\+/))
1269 return v
.replace(/\+.+$/, '');
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
);
1277 if (value
== 'wpa' || value
== 'wpa2' || value
== 'wpa3' || value
== 'wpa3-mixed')
1278 uci
.unset('wireless', section_id
, 'key');
1280 if (co
.isActive(section_id
) && e
&& (c
== 'tkip' || c
== 'ccmp' || c
== 'tkip+ccmp'))
1283 uci
.set('wireless', section_id
, 'encryption', e
);
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
;
1301 o
.cfgvalue = function(section_id
) {
1302 var v
= String(uci
.get('wireless', section_id
, 'encryption'));
1303 if (v
.match(/\+/)) {
1304 v
= v
.replace(/^[^+]+\+/, '');
1307 else if (v
== 'tkip+aes' || v
== 'aes+tkip' || v
== 'ccmp+tkip')
1314 var crypto_modes
= [];
1316 if (hwtype
== 'mac80211') {
1317 var has_supplicant
= L
.hasSystemFeature('wpasupplicant'),
1318 has_hostapd
= L
.hasSystemFeature('hostapd');
1320 // Probe EAP support
1321 var has_ap_eap
= L
.hasSystemFeature('hostapd', 'eap'),
1322 has_sta_eap
= L
.hasSystemFeature('wpasupplicant', 'eap');
1324 // Probe SAE support
1325 var has_ap_sae
= L
.hasSystemFeature('hostapd', 'sae'),
1326 has_sta_sae
= L
.hasSystemFeature('wpasupplicant', 'sae');
1328 // Probe OWE support
1329 var has_ap_owe
= L
.hasSystemFeature('hostapd', 'owe'),
1330 has_sta_owe
= L
.hasSystemFeature('wpasupplicant', 'owe');
1332 // Probe Suite-B support
1333 var has_ap_eap192
= L
.hasSystemFeature('hostapd', 'suiteb192'),
1334 has_sta_eap192
= L
.hasSystemFeature('wpasupplicant', 'suiteb192');
1336 // Probe WEP support
1337 var has_ap_wep
= L
.hasSystemFeature('hostapd', 'wep'),
1338 has_sta_wep
= L
.hasSystemFeature('wpasupplicant', 'wep');
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]);
1346 encr
.description
= _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
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]);
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]);
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]);
1365 crypto_modes
.push(['wpa2', 'WPA2-EAP', 34]);
1366 crypto_modes
.push(['wpa', 'WPA-EAP', 20]);
1369 if (has_ap_owe
|| has_sta_owe
) {
1370 crypto_modes
.push(['owe', 'OWE', 1]);
1373 encr
.crypto_support
= {
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')
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')
1405 'psk': has_supplicant
|| _('Requires wpa-supplicant'),
1406 'psk2': has_supplicant
|| _('Requires wpa-supplicant'),
1407 'psk-mixed': has_supplicant
|| _('Requires wpa-supplicant'),
1410 'sae': has_sta_sae
|| _('Requires wpa-supplicant with SAE support')
1422 encr
.crypto_support
['ap-wds'] = encr
.crypto_support
['ap'];
1423 encr
.crypto_support
['sta-wds'] = encr
.crypto_support
['sta'];
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
)];
1431 if (value
== 'none')
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
);
1437 return this.crypto_support
[modeval
][value
];
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]);
1448 crypto_modes
.push(['none', _('No Encryption'), 0]);
1450 crypto_modes
.sort(function(a
, b
) { return b
[2] - a
[2] });
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');
1457 encr
.value(crypto_modes
[i
][0], '%s (%s)'.format(crypto_modes
[i
][1], security_level
));
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'] });
1464 o
.datatype
= 'host(0)';
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'] });
1469 o
.datatype
= 'port';
1470 o
.placeholder
= '1812';
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'] });
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'] });
1480 o
.datatype
= 'host(0)';
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'] });
1485 o
.datatype
= 'port';
1486 o
.placeholder
= '1813';
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'] });
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'] });
1496 o
.datatype
= 'host(0)';
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'] });
1501 o
.datatype
= 'port';
1502 o
.placeholder
= '3799';
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'] });
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'] });
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';
1525 o
.cfgvalue = function(section_id
) {
1526 var key
= uci
.get('wireless', section_id
, 'key');
1527 return /^[1234]$/.test(key
) ? null : key
;
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');
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));
1547 o
.cfgvalue = function(section_id
) {
1548 var slot
= +uci
.get('wireless', section_id
, 'key');
1549 return (slot
>= 1 && slot
<= 4) ? String(slot
) : '';
1552 o
.write = function(section_id
, value
) {
1553 uci
.set('wireless', section_id
, 'key', value
);
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';
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
);
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');
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'] });
1579 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
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' });
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))';
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)';
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'));
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
;
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';
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))';
1622 o
= ss
.taboption('encryption', form
.Flag
, 'pmk_r1_push', _('PMK R1 Push'));
1623 o
.depends({ ieee80211r
: '1' });
1624 o
.placeholder
= '0';
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' });
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' });
1634 // End of 802.11r options
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'] });
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"));
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.");
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'] });
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'] });
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'] });
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'] });
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'] });
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'] });
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'] });
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'] });
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'] });
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
);
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.');
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"));
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.");
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'] });
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'] });
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'] });
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'] });
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'] });
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'] });
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'] });
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'] });
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'] });
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'] });
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'] });
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'] });
1758 '2': [{ encryption
: 'sae' }, { encryption
: 'owe' }, { encryption
: 'wpa3' }, { encryption
: 'wpa3-mixed' }],
1759 '1': [{ encryption
: 'sae-mixed'}],
1763 o
.write = function(section_id
, value
) {
1764 if (value
!= this.default)
1765 return form
.ListValue
.prototype.write
.call(this, section_id
, value
);
1767 return form
.ListValue
.prototype.remove
.call(this, section_id
);
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';
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';
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'] });
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'))
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');
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
]);
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' }, ' '),
1821 var stop
= E('button', {
1823 'click': L
.bind(this.handleScanStartStop
, this),
1824 'style': 'display:none',
1825 'data-state': 'stop'
1826 }, _('Stop refresh'));
1828 cbi_update_table(table
, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1830 var md
= ui
.showModal(_('Join Network: Wireless Scan'), [
1832 E('div', { 'class': 'right' }, [
1837 'click': L
.bind(this.handleScanAbort
, this)
1842 md
.style
.maxWidth
= '90%';
1843 md
.style
.maxHeight
= 'none';
1845 this.pollFn
= L
.bind(this.handleScanRefresh
, this, radioDev
, {}, table
, stop
);
1847 poll
.add(this.pollFn
);
1851 s
.handleScanRefresh = function(radioDev
, scanCache
, table
, stop
) {
1852 return radioDev
.getScanList().then(L
.bind(function(results
) {
1855 for (var i
= 0; i
< results
.length
; i
++)
1856 scanCache
[results
[i
].bssid
] = results
[i
];
1858 for (var k
in scanCache
)
1859 if (scanCache
[k
].stale
)
1860 results
.push(scanCache
[k
]);
1862 results
.sort(function(a
, b
) {
1863 var diff
= (b
.quality
- a
.quality
) || (a
.channel
- b
.channel
);
1868 if (a
.ssid
< b
.ssid
)
1870 else if (a
.ssid
> b
.ssid
)
1873 if (a
.bssid
< b
.bssid
)
1875 else if (a
.bssid
> b
.bssid
)
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' : '';
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')))
1902 cbi_update_table(table
, rows
);
1904 stop
.disabled
= false;
1905 stop
.style
.display
= '';
1906 stop
.classList
.remove('spinning');
1910 s
.handleScanStartStop = function(ev
) {
1911 var btn
= ev
.currentTarget
;
1913 if (btn
.getAttribute('data-state') == 'stop') {
1914 poll
.remove(this.pollFn
);
1915 btn
.firstChild
.data
= _('Start refresh');
1916 btn
.setAttribute('data-state', 'start');
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;
1927 s
.handleScanAbort = function(ev
) {
1928 var md
= dom
.parent(ev
.target
, 'div[aria-modal="true"]');
1930 md
.style
.maxWidth
= '';
1931 md
.style
.maxHeight
= '';
1935 poll
.remove(this.pollFn
);
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);
1957 if (nameval
== null || (passopt
&& passval
== null))
1960 var section_id
= null;
1962 return this.map
.save(function() {
1963 var wifi_sections
= uci
.sections('wireless', 'wifi-iface');
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']);
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');
1976 uci
.unset('wireless', radioDev
.getName(), 'disabled');
1979 section_id
= next_free_sid(wifi_sections
.length
);
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
);
1986 if (bss
.ssid
!= null) {
1987 uci
.set('wireless', section_id
, 'ssid', bss
.ssid
);
1989 if (bssidval
== '1')
1990 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
1992 else if (bss
.bssid
!= null) {
1993 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
1996 if (ssidval
!= null)
1997 uci
.set('wireless', section_id
, 'ssid', ssidval
);
2000 uci
.set('wireless', section_id
, 'encryption', 'sae');
2001 uci
.set('wireless', section_id
, 'key', passval
);
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');
2009 else if (enc
.wpa
[i
] == 1) {
2010 uci
.set('wireless', section_id
, 'encryption', 'psk');
2015 uci
.set('wireless', section_id
, 'key', passval
);
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
);
2023 uci
.set('wireless', section_id
, 'encryption', 'none');
2026 return network
.addNetwork(nameval
, { proto
: 'dhcp' }).then(function(net
) {
2027 firewall
.deleteNetwork(net
.getName());
2029 var zonePromise
= zoneval
2030 ? firewall
.getZone(zoneval
).then(function(zone
) { return zone
|| firewall
.addZone(zoneval
) })
2031 : Promise
.resolve();
2033 return zonePromise
.then(function(zone
) {
2035 zone
.addNetwork(net
.getName());
2038 }).then(L
.bind(function() {
2039 ui
.showModal(null, E('p', { 'class': 'spinning' }, [ _('Loading data…') ]));
2041 return this.renderMoreOptionsModal(section_id
);
2045 s
.handleJoin = function(radioDev
, bss
, ev
) {
2046 poll
.remove(this.pollFn
);
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
;
2055 var nameUsed = function(name
) {
2056 var s
= uci
.get('network', name
);
2057 if (s
!= null && s
['.type'] != 'interface')
2060 var net
= (s
!= null) ? network
.instantiateNetwork(name
) : null;
2061 return (net
!= null && !net
.isEmpty());
2064 s2
.render = function() {
2065 return Promise
.all([
2067 this.renderUCISection('_new_')
2068 ]).then(this.renderContents
.bind(this));
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;
2076 replace
= s2
.option(form
.Flag
, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
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');
2089 for (var i
= 2; nameUsed(name
.default); i
++)
2090 name
.default = 'wwan%d'.format(i
);
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;
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';
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';
2107 return m2
.render().then(L
.bind(function(nodes
) {
2108 ui
.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss
.ssid
)), [
2110 E('div', { 'class': 'right' }, [
2113 'click': ui
.hideModal
2114 }, _('Cancel')), ' ',
2116 'class': 'cbi-button cbi-button-positive important',
2117 'click': ui
.createHandlerFn(this, 'handleJoinConfirm', radioDev
, bss
, m2
)
2120 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase
|| name
).cbid('_new_'))).focus();
2124 s
.handleAdd = function(radioDev
, ev
) {
2125 var section_id
= next_free_sid(uci
.sections('wireless', 'wifi-iface').length
);
2127 uci
.unset('wireless', radioDev
.getName(), 'disabled');
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');
2135 this.addedSection
= section_id
;
2136 return this.renderMoreOptionsModal(section_id
);
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' });
2145 if (inst
.getWifiNetworks
)
2146 node
.appendChild(render_radio_badge(inst
));
2148 node
.appendChild(render_network_badge(inst
));
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
);
2158 if (inst
.getWifiNetworks
)
2159 return render_radio_status(inst
, this.section
.wifis
.filter(function(e
) {
2160 return (e
.getWifiDeviceName() == inst
.getName());
2163 return render_network_status(inst
);
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() ];
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');
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
));
2182 else if (dsc
.getAttribute('restart') == '1') {
2183 dsc
.removeAttribute('restart');
2184 btns
[0].classList
.remove('spinning');
2185 btns
[0].disabled
= false;
2189 return Promise
.all(tasks
)
2190 .then(L
.bind(function(hosts_radios
) {
2193 for (var i
= 0; i
< hosts_radios
[1].length
; i
++)
2194 tasks
.push(hosts_radios
[1][i
].getWifiNetworks());
2196 return Promise
.all(tasks
).then(function(data
) {
2197 hosts_radios
[2] = [];
2199 for (var i
= 0; i
< data
.length
; i
++)
2200 hosts_radios
[2].push
.apply(hosts_radios
[2], data
[i
]);
2202 return hosts_radios
;
2205 .then(L
.bind(function(hosts_radios_wifis
) {
2208 for (var i
= 0; i
< hosts_radios_wifis
[2].length
; i
++)
2209 tasks
.push(hosts_radios_wifis
[2][i
].getAssocList());
2211 return Promise
.all(tasks
).then(function(data
) {
2212 hosts_radios_wifis
[3] = [];
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];
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
]));
2222 return hosts_radios_wifis
;
2225 .then(L
.bind(this.poll_status
, this, nodes
));
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'))
2238 cbi_update_table(table
, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
2240 return E([ nodes
, E('h3', _('Associated Stations')), table
]);