294fec7957b6460607bf24ddd90fd263fbe93f87
12 'require tools.widgets as widgets';
14 var isReadonlyView
= !L
.hasViewPermission();
16 function count_changes(section_id
) {
17 var changes
= ui
.changes
.changes
, n
= 0;
19 if (!L
.isObject(changes
))
22 if (Array
.isArray(changes
.wireless
))
23 for (var i
= 0; i
< changes
.wireless
.length
; i
++)
24 n
+= (changes
.wireless
[i
][1] == section_id
);
29 function render_radio_badge(radioDev
) {
30 return E('span', { 'class': 'ifacebadge' }, [
31 E('img', { 'src': L
.resource('icons/wifi%s.png').format(radioDev
.isUp() ? '' : '_disabled') }),
37 function render_signal_badge(signalPercent
, signalValue
, noiseValue
, wrap
, mode
) {
38 var icon
, title
, value
;
40 if (signalPercent
< 0)
41 icon
= L
.resource('icons/signal-none.png');
42 else if (signalPercent
== 0)
43 icon
= L
.resource('icons/signal-0.png');
44 else if (signalPercent
< 25)
45 icon
= L
.resource('icons/signal-0-25.png');
46 else if (signalPercent
< 50)
47 icon
= L
.resource('icons/signal-25-50.png');
48 else if (signalPercent
< 75)
49 icon
= L
.resource('icons/signal-50-75.png');
51 icon
= L
.resource('icons/signal-75-100.png');
53 if (signalValue
!= null && signalValue
!= 0) {
54 if (noiseValue
!= null && noiseValue
!= 0) {
55 value
= '%d/%d\xa0%s'.format(signalValue
, noiseValue
, _('dBm'));
56 title
= '%s: %d %s / %s: %d %s / %s %d'.format(
57 _('Signal'), signalValue
, _('dBm'),
58 _('Noise'), noiseValue
, _('dBm'),
59 _('SNR'), signalValue
- noiseValue
);
62 value
= '%d\xa0%s'.format(signalValue
, _('dBm'));
63 title
= '%s: %d %s'.format(_('Signal'), signalValue
, _('dBm'));
66 else if (signalPercent
> -1) {
69 title
= _('No client associated');
75 title
= _('Not associated');
79 title
= _('No RX signal');
82 if (noiseValue
!= null && noiseValue
!= 0) {
83 value
= '---/%d\x0a%s'.format(noiseValue
, _('dBm'));
84 title
= '%s / %s: %d %s'.format(title
, _('Noise'), noiseValue
, _('dBm'));
87 value
= '---\xa0%s'.format(_('dBm'));
91 value
= E('em', {}, E('small', {}, [ _('disabled') ]));
92 title
= _('Interface is disabled');
96 'class': wrap
? 'center' : 'ifacebadge',
98 'data-signal': signalValue
,
99 'data-noise': noiseValue
101 E('img', { 'src': icon
}),
103 wrap
? E('br') : ' ',
109 function render_network_badge(radioNet
) {
110 return render_signal_badge(
111 radioNet
.isUp() ? radioNet
.getSignalPercent() : -1,
112 radioNet
.getSignal(), radioNet
.getNoise(), false, radioNet
.getMode());
115 function render_radio_status(radioDev
, wifiNets
) {
116 var name
= radioDev
.getI18n().replace(/ Wireless Controller
.+$/, ''),
117 node
= E('div', [ E('big', {}, E('strong', {}, name
)), E('div') ]),
118 channel
, frequency
, bitrate
;
120 for (var i
= 0; i
< wifiNets
.length
; i
++) {
121 channel
= channel
|| wifiNets
[i
].getChannel();
122 frequency
= frequency
|| wifiNets
[i
].getFrequency();
123 bitrate
= bitrate
|| wifiNets
[i
].getBitRate();
127 L
.itemlist(node
.lastElementChild
, [
128 _('Channel'), '%s (%s %s)'.format(channel
|| '?', frequency
|| '?', _('GHz')),
129 _('Bitrate'), '%s %s'.format(bitrate
|| '?', _('Mbit/s'))
132 node
.lastElementChild
.appendChild(E('em', _('Device is not active')));
137 function render_network_status(radioNet
) {
138 var mode
= radioNet
.getActiveMode(),
139 bssid
= radioNet
.getActiveBSSID(),
140 channel
= radioNet
.getChannel(),
141 disabled
= (radioNet
.get('disabled') == '1' || uci
.get('wireless', radioNet
.getWifiDeviceName(), 'disabled') == '1'),
142 is_assoc
= (bssid
&& bssid
!= '00:00:00:00:00:00' && channel
&& mode
!= 'Unknown' && !disabled
),
143 is_mesh
= (radioNet
.getMode() == 'mesh'),
144 changecount
= count_changes(radioNet
.getName()),
148 status_text
= E('a', {
150 click
: L
.bind(ui
.changes
.displayChanges
, ui
.changes
)
151 }, _('Interface has %d pending changes').format(changecount
));
153 status_text
= E('em', disabled
? _('Wireless is disabled') : _('Wireless is not associated'));
155 return L
.itemlist(E('div'), [
156 is_mesh
? _('Mesh ID') : _('SSID'), (is_mesh
? radioNet
.getMeshID() : radioNet
.getSSID()) || '?',
158 _('BSSID'), (!changecount
&& is_assoc
) ? bssid
: null,
159 _('Encryption'), (!changecount
&& is_assoc
) ? radioNet
.getActiveEncryption() || _('None') : null,
161 ], [ ' | ', E('br') ]);
164 function render_modal_status(node
, radioNet
) {
165 var mode
= radioNet
.getActiveMode(),
166 noise
= radioNet
.getNoise(),
167 bssid
= radioNet
.getActiveBSSID(),
168 channel
= radioNet
.getChannel(),
169 disabled
= (radioNet
.get('disabled') == '1'),
170 is_assoc
= (bssid
&& bssid
!= '00:00:00:00:00:00' && channel
&& mode
!= 'Unknown' && !disabled
);
173 node
= E('span', { 'class': 'ifacebadge large', 'data-network': radioNet
.getName() }, [ E('small'), E('span') ]);
175 dom
.content(node
.firstElementChild
, render_signal_badge(
176 disabled
? -1 : radioNet
.getSignalPercent(),
177 radioNet
.getSignal(), noise
, true, radioNet
.getMode()));
179 L
.itemlist(node
.lastElementChild
, [
181 _('SSID'), radioNet
.getSSID() || '?',
182 _('BSSID'), is_assoc
? bssid
: null,
183 _('Encryption'), is_assoc
? radioNet
.getActiveEncryption() || _('None') : null,
184 _('Channel'), is_assoc
? '%d (%.3f %s)'.format(radioNet
.getChannel(), radioNet
.getFrequency() || 0, _('GHz')) : null,
185 _('Tx-Power'), is_assoc
? '%d %s'.format(radioNet
.getTXPower(), _('dBm')) : null,
186 _('Signal'), is_assoc
? '%d %s'.format(radioNet
.getSignal(), _('dBm')) : null,
187 _('Noise'), (is_assoc
&& noise
!= null) ? '%d %s'.format(noise
, _('dBm')) : null,
188 _('Bitrate'), is_assoc
? '%.1f %s'.format(radioNet
.getBitRate() || 0, _('Mbit/s')) : null,
189 _('Country'), is_assoc
? radioNet
.getCountryCode() : null
190 ], [ ' | ', E('br'), E('br'), E('br'), E('br'), E('br'), ' | ', E('br'), ' | ' ]);
193 dom
.append(node
.lastElementChild
, E('em', disabled
? _('Wireless is disabled') : _('Wireless is not associated')));
198 function format_wifirate(rate
) {
199 var s
= '%.1f\xa0%s, %d\xa0%s'.format(rate
.rate
/ 1000, _('Mbit/s'), rate
.mhz
, _('MHz')),
200 ht
= rate
.ht
, vht
= rate
.vht
,
201 mhz
= rate
.mhz
, nss
= rate
.nss
,
202 mcs
= rate
.mcs
, sgi
= rate
.short_gi
,
203 he
= rate
.he
, he_gi
= rate
.he_gi
,
204 he_dcm
= rate
.he_dcm
;
207 if (vht
) s
+= ', VHT-MCS\xa0%d'.format(mcs
);
208 if (nss
) s
+= ', VHT-NSS\xa0%d'.format(nss
);
209 if (ht
) s
+= ', MCS\xa0%s'.format(mcs
);
210 if (sgi
) s
+= ', ' + _('Short GI').replace(/ /g
, '\xa0');
214 s
+= ', HE-MCS\xa0%d'.format(mcs
);
215 if (nss
) s
+= ', HE-NSS\xa0%d'.format(nss
);
216 if (he_gi
) s
+= ', HE-GI\xa0%d'.format(he_gi
);
217 if (he_dcm
) s
+= ', HE-DCM\xa0%d'.format(he_dcm
);
223 function radio_restart(id
, ev
) {
224 var row
= document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id
)),
225 dsc
= row
.querySelector('[data-name="_stat"] > div'),
226 btn
= row
.querySelector('.cbi-section-actions button');
229 btn
.classList
.add('spinning');
232 dsc
.setAttribute('restart', '');
233 dom
.content(dsc
, E('em', _('Device is restarting…')));
236 function network_updown(id
, map
, ev
) {
237 var radio
= uci
.get('wireless', id
, 'device'),
238 disabled
= (uci
.get('wireless', id
, 'disabled') == '1') ||
239 (uci
.get('wireless', radio
, 'disabled') == '1');
242 uci
.unset('wireless', id
, 'disabled');
243 uci
.unset('wireless', radio
, 'disabled');
246 uci
.set('wireless', id
, 'disabled', '1');
248 var all_networks_disabled
= true,
249 wifi_ifaces
= uci
.sections('wireless', 'wifi-iface');
251 for (var i
= 0; i
< wifi_ifaces
.length
; i
++) {
252 if (wifi_ifaces
[i
].device
== radio
&& wifi_ifaces
[i
].disabled
!= '1') {
253 all_networks_disabled
= false;
258 if (all_networks_disabled
)
259 uci
.set('wireless', radio
, 'disabled', '1');
262 return map
.save().then(function() {
267 function next_free_sid(offset
) {
268 var sid
= 'wifinet' + offset
;
270 while (uci
.get('wireless', sid
))
271 sid
= 'wifinet' + (++offset
);
276 function add_dependency_permutations(o
, deps
) {
279 for (var key
in deps
) {
280 if (!deps
.hasOwnProperty(key
) || !Array
.isArray(deps
[key
]))
283 var list
= deps
[key
],
286 for (var j
= 0; j
< list
.length
; j
++) {
287 for (var k
= 0; k
< (res
? res
.length
: 1); k
++) {
288 var item
= (res
? Object
.assign({}, res
[k
]) : {});
297 for (var i
= 0; i
< (res
? res
.length
: 0); i
++)
301 var CBIWifiFrequencyValue
= form
.Value
.extend({
302 callFrequencyList
: rpc
.declare({
305 params
: [ 'device' ],
306 expect
: { results
: [] }
309 load: function(section_id
) {
311 network
.getWifiDevice(section_id
),
312 this.callFrequencyList(section_id
)
313 ]).then(L
.bind(function(data
) {
315 '2g': L
.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
316 '5g': L
.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
317 '6g': L
.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
321 for (var i
= 0; i
< data
[1].length
; i
++) {
322 if (!data
[1][i
].band
)
325 var band
= '%dg'.format(data
[1][i
].band
);
327 this.channels
[band
].push(
329 '%d (%d Mhz)'.format(data
[1][i
].channel
, data
[1][i
].mhz
),
330 !data
[1][i
].restricted
334 var hwmodelist
= L
.toArray(data
[0] ? data
[0].getHWModes() : null)
335 .reduce(function(o
, v
) { o
[v
] = true; return o
}, {});
338 '', 'Legacy', hwmodelist
.a
|| hwmodelist
.b
|| hwmodelist
.g
,
339 'n', 'N', hwmodelist
.n
,
340 'ac', 'AC', L
.hasSystemFeature('hostapd', '11ac') && hwmodelist
.ac
,
341 'ax', 'AX', L
.hasSystemFeature('hostapd', '11ax') && hwmodelist
.ax
344 var htmodelist
= L
.toArray(data
[0] ? data
[0].getHTModes() : null)
345 .reduce(function(o
, v
) { o
[v
] = true; return o
}, {});
348 '': [ '', '-', true ],
350 'HT20', '20 MHz', htmodelist
.HT20
,
351 'HT40', '40 MHz', htmodelist
.HT40
354 'VHT20', '20 MHz', htmodelist
.VHT20
,
355 'VHT40', '40 MHz', htmodelist
.VHT40
,
356 'VHT80', '80 MHz', htmodelist
.VHT80
,
357 'VHT160', '160 MHz', htmodelist
.VHT160
360 'HE20', '20 MHz', htmodelist
.HE20
,
361 'HE40', '40 MHz', htmodelist
.HE40
,
362 'HE80', '80 MHz', htmodelist
.HE80
,
363 'HE160', '160 MHz', htmodelist
.HE160
369 '2g', '2.4 GHz', this.channels
['2g'].length
> 3,
370 '5g', '5 GHz', this.channels
['5g'].length
> 3,
371 '60g', '60 GHz', this.channels
['60g'].length
> 0
374 '2g', '2.4 GHz', this.channels
['2g'].length
> 3,
375 '5g', '5 GHz', this.channels
['5g'].length
> 3
381 '2g', '2.4 GHz', this.channels
['2g'].length
> 3,
382 '5g', '5 GHz', this.channels
['5g'].length
> 3,
383 '6g', '6 GHz', this.channels
['6g'].length
> 3
389 setValues: function(sel
, vals
) {
391 sel
.vals
.selected
= sel
.selectedIndex
;
393 while (sel
.options
[0])
396 for (var i
= 0; vals
&& i
< vals
.length
; i
+= 3)
398 sel
.add(E('option', { value
: vals
[i
+0] }, [ vals
[i
+1] ]));
400 if (vals
&& !isNaN(vals
.selected
))
401 sel
.selectedIndex
= vals
.selected
;
403 sel
.parentNode
.style
.display
= (sel
.options
.length
<= 1) ? 'none' : '';
407 toggleWifiMode: function(elem
) {
408 this.toggleWifiHTMode(elem
);
409 this.toggleWifiBand(elem
);
412 toggleWifiHTMode: function(elem
) {
413 var mode
= elem
.querySelector('.mode');
414 var bwdt
= elem
.querySelector('.htmode');
416 this.setValues(bwdt
, this.htmodes
[mode
.value
]);
419 toggleWifiBand: function(elem
) {
420 var mode
= elem
.querySelector('.mode');
421 var band
= elem
.querySelector('.band');
423 this.setValues(band
, this.bands
[mode
.value
]);
424 this.toggleWifiChannel(elem
);
426 this.map
.checkDepends();
429 toggleWifiChannel: function(elem
) {
430 var band
= elem
.querySelector('.band');
431 var chan
= elem
.querySelector('.channel');
433 this.setValues(chan
, this.channels
[band
.value
]);
436 setInitialValues: function(section_id
, elem
) {
437 var mode
= elem
.querySelector('.mode'),
438 band
= elem
.querySelector('.band'),
439 chan
= elem
.querySelector('.channel'),
440 bwdt
= elem
.querySelector('.htmode'),
441 htval
= uci
.get('wireless', section_id
, 'htmode'),
442 hwval
= uci
.get('wireless', section_id
, 'hwmode'),
443 chval
= uci
.get('wireless', section_id
, 'channel'),
444 bandval
= uci
.get('wireless', section_id
, 'band');
446 this.setValues(mode
, this.modes
);
448 if (/HE20|HE40|HE80|HE160/.test(htval
))
450 else if (/VHT20|VHT40|VHT80|VHT160/.test(htval
))
452 else if (/HT20|HT40/.test(htval
))
457 this.toggleWifiMode(elem
);
460 this.useBandOption
= false;
468 this.useBandOption
= true;
470 band
.value
= bandval
;
473 this.toggleWifiBand(elem
);
476 chan
.value
= chval
|| (chan
.options
[0] ? chan
.options
[0].value
: 'auto');
481 renderWidget: function(section_id
, option_index
, cfgvalue
) {
485 E('label', { 'style': 'float:left; margin-right:3px' }, [
489 'style': 'width:auto',
490 'change': L
.bind(this.toggleWifiMode
, this, elem
),
491 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
494 E('label', { 'style': 'float:left; margin-right:3px' }, [
498 'style': 'width:auto',
499 'change': L
.bind(this.toggleWifiBand
, this, elem
),
500 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
503 E('label', { 'style': 'float:left; margin-right:3px' }, [
504 _('Channel'), E('br'),
507 'style': 'width:auto',
508 'change': L
.bind(this.map
.checkDepends
, this.map
),
509 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
512 E('label', { 'style': 'float:left; margin-right:3px' }, [
516 'style': 'width:auto',
517 'change': L
.bind(this.map
.checkDepends
, this.map
),
518 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
521 E('br', { 'style': 'clear:left' })
524 return this.setInitialValues(section_id
, elem
);
527 cfgvalue: function(section_id
) {
529 uci
.get('wireless', section_id
, 'htmode'),
530 uci
.get('wireless', section_id
, 'hwmode') || uci
.get('wireless', section_id
, 'band'),
531 uci
.get('wireless', section_id
, 'channel')
535 formvalue: function(section_id
) {
536 var node
= this.map
.findElement('data-field', this.cbid(section_id
));
539 node
.querySelector('.htmode').value
,
540 node
.querySelector('.band').value
,
541 node
.querySelector('.channel').value
545 write: function(section_id
, value
) {
546 uci
.set('wireless', section_id
, 'htmode', value
[0] || null);
548 if (this.useBandOption
)
549 uci
.set('wireless', section_id
, 'band', value
[1]);
551 uci
.set('wireless', section_id
, 'hwmode', (value
[1] == '2g') ? '11g' : '11a');
553 uci
.set('wireless', section_id
, 'channel', value
[2]);
557 var CBIWifiTxPowerValue
= form
.ListValue
.extend({
558 callTxPowerList
: rpc
.declare({
560 method
: 'txpowerlist',
561 params
: [ 'device' ],
562 expect
: { results
: [] }
565 load: function(section_id
) {
566 return this.callTxPowerList(section_id
).then(L
.bind(function(pwrlist
) {
567 this.powerval
= this.wifiNetwork
? this.wifiNetwork
.getTXPower() : null;
568 this.poweroff
= this.wifiNetwork
? this.wifiNetwork
.getTXPowerOffset() : null;
570 this.value('', _('driver default'));
572 for (var i
= 0; i
< pwrlist
.length
; i
++)
573 this.value(pwrlist
[i
].dbm
, '%d dBm (%d mW)'.format(pwrlist
[i
].dbm
, pwrlist
[i
].mw
));
575 return form
.ListValue
.prototype.load
.apply(this, [section_id
]);
579 renderWidget: function(section_id
, option_index
, cfgvalue
) {
580 var widget
= form
.ListValue
.prototype.renderWidget
.apply(this, [section_id
, option_index
, cfgvalue
]);
581 widget
.firstElementChild
.style
.width
= 'auto';
583 dom
.append(widget
, E('span', [
584 ' - ', _('Current power'), ': ',
585 E('span', [ this.powerval
!= null ? '%d dBm'.format(this.powerval
) : E('em', _('unknown')) ]),
586 this.poweroff
? ' + %d dB offset = %s dBm'.format(this.poweroff
, this.powerval
!= null ? this.powerval
+ this.poweroff
: '?') : ''
593 var CBIWifiCountryValue
= form
.Value
.extend({
594 callCountryList
: rpc
.declare({
596 method
: 'countrylist',
597 params
: [ 'device' ],
598 expect
: { results
: [] }
601 load: function(section_id
) {
602 return this.callCountryList(section_id
).then(L
.bind(function(countrylist
) {
603 if (Array
.isArray(countrylist
) && countrylist
.length
> 0) {
604 this.value('', _('driver default'));
606 for (var i
= 0; i
< countrylist
.length
; i
++)
607 this.value(countrylist
[i
].iso3166
, '%s - %s'.format(countrylist
[i
].iso3166
, countrylist
[i
].country
));
610 return form
.Value
.prototype.load
.apply(this, [section_id
]);
614 validate: function(section_id
, formvalue
) {
615 if (formvalue
!= null && formvalue
!= '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue
))
616 return _('Use ISO/IEC 3166 alpha2 country codes.');
621 renderWidget: function(section_id
, option_index
, cfgvalue
) {
622 var typeClass
= (this.keylist
&& this.keylist
.length
) ? form
.ListValue
: form
.Value
;
623 return typeClass
.prototype.renderWidget
.apply(this, [section_id
, option_index
, cfgvalue
]);
628 poll_status: function(map
, data
) {
629 var rows
= map
.querySelectorAll('.cbi-section-table-row[data-sid]');
631 for (var i
= 0; i
< rows
.length
; i
++) {
632 var section_id
= rows
[i
].getAttribute('data-sid'),
633 radioDev
= data
[1].filter(function(d
) { return d
.getName() == section_id
})[0],
634 radioNet
= data
[2].filter(function(n
) { return n
.getName() == section_id
})[0],
635 badge
= rows
[i
].querySelector('[data-name="_badge"] > div'),
636 stat
= rows
[i
].querySelector('[data-name="_stat"]'),
637 btns
= rows
[i
].querySelectorAll('.cbi-section-actions button'),
638 busy
= btns
[0].classList
.contains('spinning') || btns
[1].classList
.contains('spinning') || btns
[2].classList
.contains('spinning');
641 dom
.content(badge
, render_radio_badge(radioDev
));
642 dom
.content(stat
, render_radio_status(radioDev
, data
[2].filter(function(n
) { return n
.getWifiDeviceName() == radioDev
.getName() })));
645 dom
.content(badge
, render_network_badge(radioNet
));
646 dom
.content(stat
, render_network_status(radioNet
));
649 if (stat
.hasAttribute('restart'))
650 dom
.content(stat
, E('em', _('Device is restarting…')));
652 btns
[0].disabled
= isReadonlyView
|| busy
;
653 btns
[1].disabled
= (isReadonlyView
&& radioDev
) || busy
;
654 btns
[2].disabled
= isReadonlyView
|| busy
;
657 var table
= document
.querySelector('#wifi_assoclist_table'),
661 for (var i
= 0; i
< data
[3].length
; i
++) {
662 var bss
= data
[3][i
],
663 name
= hosts
.getHostnameByMACAddr(bss
.mac
),
664 ipv4
= hosts
.getIPAddrByMACAddr(bss
.mac
),
665 ipv6
= hosts
.getIP6AddrByMACAddr(bss
.mac
);
669 if (name
&& ipv4
&& ipv6
)
670 hint
= '%s <span class="hide-xs">(%s, %s)</span>'.format(name
, ipv4
, ipv6
);
671 else if (name
&& (ipv4
|| ipv6
))
672 hint
= '%s <span class="hide-xs">(%s)</span>'.format(name
, ipv4
|| ipv6
);
674 hint
= name
|| ipv4
|| ipv6
|| '?';
678 'class': 'ifacebadge',
679 'data-ifname': bss
.network
.getIfname(),
680 'data-ssid': bss
.network
.getSSID()
683 'src': L
.resource('icons/wifi%s.png').format(bss
.network
.isUp() ? '' : '_disabled'),
684 'title': bss
.radio
.getI18n()
687 ' %s '.format(bss
.network
.getShortName()),
688 E('small', '(%s)'.format(bss
.network
.getIfname()))
693 render_signal_badge(Math
.min((bss
.signal
+ 110) / 70 * 100, 100), bss
.signal
, bss
.noise
),
695 E('span', format_wifirate(bss
.rx
)),
697 E('span', format_wifirate(bss
.tx
))
701 if (bss
.network
.isClientDisconnectSupported()) {
702 if (table
.firstElementChild
.childNodes
.length
< 6)
703 table
.firstElementChild
.appendChild(E('th', { 'class': 'th cbi-section-actions'}));
705 row
.push(E('button', {
706 'class': 'cbi-button cbi-button-remove',
707 'click': L
.bind(function(net
, mac
, ev
) {
708 dom
.parent(ev
.currentTarget
, '.tr').style
.opacity
= 0.5;
709 ev
.currentTarget
.classList
.add('spinning');
710 ev
.currentTarget
.disabled
= true;
711 ev
.currentTarget
.blur();
713 net
.disconnectClient(mac
, true, 5, 60000);
714 }, this, bss
.network
, bss
.mac
),
715 'disabled': isReadonlyView
|| null
716 }, [ _('Disconnect') ]));
725 cbi_update_table(table
, trows
, E('em', _('No information available')));
727 var stat
= document
.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large');
730 render_modal_status(stat
, data
[2].filter(function(n
) { return n
.getName() == stat
.getAttribute('data-network') })[0]);
732 return network
.flushCache();
738 uci
.load('wireless'),
743 checkAnonymousSections: function() {
744 var wifiIfaces
= uci
.sections('wireless', 'wifi-iface');
746 for (var i
= 0; i
< wifiIfaces
.length
; i
++)
747 if (wifiIfaces
[i
]['.anonymous'])
753 callUciRename
: rpc
.declare({
756 params
: [ 'config', 'section', 'name' ]
760 if (this.checkAnonymousSections())
761 return this.renderMigration();
763 return this.renderOverview();
766 handleMigration: function(ev
) {
767 var wifiIfaces
= uci
.sections('wireless', 'wifi-iface'),
771 for (var i
= 0; i
< wifiIfaces
.length
; i
++) {
772 if (!wifiIfaces
[i
]['.anonymous'])
775 var new_name
= next_free_sid(id_offset
);
777 tasks
.push(this.callUciRename('wireless', wifiIfaces
[i
]['.name'], new_name
));
778 id_offset
= +new_name
.substring(7) + 1;
781 return Promise
.all(tasks
)
782 .then(L
.bind(ui
.changes
.init
, ui
.changes
))
783 .then(L
.bind(ui
.changes
.apply
, ui
.changes
));
786 renderMigration: function() {
787 ui
.showModal(_('Wireless configuration migration'), [
788 E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')),
789 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.')),
790 E('div', { 'class': 'right' },
792 'class': 'btn cbi-button-action important',
793 'click': ui
.createHandlerFn(this, 'handleMigration')
798 renderOverview: function() {
801 m
= new form
.Map('wireless');
805 s
= m
.section(form
.GridSection
, 'wifi-device', _('Wireless Overview'));
809 s
.load = function() {
810 return network
.getWifiDevices().then(L
.bind(function(radios
) {
811 this.radios
= radios
.sort(function(a
, b
) {
812 return a
.getName() > b
.getName();
817 for (var i
= 0; i
< radios
.length
; i
++)
818 tasks
.push(radios
[i
].getWifiNetworks());
820 return Promise
.all(tasks
);
821 }, this)).then(L
.bind(function(data
) {
824 for (var i
= 0; i
< data
.length
; i
++)
825 this.wifis
.push
.apply(this.wifis
, data
[i
]);
829 s
.cfgsections = function() {
832 for (var i
= 0; i
< this.radios
.length
; i
++) {
833 rv
.push(this.radios
[i
].getName());
835 for (var j
= 0; j
< this.wifis
.length
; j
++)
836 if (this.wifis
[j
].getWifiDeviceName() == this.radios
[i
].getName())
837 rv
.push(this.wifis
[j
].getName());
843 s
.modaltitle = function(section_id
) {
844 var radioNet
= this.wifis
.filter(function(w
) { return w
.getName() == section_id
})[0];
845 return radioNet
? radioNet
.getI18n() : _('Edit wireless network');
848 s
.lookupRadioOrNetwork = function(section_id
) {
849 var radioDev
= this.radios
.filter(function(r
) { return r
.getName() == section_id
})[0];
853 var radioNet
= this.wifis
.filter(function(w
) { return w
.getName() == section_id
})[0];
860 s
.renderRowActions = function(section_id
) {
861 var inst
= this.lookupRadioOrNetwork(section_id
), btns
;
863 if (inst
.getWifiNetworks
) {
866 'class': 'cbi-button cbi-button-neutral',
867 'title': _('Restart radio interface'),
868 'click': ui
.createHandlerFn(this, radio_restart
, section_id
)
871 'class': 'cbi-button cbi-button-action important',
872 'title': _('Find and join network'),
873 'click': ui
.createHandlerFn(this, 'handleScan', inst
)
876 'class': 'cbi-button cbi-button-add',
877 'title': _('Provide new network'),
878 'click': ui
.createHandlerFn(this, 'handleAdd', inst
)
883 var isDisabled
= (inst
.get('disabled') == '1' ||
884 uci
.get('wireless', inst
.getWifiDeviceName(), 'disabled') == '1');
888 'class': 'cbi-button cbi-button-neutral enable-disable',
889 'title': isDisabled
? _('Enable this network') : _('Disable this network'),
890 'click': ui
.createHandlerFn(this, network_updown
, section_id
, this.map
)
891 }, isDisabled
? _('Enable') : _('Disable')),
893 'class': 'cbi-button cbi-button-action important',
894 'title': _('Edit this network'),
895 'click': ui
.createHandlerFn(this, 'renderMoreOptionsModal', section_id
)
898 'class': 'cbi-button cbi-button-negative remove',
899 'title': _('Delete this network'),
900 'click': ui
.createHandlerFn(this, 'handleRemove', section_id
)
905 return E('td', { 'class': 'td middle cbi-section-actions' }, E('div', btns
));
908 s
.addModalOptions = function(s
) {
909 return network
.getWifiNetwork(s
.section
).then(function(radioNet
) {
910 var hwtype
= uci
.get('wireless', radioNet
.getWifiDeviceName(), 'type');
913 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
917 ss
.tab('general', _('General Setup'));
918 ss
.tab('advanced', _('Advanced Settings'));
920 var isDisabled
= (radioNet
.get('disabled') == '1' ||
921 uci
.get('wireless', radioNet
.getWifiDeviceName(), 'disabled') == 1);
923 o
= ss
.taboption('general', form
.DummyValue
, '_wifistat_modal', _('Status'));
924 o
.cfgvalue
= L
.bind(function(radioNet
) {
925 return render_modal_status(null, radioNet
);
927 o
.write = function() {};
929 o
= ss
.taboption('general', form
.Button
, '_toggle', isDisabled
? _('Wireless network is disabled') : _('Wireless network is enabled'));
930 o
.inputstyle
= isDisabled
? 'apply' : 'reset';
931 o
.inputtitle
= isDisabled
? _('Enable') : _('Disable');
932 o
.onclick
= ui
.createHandlerFn(s
, network_updown
, s
.section
, s
.map
);
934 o
= ss
.taboption('general', CBIWifiFrequencyValue
, '_freq', '<br />' + _('Operating frequency'));
935 o
.ucisection
= s
.section
;
937 if (hwtype
== 'mac80211') {
938 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.'));
939 o
.depends({'_freq': '2g', '!contains': true});
941 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.'));
942 o
.wifiNetwork
= radioNet
;
944 o
= ss
.taboption('advanced', CBIWifiCountryValue
, 'country', _('Country Code'));
945 o
.wifiNetwork
= radioNet
;
947 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.'));
948 o
.value('0', _('Disabled'));
949 o
.value('1', _('Normal'));
950 o
.value('2', _('High'));
951 o
.value('3', _('Very High'));
953 o
= ss
.taboption('advanced', form
.Value
, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters. Set only for distances above one kilometer; otherwise it is harmful.'));
954 o
.datatype
= 'or(range(0,114750),"auto")';
955 o
.placeholder
= 'auto';
957 o
= ss
.taboption('advanced', form
.Value
, 'frag', _('Fragmentation Threshold'));
958 o
.datatype
= 'min(256)';
959 o
.placeholder
= _('off');
961 o
= ss
.taboption('advanced', form
.Value
, 'rts', _('RTS/CTS Threshold'));
962 o
.datatype
= 'uinteger';
963 o
.placeholder
= _('off');
965 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!'));
968 o
= ss
.taboption('advanced', form
.Value
, 'beacon_int', _('Beacon Interval'));
969 o
.datatype
= 'range(15,65535)';
975 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getName(), 'wifi-iface', _('Interface Configuration'));
979 ss
.tab('general', _('General Setup'));
980 ss
.tab('encryption', _('Wireless Security'));
981 ss
.tab('macfilter', _('MAC-Filter'));
982 ss
.tab('advanced', _('Advanced Settings'));
983 ss
.tab('roaming', _('WLAN roaming'), _('Settings for assisting wireless clients in roaming between multiple APs: 802.11r, 802.11k and 802.11v'));
985 o
= ss
.taboption('general', form
.ListValue
, 'mode', _('Mode'));
986 o
.value('ap', _('Access Point'));
987 o
.value('sta', _('Client'));
988 o
.value('adhoc', _('Ad-Hoc'));
990 o
= ss
.taboption('general', form
.Value
, 'mesh_id', _('Mesh Id'));
991 o
.depends('mode', 'mesh');
993 o
= ss
.taboption('advanced', form
.Flag
, 'mesh_fwding', _('Forward mesh peer traffic'));
996 o
.depends('mode', 'mesh');
998 o
= ss
.taboption('advanced', form
.Value
, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
1001 o
.datatype
= 'range(-255,1)';
1002 o
.depends('mode', 'mesh');
1004 o
= ss
.taboption('general', form
.Value
, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
1005 o
.datatype
= 'maxlength(32)';
1006 o
.depends('mode', 'ap');
1007 o
.depends('mode', 'sta');
1008 o
.depends('mode', 'adhoc');
1009 o
.depends('mode', 'ahdemo');
1010 o
.depends('mode', 'monitor');
1011 o
.depends('mode', 'ap-wds');
1012 o
.depends('mode', 'sta-wds');
1013 o
.depends('mode', 'wds');
1015 o
= ss
.taboption('general', form
.Value
, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
1016 o
.datatype
= 'macaddr';
1018 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.'));
1022 o
.write = function(section_id
, value
) {
1023 return network
.getDevice(section_id
).then(L
.bind(function(dev
) {
1024 var old_networks
= dev
.getNetworks().reduce(function(o
, v
) { o
[v
.getName()] = v
; return o
}, {}),
1026 values
= L
.toArray(value
),
1029 for (var i
= 0; i
< values
.length
; i
++) {
1030 new_networks
[values
[i
]] = true;
1032 if (old_networks
[values
[i
]])
1035 tasks
.push(network
.getNetwork(values
[i
]).then(L
.bind(function(name
, net
) {
1036 return net
|| network
.addNetwork(name
, { proto
: 'none' });
1037 }, this, values
[i
])).then(L
.bind(function(dev
, net
) {
1039 if (!net
.isEmpty()) {
1040 var target_dev
= net
.getDevice();
1042 /* Resolve parent interface of vlan */
1043 while (target_dev
&& target_dev
.getType() == 'vlan')
1044 target_dev
= target_dev
.getParent();
1046 if (!target_dev
|| target_dev
.getType() != 'bridge')
1047 net
.set('type', 'bridge');
1055 for (var name
in old_networks
)
1056 if (!new_networks
[name
])
1057 tasks
.push(network
.getNetwork(name
).then(L
.bind(function(dev
, net
) {
1059 net
.deleteDevice(dev
);
1062 return Promise
.all(tasks
);
1066 if (hwtype
== 'mac80211') {
1067 var mode
= ss
.children
[0],
1068 bssid
= ss
.children
[5],
1071 mode
.value('mesh', '802.11s');
1072 mode
.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
1073 mode
.value('monitor', _('Monitor'));
1075 bssid
.depends('mode', 'adhoc');
1076 bssid
.depends('mode', 'sta');
1077 bssid
.depends('mode', 'sta-wds');
1079 o
= ss
.taboption('macfilter', form
.ListValue
, 'macfilter', _('MAC Address Filter'));
1080 o
.depends('mode', 'ap');
1081 o
.depends('mode', 'ap-wds');
1082 o
.value('', _('disable'));
1083 o
.value('allow', _('Allow listed only'));
1084 o
.value('deny', _('Allow all except listed'));
1086 o
= ss
.taboption('macfilter', form
.DynamicList
, 'maclist', _('MAC-List'));
1087 o
.datatype
= 'macaddr';
1089 o
.depends('macfilter', 'allow');
1090 o
.depends('macfilter', 'deny');
1091 o
.load = function(section_id
) {
1092 return network
.getHostHints().then(L
.bind(function(hints
) {
1093 hints
.getMACHints().map(L
.bind(function(hint
) {
1094 this.value(hint
[0], hint
[1] ? '%s (%s)'.format(hint
[0], hint
[1]) : hint
[0]);
1097 return form
.DynamicList
.prototype.load
.apply(this, [section_id
]);
1101 mode
.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
1102 mode
.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
1104 mode
.write = function(section_id
, value
) {
1107 uci
.set('wireless', section_id
, 'mode', 'ap');
1108 uci
.set('wireless', section_id
, 'wds', '1');
1112 uci
.set('wireless', section_id
, 'mode', 'sta');
1113 uci
.set('wireless', section_id
, 'wds', '1');
1117 uci
.set('wireless', section_id
, 'mode', value
);
1118 uci
.unset('wireless', section_id
, 'wds');
1123 mode
.cfgvalue = function(section_id
) {
1124 var mode
= uci
.get('wireless', section_id
, 'mode'),
1125 wds
= uci
.get('wireless', section_id
, 'wds');
1127 if (mode
== 'ap' && wds
)
1129 else if (mode
== 'sta' && wds
)
1135 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.'));
1136 o
.depends('mode', 'ap');
1137 o
.depends('mode', 'ap-wds');
1139 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.'));
1140 o
.depends('mode', 'ap');
1141 o
.depends('mode', 'ap-wds');
1142 o
.default = o
.enabled
;
1144 /* https://w1.fi/cgit/hostap/commit/?id=34f7c699a6bcb5c45f82ceb6743354ad79296078 */
1145 /* multicast_to_unicast https://github.com/openwrt/openwrt/commit/7babb978ad9d7fc29acb1ff86afb1eb343af303a */
1146 o = ss.taboption('advanced', form.Flag, 'multicast_to_unicast_all', _('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.'));
1149 o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
1150 o.depends('mode', 'ap');
1151 o.depends('mode', 'ap-wds');
1153 o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name'));
1155 o.datatype = 'netdevname';
1156 o.placeholder = radioNet.getIfname();
1157 if (/^radio\d+\.network/.test(o.placeholder))
1160 var macaddr = uci.get('wireless', radioNet.getName(), 'macaddr');
1161 o = ss.taboption('advanced', form.Value, 'macaddr', _('MAC address'), _('Override default MAC address - the range of usable addresses might be limited by the driver'));
1162 o.value('', _('driver default (%s)').format(!macaddr ? radioNet.getActiveBSSID() : _('no override')));
1163 o.value('random', _('randomly generated'));
1164 o.datatype = "or('random',macaddr)";
1166 o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble'));
1167 o.default = o.enabled;
1169 o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
1172 o.datatype = 'range(1,255)';
1174 o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
1176 o.placeholder = 600;
1177 o.datatype = 'uinteger';
1179 o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling'));
1181 o.datatype = 'uinteger';
1183 o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('802.11v: BSS Max Idle. Units: seconds.'));
1185 o.placeholder = 300;
1186 o.datatype = 'uinteger';
1188 o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval'));
1190 o.placeholder = 65535;
1191 o.datatype = 'uinteger';
1193 o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
1194 o.default = o.enabled;
1198 encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption'));
1199 o.depends('mode', 'ap');
1200 o.depends('mode', 'sta');
1201 o.depends('mode', 'adhoc');
1202 o.depends('mode', 'ahdemo');
1203 o.depends('mode', 'ap-wds');
1204 o.depends('mode', 'sta-wds');
1205 o.depends('mode', 'mesh');
1207 o.cfgvalue = function(section_id) {
1208 var v = String(uci.get('wireless', section_id, 'encryption'));
1211 else if (v.match(/\+/))
1212 return v.replace(/\+.+$/, '');
1216 o.write = function(section_id, value) {
1217 var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id),
1218 co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id);
1220 if (value == 'wpa' || value == 'wpa2' || value == 'wpa3' || value == 'wpa3-mixed')
1221 uci.unset('wireless', section_id, 'key');
1223 if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp'))
1226 uci.set('wireless', section_id, 'encryption', e);
1229 o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher'));
1230 o.depends('encryption', 'wpa');
1231 o.depends('encryption', 'wpa2');
1232 o.depends('encryption', 'wpa3');
1233 o.depends('encryption', 'wpa3-mixed');
1234 o.depends('encryption', 'psk');
1235 o.depends('encryption', 'psk2');
1236 o.depends('encryption', 'wpa-mixed');
1237 o.depends('encryption', 'psk-mixed');
1238 o.value('auto', _('auto'));
1239 o.value('ccmp', _('Force CCMP (AES)'));
1240 o.value('tkip', _('Force TKIP'));
1241 o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1242 o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write;
1244 o.cfgvalue = function(section_id) {
1245 var v = String(uci.get('wireless', section_id, 'encryption'));
1246 if (v.match(/\+/)) {
1247 v = v.replace(/^[^+]+\+/, '');
1250 else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip')
1257 var crypto_modes = [];
1259 if (hwtype == 'mac80211') {
1260 var has_supplicant = L.hasSystemFeature('wpasupplicant'),
1261 has_hostapd = L.hasSystemFeature('hostapd');
1263 // Probe EAP support
1264 var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'),
1265 has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap');
1267 // Probe SAE support
1268 var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'),
1269 has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae');
1271 // Probe OWE support
1272 var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
1273 has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
1275 // Probe Suite-B support
1276 var has_ap_eap192 = L.hasSystemFeature('hostapd', 'suiteb192'),
1277 has_sta_eap192 = L.hasSystemFeature('wpasupplicant', 'suiteb192');
1279 // Probe WEP support
1280 var has_ap_wep = L.hasSystemFeature('hostapd', 'wep'),
1281 has_sta_wep = L.hasSystemFeature('wpasupplicant', 'wep');
1283 if (has_hostapd || has_supplicant) {
1284 crypto_modes.push(['psk2', 'WPA2-PSK', 35]);
1285 crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1286 crypto_modes.push(['psk', 'WPA-PSK', 12]);
1289 encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1292 if (has_ap_sae || has_sta_sae) {
1293 crypto_modes.push(['sae', 'WPA3-SAE', 31]);
1294 crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1297 if (has_ap_wep || has_sta_wep) {
1298 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1299 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1302 if (has_ap_eap || has_sta_eap) {
1303 if (has_ap_eap192 || has_sta_eap192) {
1304 crypto_modes.push(['wpa3', 'WPA3-EAP', 33]);
1305 crypto_modes.push(['wpa3-mixed', 'WPA2-EAP/WPA3-EAP Mixed Mode', 32]);
1308 crypto_modes.push(['wpa2', 'WPA2-EAP', 34]);
1309 crypto_modes.push(['wpa', 'WPA-EAP', 20]);
1312 if (has_ap_owe || has_sta_owe) {
1313 crypto_modes.push(['owe', 'OWE', 1]);
1316 encr.crypto_support = {
1318 'wep-open': has_ap_wep || _('Requires hostapd with WEP support'),
1319 'wep-shared': has_ap_wep || _('Requires hostapd with WEP support'),
1320 'psk': has_hostapd || _('Requires hostapd'),
1321 'psk2': has_hostapd || _('Requires hostapd'),
1322 'psk-mixed': has_hostapd || _('Requires hostapd'),
1323 'sae': has_ap_sae || _('Requires hostapd with SAE support'),
1324 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
1325 'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
1326 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
1327 'wpa3': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
1328 'wpa3-mixed': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
1329 'owe': has_ap_owe || _('Requires hostapd with OWE support')
1332 'wep-open': has_sta_wep || _('Requires wpa-supplicant with WEP support'),
1333 'wep-shared': has_sta_wep || _('Requires wpa-supplicant with WEP support'),
1334 'psk': has_supplicant || _('Requires wpa-supplicant'),
1335 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1336 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1337 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1338 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1339 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1340 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1341 'wpa3': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
1342 'wpa3-mixed': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
1343 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
1348 'psk': has_supplicant || _('Requires wpa-supplicant'),
1349 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1350 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1353 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support')
1365 encr.crypto_support['ap-wds'] = encr.crypto_support['ap'];
1366 encr.crypto_support['sta-wds'] = encr.crypto_support['sta'];
1368 encr.validate = function(section_id, value) {
1369 var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
1370 modeval = modeopt.formvalue(section_id),
1371 modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)],
1372 enctitle = this.vallist[this.keylist.indexOf(value)];
1374 if (value == 'none')
1377 if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value))
1378 return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle);
1380 return this.crypto_support[modeval][value];
1383 else if (hwtype == 'broadcom') {
1384 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1385 crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1386 crypto_modes.push(['psk', 'WPA-PSK', 12]);
1387 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1388 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1391 crypto_modes.push(['none', _('No Encryption'), 0]);
1393 crypto_modes.sort(function(a, b) { return b[2] - a[2] });
1395 for (var i = 0; i < crypto_modes.length; i++) {
1396 var security_level = (crypto_modes[i][2] >= 30) ? _('strong security')
1397 : (crypto_modes[i][2] >= 20) ? _('medium security')
1398 : (crypto_modes[i][2] >= 10) ? _('weak security') : _('open network');
1400 encr.value(crypto_modes[i][0], '%s (%s)'.format(crypto_modes[i][1], security_level));
1404 o = ss.taboption('encryption', form.Value, 'auth_server', _('RADIUS Authentication Server'));
1405 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1407 o.datatype = 'host(0)';
1409 o = ss.taboption('encryption', form.Value, 'auth_port', _('RADIUS Authentication Port'));
1410 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1412 o.datatype = 'port';
1413 o.placeholder = '1812';
1415 o = ss.taboption('encryption', form.Value, 'auth_secret', _('RADIUS Authentication Secret'));
1416 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1420 o = ss.taboption('encryption', form.Value, 'acct_server', _('RADIUS Accounting Server'));
1421 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1423 o.datatype = 'host(0)';
1425 o = ss.taboption('encryption', form.Value, 'acct_port', _('RADIUS Accounting Port'));
1426 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1428 o.datatype = 'port';
1429 o.placeholder = '1813';
1431 o = ss.taboption('encryption', form.Value, 'acct_secret', _('RADIUS Accounting Secret'));
1432 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1436 /* extra RADIUS settings start */
1437 o
= ss
.taboption('encryption', form
.ListValue
, 'dynamic_vlan', _('RADIUS Dynamic VLAN Assignment'), _('Required: Rejects auth if RADIUS server does not provide appropriate VLAN attributes.'));
1438 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1439 o
.value('0', _('Disabled'));
1440 o
.value('1', _('Optional'));
1441 o
.value('2', _('Required'));
1442 o
.write = function (section_id
, value
) {
1443 return this.super('write', [section_id
, (value
== 0) ? null: value
]);
1446 o
= ss
.taboption('encryption', form
.Flag
, 'per_sta_vif', _('RADIUS Per STA VLAN'), _('Each STA is assigned its own AP_VLAN interface.'));
1447 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1449 //hostapd internally defaults to vlan_naming=1 even with dynamic VLAN off
1450 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>.'));
1451 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1453 o
= ss
.taboption('encryption', widgets
.DeviceSelect
, 'vlan_tagged_interface', _('RADIUS VLAN Tagged Interface'), _('E.g. eth0, eth1'));
1454 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1460 o
.noinactive
= true;
1462 o
= ss
.taboption('encryption', form
.Value
, 'vlan_bridge', _('RADIUS VLAN Bridge Naming Scheme'), _('E.g. <code>br-vlan</code> or <code>brvlan</code>.'));
1463 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1465 /* extra RADIUS settings end */
1467 o
= ss
.taboption('encryption', form
.Value
, 'dae_client', _('DAE-Client'), _('Dynamic Authorization Extension client.'));
1468 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1470 o
.datatype
= 'host(0)';
1472 o
= ss
.taboption('encryption', form
.Value
, 'dae_port', _('DAE-Port'), _('Dynamic Authorization Extension port.'));
1473 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1475 o
.datatype
= 'port';
1476 o
.placeholder
= '3799';
1478 o
= ss
.taboption('encryption', form
.Value
, 'dae_secret', _('DAE-Secret'), _('Dynamic Authorization Extension secret.'));
1479 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1483 //WPA(1) has only WPA IE. Only >= WPA2 has RSN IE Preauth frames.
1484 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.'));
1485 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa2', 'wpa3', 'wpa3-mixed'] });
1488 o
= ss
.taboption('encryption', form
.Value
, '_wpa_key', _('Key'));
1489 o
.depends('encryption', 'psk');
1490 o
.depends('encryption', 'psk2');
1491 o
.depends('encryption', 'psk+psk2');
1492 o
.depends('encryption', 'psk-mixed');
1493 o
.depends('encryption', 'sae');
1494 o
.depends('encryption', 'sae-mixed');
1495 o
.datatype
= 'wpakey';
1499 o
.cfgvalue = function(section_id
) {
1500 var key
= uci
.get('wireless', section_id
, 'key');
1501 return /^[1234]$/.test(key
) ? null : key
;
1504 o
.write = function(section_id
, value
) {
1505 uci
.set('wireless', section_id
, 'key', value
);
1506 uci
.unset('wireless', section_id
, 'key1');
1507 uci
.unset('wireless', section_id
, 'key2');
1508 uci
.unset('wireless', section_id
, 'key3');
1509 uci
.unset('wireless', section_id
, 'key4');
1513 o
= ss
.taboption('encryption', form
.ListValue
, '_wep_key', _('Used Key Slot'));
1514 o
.depends('encryption', 'wep-open');
1515 o
.depends('encryption', 'wep-shared');
1516 o
.value('1', _('Key #%d').format(1));
1517 o
.value('2', _('Key #%d').format(2));
1518 o
.value('3', _('Key #%d').format(3));
1519 o
.value('4', _('Key #%d').format(4));
1521 o
.cfgvalue = function(section_id
) {
1522 var slot
= +uci
.get('wireless', section_id
, 'key');
1523 return (slot
>= 1 && slot
<= 4) ? String(slot
) : '';
1526 o
.write = function(section_id
, value
) {
1527 uci
.set('wireless', section_id
, 'key', value
);
1530 for (var slot
= 1; slot
<= 4; slot
++) {
1531 o
= ss
.taboption('encryption', form
.Value
, 'key%d'.format(slot
), _('Key #%d').format(slot
));
1532 o
.depends('encryption', 'wep-open');
1533 o
.depends('encryption', 'wep-shared');
1534 o
.datatype
= 'wepkey';
1538 o
.write = function(section_id
, value
) {
1539 if (value
!= null && (value
.length
== 5 || value
.length
== 13))
1540 value
= 's:%s'.format(value
);
1541 uci
.set('wireless', section_id
, this.option
, value
);
1546 if (hwtype
== 'mac80211') {
1547 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1548 var has_80211r
= L
.hasSystemFeature('hostapd', '11r') || L
.hasSystemFeature('hostapd', 'eap');
1550 o
= ss
.taboption('roaming', form
.Flag
, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1551 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa2', 'wpa3', 'wpa3-mixed'] });
1553 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1556 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.'));
1557 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1558 o
.depends({ ieee80211r
: '1' });
1561 o
= ss
.taboption('roaming', form
.Value
, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1562 o
.depends({ ieee80211r
: '1' });
1563 o
.placeholder
= '4f57';
1564 o
.datatype
= 'and(hexstring,length(4))';
1567 o
= ss
.taboption('roaming', form
.Value
, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1568 o
.depends({ ieee80211r
: '1' });
1569 o
.placeholder
= '1000';
1570 o
.datatype
= 'range(1000,65535)';
1573 o
= ss
.taboption('roaming', form
.ListValue
, 'ft_over_ds', _('FT protocol'));
1574 o
.depends({ ieee80211r
: '1' });
1575 o
.value('0', _('FT over the Air'));
1576 o
.value('1', _('FT over DS'));
1579 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.'));
1580 add_dependency_permutations(o
, { ieee80211r
: ['1'], mode
: ['ap', 'ap-wds'], encryption
: ['psk2', 'psk-mixed'] });
1581 o
.default = o
.enabled
;
1584 o
= ss
.taboption('roaming', form
.Value
, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1585 o
.depends({ ieee80211r
: '1' });
1586 o
.placeholder
= '10000';
1587 o
.datatype
= 'uinteger';
1590 o
= ss
.taboption('roaming', form
.Value
, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1591 o
.depends({ ieee80211r
: '1' });
1592 o
.placeholder
= '00004f577274';
1593 o
.datatype
= 'and(hexstring,length(12))';
1596 o
= ss
.taboption('roaming', form
.Flag
, 'pmk_r1_push', _('PMK R1 Push'));
1597 o
.depends({ ieee80211r
: '1' });
1598 o
.placeholder
= '0';
1601 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.'));
1602 o
.depends({ ieee80211r
: '1' });
1605 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.'));
1606 o
.depends({ ieee80211r
: '1' });
1608 // End of 802.11r options
1610 // Probe 802.11k and 802.11v support via EAP support (full hostapd has EAP)
1611 if (L
.hasSystemFeature('hostapd', 'eap')) {
1612 /* 802.11k settings start */
1613 o
= ss
.taboption('roaming', form
.Flag
, 'ieee80211k', _('802.11k RRM'), _('Radio Resource Measurement - Sends beacons to assist roaming. Not all clients support this.'));
1614 // add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1615 o
.depends('mode', 'ap');
1616 o
.depends('mode', 'ap-wds');
1618 o
= ss
.taboption('roaming', form
.Flag
, 'rrm_neighbor_report', _('Neighbour Report'), _('802.11k: Enable neighbor report via radio measurements.'));
1619 o
.depends({ ieee80211k
: '1' });
1620 o
.default = o
.enabled
;
1622 o
= ss
.taboption('roaming', form
.Flag
, 'rrm_beacon_report', _('Beacon Report'), _('802.11k: Enable beacon report via radio measurements.'));
1623 o
.depends({ ieee80211k
: '1' });
1624 o
.default = o
.enabled
;
1625 /* 802.11k settings end */
1627 /* 802.11v settings start */
1628 o
= ss
.taboption('roaming', form
.ListValue
, 'time_advertisement', _('Time advertisement'), _('802.11v: Time Advertisement in management frames.'));
1629 o
.value('0', _('Disabled'));
1630 o
.value('2', _('Enabled'));
1631 o
.write = function (section_id
, value
) {
1632 return this.super('write', [section_id
, (value
== 2) ? value
: null]);
1635 //Pull current System TZ setting
1636 var tz
= uci
.get('system', '@system[0]', 'timezone');
1637 o
= ss
.taboption('roaming', form
.Value
, 'time_zone', _('Time zone'), _('802.11v: Local Time Zone Advertisement in management frames.'));
1641 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).'));
1644 /* wnm_sleep_mode_no_keys: https://git.openwrt.org/?p=openwrt/openwrt.git;a=commitdiff;h=bf98faaac8ed24cf7d3d93dd4fcd7304d109363b */
1645 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.'));
1648 o = ss.taboption('roaming', form.Flag, 'bss_transition', _('BSS Transition'), _('802.11v: Basic Service Set (BSS) transition management.'));
1651 /* in master, but not 21.02.1: proxy_arp */
1652 o
= ss
.taboption('roaming', form
.Flag
, 'proxy_arp', _('ProxyARP'), _('802.11v: Proxy ARP enables non-AP STA to remain in power-save for longer.'));
1655 /* TODO: na_mcast_to_ucast is missing: needs adding to hostapd.sh - nice to have */
1657 /* 802.11v settings end */
1660 if (hwtype
== 'mac80211') {
1661 o
= ss
.taboption('encryption', form
.ListValue
, 'eap_type', _('EAP-Method'));
1662 o
.value('tls', 'TLS');
1663 o
.value('ttls', 'TTLS');
1664 o
.value('peap', 'PEAP');
1665 o
.value('fast', 'FAST');
1666 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1668 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"));
1671 o
.default = o
.disabled
;
1672 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1673 o
.validate = function(section_id
, value
) {
1674 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1675 return _("This option cannot be used because the ca-bundle package is not installed.");
1680 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert', _('Path to CA-Certificate'));
1681 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], ca_cert_usesystem
: ['0'] });
1683 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"));
1684 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1686 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"));
1687 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1689 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)"));
1690 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1692 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)"));
1693 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1695 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert', _('Path to Client-Certificate'));
1696 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1698 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key', _('Path to Private Key'));
1699 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1701 o
= ss
.taboption('encryption', form
.Value
, 'priv_key_pwd', _('Password of Private Key'));
1702 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1705 o
= ss
.taboption('encryption', form
.ListValue
, 'auth', _('Authentication'));
1706 o
.value('PAP', 'PAP');
1707 o
.value('CHAP', 'CHAP');
1708 o
.value('MSCHAP', 'MSCHAP');
1709 o
.value('MSCHAPV2', 'MSCHAPv2');
1710 o
.value('EAP-GTC', 'EAP-GTC');
1711 o
.value('EAP-MD5', 'EAP-MD5');
1712 o
.value('EAP-MSCHAPV2', 'EAP-MSCHAPv2');
1713 o
.value('EAP-TLS', 'EAP-TLS');
1714 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1716 o
.validate = function(section_id
, value
) {
1717 var eo
= this.section
.children
.filter(function(o
) { return o
.option
== 'eap_type' })[0],
1718 ev
= eo
.formvalue(section_id
);
1720 if (ev
!= 'ttls' && (value
== 'PAP' || value
== 'CHAP' || value
== 'MSCHAP' || value
== 'MSCHAPV2'))
1721 return _('This authentication type is not applicable to the selected EAP method.');
1726 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"));
1729 o
.default = o
.disabled
;
1730 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1731 o
.validate = function(section_id
, value
) {
1732 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1733 return _("This option cannot be used because the ca-bundle package is not installed.");
1738 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert2', _('Path to inner CA-Certificate'));
1739 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'], ca_cert2_usesystem
: ['0'] });
1741 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"));
1742 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1744 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"));
1745 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1747 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)"));
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
, 'domain_suffix_match2', _('Inner certificate constraint (Wildcard)'), _("Certificate constraint(s) against DNS SAN values (if available)<br />or Subject CN (suffix match)"));
1751 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1753 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert2', _('Path to inner Client-Certificate'));
1754 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1756 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key2', _('Path to inner Private Key'));
1757 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1759 o
= ss
.taboption('encryption', form
.Value
, 'priv_key2_pwd', _('Password of inner Private Key'));
1760 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1763 o
= ss
.taboption('encryption', form
.Value
, 'identity', _('Identity'));
1764 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1766 o
= ss
.taboption('encryption', form
.Value
, 'anonymous_identity', _('Anonymous Identity'));
1767 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1769 o
= ss
.taboption('encryption', form
.Value
, 'password', _('Password'));
1770 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1774 if (hwtype
== 'mac80211') {
1775 // ieee802.11w options
1776 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"));
1777 o
.value('0', _('Disabled'));
1778 o
.value('1', _('Optional'));
1779 o
.value('2', _('Required'));
1780 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds', 'sta', 'sta-wds'], encryption
: ['owe', 'psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1783 '2': [{ encryption
: 'sae' }, { encryption
: 'owe' }, { encryption
: 'wpa3' }, { encryption
: 'wpa3-mixed' }],
1784 '1': [{ encryption
: 'sae-mixed'}],
1788 o
.write = function(section_id
, value
) {
1789 if (value
!= this.default)
1790 return form
.ListValue
.prototype.write
.call(this, section_id
, value
);
1792 return form
.ListValue
.prototype.remove
.call(this, section_id
);
1795 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1796 o
.depends('ieee80211w', '1');
1797 o
.depends('ieee80211w', '2');
1798 o
.datatype
= 'uinteger';
1799 o
.placeholder
= '1000';
1802 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1803 o
.depends('ieee80211w', '1');
1804 o
.depends('ieee80211w', '2');
1805 o
.datatype
= 'uinteger';
1806 o
.placeholder
= '201';
1809 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.'));
1810 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1812 if (L
.hasSystemFeature('hostapd', 'wps') && L
.hasSystemFeature('wpasupplicant')) {
1813 o
= ss
.taboption('encryption', form
.Flag
, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1816 o
.default = o
.disabled
;
1817 o
.depends('encryption', 'psk');
1818 o
.depends('encryption', 'psk2');
1819 o
.depends('encryption', 'psk-mixed');
1820 o
.depends('encryption', 'sae');
1821 o
.depends('encryption', 'sae-mixed');
1828 s
.handleRemove = function(section_id
, ev
) {
1829 document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id
)).style
.opacity
= 0.5;
1830 return form
.TypedSection
.prototype.handleRemove
.apply(this, [section_id
, ev
]);
1833 s
.handleScan = function(radioDev
, ev
) {
1834 var table
= E('table', { 'class': 'table' }, [
1835 E('tr', { 'class': 'tr table-titles' }, [
1836 E('th', { 'class': 'th col-2 middle center' }, _('Signal')),
1837 E('th', { 'class': 'th col-4 middle left' }, _('SSID')),
1838 E('th', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1839 E('th', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1840 E('th', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1841 E('th', { 'class': 'th col-3 middle left' }, _('Encryption')),
1842 E('th', { 'class': 'th cbi-section-actions right' }, ' '),
1846 var stop
= E('button', {
1848 'click': L
.bind(this.handleScanStartStop
, this),
1849 'style': 'display:none',
1850 'data-state': 'stop'
1851 }, _('Stop refresh'));
1853 cbi_update_table(table
, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1855 var md
= ui
.showModal(_('Join Network: Wireless Scan'), [
1857 E('div', { 'class': 'right' }, [
1862 'click': L
.bind(this.handleScanAbort
, this)
1867 md
.style
.maxWidth
= '90%';
1868 md
.style
.maxHeight
= 'none';
1870 this.pollFn
= L
.bind(this.handleScanRefresh
, this, radioDev
, {}, table
, stop
);
1872 poll
.add(this.pollFn
);
1876 s
.handleScanRefresh = function(radioDev
, scanCache
, table
, stop
) {
1877 return radioDev
.getScanList().then(L
.bind(function(results
) {
1880 for (var i
= 0; i
< results
.length
; i
++)
1881 scanCache
[results
[i
].bssid
] = results
[i
];
1883 for (var k
in scanCache
)
1884 if (scanCache
[k
].stale
)
1885 results
.push(scanCache
[k
]);
1887 results
.sort(function(a
, b
) {
1888 var diff
= (b
.quality
- a
.quality
) || (a
.channel
- b
.channel
);
1893 if (a
.ssid
< b
.ssid
)
1895 else if (a
.ssid
> b
.ssid
)
1898 if (a
.bssid
< b
.bssid
)
1900 else if (a
.bssid
> b
.bssid
)
1904 for (var i
= 0; i
< results
.length
; i
++) {
1905 var res
= results
[i
],
1906 qv
= res
.quality
|| 0,
1907 qm
= res
.quality_max
|| 0,
1908 q
= (qv
> 0 && qm
> 0) ? Math
.floor((100 / qm
) * qv
) : 0,
1909 s
= res
.stale
? 'opacity:0.5' : '';
1912 E('span', { 'style': s
}, render_signal_badge(q
, res
.signal
, res
.noise
)),
1913 E('span', { 'style': s
}, (res
.ssid
!= null) ? '%h'.format(res
.ssid
) : E('em', _('hidden'))),
1914 E('span', { 'style': s
}, '%d'.format(res
.channel
)),
1915 E('span', { 'style': s
}, '%h'.format(res
.mode
)),
1916 E('span', { 'style': s
}, '%h'.format(res
.bssid
)),
1917 E('span', { 'style': s
}, '%h'.format(network
.formatWifiEncryption(res
.encryption
))),
1918 E('div', { 'class': 'right' }, E('button', {
1919 'class': 'cbi-button cbi-button-action important',
1920 'click': ui
.createHandlerFn(this, 'handleJoin', radioDev
, res
)
1921 }, _('Join Network')))
1927 cbi_update_table(table
, rows
);
1929 stop
.disabled
= false;
1930 stop
.style
.display
= '';
1931 stop
.classList
.remove('spinning');
1935 s
.handleScanStartStop = function(ev
) {
1936 var btn
= ev
.currentTarget
;
1938 if (btn
.getAttribute('data-state') == 'stop') {
1939 poll
.remove(this.pollFn
);
1940 btn
.firstChild
.data
= _('Start refresh');
1941 btn
.setAttribute('data-state', 'start');
1944 poll
.add(this.pollFn
);
1945 btn
.firstChild
.data
= _('Stop refresh');
1946 btn
.setAttribute('data-state', 'stop');
1947 btn
.classList
.add('spinning');
1948 btn
.disabled
= true;
1952 s
.handleScanAbort = function(ev
) {
1953 var md
= dom
.parent(ev
.target
, 'div[aria-modal="true"]');
1955 md
.style
.maxWidth
= '';
1956 md
.style
.maxHeight
= '';
1960 poll
.remove(this.pollFn
);
1965 s
.handleJoinConfirm = function(radioDev
, bss
, form
, ev
) {
1966 var nameopt
= L
.toArray(form
.lookupOption('name', '_new_'))[0],
1967 passopt
= L
.toArray(form
.lookupOption('password', '_new_'))[0],
1968 ssidopt
= L
.toArray(form
.lookupOption('ssid', '_new_'))[0],
1969 bssidopt
= L
.toArray(form
.lookupOption('bssid', '_new_'))[0],
1970 zoneopt
= L
.toArray(form
.lookupOption('zone', '_new_'))[0],
1971 replopt
= L
.toArray(form
.lookupOption('replace', '_new_'))[0],
1972 nameval
= (nameopt
&& nameopt
.isValid('_new_')) ? nameopt
.formvalue('_new_') : null,
1973 passval
= (passopt
&& passopt
.isValid('_new_')) ? passopt
.formvalue('_new_') : null,
1974 ssidval
= (ssidopt
&& ssidopt
.isValid('_new_')) ? ssidopt
.formvalue('_new_') : null,
1975 bssidval
= (bssidopt
&& bssidopt
.isValid('_new_')) ? bssidopt
.formvalue('_new_') : null,
1976 zoneval
= zoneopt
? zoneopt
.formvalue('_new_') : null,
1977 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
1978 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
1979 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' }).length
> 0),
1980 is_sae
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'sae' }).length
> 0);
1982 if (nameval
== null || (passopt
&& passval
== null))
1985 var section_id
= null;
1987 return this.map
.save(function() {
1988 var wifi_sections
= uci
.sections('wireless', 'wifi-iface');
1990 if (replopt
.formvalue('_new_') == '1') {
1991 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1992 if (wifi_sections
[i
].device
== radioDev
.getName())
1993 uci
.remove('wireless', wifi_sections
[i
]['.name']);
1996 if (uci
.get('wireless', radioDev
.getName(), 'disabled') == '1') {
1997 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1998 if (wifi_sections
[i
].device
== radioDev
.getName())
1999 uci
.set('wireless', wifi_sections
[i
]['.name'], 'disabled', '1');
2001 uci
.unset('wireless', radioDev
.getName(), 'disabled');
2004 section_id
= next_free_sid(wifi_sections
.length
);
2006 uci
.add('wireless', 'wifi-iface', section_id
);
2007 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
2008 uci
.set('wireless', section_id
, 'mode', (bss
.mode
== 'Ad-Hoc') ? 'adhoc' : 'sta');
2009 uci
.set('wireless', section_id
, 'network', nameval
);
2011 if (bss
.ssid
!= null) {
2012 uci
.set('wireless', section_id
, 'ssid', bss
.ssid
);
2014 if (bssidval
== '1')
2015 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
2017 else if (bss
.bssid
!= null) {
2018 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
2021 if (ssidval
!= null)
2022 uci
.set('wireless', section_id
, 'ssid', ssidval
);
2025 uci
.set('wireless', section_id
, 'encryption', 'sae');
2026 uci
.set('wireless', section_id
, 'key', passval
);
2029 for (var i
= enc
.wpa
.length
- 1; i
>= 0; i
--) {
2030 if (enc
.wpa
[i
] == 2) {
2031 uci
.set('wireless', section_id
, 'encryption', 'psk2');
2034 else if (enc
.wpa
[i
] == 1) {
2035 uci
.set('wireless', section_id
, 'encryption', 'psk');
2040 uci
.set('wireless', section_id
, 'key', passval
);
2043 uci
.set('wireless', section_id
, 'encryption', 'wep-open');
2044 uci
.set('wireless', section_id
, 'key', '1');
2045 uci
.set('wireless', section_id
, 'key1', passval
);
2048 uci
.set('wireless', section_id
, 'encryption', 'none');
2051 return network
.addNetwork(nameval
, { proto
: 'dhcp' }).then(function(net
) {
2052 firewall
.deleteNetwork(net
.getName());
2054 var zonePromise
= zoneval
2055 ? firewall
.getZone(zoneval
).then(function(zone
) { return zone
|| firewall
.addZone(zoneval
) })
2056 : Promise
.resolve();
2058 return zonePromise
.then(function(zone
) {
2060 zone
.addNetwork(net
.getName());
2063 }).then(L
.bind(function() {
2064 ui
.showModal(null, E('p', { 'class': 'spinning' }, [ _('Loading data…') ]));
2066 return this.renderMoreOptionsModal(section_id
);
2070 s
.handleJoin = function(radioDev
, bss
, ev
) {
2071 poll
.remove(this.pollFn
);
2073 var m2
= new form
.Map('wireless'),
2074 s2
= m2
.section(form
.NamedSection
, '_new_'),
2075 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
2076 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
2077 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' || a
== 'sae' })),
2078 replace
, passphrase
, name
, bssid
, zone
;
2080 var nameUsed = function(name
) {
2081 var s
= uci
.get('network', name
);
2082 if (s
!= null && s
['.type'] != 'interface')
2085 var net
= (s
!= null) ? network
.instantiateNetwork(name
) : null;
2086 return (net
!= null && !net
.isEmpty());
2089 s2
.render = function() {
2090 return Promise
.all([
2092 this.renderUCISection('_new_')
2093 ]).then(this.renderContents
.bind(this));
2096 if (bss
.ssid
== null) {
2097 name
= s2
.option(form
.Value
, 'ssid', _('Network SSID'), _('The correct SSID must be manually specified when joining a hidden wireless network'));
2098 name
.rmempty
= false;
2101 replace
= s2
.option(form
.Flag
, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
2103 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>'));
2104 name
.datatype
= 'uciname';
2105 name
.default = 'wwan';
2106 name
.rmempty
= false;
2107 name
.validate = function(section_id
, value
) {
2108 if (nameUsed(value
))
2109 return _('The network name is already used');
2114 for (var i
= 2; nameUsed(name
.default); i
++)
2115 name
.default = 'wwan%d'.format(i
);
2117 if (is_wep
|| is_psk
) {
2118 passphrase
= s2
.option(form
.Value
, 'password', is_wep
? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
2119 passphrase
.datatype
= is_wep
? 'wepkey' : 'wpakey';
2120 passphrase
.password
= true;
2121 passphrase
.rmempty
= false;
2124 if (bss
.ssid
!= null) {
2125 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
));
2126 bssid
.default = '0';
2129 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.'));
2130 zone
.default = 'wan';
2132 return m2
.render().then(L
.bind(function(nodes
) {
2133 ui
.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss
.ssid
)), [
2135 E('div', { 'class': 'right' }, [
2138 'click': ui
.hideModal
2139 }, _('Cancel')), ' ',
2141 'class': 'cbi-button cbi-button-positive important',
2142 'click': ui
.createHandlerFn(this, 'handleJoinConfirm', radioDev
, bss
, m2
)
2145 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase
|| name
).cbid('_new_'))).focus();
2149 s
.handleAdd = function(radioDev
, ev
) {
2150 var section_id
= next_free_sid(uci
.sections('wireless', 'wifi-iface').length
);
2152 uci
.unset('wireless', radioDev
.getName(), 'disabled');
2154 uci
.add('wireless', 'wifi-iface', section_id
);
2155 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
2156 uci
.set('wireless', section_id
, 'mode', 'ap');
2157 uci
.set('wireless', section_id
, 'ssid', 'OpenWrt');
2158 uci
.set('wireless', section_id
, 'encryption', 'none');
2160 this.addedSection
= section_id
;
2161 return this.renderMoreOptionsModal(section_id
);
2164 o
= s
.option(form
.DummyValue
, '_badge');
2165 o
.modalonly
= false;
2166 o
.textvalue = function(section_id
) {
2167 var inst
= this.section
.lookupRadioOrNetwork(section_id
),
2168 node
= E('div', { 'class': 'center' });
2170 if (inst
.getWifiNetworks
)
2171 node
.appendChild(render_radio_badge(inst
));
2173 node
.appendChild(render_network_badge(inst
));
2178 o
= s
.option(form
.DummyValue
, '_stat');
2179 o
.modalonly
= false;
2180 o
.textvalue = function(section_id
) {
2181 var inst
= this.section
.lookupRadioOrNetwork(section_id
);
2183 if (inst
.getWifiNetworks
)
2184 return render_radio_status(inst
, this.section
.wifis
.filter(function(e
) {
2185 return (e
.getWifiDeviceName() == inst
.getName());
2188 return render_network_status(inst
);
2191 return m
.render().then(L
.bind(function(m
, nodes
) {
2192 poll
.add(L
.bind(function() {
2193 var section_ids
= m
.children
[0].cfgsections(),
2194 tasks
= [ network
.getHostHints(), network
.getWifiDevices() ];
2196 for (var i
= 0; i
< section_ids
.length
; i
++) {
2197 var row
= nodes
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids
[i
])),
2198 dsc
= row
.querySelector('[data-name="_stat"] > div'),
2199 btns
= row
.querySelectorAll('.cbi-section-actions button');
2201 if (dsc
.getAttribute('restart') == '') {
2202 dsc
.setAttribute('restart', '1');
2203 tasks
.push(fs
.exec('/sbin/wifi', ['up', section_ids
[i
]]).catch(function(e
) {
2204 ui
.addNotification(null, E('p', e
.message
));
2207 else if (dsc
.getAttribute('restart') == '1') {
2208 dsc
.removeAttribute('restart');
2209 btns
[0].classList
.remove('spinning');
2210 btns
[0].disabled
= false;
2214 return Promise
.all(tasks
)
2215 .then(L
.bind(function(hosts_radios
) {
2218 for (var i
= 0; i
< hosts_radios
[1].length
; i
++)
2219 tasks
.push(hosts_radios
[1][i
].getWifiNetworks());
2221 return Promise
.all(tasks
).then(function(data
) {
2222 hosts_radios
[2] = [];
2224 for (var i
= 0; i
< data
.length
; i
++)
2225 hosts_radios
[2].push
.apply(hosts_radios
[2], data
[i
]);
2227 return hosts_radios
;
2230 .then(L
.bind(function(hosts_radios_wifis
) {
2233 for (var i
= 0; i
< hosts_radios_wifis
[2].length
; i
++)
2234 tasks
.push(hosts_radios_wifis
[2][i
].getAssocList());
2236 return Promise
.all(tasks
).then(function(data
) {
2237 hosts_radios_wifis
[3] = [];
2239 for (var i
= 0; i
< data
.length
; i
++) {
2240 var wifiNetwork
= hosts_radios_wifis
[2][i
],
2241 radioDev
= hosts_radios_wifis
[1].filter(function(d
) { return d
.getName() == wifiNetwork
.getWifiDeviceName() })[0];
2243 for (var j
= 0; j
< data
[i
].length
; j
++)
2244 hosts_radios_wifis
[3].push(Object
.assign({ radio
: radioDev
, network
: wifiNetwork
}, data
[i
][j
]));
2247 return hosts_radios_wifis
;
2250 .then(L
.bind(this.poll_status
, this, nodes
));
2253 var table
= E('table', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [
2254 E('tr', { 'class': 'tr table-titles' }, [
2255 E('th', { 'class': 'th nowrap' }, _('Network')),
2256 E('th', { 'class': 'th hide-xs' }, _('MAC address')),
2257 E('th', { 'class': 'th' }, _('Host')),
2258 E('th', { 'class': 'th' }, _('Signal / Noise')),
2259 E('th', { 'class': 'th' }, _('RX Rate / TX Rate'))
2263 cbi_update_table(table
, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
2265 return E([ nodes
, E('h3', _('Associated Stations')), table
]);