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'));
990 ss
.tab('roaming', _('WLAN roaming'), _('Settings for assisting wireless clients in roaming between multiple APs: 802.11r, 802.11k and 802.11v'));
992 o
= ss
.taboption('general', form
.ListValue
, 'mode', _('Mode'));
993 o
.value('ap', _('Access Point'));
994 o
.value('sta', _('Client'));
995 o
.value('adhoc', _('Ad-Hoc'));
997 o
= ss
.taboption('general', form
.Value
, 'mesh_id', _('Mesh Id'));
998 o
.depends('mode', 'mesh');
1000 o
= ss
.taboption('advanced', form
.Flag
, 'mesh_fwding', _('Forward mesh peer traffic'));
1003 o
.depends('mode', 'mesh');
1005 o
= ss
.taboption('advanced', form
.Value
, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
1008 o
.datatype
= 'range(-255,1)';
1009 o
.depends('mode', 'mesh');
1011 o
= ss
.taboption('general', form
.Value
, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
1012 o
.datatype
= 'maxlength(32)';
1013 o
.depends('mode', 'ap');
1014 o
.depends('mode', 'sta');
1015 o
.depends('mode', 'adhoc');
1016 o
.depends('mode', 'ahdemo');
1017 o
.depends('mode', 'monitor');
1018 o
.depends('mode', 'ap-wds');
1019 o
.depends('mode', 'sta-wds');
1020 o
.depends('mode', 'wds');
1022 o
= ss
.taboption('general', form
.Value
, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
1023 o
.datatype
= 'macaddr';
1025 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.'));
1029 o
.write = function(section_id
, value
) {
1030 return network
.getDevice(section_id
).then(L
.bind(function(dev
) {
1031 var old_networks
= dev
.getNetworks().reduce(function(o
, v
) { o
[v
.getName()] = v
; return o
}, {}),
1033 values
= L
.toArray(value
),
1036 for (var i
= 0; i
< values
.length
; i
++) {
1037 new_networks
[values
[i
]] = true;
1039 if (old_networks
[values
[i
]])
1042 tasks
.push(network
.getNetwork(values
[i
]).then(L
.bind(function(name
, net
) {
1043 return net
|| network
.addNetwork(name
, { proto
: 'none' });
1044 }, this, values
[i
])).then(L
.bind(function(dev
, net
) {
1046 if (!net
.isEmpty()) {
1047 var target_dev
= net
.getDevice();
1049 /* Resolve parent interface of vlan */
1050 while (target_dev
&& target_dev
.getType() == 'vlan')
1051 target_dev
= target_dev
.getParent();
1053 if (!target_dev
|| target_dev
.getType() != 'bridge')
1054 net
.set('type', 'bridge');
1062 for (var name
in old_networks
)
1063 if (!new_networks
[name
])
1064 tasks
.push(network
.getNetwork(name
).then(L
.bind(function(dev
, net
) {
1066 net
.deleteDevice(dev
);
1069 return Promise
.all(tasks
);
1073 if (hwtype
== 'mac80211') {
1074 var mode
= ss
.children
[0],
1075 bssid
= ss
.children
[5],
1078 mode
.value('mesh', '802.11s');
1079 mode
.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
1080 mode
.value('monitor', _('Monitor'));
1082 bssid
.depends('mode', 'adhoc');
1083 bssid
.depends('mode', 'sta');
1084 bssid
.depends('mode', 'sta-wds');
1086 o
= ss
.taboption('macfilter', form
.ListValue
, 'macfilter', _('MAC Address Filter'));
1087 o
.depends('mode', 'ap');
1088 o
.depends('mode', 'ap-wds');
1089 o
.value('', _('disable'));
1090 o
.value('allow', _('Allow listed only'));
1091 o
.value('deny', _('Allow all except listed'));
1093 o
= ss
.taboption('macfilter', form
.DynamicList
, 'maclist', _('MAC-List'));
1094 o
.datatype
= 'macaddr';
1096 o
.depends('macfilter', 'allow');
1097 o
.depends('macfilter', 'deny');
1098 o
.load = function(section_id
) {
1099 return network
.getHostHints().then(L
.bind(function(hints
) {
1100 hints
.getMACHints().map(L
.bind(function(hint
) {
1101 this.value(hint
[0], hint
[1] ? '%s (%s)'.format(hint
[0], hint
[1]) : hint
[0]);
1104 return form
.DynamicList
.prototype.load
.apply(this, [section_id
]);
1108 mode
.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
1109 mode
.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
1111 mode
.write = function(section_id
, value
) {
1114 uci
.set('wireless', section_id
, 'mode', 'ap');
1115 uci
.set('wireless', section_id
, 'wds', '1');
1119 uci
.set('wireless', section_id
, 'mode', 'sta');
1120 uci
.set('wireless', section_id
, 'wds', '1');
1124 uci
.set('wireless', section_id
, 'mode', value
);
1125 uci
.unset('wireless', section_id
, 'wds');
1130 mode
.cfgvalue = function(section_id
) {
1131 var mode
= uci
.get('wireless', section_id
, 'mode'),
1132 wds
= uci
.get('wireless', section_id
, 'wds');
1134 if (mode
== 'ap' && wds
)
1136 else if (mode
== 'sta' && wds
)
1142 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.'));
1143 o
.depends('mode', 'ap');
1144 o
.depends('mode', 'ap-wds');
1146 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.'));
1147 o
.depends('mode', 'ap');
1148 o
.depends('mode', 'ap-wds');
1149 o
.default = o
.enabled
;
1151 /* https://w1.fi/cgit/hostap/commit/?id=34f7c699a6bcb5c45f82ceb6743354ad79296078 */
1152 /* multicast_to_unicast https://github.com/openwrt/openwrt/commit/7babb978ad9d7fc29acb1ff86afb1eb343af303a */
1153 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.'));
1156 o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
1157 o.depends('mode', 'ap');
1158 o.depends('mode', 'ap-wds');
1160 o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name'));
1162 o.placeholder = radioNet.getIfname();
1163 if (/^radio\d+\.network/.test(o.placeholder))
1166 o = ss.taboption('advanced', form.Value, 'macaddr', _('MAC address'), _('Override default MAC address - the range of usable addresses might be limited by the driver'));
1168 o.placeholder = radioNet.getActiveBSSID();
1169 o.datatype = 'macaddr';
1171 o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble'));
1172 o.default = o.enabled;
1174 o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
1177 o.datatype = 'range(1,255)';
1179 o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
1181 o.placeholder = 600;
1182 o.datatype = 'uinteger';
1184 o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling'));
1186 o.datatype = 'uinteger';
1188 o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('802.11v: BSS Max Idle. Units: seconds.'));
1190 o.placeholder = 300;
1191 o.datatype = 'uinteger';
1193 o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval'));
1195 o.placeholder = 65535;
1196 o.datatype = 'uinteger';
1198 o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
1199 o.default = o.enabled;
1203 encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption'));
1204 o.depends('mode', 'ap');
1205 o.depends('mode', 'sta');
1206 o.depends('mode', 'adhoc');
1207 o.depends('mode', 'ahdemo');
1208 o.depends('mode', 'ap-wds');
1209 o.depends('mode', 'sta-wds');
1210 o.depends('mode', 'mesh');
1212 o.cfgvalue = function(section_id) {
1213 var v = String(uci.get('wireless', section_id, 'encryption'));
1216 else if (v.match(/\+/))
1217 return v.replace(/\+.+$/, '');
1221 o.write = function(section_id, value) {
1222 var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id),
1223 co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id);
1225 if (value == 'wpa' || value == 'wpa2' || value == 'wpa3' || value == 'wpa3-mixed')
1226 uci.unset('wireless', section_id, 'key');
1228 if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp'))
1231 uci.set('wireless', section_id, 'encryption', e);
1234 o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher'));
1235 o.depends('encryption', 'wpa');
1236 o.depends('encryption', 'wpa2');
1237 o.depends('encryption', 'wpa3');
1238 o.depends('encryption', 'wpa3-mixed');
1239 o.depends('encryption', 'psk');
1240 o.depends('encryption', 'psk2');
1241 o.depends('encryption', 'wpa-mixed');
1242 o.depends('encryption', 'psk-mixed');
1243 o.value('auto', _('auto'));
1244 o.value('ccmp', _('Force CCMP (AES)'));
1245 o.value('tkip', _('Force TKIP'));
1246 o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1247 o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write;
1249 o.cfgvalue = function(section_id) {
1250 var v = String(uci.get('wireless', section_id, 'encryption'));
1251 if (v.match(/\+/)) {
1252 v = v.replace(/^[^+]+\+/, '');
1255 else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip')
1262 var crypto_modes = [];
1264 if (hwtype == 'mac80211') {
1265 var has_supplicant = L.hasSystemFeature('wpasupplicant'),
1266 has_hostapd = L.hasSystemFeature('hostapd');
1268 // Probe EAP support
1269 var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'),
1270 has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap');
1272 // Probe SAE support
1273 var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'),
1274 has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae');
1276 // Probe OWE support
1277 var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
1278 has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
1280 // Probe Suite-B support
1281 var has_ap_eap192 = L.hasSystemFeature('hostapd', 'suiteb192'),
1282 has_sta_eap192 = L.hasSystemFeature('wpasupplicant', 'suiteb192');
1284 // Probe WEP support
1285 var has_ap_wep = L.hasSystemFeature('hostapd', 'wep'),
1286 has_sta_wep = L.hasSystemFeature('wpasupplicant', 'wep');
1288 if (has_hostapd || has_supplicant) {
1289 crypto_modes.push(['psk2', 'WPA2-PSK', 35]);
1290 crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1291 crypto_modes.push(['psk', 'WPA-PSK', 12]);
1294 encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1297 if (has_ap_sae || has_sta_sae) {
1298 crypto_modes.push(['sae', 'WPA3-SAE', 31]);
1299 crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1302 if (has_ap_wep || has_sta_wep) {
1303 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1304 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1307 if (has_ap_eap || has_sta_eap) {
1308 if (has_ap_eap192 || has_sta_eap192) {
1309 crypto_modes.push(['wpa3', 'WPA3-EAP', 33]);
1310 crypto_modes.push(['wpa3-mixed', 'WPA2-EAP/WPA3-EAP Mixed Mode', 32]);
1313 crypto_modes.push(['wpa2', 'WPA2-EAP', 34]);
1314 crypto_modes.push(['wpa', 'WPA-EAP', 20]);
1317 if (has_ap_owe || has_sta_owe) {
1318 crypto_modes.push(['owe', 'OWE', 1]);
1321 encr.crypto_support = {
1323 'wep-open': has_ap_wep || _('Requires hostapd with WEP support'),
1324 'wep-shared': has_ap_wep || _('Requires hostapd with WEP support'),
1325 'psk': has_hostapd || _('Requires hostapd'),
1326 'psk2': has_hostapd || _('Requires hostapd'),
1327 'psk-mixed': has_hostapd || _('Requires hostapd'),
1328 'sae': has_ap_sae || _('Requires hostapd with SAE support'),
1329 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
1330 'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
1331 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
1332 'wpa3': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
1333 'wpa3-mixed': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
1334 'owe': has_ap_owe || _('Requires hostapd with OWE support')
1337 'wep-open': has_sta_wep || _('Requires wpa-supplicant with WEP support'),
1338 'wep-shared': has_sta_wep || _('Requires wpa-supplicant with WEP support'),
1339 'psk': has_supplicant || _('Requires wpa-supplicant'),
1340 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1341 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1342 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1343 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1344 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1345 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1346 'wpa3': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
1347 'wpa3-mixed': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
1348 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
1353 'psk': has_supplicant || _('Requires wpa-supplicant'),
1354 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1355 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1358 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support')
1370 encr.crypto_support['ap-wds'] = encr.crypto_support['ap'];
1371 encr.crypto_support['sta-wds'] = encr.crypto_support['sta'];
1373 encr.validate = function(section_id, value) {
1374 var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
1375 modeval = modeopt.formvalue(section_id),
1376 modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)],
1377 enctitle = this.vallist[this.keylist.indexOf(value)];
1379 if (value == 'none')
1382 if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value))
1383 return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle);
1385 return this.crypto_support[modeval][value];
1388 else if (hwtype == 'broadcom') {
1389 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1390 crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1391 crypto_modes.push(['psk', 'WPA-PSK', 12]);
1392 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1393 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1396 crypto_modes.push(['none', _('No Encryption'), 0]);
1398 crypto_modes.sort(function(a, b) { return b[2] - a[2] });
1400 for (var i = 0; i < crypto_modes.length; i++) {
1401 var security_level = (crypto_modes[i][2] >= 30) ? _('strong security')
1402 : (crypto_modes[i][2] >= 20) ? _('medium security')
1403 : (crypto_modes[i][2] >= 10) ? _('weak security') : _('open network');
1405 encr.value(crypto_modes[i][0], '%s (%s)'.format(crypto_modes[i][1], security_level));
1409 o = ss.taboption('encryption', form.Value, 'auth_server', _('RADIUS Authentication Server'));
1410 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1412 o.datatype = 'host(0)';
1414 o = ss.taboption('encryption', form.Value, 'auth_port', _('RADIUS Authentication Port'));
1415 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1417 o.datatype = 'port';
1418 o.placeholder = '1812';
1420 o = ss.taboption('encryption', form.Value, 'auth_secret', _('RADIUS Authentication Secret'));
1421 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1425 o = ss.taboption('encryption', form.Value, 'acct_server', _('RADIUS Accounting Server'));
1426 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1428 o.datatype = 'host(0)';
1430 o = ss.taboption('encryption', form.Value, 'acct_port', _('RADIUS Accounting Port'));
1431 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1433 o.datatype = 'port';
1434 o.placeholder = '1813';
1436 o = ss.taboption('encryption', form.Value, 'acct_secret', _('RADIUS Accounting Secret'));
1437 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1441 /* extra RADIUS settings start */
1442 o
= ss
.taboption('encryption', form
.ListValue
, 'dynamic_vlan', _('RADIUS Dynamic VLAN Assignment'), _('Required: Rejects auth if RADIUS server does not provide appropriate VLAN attributes.'));
1443 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1444 o
.value('0', _('Disabled'));
1445 o
.value('1', _('Optional'));
1446 o
.value('2', _('Required'));
1447 o
.write = function (section_id
, value
) {
1448 return this.super('write', [section_id
, (value
== 0) ? null: value
]);
1451 o
= ss
.taboption('encryption', form
.Flag
, 'per_sta_vif', _('RADIUS Per STA VLAN'), _('Each STA is assigned its own AP_VLAN interface.'));
1452 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1454 //hostapd internally defaults to vlan_naming=1 even with dynamic VLAN off
1455 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>.'));
1456 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1458 o
= ss
.taboption('encryption', widgets
.DeviceSelect
, 'vlan_tagged_interface', _('RADIUS VLAN Tagged Interface'), _('E.g. eth0, eth1'));
1459 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1466 o
.noinactive
= true;
1468 o
= ss
.taboption('encryption', form
.Value
, 'vlan_bridge', _('RADIUS VLAN Bridge Naming Scheme'), _('E.g. <code>br-vlan</code> or <code>brvlan</code>.'));
1469 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1471 /* extra RADIUS settings end */
1473 o
= ss
.taboption('encryption', form
.Value
, 'dae_client', _('DAE-Client'), _('Dynamic Authorization Extension client.'));
1474 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1476 o
.datatype
= 'host(0)';
1478 o
= ss
.taboption('encryption', form
.Value
, 'dae_port', _('DAE-Port'), _('Dynamic Authorization Extension port.'));
1479 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1481 o
.datatype
= 'port';
1482 o
.placeholder
= '3799';
1484 o
= ss
.taboption('encryption', form
.Value
, 'dae_secret', _('DAE-Secret'), _('Dynamic Authorization Extension secret.'));
1485 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1489 //WPA(1) has only WPA IE. Only >= WPA2 has RSN IE Preauth frames.
1490 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.'));
1491 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa2', 'wpa3', 'wpa3-mixed'] });
1494 o
= ss
.taboption('encryption', form
.Value
, '_wpa_key', _('Key'));
1495 o
.depends('encryption', 'psk');
1496 o
.depends('encryption', 'psk2');
1497 o
.depends('encryption', 'psk+psk2');
1498 o
.depends('encryption', 'psk-mixed');
1499 o
.depends('encryption', 'sae');
1500 o
.depends('encryption', 'sae-mixed');
1501 o
.datatype
= 'wpakey';
1505 o
.cfgvalue = function(section_id
) {
1506 var key
= uci
.get('wireless', section_id
, 'key');
1507 return /^[1234]$/.test(key
) ? null : key
;
1510 o
.write = function(section_id
, value
) {
1511 uci
.set('wireless', section_id
, 'key', value
);
1512 uci
.unset('wireless', section_id
, 'key1');
1513 uci
.unset('wireless', section_id
, 'key2');
1514 uci
.unset('wireless', section_id
, 'key3');
1515 uci
.unset('wireless', section_id
, 'key4');
1519 o
= ss
.taboption('encryption', form
.ListValue
, '_wep_key', _('Used Key Slot'));
1520 o
.depends('encryption', 'wep-open');
1521 o
.depends('encryption', 'wep-shared');
1522 o
.value('1', _('Key #%d').format(1));
1523 o
.value('2', _('Key #%d').format(2));
1524 o
.value('3', _('Key #%d').format(3));
1525 o
.value('4', _('Key #%d').format(4));
1527 o
.cfgvalue = function(section_id
) {
1528 var slot
= +uci
.get('wireless', section_id
, 'key');
1529 return (slot
>= 1 && slot
<= 4) ? String(slot
) : '';
1532 o
.write = function(section_id
, value
) {
1533 uci
.set('wireless', section_id
, 'key', value
);
1536 for (var slot
= 1; slot
<= 4; slot
++) {
1537 o
= ss
.taboption('encryption', form
.Value
, 'key%d'.format(slot
), _('Key #%d').format(slot
));
1538 o
.depends('encryption', 'wep-open');
1539 o
.depends('encryption', 'wep-shared');
1540 o
.datatype
= 'wepkey';
1544 o
.write = function(section_id
, value
) {
1545 if (value
!= null && (value
.length
== 5 || value
.length
== 13))
1546 value
= 's:%s'.format(value
);
1547 uci
.set('wireless', section_id
, this.option
, value
);
1552 if (hwtype
== 'mac80211') {
1553 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1554 var has_80211r
= L
.hasSystemFeature('hostapd', '11r') || L
.hasSystemFeature('hostapd', 'eap');
1556 o
= ss
.taboption('roaming', form
.Flag
, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1557 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1559 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1562 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.'));
1563 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1564 o
.depends({ ieee80211r
: '1' });
1567 o
= ss
.taboption('roaming', form
.Value
, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1568 o
.depends({ ieee80211r
: '1' });
1569 o
.placeholder
= '4f57';
1570 o
.datatype
= 'and(hexstring,length(4))';
1573 o
= ss
.taboption('roaming', form
.Value
, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1574 o
.depends({ ieee80211r
: '1' });
1575 o
.placeholder
= '1000';
1576 o
.datatype
= 'range(1000,65535)';
1579 o
= ss
.taboption('roaming', form
.ListValue
, 'ft_over_ds', _('FT protocol'));
1580 o
.depends({ ieee80211r
: '1' });
1581 o
.value('0', _('FT over the Air'));
1582 o
.value('1', _('FT over DS'));
1585 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.'));
1586 o
.depends({ ieee80211r
: '1' });
1587 o
.default = o
.enabled
;
1590 o
= ss
.taboption('roaming', form
.Value
, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1591 o
.depends({ ieee80211r
: '1' });
1592 o
.placeholder
= '10000';
1593 o
.datatype
= 'uinteger';
1596 o
= ss
.taboption('roaming', form
.Value
, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1597 o
.depends({ ieee80211r
: '1' });
1598 o
.placeholder
= '00004f577274';
1599 o
.datatype
= 'and(hexstring,length(12))';
1602 o
= ss
.taboption('roaming', form
.Flag
, 'pmk_r1_push', _('PMK R1 Push'));
1603 o
.depends({ ieee80211r
: '1' });
1604 o
.placeholder
= '0';
1607 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.'));
1608 o
.depends({ ieee80211r
: '1' });
1611 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.'));
1612 o
.depends({ ieee80211r
: '1' });
1614 // End of 802.11r options
1616 // Probe 802.11k and 802.11v support via EAP support (full hostapd has EAP)
1617 if (L
.hasSystemFeature('hostapd', 'eap')) {
1618 /* 802.11k settings start */ o
=
1619 ss
.taboption('roaming', form
.Flag
, 'ieee80211k', _('802.11k RRM'), _('Radio Resource Measurement - Sends beacons to assist roaming. Not all clients support this.'));
1620 // add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1621 o
.depends('mode', 'ap');
1622 o
.depends('mode', 'ap-wds');
1624 o
= ss
.taboption('roaming', form
.Flag
, 'rrm_neighbor_report', _('Neighbour Report'), _('802.11k: Enable neighbor report via radio measurements.'));
1625 o
.depends({ ieee80211k
: '1' });
1626 o
.default = o
.enabled
;
1628 o
= ss
.taboption('roaming', form
.Flag
, 'rrm_beacon_report', _('Beacon Report'), _('802.11k: Enable beacon report via radio measurements.'));
1629 o
.depends({ ieee80211k
: '1' });
1630 o
.default = o
.enabled
;
1631 /* 802.11k settings end */
1633 /* 802.11v settings start */
1634 o
= ss
.taboption('roaming', form
.ListValue
, 'time_advertisement', _('Time advertisement'), _('802.11v: Time Advertisement in management frames.'));
1635 o
.value('0', _('Disabled'));
1636 o
.value('2', _('Enabled'));
1637 o
.write = function (section_id
, value
) {
1638 return this.super('write', [section_id
, (value
== 2) ? value
: null]);
1641 //Pull current System TZ setting
1642 var tz
= uci
.get('system', '@system[0]', 'timezone');
1643 o
= ss
.taboption('roaming', form
.Value
, 'time_zone', _('Time zone'), _('802.11v: Local Time Zone Advertisement in management frames.'));
1647 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).'));
1650 /* wnm_sleep_mode_no_keys: https://git.openwrt.org/?p=openwrt/openwrt.git;a=commitdiff;h=bf98faaac8ed24cf7d3d93dd4fcd7304d109363b */
1651 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.'));
1654 o = ss.taboption('roaming', form.Flag, 'bss_transition', _('BSS Transition'), _('802.11v: Basic Service Set (BSS) transition management.'));
1657 /* in master, but not 21.02.1: proxy_arp */
1658 o
= ss
.taboption('roaming', form
.Flag
, 'proxy_arp', _('ProxyARP'), _('802.11v: Proxy ARP enables non-AP STA to remain in power-save for longer.'));
1661 /* TODO: na_mcast_to_ucast is missing: needs adding to hostapd.sh - nice to have */
1663 /* 802.11v settings end */
1666 if (hwtype
== 'mac80211') {
1667 o
= ss
.taboption('encryption', form
.ListValue
, 'eap_type', _('EAP-Method'));
1668 o
.value('tls', 'TLS');
1669 o
.value('ttls', 'TTLS');
1670 o
.value('peap', 'PEAP');
1671 o
.value('fast', 'FAST');
1672 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1674 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"));
1677 o
.default = o
.disabled
;
1678 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1679 o
.validate = function(section_id
, value
) {
1680 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1681 return _("This option cannot be used because the ca-bundle package is not installed.");
1686 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert', _('Path to CA-Certificate'));
1687 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], ca_cert_usesystem
: ['0'] });
1689 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"));
1690 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1692 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"));
1693 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1695 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)"));
1696 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1698 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)"));
1699 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1701 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert', _('Path to Client-Certificate'));
1702 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1704 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key', _('Path to Private Key'));
1705 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1707 o
= ss
.taboption('encryption', form
.Value
, 'priv_key_pwd', _('Password of Private Key'));
1708 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1711 o
= ss
.taboption('encryption', form
.ListValue
, 'auth', _('Authentication'));
1712 o
.value('PAP', 'PAP');
1713 o
.value('CHAP', 'CHAP');
1714 o
.value('MSCHAP', 'MSCHAP');
1715 o
.value('MSCHAPV2', 'MSCHAPv2');
1716 o
.value('EAP-GTC', 'EAP-GTC');
1717 o
.value('EAP-MD5', 'EAP-MD5');
1718 o
.value('EAP-MSCHAPV2', 'EAP-MSCHAPv2');
1719 o
.value('EAP-TLS', 'EAP-TLS');
1720 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1722 o
.validate = function(section_id
, value
) {
1723 var eo
= this.section
.children
.filter(function(o
) { return o
.option
== 'eap_type' })[0],
1724 ev
= eo
.formvalue(section_id
);
1726 if (ev
!= 'ttls' && (value
== 'PAP' || value
== 'CHAP' || value
== 'MSCHAP' || value
== 'MSCHAPV2'))
1727 return _('This authentication type is not applicable to the selected EAP method.');
1732 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"));
1735 o
.default = o
.disabled
;
1736 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1737 o
.validate = function(section_id
, value
) {
1738 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1739 return _("This option cannot be used because the ca-bundle package is not installed.");
1744 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert2', _('Path to inner CA-Certificate'));
1745 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'], ca_cert2_usesystem
: ['0'] });
1747 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"));
1748 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1750 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"));
1751 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1753 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)"));
1754 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1756 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)"));
1757 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1759 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert2', _('Path to inner Client-Certificate'));
1760 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1762 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key2', _('Path to inner Private Key'));
1763 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1765 o
= ss
.taboption('encryption', form
.Value
, 'priv_key2_pwd', _('Password of inner Private Key'));
1766 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1769 o
= ss
.taboption('encryption', form
.Value
, 'identity', _('Identity'));
1770 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1772 o
= ss
.taboption('encryption', form
.Value
, 'anonymous_identity', _('Anonymous Identity'));
1773 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1775 o
= ss
.taboption('encryption', form
.Value
, 'password', _('Password'));
1776 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1780 if (hwtype
== 'mac80211') {
1781 // ieee802.11w options
1782 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"));
1783 o
.value('0', _('Disabled'));
1784 o
.value('1', _('Optional'));
1785 o
.value('2', _('Required'));
1786 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds', 'sta', 'sta-wds'], encryption
: ['owe', 'psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1789 '2': [{ encryption
: 'sae' }, { encryption
: 'owe' }, { encryption
: 'wpa3' }, { encryption
: 'wpa3-mixed' }],
1790 '1': [{ encryption
: 'sae-mixed'}],
1794 o
.write = function(section_id
, value
) {
1795 if (value
!= this.default)
1796 return form
.ListValue
.prototype.write
.call(this, section_id
, value
);
1798 return form
.ListValue
.prototype.remove
.call(this, section_id
);
1801 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1802 o
.depends('ieee80211w', '1');
1803 o
.depends('ieee80211w', '2');
1804 o
.datatype
= 'uinteger';
1805 o
.placeholder
= '1000';
1808 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1809 o
.depends('ieee80211w', '1');
1810 o
.depends('ieee80211w', '2');
1811 o
.datatype
= 'uinteger';
1812 o
.placeholder
= '201';
1815 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.'));
1816 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1818 if (L
.hasSystemFeature('hostapd', 'wps') && L
.hasSystemFeature('wpasupplicant')) {
1819 o
= ss
.taboption('encryption', form
.Flag
, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1822 o
.default = o
.disabled
;
1823 o
.depends('encryption', 'psk');
1824 o
.depends('encryption', 'psk2');
1825 o
.depends('encryption', 'psk-mixed');
1826 o
.depends('encryption', 'sae');
1827 o
.depends('encryption', 'sae-mixed');
1834 s
.handleRemove = function(section_id
, ev
) {
1835 document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id
)).style
.opacity
= 0.5;
1836 return form
.TypedSection
.prototype.handleRemove
.apply(this, [section_id
, ev
]);
1839 s
.handleScan = function(radioDev
, ev
) {
1840 var table
= E('table', { 'class': 'table' }, [
1841 E('tr', { 'class': 'tr table-titles' }, [
1842 E('th', { 'class': 'th col-2 middle center' }, _('Signal')),
1843 E('th', { 'class': 'th col-4 middle left' }, _('SSID')),
1844 E('th', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1845 E('th', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1846 E('th', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1847 E('th', { 'class': 'th col-3 middle left' }, _('Encryption')),
1848 E('th', { 'class': 'th cbi-section-actions right' }, ' '),
1852 var stop
= E('button', {
1854 'click': L
.bind(this.handleScanStartStop
, this),
1855 'style': 'display:none',
1856 'data-state': 'stop'
1857 }, _('Stop refresh'));
1859 cbi_update_table(table
, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1861 var md
= ui
.showModal(_('Join Network: Wireless Scan'), [
1863 E('div', { 'class': 'right' }, [
1868 'click': L
.bind(this.handleScanAbort
, this)
1873 md
.style
.maxWidth
= '90%';
1874 md
.style
.maxHeight
= 'none';
1876 this.pollFn
= L
.bind(this.handleScanRefresh
, this, radioDev
, {}, table
, stop
);
1878 poll
.add(this.pollFn
);
1882 s
.handleScanRefresh = function(radioDev
, scanCache
, table
, stop
) {
1883 return radioDev
.getScanList().then(L
.bind(function(results
) {
1886 for (var i
= 0; i
< results
.length
; i
++)
1887 scanCache
[results
[i
].bssid
] = results
[i
];
1889 for (var k
in scanCache
)
1890 if (scanCache
[k
].stale
)
1891 results
.push(scanCache
[k
]);
1893 results
.sort(function(a
, b
) {
1894 var diff
= (b
.quality
- a
.quality
) || (a
.channel
- b
.channel
);
1899 if (a
.ssid
< b
.ssid
)
1901 else if (a
.ssid
> b
.ssid
)
1904 if (a
.bssid
< b
.bssid
)
1906 else if (a
.bssid
> b
.bssid
)
1910 for (var i
= 0; i
< results
.length
; i
++) {
1911 var res
= results
[i
],
1912 qv
= res
.quality
|| 0,
1913 qm
= res
.quality_max
|| 0,
1914 q
= (qv
> 0 && qm
> 0) ? Math
.floor((100 / qm
) * qv
) : 0,
1915 s
= res
.stale
? 'opacity:0.5' : '';
1918 E('span', { 'style': s
}, render_signal_badge(q
, res
.signal
, res
.noise
)),
1919 E('span', { 'style': s
}, (res
.ssid
!= null) ? '%h'.format(res
.ssid
) : E('em', _('hidden'))),
1920 E('span', { 'style': s
}, '%d'.format(res
.channel
)),
1921 E('span', { 'style': s
}, '%h'.format(res
.mode
)),
1922 E('span', { 'style': s
}, '%h'.format(res
.bssid
)),
1923 E('span', { 'style': s
}, '%h'.format(network
.formatWifiEncryption(res
.encryption
))),
1924 E('div', { 'class': 'right' }, E('button', {
1925 'class': 'cbi-button cbi-button-action important',
1926 'click': ui
.createHandlerFn(this, 'handleJoin', radioDev
, res
)
1927 }, _('Join Network')))
1933 cbi_update_table(table
, rows
);
1935 stop
.disabled
= false;
1936 stop
.style
.display
= '';
1937 stop
.classList
.remove('spinning');
1941 s
.handleScanStartStop = function(ev
) {
1942 var btn
= ev
.currentTarget
;
1944 if (btn
.getAttribute('data-state') == 'stop') {
1945 poll
.remove(this.pollFn
);
1946 btn
.firstChild
.data
= _('Start refresh');
1947 btn
.setAttribute('data-state', 'start');
1950 poll
.add(this.pollFn
);
1951 btn
.firstChild
.data
= _('Stop refresh');
1952 btn
.setAttribute('data-state', 'stop');
1953 btn
.classList
.add('spinning');
1954 btn
.disabled
= true;
1958 s
.handleScanAbort = function(ev
) {
1959 var md
= dom
.parent(ev
.target
, 'div[aria-modal="true"]');
1961 md
.style
.maxWidth
= '';
1962 md
.style
.maxHeight
= '';
1966 poll
.remove(this.pollFn
);
1971 s
.handleJoinConfirm = function(radioDev
, bss
, form
, ev
) {
1972 var nameopt
= L
.toArray(form
.lookupOption('name', '_new_'))[0],
1973 passopt
= L
.toArray(form
.lookupOption('password', '_new_'))[0],
1974 ssidopt
= L
.toArray(form
.lookupOption('ssid', '_new_'))[0],
1975 bssidopt
= L
.toArray(form
.lookupOption('bssid', '_new_'))[0],
1976 zoneopt
= L
.toArray(form
.lookupOption('zone', '_new_'))[0],
1977 replopt
= L
.toArray(form
.lookupOption('replace', '_new_'))[0],
1978 nameval
= (nameopt
&& nameopt
.isValid('_new_')) ? nameopt
.formvalue('_new_') : null,
1979 passval
= (passopt
&& passopt
.isValid('_new_')) ? passopt
.formvalue('_new_') : null,
1980 ssidval
= (ssidopt
&& ssidopt
.isValid('_new_')) ? ssidopt
.formvalue('_new_') : null,
1981 bssidval
= (bssidopt
&& bssidopt
.isValid('_new_')) ? bssidopt
.formvalue('_new_') : null,
1982 zoneval
= zoneopt
? zoneopt
.formvalue('_new_') : null,
1983 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
1984 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
1985 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' }).length
> 0),
1986 is_sae
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'sae' }).length
> 0);
1988 if (nameval
== null || (passopt
&& passval
== null))
1991 var section_id
= null;
1993 return this.map
.save(function() {
1994 var wifi_sections
= uci
.sections('wireless', 'wifi-iface');
1996 if (replopt
.formvalue('_new_') == '1') {
1997 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1998 if (wifi_sections
[i
].device
== radioDev
.getName())
1999 uci
.remove('wireless', wifi_sections
[i
]['.name']);
2002 if (uci
.get('wireless', radioDev
.getName(), 'disabled') == '1') {
2003 for (var i
= 0; i
< wifi_sections
.length
; i
++)
2004 if (wifi_sections
[i
].device
== radioDev
.getName())
2005 uci
.set('wireless', wifi_sections
[i
]['.name'], 'disabled', '1');
2007 uci
.unset('wireless', radioDev
.getName(), 'disabled');
2010 section_id
= next_free_sid(wifi_sections
.length
);
2012 uci
.add('wireless', 'wifi-iface', section_id
);
2013 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
2014 uci
.set('wireless', section_id
, 'mode', (bss
.mode
== 'Ad-Hoc') ? 'adhoc' : 'sta');
2015 uci
.set('wireless', section_id
, 'network', nameval
);
2017 if (bss
.ssid
!= null) {
2018 uci
.set('wireless', section_id
, 'ssid', bss
.ssid
);
2020 if (bssidval
== '1')
2021 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
2023 else if (bss
.bssid
!= null) {
2024 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
2027 if (ssidval
!= null)
2028 uci
.set('wireless', section_id
, 'ssid', ssidval
);
2031 uci
.set('wireless', section_id
, 'encryption', 'sae');
2032 uci
.set('wireless', section_id
, 'key', passval
);
2035 for (var i
= enc
.wpa
.length
- 1; i
>= 0; i
--) {
2036 if (enc
.wpa
[i
] == 2) {
2037 uci
.set('wireless', section_id
, 'encryption', 'psk2');
2040 else if (enc
.wpa
[i
] == 1) {
2041 uci
.set('wireless', section_id
, 'encryption', 'psk');
2046 uci
.set('wireless', section_id
, 'key', passval
);
2049 uci
.set('wireless', section_id
, 'encryption', 'wep-open');
2050 uci
.set('wireless', section_id
, 'key', '1');
2051 uci
.set('wireless', section_id
, 'key1', passval
);
2054 uci
.set('wireless', section_id
, 'encryption', 'none');
2057 return network
.addNetwork(nameval
, { proto
: 'dhcp' }).then(function(net
) {
2058 firewall
.deleteNetwork(net
.getName());
2060 var zonePromise
= zoneval
2061 ? firewall
.getZone(zoneval
).then(function(zone
) { return zone
|| firewall
.addZone(zoneval
) })
2062 : Promise
.resolve();
2064 return zonePromise
.then(function(zone
) {
2066 zone
.addNetwork(net
.getName());
2069 }).then(L
.bind(function() {
2070 ui
.showModal(null, E('p', { 'class': 'spinning' }, [ _('Loading data…') ]));
2072 return this.renderMoreOptionsModal(section_id
);
2076 s
.handleJoin = function(radioDev
, bss
, ev
) {
2077 poll
.remove(this.pollFn
);
2079 var m2
= new form
.Map('wireless'),
2080 s2
= m2
.section(form
.NamedSection
, '_new_'),
2081 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
2082 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
2083 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' || a
== 'sae' })),
2084 replace
, passphrase
, name
, bssid
, zone
;
2086 var nameUsed = function(name
) {
2087 var s
= uci
.get('network', name
);
2088 if (s
!= null && s
['.type'] != 'interface')
2091 var net
= (s
!= null) ? network
.instantiateNetwork(name
) : null;
2092 return (net
!= null && !net
.isEmpty());
2095 s2
.render = function() {
2096 return Promise
.all([
2098 this.renderUCISection('_new_')
2099 ]).then(this.renderContents
.bind(this));
2102 if (bss
.ssid
== null) {
2103 name
= s2
.option(form
.Value
, 'ssid', _('Network SSID'), _('The correct SSID must be manually specified when joining a hidden wireless network'));
2104 name
.rmempty
= false;
2107 replace
= s2
.option(form
.Flag
, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
2109 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>'));
2110 name
.datatype
= 'uciname';
2111 name
.default = 'wwan';
2112 name
.rmempty
= false;
2113 name
.validate = function(section_id
, value
) {
2114 if (nameUsed(value
))
2115 return _('The network name is already used');
2120 for (var i
= 2; nameUsed(name
.default); i
++)
2121 name
.default = 'wwan%d'.format(i
);
2123 if (is_wep
|| is_psk
) {
2124 passphrase
= s2
.option(form
.Value
, 'password', is_wep
? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
2125 passphrase
.datatype
= is_wep
? 'wepkey' : 'wpakey';
2126 passphrase
.password
= true;
2127 passphrase
.rmempty
= false;
2130 if (bss
.ssid
!= null) {
2131 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
));
2132 bssid
.default = '0';
2135 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.'));
2136 zone
.default = 'wan';
2138 return m2
.render().then(L
.bind(function(nodes
) {
2139 ui
.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss
.ssid
)), [
2141 E('div', { 'class': 'right' }, [
2144 'click': ui
.hideModal
2145 }, _('Cancel')), ' ',
2147 'class': 'cbi-button cbi-button-positive important',
2148 'click': ui
.createHandlerFn(this, 'handleJoinConfirm', radioDev
, bss
, m2
)
2151 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase
|| name
).cbid('_new_'))).focus();
2155 s
.handleAdd = function(radioDev
, ev
) {
2156 var section_id
= next_free_sid(uci
.sections('wireless', 'wifi-iface').length
);
2158 uci
.unset('wireless', radioDev
.getName(), 'disabled');
2160 uci
.add('wireless', 'wifi-iface', section_id
);
2161 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
2162 uci
.set('wireless', section_id
, 'mode', 'ap');
2163 uci
.set('wireless', section_id
, 'ssid', 'OpenWrt');
2164 uci
.set('wireless', section_id
, 'encryption', 'none');
2166 this.addedSection
= section_id
;
2167 return this.renderMoreOptionsModal(section_id
);
2170 o
= s
.option(form
.DummyValue
, '_badge');
2171 o
.modalonly
= false;
2172 o
.textvalue = function(section_id
) {
2173 var inst
= this.section
.lookupRadioOrNetwork(section_id
),
2174 node
= E('div', { 'class': 'center' });
2176 if (inst
.getWifiNetworks
)
2177 node
.appendChild(render_radio_badge(inst
));
2179 node
.appendChild(render_network_badge(inst
));
2184 o
= s
.option(form
.DummyValue
, '_stat');
2185 o
.modalonly
= false;
2186 o
.textvalue = function(section_id
) {
2187 var inst
= this.section
.lookupRadioOrNetwork(section_id
);
2189 if (inst
.getWifiNetworks
)
2190 return render_radio_status(inst
, this.section
.wifis
.filter(function(e
) {
2191 return (e
.getWifiDeviceName() == inst
.getName());
2194 return render_network_status(inst
);
2197 return m
.render().then(L
.bind(function(m
, nodes
) {
2198 poll
.add(L
.bind(function() {
2199 var section_ids
= m
.children
[0].cfgsections(),
2200 tasks
= [ network
.getHostHints(), network
.getWifiDevices() ];
2202 for (var i
= 0; i
< section_ids
.length
; i
++) {
2203 var row
= nodes
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids
[i
])),
2204 dsc
= row
.querySelector('[data-name="_stat"] > div'),
2205 btns
= row
.querySelectorAll('.cbi-section-actions button');
2207 if (dsc
.getAttribute('restart') == '') {
2208 dsc
.setAttribute('restart', '1');
2209 tasks
.push(fs
.exec('/sbin/wifi', ['up', section_ids
[i
]]).catch(function(e
) {
2210 ui
.addNotification(null, E('p', e
.message
));
2213 else if (dsc
.getAttribute('restart') == '1') {
2214 dsc
.removeAttribute('restart');
2215 btns
[0].classList
.remove('spinning');
2216 btns
[0].disabled
= false;
2220 return Promise
.all(tasks
)
2221 .then(L
.bind(function(hosts_radios
) {
2224 for (var i
= 0; i
< hosts_radios
[1].length
; i
++)
2225 tasks
.push(hosts_radios
[1][i
].getWifiNetworks());
2227 return Promise
.all(tasks
).then(function(data
) {
2228 hosts_radios
[2] = [];
2230 for (var i
= 0; i
< data
.length
; i
++)
2231 hosts_radios
[2].push
.apply(hosts_radios
[2], data
[i
]);
2233 return hosts_radios
;
2236 .then(L
.bind(function(hosts_radios_wifis
) {
2239 for (var i
= 0; i
< hosts_radios_wifis
[2].length
; i
++)
2240 tasks
.push(hosts_radios_wifis
[2][i
].getAssocList());
2242 return Promise
.all(tasks
).then(function(data
) {
2243 hosts_radios_wifis
[3] = [];
2245 for (var i
= 0; i
< data
.length
; i
++) {
2246 var wifiNetwork
= hosts_radios_wifis
[2][i
],
2247 radioDev
= hosts_radios_wifis
[1].filter(function(d
) { return d
.getName() == wifiNetwork
.getWifiDeviceName() })[0];
2249 for (var j
= 0; j
< data
[i
].length
; j
++)
2250 hosts_radios_wifis
[3].push(Object
.assign({ radio
: radioDev
, network
: wifiNetwork
}, data
[i
][j
]));
2253 return hosts_radios_wifis
;
2256 .then(L
.bind(this.poll_status
, this, nodes
));
2259 var table
= E('table', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [
2260 E('tr', { 'class': 'tr table-titles' }, [
2261 E('th', { 'class': 'th nowrap' }, _('Network')),
2262 E('th', { 'class': 'th hide-xs' }, _('MAC address')),
2263 E('th', { 'class': 'th' }, _('Host')),
2264 E('th', { 'class': 'th' }, _('Signal / Noise')),
2265 E('th', { 'class': 'th' }, _('RX Rate / TX Rate'))
2269 cbi_update_table(table
, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
2271 return E([ nodes
, E('h3', _('Associated Stations')), table
]);