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
}, {});
346 '', 'Legacy', hwmodelist
.a
|| hwmodelist
.b
|| hwmodelist
.g
,
347 'n', 'N', hwmodelist
.n
,
348 'ac', 'AC', L
.hasSystemFeature('hostapd', '11ac') && hwmodelist
.ac
,
349 'ax', 'AX', L
.hasSystemFeature('hostapd', '11ax') && hwmodelist
.ax
352 var htmodelist
= L
.toArray(data
[0] ? data
[0].getHTModes() : null)
353 .reduce(function(o
, v
) { o
[v
] = true; return o
}, {});
356 '': [ '', '-', true ],
358 'HT20', '20 MHz', htmodelist
.HT20
,
359 'HT40', '40 MHz', htmodelist
.HT40
362 'VHT20', '20 MHz', htmodelist
.VHT20
,
363 'VHT40', '40 MHz', htmodelist
.VHT40
,
364 'VHT80', '80 MHz', htmodelist
.VHT80
,
365 'VHT160', '160 MHz', htmodelist
.VHT160
368 'HE20', '20 MHz', htmodelist
.HE20
,
369 'HE40', '40 MHz', htmodelist
.HE40
,
370 'HE80', '80 MHz', htmodelist
.HE80
,
371 'HE160', '160 MHz', htmodelist
.HE160
377 '2g', '2.4 GHz', this.channels
['2g'].length
> 3,
378 '5g', '5 GHz', this.channels
['5g'].length
> 3,
379 '60g', '60 GHz', this.channels
['60g'].length
> 0
382 '2g', '2.4 GHz', this.channels
['2g'].length
> 3,
383 '5g', '5 GHz', this.channels
['5g'].length
> 3
389 '2g', '2.4 GHz', this.channels
['2g'].length
> 3,
390 '5g', '5 GHz', this.channels
['5g'].length
> 3
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.datatype = 'maxlength(15)';
1163 o.placeholder = radioNet.getIfname();
1164 if (/^radio\d+\.network/.test(o.placeholder))
1167 o = ss.taboption('advanced', form.Value, 'macaddr', _('MAC address'), _('Override default MAC address - the range of usable addresses might be limited by the driver'));
1169 o.placeholder = radioNet.getActiveBSSID();
1170 o.datatype = 'macaddr';
1172 o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble'));
1173 o.default = o.enabled;
1175 o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
1178 o.datatype = 'range(1,255)';
1180 o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
1182 o.placeholder = 600;
1183 o.datatype = 'uinteger';
1185 o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling'));
1187 o.datatype = 'uinteger';
1189 o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('802.11v: BSS Max Idle. Units: seconds.'));
1191 o.placeholder = 300;
1192 o.datatype = 'uinteger';
1194 o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval'));
1196 o.placeholder = 65535;
1197 o.datatype = 'uinteger';
1199 o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
1200 o.default = o.enabled;
1204 encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption'));
1205 o.depends('mode', 'ap');
1206 o.depends('mode', 'sta');
1207 o.depends('mode', 'adhoc');
1208 o.depends('mode', 'ahdemo');
1209 o.depends('mode', 'ap-wds');
1210 o.depends('mode', 'sta-wds');
1211 o.depends('mode', 'mesh');
1213 o.cfgvalue = function(section_id) {
1214 var v = String(uci.get('wireless', section_id, 'encryption'));
1217 else if (v.match(/\+/))
1218 return v.replace(/\+.+$/, '');
1222 o.write = function(section_id, value) {
1223 var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id),
1224 co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id);
1226 if (value == 'wpa' || value == 'wpa2' || value == 'wpa3' || value == 'wpa3-mixed')
1227 uci.unset('wireless', section_id, 'key');
1229 if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp'))
1232 uci.set('wireless', section_id, 'encryption', e);
1235 o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher'));
1236 o.depends('encryption', 'wpa');
1237 o.depends('encryption', 'wpa2');
1238 o.depends('encryption', 'wpa3');
1239 o.depends('encryption', 'wpa3-mixed');
1240 o.depends('encryption', 'psk');
1241 o.depends('encryption', 'psk2');
1242 o.depends('encryption', 'wpa-mixed');
1243 o.depends('encryption', 'psk-mixed');
1244 o.value('auto', _('auto'));
1245 o.value('ccmp', _('Force CCMP (AES)'));
1246 o.value('tkip', _('Force TKIP'));
1247 o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1248 o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write;
1250 o.cfgvalue = function(section_id) {
1251 var v = String(uci.get('wireless', section_id, 'encryption'));
1252 if (v.match(/\+/)) {
1253 v = v.replace(/^[^+]+\+/, '');
1256 else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip')
1263 var crypto_modes = [];
1265 if (hwtype == 'mac80211') {
1266 var has_supplicant = L.hasSystemFeature('wpasupplicant'),
1267 has_hostapd = L.hasSystemFeature('hostapd');
1269 // Probe EAP support
1270 var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'),
1271 has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap');
1273 // Probe SAE support
1274 var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'),
1275 has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae');
1277 // Probe OWE support
1278 var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
1279 has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
1281 // Probe Suite-B support
1282 var has_ap_eap192 = L.hasSystemFeature('hostapd', 'suiteb192'),
1283 has_sta_eap192 = L.hasSystemFeature('wpasupplicant', 'suiteb192');
1285 // Probe WEP support
1286 var has_ap_wep = L.hasSystemFeature('hostapd', 'wep'),
1287 has_sta_wep = L.hasSystemFeature('wpasupplicant', 'wep');
1289 if (has_hostapd || has_supplicant) {
1290 crypto_modes.push(['psk2', 'WPA2-PSK', 35]);
1291 crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1292 crypto_modes.push(['psk', 'WPA-PSK', 12]);
1295 encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1298 if (has_ap_sae || has_sta_sae) {
1299 crypto_modes.push(['sae', 'WPA3-SAE', 31]);
1300 crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1303 if (has_ap_wep || has_sta_wep) {
1304 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1305 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1308 if (has_ap_eap || has_sta_eap) {
1309 if (has_ap_eap192 || has_sta_eap192) {
1310 crypto_modes.push(['wpa3', 'WPA3-EAP', 33]);
1311 crypto_modes.push(['wpa3-mixed', 'WPA2-EAP/WPA3-EAP Mixed Mode', 32]);
1314 crypto_modes.push(['wpa2', 'WPA2-EAP', 34]);
1315 crypto_modes.push(['wpa', 'WPA-EAP', 20]);
1318 if (has_ap_owe || has_sta_owe) {
1319 crypto_modes.push(['owe', 'OWE', 1]);
1322 encr.crypto_support = {
1324 'wep-open': has_ap_wep || _('Requires hostapd with WEP support'),
1325 'wep-shared': has_ap_wep || _('Requires hostapd with WEP support'),
1326 'psk': has_hostapd || _('Requires hostapd'),
1327 'psk2': has_hostapd || _('Requires hostapd'),
1328 'psk-mixed': has_hostapd || _('Requires hostapd'),
1329 'sae': has_ap_sae || _('Requires hostapd with SAE support'),
1330 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
1331 'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
1332 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
1333 'wpa3': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
1334 'wpa3-mixed': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
1335 'owe': has_ap_owe || _('Requires hostapd with OWE support')
1338 'wep-open': has_sta_wep || _('Requires wpa-supplicant with WEP support'),
1339 'wep-shared': has_sta_wep || _('Requires wpa-supplicant with WEP support'),
1340 'psk': has_supplicant || _('Requires wpa-supplicant'),
1341 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1342 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1343 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1344 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1345 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1346 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1347 'wpa3': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
1348 'wpa3-mixed': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
1349 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
1354 'psk': has_supplicant || _('Requires wpa-supplicant'),
1355 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1356 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1359 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support')
1371 encr.crypto_support['ap-wds'] = encr.crypto_support['ap'];
1372 encr.crypto_support['sta-wds'] = encr.crypto_support['sta'];
1374 encr.validate = function(section_id, value) {
1375 var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
1376 modeval = modeopt.formvalue(section_id),
1377 modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)],
1378 enctitle = this.vallist[this.keylist.indexOf(value)];
1380 if (value == 'none')
1383 if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value))
1384 return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle);
1386 return this.crypto_support[modeval][value];
1389 else if (hwtype == 'broadcom') {
1390 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1391 crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1392 crypto_modes.push(['psk', 'WPA-PSK', 12]);
1393 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1394 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1397 crypto_modes.push(['none', _('No Encryption'), 0]);
1399 crypto_modes.sort(function(a, b) { return b[2] - a[2] });
1401 for (var i = 0; i < crypto_modes.length; i++) {
1402 var security_level = (crypto_modes[i][2] >= 30) ? _('strong security')
1403 : (crypto_modes[i][2] >= 20) ? _('medium security')
1404 : (crypto_modes[i][2] >= 10) ? _('weak security') : _('open network');
1406 encr.value(crypto_modes[i][0], '%s (%s)'.format(crypto_modes[i][1], security_level));
1410 o = ss.taboption('encryption', form.Value, 'auth_server', _('RADIUS Authentication Server'));
1411 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1413 o.datatype = 'host(0)';
1415 o = ss.taboption('encryption', form.Value, 'auth_port', _('RADIUS Authentication Port'));
1416 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1418 o.datatype = 'port';
1419 o.placeholder = '1812';
1421 o = ss.taboption('encryption', form.Value, 'auth_secret', _('RADIUS Authentication Secret'));
1422 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1426 o = ss.taboption('encryption', form.Value, 'acct_server', _('RADIUS Accounting Server'));
1427 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1429 o.datatype = 'host(0)';
1431 o = ss.taboption('encryption', form.Value, 'acct_port', _('RADIUS Accounting Port'));
1432 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1434 o.datatype = 'port';
1435 o.placeholder = '1813';
1437 o = ss.taboption('encryption', form.Value, 'acct_secret', _('RADIUS Accounting Secret'));
1438 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1442 /* extra RADIUS settings start */
1443 o
= ss
.taboption('encryption', form
.ListValue
, 'dynamic_vlan', _('RADIUS Dynamic VLAN Assignment'), _('Required: Rejects auth if RADIUS server does not provide appropriate VLAN attributes.'));
1444 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1445 o
.value('0', _('Disabled'));
1446 o
.value('1', _('Optional'));
1447 o
.value('2', _('Required'));
1448 o
.write = function (section_id
, value
) {
1449 return this.super('write', [section_id
, (value
== 0) ? null: value
]);
1452 o
= ss
.taboption('encryption', form
.Flag
, 'per_sta_vif', _('RADIUS Per STA VLAN'), _('Each STA is assigned its own AP_VLAN interface.'));
1453 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1455 //hostapd internally defaults to vlan_naming=1 even with dynamic VLAN off
1456 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>.'));
1457 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1459 o
= ss
.taboption('encryption', widgets
.DeviceSelect
, 'vlan_tagged_interface', _('RADIUS VLAN Tagged Interface'), _('E.g. eth0, eth1'));
1460 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1467 o
.noinactive
= true;
1469 o
= ss
.taboption('encryption', form
.Value
, 'vlan_bridge', _('RADIUS VLAN Bridge Naming Scheme'), _('E.g. <code>br-vlan</code> or <code>brvlan</code>.'));
1470 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1472 /* extra RADIUS settings end */
1474 o
= ss
.taboption('encryption', form
.Value
, 'dae_client', _('DAE-Client'), _('Dynamic Authorization Extension client.'));
1475 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1477 o
.datatype
= 'host(0)';
1479 o
= ss
.taboption('encryption', form
.Value
, 'dae_port', _('DAE-Port'), _('Dynamic Authorization Extension port.'));
1480 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1482 o
.datatype
= 'port';
1483 o
.placeholder
= '3799';
1485 o
= ss
.taboption('encryption', form
.Value
, 'dae_secret', _('DAE-Secret'), _('Dynamic Authorization Extension secret.'));
1486 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1490 //WPA(1) has only WPA IE. Only >= WPA2 has RSN IE Preauth frames.
1491 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.'));
1492 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa2', 'wpa3', 'wpa3-mixed'] });
1495 o
= ss
.taboption('encryption', form
.Value
, '_wpa_key', _('Key'));
1496 o
.depends('encryption', 'psk');
1497 o
.depends('encryption', 'psk2');
1498 o
.depends('encryption', 'psk+psk2');
1499 o
.depends('encryption', 'psk-mixed');
1500 o
.depends('encryption', 'sae');
1501 o
.depends('encryption', 'sae-mixed');
1502 o
.datatype
= 'wpakey';
1506 o
.cfgvalue = function(section_id
) {
1507 var key
= uci
.get('wireless', section_id
, 'key');
1508 return /^[1234]$/.test(key
) ? null : key
;
1511 o
.write = function(section_id
, value
) {
1512 uci
.set('wireless', section_id
, 'key', value
);
1513 uci
.unset('wireless', section_id
, 'key1');
1514 uci
.unset('wireless', section_id
, 'key2');
1515 uci
.unset('wireless', section_id
, 'key3');
1516 uci
.unset('wireless', section_id
, 'key4');
1520 o
= ss
.taboption('encryption', form
.ListValue
, '_wep_key', _('Used Key Slot'));
1521 o
.depends('encryption', 'wep-open');
1522 o
.depends('encryption', 'wep-shared');
1523 o
.value('1', _('Key #%d').format(1));
1524 o
.value('2', _('Key #%d').format(2));
1525 o
.value('3', _('Key #%d').format(3));
1526 o
.value('4', _('Key #%d').format(4));
1528 o
.cfgvalue = function(section_id
) {
1529 var slot
= +uci
.get('wireless', section_id
, 'key');
1530 return (slot
>= 1 && slot
<= 4) ? String(slot
) : '';
1533 o
.write = function(section_id
, value
) {
1534 uci
.set('wireless', section_id
, 'key', value
);
1537 for (var slot
= 1; slot
<= 4; slot
++) {
1538 o
= ss
.taboption('encryption', form
.Value
, 'key%d'.format(slot
), _('Key #%d').format(slot
));
1539 o
.depends('encryption', 'wep-open');
1540 o
.depends('encryption', 'wep-shared');
1541 o
.datatype
= 'wepkey';
1545 o
.write = function(section_id
, value
) {
1546 if (value
!= null && (value
.length
== 5 || value
.length
== 13))
1547 value
= 's:%s'.format(value
);
1548 uci
.set('wireless', section_id
, this.option
, value
);
1553 if (hwtype
== 'mac80211') {
1554 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1555 var has_80211r
= L
.hasSystemFeature('hostapd', '11r') || L
.hasSystemFeature('hostapd', 'eap');
1557 o
= ss
.taboption('roaming', form
.Flag
, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1558 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1560 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1563 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.'));
1564 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1565 o
.depends({ ieee80211r
: '1' });
1568 o
= ss
.taboption('roaming', form
.Value
, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1569 o
.depends({ ieee80211r
: '1' });
1570 o
.placeholder
= '4f57';
1571 o
.datatype
= 'and(hexstring,length(4))';
1574 o
= ss
.taboption('roaming', form
.Value
, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1575 o
.depends({ ieee80211r
: '1' });
1576 o
.placeholder
= '1000';
1577 o
.datatype
= 'range(1000,65535)';
1580 o
= ss
.taboption('roaming', form
.ListValue
, 'ft_over_ds', _('FT protocol'));
1581 o
.depends({ ieee80211r
: '1' });
1582 o
.value('0', _('FT over the Air'));
1583 o
.value('1', _('FT over DS'));
1586 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.'));
1587 o
.depends({ ieee80211r
: '1' });
1588 o
.default = o
.enabled
;
1591 o
= ss
.taboption('roaming', form
.Value
, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1592 o
.depends({ ieee80211r
: '1' });
1593 o
.placeholder
= '10000';
1594 o
.datatype
= 'uinteger';
1597 o
= ss
.taboption('roaming', form
.Value
, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1598 o
.depends({ ieee80211r
: '1' });
1599 o
.placeholder
= '00004f577274';
1600 o
.datatype
= 'and(hexstring,length(12))';
1603 o
= ss
.taboption('roaming', form
.Flag
, 'pmk_r1_push', _('PMK R1 Push'));
1604 o
.depends({ ieee80211r
: '1' });
1605 o
.placeholder
= '0';
1608 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.'));
1609 o
.depends({ ieee80211r
: '1' });
1612 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.'));
1613 o
.depends({ ieee80211r
: '1' });
1615 // End of 802.11r options
1617 // Probe 802.11k and 802.11v support via EAP support (full hostapd has EAP)
1618 if (L
.hasSystemFeature('hostapd', 'eap')) {
1619 /* 802.11k settings start */ o
=
1620 ss
.taboption('roaming', form
.Flag
, 'ieee80211k', _('802.11k RRM'), _('Radio Resource Measurement - Sends beacons to assist roaming. Not all clients support this.'));
1621 // add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1622 o
.depends('mode', 'ap');
1623 o
.depends('mode', 'ap-wds');
1625 o
= ss
.taboption('roaming', form
.Flag
, 'rrm_neighbor_report', _('Neighbour Report'), _('802.11k: Enable neighbor report via radio measurements.'));
1626 o
.depends({ ieee80211k
: '1' });
1627 o
.default = o
.enabled
;
1629 o
= ss
.taboption('roaming', form
.Flag
, 'rrm_beacon_report', _('Beacon Report'), _('802.11k: Enable beacon report via radio measurements.'));
1630 o
.depends({ ieee80211k
: '1' });
1631 o
.default = o
.enabled
;
1632 /* 802.11k settings end */
1634 /* 802.11v settings start */
1635 o
= ss
.taboption('roaming', form
.ListValue
, 'time_advertisement', _('Time advertisement'), _('802.11v: Time Advertisement in management frames.'));
1636 o
.value('0', _('Disabled'));
1637 o
.value('2', _('Enabled'));
1638 o
.write = function (section_id
, value
) {
1639 return this.super('write', [section_id
, (value
== 2) ? value
: null]);
1642 //Pull current System TZ setting
1643 var tz
= uci
.get('system', '@system[0]', 'timezone');
1644 o
= ss
.taboption('roaming', form
.Value
, 'time_zone', _('Time zone'), _('802.11v: Local Time Zone Advertisement in management frames.'));
1648 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).'));
1651 /* wnm_sleep_mode_no_keys: https://git.openwrt.org/?p=openwrt/openwrt.git;a=commitdiff;h=bf98faaac8ed24cf7d3d93dd4fcd7304d109363b */
1652 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.'));
1655 o = ss.taboption('roaming', form.Flag, 'bss_transition', _('BSS Transition'), _('802.11v: Basic Service Set (BSS) transition management.'));
1658 /* in master, but not 21.02.1: proxy_arp */
1659 o
= ss
.taboption('roaming', form
.Flag
, 'proxy_arp', _('ProxyARP'), _('802.11v: Proxy ARP enables non-AP STA to remain in power-save for longer.'));
1662 /* TODO: na_mcast_to_ucast is missing: needs adding to hostapd.sh - nice to have */
1664 /* 802.11v settings end */
1667 if (hwtype
== 'mac80211') {
1668 o
= ss
.taboption('encryption', form
.ListValue
, 'eap_type', _('EAP-Method'));
1669 o
.value('tls', 'TLS');
1670 o
.value('ttls', 'TTLS');
1671 o
.value('peap', 'PEAP');
1672 o
.value('fast', 'FAST');
1673 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1675 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"));
1678 o
.default = o
.disabled
;
1679 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1680 o
.validate = function(section_id
, value
) {
1681 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1682 return _("This option cannot be used because the ca-bundle package is not installed.");
1687 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert', _('Path to CA-Certificate'));
1688 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], ca_cert_usesystem
: ['0'] });
1690 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"));
1691 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1693 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"));
1694 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1696 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)"));
1697 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1699 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)"));
1700 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1702 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert', _('Path to Client-Certificate'));
1703 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1705 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key', _('Path to Private Key'));
1706 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1708 o
= ss
.taboption('encryption', form
.Value
, 'priv_key_pwd', _('Password of Private Key'));
1709 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1712 o
= ss
.taboption('encryption', form
.ListValue
, 'auth', _('Authentication'));
1713 o
.value('PAP', 'PAP');
1714 o
.value('CHAP', 'CHAP');
1715 o
.value('MSCHAP', 'MSCHAP');
1716 o
.value('MSCHAPV2', 'MSCHAPv2');
1717 o
.value('EAP-GTC', 'EAP-GTC');
1718 o
.value('EAP-MD5', 'EAP-MD5');
1719 o
.value('EAP-MSCHAPV2', 'EAP-MSCHAPv2');
1720 o
.value('EAP-TLS', 'EAP-TLS');
1721 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1723 o
.validate = function(section_id
, value
) {
1724 var eo
= this.section
.children
.filter(function(o
) { return o
.option
== 'eap_type' })[0],
1725 ev
= eo
.formvalue(section_id
);
1727 if (ev
!= 'ttls' && (value
== 'PAP' || value
== 'CHAP' || value
== 'MSCHAP' || value
== 'MSCHAPV2'))
1728 return _('This authentication type is not applicable to the selected EAP method.');
1733 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"));
1736 o
.default = o
.disabled
;
1737 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1738 o
.validate = function(section_id
, value
) {
1739 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1740 return _("This option cannot be used because the ca-bundle package is not installed.");
1745 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert2', _('Path to inner CA-Certificate'));
1746 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'], ca_cert2_usesystem
: ['0'] });
1748 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"));
1749 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1751 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"));
1752 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1754 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)"));
1755 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1757 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)"));
1758 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1760 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert2', _('Path to inner Client-Certificate'));
1761 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1763 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key2', _('Path to inner Private Key'));
1764 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1766 o
= ss
.taboption('encryption', form
.Value
, 'priv_key2_pwd', _('Password of inner Private Key'));
1767 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1770 o
= ss
.taboption('encryption', form
.Value
, 'identity', _('Identity'));
1771 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1773 o
= ss
.taboption('encryption', form
.Value
, 'anonymous_identity', _('Anonymous Identity'));
1774 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1776 o
= ss
.taboption('encryption', form
.Value
, 'password', _('Password'));
1777 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1781 if (hwtype
== 'mac80211') {
1782 // ieee802.11w options
1783 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"));
1784 o
.value('0', _('Disabled'));
1785 o
.value('1', _('Optional'));
1786 o
.value('2', _('Required'));
1787 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds', 'sta', 'sta-wds'], encryption
: ['owe', 'psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1790 '2': [{ encryption
: 'sae' }, { encryption
: 'owe' }, { encryption
: 'wpa3' }, { encryption
: 'wpa3-mixed' }],
1791 '1': [{ encryption
: 'sae-mixed'}],
1795 o
.write = function(section_id
, value
) {
1796 if (value
!= this.default)
1797 return form
.ListValue
.prototype.write
.call(this, section_id
, value
);
1799 return form
.ListValue
.prototype.remove
.call(this, section_id
);
1802 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1803 o
.depends('ieee80211w', '1');
1804 o
.depends('ieee80211w', '2');
1805 o
.datatype
= 'uinteger';
1806 o
.placeholder
= '1000';
1809 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1810 o
.depends('ieee80211w', '1');
1811 o
.depends('ieee80211w', '2');
1812 o
.datatype
= 'uinteger';
1813 o
.placeholder
= '201';
1816 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.'));
1817 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1819 if (L
.hasSystemFeature('hostapd', 'wps') && L
.hasSystemFeature('wpasupplicant')) {
1820 o
= ss
.taboption('encryption', form
.Flag
, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1823 o
.default = o
.disabled
;
1824 o
.depends('encryption', 'psk');
1825 o
.depends('encryption', 'psk2');
1826 o
.depends('encryption', 'psk-mixed');
1827 o
.depends('encryption', 'sae');
1828 o
.depends('encryption', 'sae-mixed');
1835 s
.handleRemove = function(section_id
, ev
) {
1836 document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id
)).style
.opacity
= 0.5;
1837 return form
.TypedSection
.prototype.handleRemove
.apply(this, [section_id
, ev
]);
1840 s
.handleScan = function(radioDev
, ev
) {
1841 var table
= E('table', { 'class': 'table' }, [
1842 E('tr', { 'class': 'tr table-titles' }, [
1843 E('th', { 'class': 'th col-2 middle center' }, _('Signal')),
1844 E('th', { 'class': 'th col-4 middle left' }, _('SSID')),
1845 E('th', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1846 E('th', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1847 E('th', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1848 E('th', { 'class': 'th col-3 middle left' }, _('Encryption')),
1849 E('th', { 'class': 'th cbi-section-actions right' }, ' '),
1853 var stop
= E('button', {
1855 'click': L
.bind(this.handleScanStartStop
, this),
1856 'style': 'display:none',
1857 'data-state': 'stop'
1858 }, _('Stop refresh'));
1860 cbi_update_table(table
, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1862 var md
= ui
.showModal(_('Join Network: Wireless Scan'), [
1864 E('div', { 'class': 'right' }, [
1869 'click': L
.bind(this.handleScanAbort
, this)
1874 md
.style
.maxWidth
= '90%';
1875 md
.style
.maxHeight
= 'none';
1877 this.pollFn
= L
.bind(this.handleScanRefresh
, this, radioDev
, {}, table
, stop
);
1879 poll
.add(this.pollFn
);
1883 s
.handleScanRefresh = function(radioDev
, scanCache
, table
, stop
) {
1884 return radioDev
.getScanList().then(L
.bind(function(results
) {
1887 for (var i
= 0; i
< results
.length
; i
++)
1888 scanCache
[results
[i
].bssid
] = results
[i
];
1890 for (var k
in scanCache
)
1891 if (scanCache
[k
].stale
)
1892 results
.push(scanCache
[k
]);
1894 results
.sort(function(a
, b
) {
1895 var diff
= (b
.quality
- a
.quality
) || (a
.channel
- b
.channel
);
1900 if (a
.ssid
< b
.ssid
)
1902 else if (a
.ssid
> b
.ssid
)
1905 if (a
.bssid
< b
.bssid
)
1907 else if (a
.bssid
> b
.bssid
)
1911 for (var i
= 0; i
< results
.length
; i
++) {
1912 var res
= results
[i
],
1913 qv
= res
.quality
|| 0,
1914 qm
= res
.quality_max
|| 0,
1915 q
= (qv
> 0 && qm
> 0) ? Math
.floor((100 / qm
) * qv
) : 0,
1916 s
= res
.stale
? 'opacity:0.5' : '';
1919 E('span', { 'style': s
}, render_signal_badge(q
, res
.signal
, res
.noise
)),
1920 E('span', { 'style': s
}, (res
.ssid
!= null) ? '%h'.format(res
.ssid
) : E('em', _('hidden'))),
1921 E('span', { 'style': s
}, '%d'.format(res
.channel
)),
1922 E('span', { 'style': s
}, '%h'.format(res
.mode
)),
1923 E('span', { 'style': s
}, '%h'.format(res
.bssid
)),
1924 E('span', { 'style': s
}, '%h'.format(network
.formatWifiEncryption(res
.encryption
))),
1925 E('div', { 'class': 'right' }, E('button', {
1926 'class': 'cbi-button cbi-button-action important',
1927 'click': ui
.createHandlerFn(this, 'handleJoin', radioDev
, res
)
1928 }, _('Join Network')))
1934 cbi_update_table(table
, rows
);
1936 stop
.disabled
= false;
1937 stop
.style
.display
= '';
1938 stop
.classList
.remove('spinning');
1942 s
.handleScanStartStop = function(ev
) {
1943 var btn
= ev
.currentTarget
;
1945 if (btn
.getAttribute('data-state') == 'stop') {
1946 poll
.remove(this.pollFn
);
1947 btn
.firstChild
.data
= _('Start refresh');
1948 btn
.setAttribute('data-state', 'start');
1951 poll
.add(this.pollFn
);
1952 btn
.firstChild
.data
= _('Stop refresh');
1953 btn
.setAttribute('data-state', 'stop');
1954 btn
.classList
.add('spinning');
1955 btn
.disabled
= true;
1959 s
.handleScanAbort = function(ev
) {
1960 var md
= dom
.parent(ev
.target
, 'div[aria-modal="true"]');
1962 md
.style
.maxWidth
= '';
1963 md
.style
.maxHeight
= '';
1967 poll
.remove(this.pollFn
);
1972 s
.handleJoinConfirm = function(radioDev
, bss
, form
, ev
) {
1973 var nameopt
= L
.toArray(form
.lookupOption('name', '_new_'))[0],
1974 passopt
= L
.toArray(form
.lookupOption('password', '_new_'))[0],
1975 ssidopt
= L
.toArray(form
.lookupOption('ssid', '_new_'))[0],
1976 bssidopt
= L
.toArray(form
.lookupOption('bssid', '_new_'))[0],
1977 zoneopt
= L
.toArray(form
.lookupOption('zone', '_new_'))[0],
1978 replopt
= L
.toArray(form
.lookupOption('replace', '_new_'))[0],
1979 nameval
= (nameopt
&& nameopt
.isValid('_new_')) ? nameopt
.formvalue('_new_') : null,
1980 passval
= (passopt
&& passopt
.isValid('_new_')) ? passopt
.formvalue('_new_') : null,
1981 ssidval
= (ssidopt
&& ssidopt
.isValid('_new_')) ? ssidopt
.formvalue('_new_') : null,
1982 bssidval
= (bssidopt
&& bssidopt
.isValid('_new_')) ? bssidopt
.formvalue('_new_') : null,
1983 zoneval
= zoneopt
? zoneopt
.formvalue('_new_') : null,
1984 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
1985 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
1986 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' }).length
> 0),
1987 is_sae
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'sae' }).length
> 0);
1989 if (nameval
== null || (passopt
&& passval
== null))
1992 var section_id
= null;
1994 return this.map
.save(function() {
1995 var wifi_sections
= uci
.sections('wireless', 'wifi-iface');
1997 if (replopt
.formvalue('_new_') == '1') {
1998 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1999 if (wifi_sections
[i
].device
== radioDev
.getName())
2000 uci
.remove('wireless', wifi_sections
[i
]['.name']);
2003 if (uci
.get('wireless', radioDev
.getName(), 'disabled') == '1') {
2004 for (var i
= 0; i
< wifi_sections
.length
; i
++)
2005 if (wifi_sections
[i
].device
== radioDev
.getName())
2006 uci
.set('wireless', wifi_sections
[i
]['.name'], 'disabled', '1');
2008 uci
.unset('wireless', radioDev
.getName(), 'disabled');
2011 section_id
= next_free_sid(wifi_sections
.length
);
2013 uci
.add('wireless', 'wifi-iface', section_id
);
2014 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
2015 uci
.set('wireless', section_id
, 'mode', (bss
.mode
== 'Ad-Hoc') ? 'adhoc' : 'sta');
2016 uci
.set('wireless', section_id
, 'network', nameval
);
2018 if (bss
.ssid
!= null) {
2019 uci
.set('wireless', section_id
, 'ssid', bss
.ssid
);
2021 if (bssidval
== '1')
2022 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
2024 else if (bss
.bssid
!= null) {
2025 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
2028 if (ssidval
!= null)
2029 uci
.set('wireless', section_id
, 'ssid', ssidval
);
2032 uci
.set('wireless', section_id
, 'encryption', 'sae');
2033 uci
.set('wireless', section_id
, 'key', passval
);
2036 for (var i
= enc
.wpa
.length
- 1; i
>= 0; i
--) {
2037 if (enc
.wpa
[i
] == 2) {
2038 uci
.set('wireless', section_id
, 'encryption', 'psk2');
2041 else if (enc
.wpa
[i
] == 1) {
2042 uci
.set('wireless', section_id
, 'encryption', 'psk');
2047 uci
.set('wireless', section_id
, 'key', passval
);
2050 uci
.set('wireless', section_id
, 'encryption', 'wep-open');
2051 uci
.set('wireless', section_id
, 'key', '1');
2052 uci
.set('wireless', section_id
, 'key1', passval
);
2055 uci
.set('wireless', section_id
, 'encryption', 'none');
2058 return network
.addNetwork(nameval
, { proto
: 'dhcp' }).then(function(net
) {
2059 firewall
.deleteNetwork(net
.getName());
2061 var zonePromise
= zoneval
2062 ? firewall
.getZone(zoneval
).then(function(zone
) { return zone
|| firewall
.addZone(zoneval
) })
2063 : Promise
.resolve();
2065 return zonePromise
.then(function(zone
) {
2067 zone
.addNetwork(net
.getName());
2070 }).then(L
.bind(function() {
2071 ui
.showModal(null, E('p', { 'class': 'spinning' }, [ _('Loading data…') ]));
2073 return this.renderMoreOptionsModal(section_id
);
2077 s
.handleJoin = function(radioDev
, bss
, ev
) {
2078 poll
.remove(this.pollFn
);
2080 var m2
= new form
.Map('wireless'),
2081 s2
= m2
.section(form
.NamedSection
, '_new_'),
2082 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
2083 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
2084 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' || a
== 'sae' })),
2085 replace
, passphrase
, name
, bssid
, zone
;
2087 var nameUsed = function(name
) {
2088 var s
= uci
.get('network', name
);
2089 if (s
!= null && s
['.type'] != 'interface')
2092 var net
= (s
!= null) ? network
.instantiateNetwork(name
) : null;
2093 return (net
!= null && !net
.isEmpty());
2096 s2
.render = function() {
2097 return Promise
.all([
2099 this.renderUCISection('_new_')
2100 ]).then(this.renderContents
.bind(this));
2103 if (bss
.ssid
== null) {
2104 name
= s2
.option(form
.Value
, 'ssid', _('Network SSID'), _('The correct SSID must be manually specified when joining a hidden wireless network'));
2105 name
.rmempty
= false;
2108 replace
= s2
.option(form
.Flag
, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
2110 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>'));
2111 name
.datatype
= 'uciname';
2112 name
.default = 'wwan';
2113 name
.rmempty
= false;
2114 name
.validate = function(section_id
, value
) {
2115 if (nameUsed(value
))
2116 return _('The network name is already used');
2121 for (var i
= 2; nameUsed(name
.default); i
++)
2122 name
.default = 'wwan%d'.format(i
);
2124 if (is_wep
|| is_psk
) {
2125 passphrase
= s2
.option(form
.Value
, 'password', is_wep
? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
2126 passphrase
.datatype
= is_wep
? 'wepkey' : 'wpakey';
2127 passphrase
.password
= true;
2128 passphrase
.rmempty
= false;
2131 if (bss
.ssid
!= null) {
2132 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
));
2133 bssid
.default = '0';
2136 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.'));
2137 zone
.default = 'wan';
2139 return m2
.render().then(L
.bind(function(nodes
) {
2140 ui
.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss
.ssid
)), [
2142 E('div', { 'class': 'right' }, [
2145 'click': ui
.hideModal
2146 }, _('Cancel')), ' ',
2148 'class': 'cbi-button cbi-button-positive important',
2149 'click': ui
.createHandlerFn(this, 'handleJoinConfirm', radioDev
, bss
, m2
)
2152 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase
|| name
).cbid('_new_'))).focus();
2156 s
.handleAdd = function(radioDev
, ev
) {
2157 var section_id
= next_free_sid(uci
.sections('wireless', 'wifi-iface').length
);
2159 uci
.unset('wireless', radioDev
.getName(), 'disabled');
2161 uci
.add('wireless', 'wifi-iface', section_id
);
2162 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
2163 uci
.set('wireless', section_id
, 'mode', 'ap');
2164 uci
.set('wireless', section_id
, 'ssid', 'OpenWrt');
2165 uci
.set('wireless', section_id
, 'encryption', 'none');
2167 this.addedSection
= section_id
;
2168 return this.renderMoreOptionsModal(section_id
);
2171 o
= s
.option(form
.DummyValue
, '_badge');
2172 o
.modalonly
= false;
2173 o
.textvalue = function(section_id
) {
2174 var inst
= this.section
.lookupRadioOrNetwork(section_id
),
2175 node
= E('div', { 'class': 'center' });
2177 if (inst
.getWifiNetworks
)
2178 node
.appendChild(render_radio_badge(inst
));
2180 node
.appendChild(render_network_badge(inst
));
2185 o
= s
.option(form
.DummyValue
, '_stat');
2186 o
.modalonly
= false;
2187 o
.textvalue = function(section_id
) {
2188 var inst
= this.section
.lookupRadioOrNetwork(section_id
);
2190 if (inst
.getWifiNetworks
)
2191 return render_radio_status(inst
, this.section
.wifis
.filter(function(e
) {
2192 return (e
.getWifiDeviceName() == inst
.getName());
2195 return render_network_status(inst
);
2198 return m
.render().then(L
.bind(function(m
, nodes
) {
2199 poll
.add(L
.bind(function() {
2200 var section_ids
= m
.children
[0].cfgsections(),
2201 tasks
= [ network
.getHostHints(), network
.getWifiDevices() ];
2203 for (var i
= 0; i
< section_ids
.length
; i
++) {
2204 var row
= nodes
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids
[i
])),
2205 dsc
= row
.querySelector('[data-name="_stat"] > div'),
2206 btns
= row
.querySelectorAll('.cbi-section-actions button');
2208 if (dsc
.getAttribute('restart') == '') {
2209 dsc
.setAttribute('restart', '1');
2210 tasks
.push(fs
.exec('/sbin/wifi', ['up', section_ids
[i
]]).catch(function(e
) {
2211 ui
.addNotification(null, E('p', e
.message
));
2214 else if (dsc
.getAttribute('restart') == '1') {
2215 dsc
.removeAttribute('restart');
2216 btns
[0].classList
.remove('spinning');
2217 btns
[0].disabled
= false;
2221 return Promise
.all(tasks
)
2222 .then(L
.bind(function(hosts_radios
) {
2225 for (var i
= 0; i
< hosts_radios
[1].length
; i
++)
2226 tasks
.push(hosts_radios
[1][i
].getWifiNetworks());
2228 return Promise
.all(tasks
).then(function(data
) {
2229 hosts_radios
[2] = [];
2231 for (var i
= 0; i
< data
.length
; i
++)
2232 hosts_radios
[2].push
.apply(hosts_radios
[2], data
[i
]);
2234 return hosts_radios
;
2237 .then(L
.bind(function(hosts_radios_wifis
) {
2240 for (var i
= 0; i
< hosts_radios_wifis
[2].length
; i
++)
2241 tasks
.push(hosts_radios_wifis
[2][i
].getAssocList());
2243 return Promise
.all(tasks
).then(function(data
) {
2244 hosts_radios_wifis
[3] = [];
2246 for (var i
= 0; i
< data
.length
; i
++) {
2247 var wifiNetwork
= hosts_radios_wifis
[2][i
],
2248 radioDev
= hosts_radios_wifis
[1].filter(function(d
) { return d
.getName() == wifiNetwork
.getWifiDeviceName() })[0];
2250 for (var j
= 0; j
< data
[i
].length
; j
++)
2251 hosts_radios_wifis
[3].push(Object
.assign({ radio
: radioDev
, network
: wifiNetwork
}, data
[i
][j
]));
2254 return hosts_radios_wifis
;
2257 .then(L
.bind(this.poll_status
, this, nodes
));
2260 var table
= E('table', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [
2261 E('tr', { 'class': 'tr table-titles' }, [
2262 E('th', { 'class': 'th nowrap' }, _('Network')),
2263 E('th', { 'class': 'th hide-xs' }, _('MAC address')),
2264 E('th', { 'class': 'th' }, _('Host')),
2265 E('th', { 'class': 'th' }, _('Signal / Noise')),
2266 E('th', { 'class': 'th' }, _('RX Rate / TX Rate'))
2270 cbi_update_table(table
, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
2272 return E([ nodes
, E('h3', _('Associated Stations')), table
]);