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
>= 58329 && data
[1][i
].mhz
<= 69120)
335 this.channels
[band
].push(
337 '%d (%d Mhz)'.format(data
[1][i
].channel
, data
[1][i
].mhz
),
338 !data
[1][i
].restricted
342 var hwmodelist
= L
.toArray(data
[0] ? data
[0].getHWModes() : null)
343 .reduce(function(o
, v
) { o
[v
] = true; return o
}, {});
347 'n', 'N', hwmodelist
.n
,
348 'ac', 'AC', hwmodelist
.ac
,
349 'ax', 'AX', hwmodelist
.ax
352 var htmodelist
= L
.toArray(data
[0] ? data
[0].getHTModes() : null)
353 .reduce(function(o
, v
) { o
[v
] = true; return o
}, {});
356 '': [ '', '-', true ],
358 'HT20', '20 MHz', htmodelist
.HT20
,
359 'HT40', '40 MHz', htmodelist
.HT40
362 'VHT20', '20 MHz', htmodelist
.VHT20
,
363 'VHT40', '40 MHz', htmodelist
.VHT40
,
364 'VHT80', '80 MHz', htmodelist
.VHT80
,
365 'VHT160', '160 MHz', htmodelist
.VHT160
368 'HE20', '20 MHz', htmodelist
.HE20
,
369 'HE40', '40 MHz', htmodelist
.HE40
,
370 'HE80', '80 MHz', htmodelist
.HE80
,
371 'HE160', '160 MHz', htmodelist
.HE160
377 '2g', '2.4 GHz', this.channels
['2g'].length
> 3,
378 '5g', '5 GHz', this.channels
['5g'].length
> 3
381 '2g', '2.4 GHz', this.channels
['2g'].length
> 3,
382 '5g', '5 GHz', this.channels
['5g'].length
> 3
388 '2g', '2.4 GHz', this.channels
['2g'].length
> 3,
389 '5g', '5 GHz', this.channels
['5g'].length
> 3
395 setValues: function(sel
, vals
) {
397 sel
.vals
.selected
= sel
.selectedIndex
;
399 while (sel
.options
[0])
402 for (var i
= 0; vals
&& i
< vals
.length
; i
+= 3)
404 sel
.add(E('option', { value
: vals
[i
+0] }, [ vals
[i
+1] ]));
406 if (vals
&& !isNaN(vals
.selected
))
407 sel
.selectedIndex
= vals
.selected
;
409 sel
.parentNode
.style
.display
= (sel
.options
.length
<= 1) ? 'none' : '';
413 toggleWifiMode: function(elem
) {
414 this.toggleWifiHTMode(elem
);
415 this.toggleWifiBand(elem
);
418 toggleWifiHTMode: function(elem
) {
419 var mode
= elem
.querySelector('.mode');
420 var bwdt
= elem
.querySelector('.htmode');
422 this.setValues(bwdt
, this.htmodes
[mode
.value
]);
425 toggleWifiBand: function(elem
) {
426 var mode
= elem
.querySelector('.mode');
427 var band
= elem
.querySelector('.band');
429 this.setValues(band
, this.bands
[mode
.value
]);
430 this.toggleWifiChannel(elem
);
432 this.map
.checkDepends();
435 toggleWifiChannel: function(elem
) {
436 var band
= elem
.querySelector('.band');
437 var chan
= elem
.querySelector('.channel');
439 this.setValues(chan
, this.channels
[band
.value
]);
442 setInitialValues: function(section_id
, elem
) {
443 var mode
= elem
.querySelector('.mode'),
444 band
= elem
.querySelector('.band'),
445 chan
= elem
.querySelector('.channel'),
446 bwdt
= elem
.querySelector('.htmode'),
447 htval
= uci
.get('wireless', section_id
, 'htmode'),
448 hwval
= uci
.get('wireless', section_id
, 'hwmode'),
449 chval
= uci
.get('wireless', section_id
, 'channel'),
450 bandval
= uci
.get('wireless', section_id
, 'band');
452 this.setValues(mode
, this.modes
);
454 if (/VHT20|VHT40|VHT80|VHT160/.test(htval
))
456 else if (/HT20|HT40/.test(htval
))
461 this.toggleWifiMode(elem
);
464 this.useBandOption
= false;
472 this.useBandOption
= true;
474 band
.value
= bandval
;
477 this.toggleWifiBand(elem
);
480 chan
.value
= chval
|| chan
.options
[0].value
;
485 renderWidget: function(section_id
, option_index
, cfgvalue
) {
489 E('label', { 'style': 'float:left; margin-right:3px' }, [
493 'style': 'width:auto',
494 'change': L
.bind(this.toggleWifiMode
, this, elem
),
495 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
498 E('label', { 'style': 'float:left; margin-right:3px' }, [
502 'style': 'width:auto',
503 'change': L
.bind(this.toggleWifiBand
, this, elem
),
504 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
507 E('label', { 'style': 'float:left; margin-right:3px' }, [
508 _('Channel'), E('br'),
511 'style': 'width:auto',
512 'change': L
.bind(this.map
.checkDepends
, this.map
),
513 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
516 E('label', { 'style': 'float:left; margin-right:3px' }, [
520 'style': 'width:auto',
521 'change': L
.bind(this.map
.checkDepends
, this.map
),
522 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
525 E('br', { 'style': 'clear:left' })
528 return this.setInitialValues(section_id
, elem
);
531 cfgvalue: function(section_id
) {
533 uci
.get('wireless', section_id
, 'htmode'),
534 uci
.get('wireless', section_id
, 'hwmode') || uci
.get('wireless', section_id
, 'band'),
535 uci
.get('wireless', section_id
, 'channel')
539 formvalue: function(section_id
) {
540 var node
= this.map
.findElement('data-field', this.cbid(section_id
));
543 node
.querySelector('.htmode').value
,
544 node
.querySelector('.band').value
,
545 node
.querySelector('.channel').value
549 write: function(section_id
, value
) {
550 uci
.set('wireless', section_id
, 'htmode', value
[0] || null);
552 if (this.useBandOption
)
553 uci
.set('wireless', section_id
, 'band', value
[1]);
555 uci
.set('wireless', section_id
, 'hwmode', (value
[1] == '2g') ? '11g' : '11a');
557 uci
.set('wireless', section_id
, 'channel', value
[2]);
561 var CBIWifiTxPowerValue
= form
.ListValue
.extend({
562 callTxPowerList
: rpc
.declare({
564 method
: 'txpowerlist',
565 params
: [ 'device' ],
566 expect
: { results
: [] }
569 load: function(section_id
) {
570 return this.callTxPowerList(section_id
).then(L
.bind(function(pwrlist
) {
571 this.powerval
= this.wifiNetwork
? this.wifiNetwork
.getTXPower() : null;
572 this.poweroff
= this.wifiNetwork
? this.wifiNetwork
.getTXPowerOffset() : null;
574 this.value('', _('driver default'));
576 for (var i
= 0; i
< pwrlist
.length
; i
++)
577 this.value(pwrlist
[i
].dbm
, '%d dBm (%d mW)'.format(pwrlist
[i
].dbm
, pwrlist
[i
].mw
));
579 return form
.ListValue
.prototype.load
.apply(this, [section_id
]);
583 renderWidget: function(section_id
, option_index
, cfgvalue
) {
584 var widget
= form
.ListValue
.prototype.renderWidget
.apply(this, [section_id
, option_index
, cfgvalue
]);
585 widget
.firstElementChild
.style
.width
= 'auto';
587 dom
.append(widget
, E('span', [
588 ' - ', _('Current power'), ': ',
589 E('span', [ this.powerval
!= null ? '%d dBm'.format(this.powerval
) : E('em', _('unknown')) ]),
590 this.poweroff
? ' + %d dB offset = %s dBm'.format(this.poweroff
, this.powerval
!= null ? this.powerval
+ this.poweroff
: '?') : ''
597 var CBIWifiCountryValue
= form
.Value
.extend({
598 callCountryList
: rpc
.declare({
600 method
: 'countrylist',
601 params
: [ 'device' ],
602 expect
: { results
: [] }
605 load: function(section_id
) {
606 return this.callCountryList(section_id
).then(L
.bind(function(countrylist
) {
607 if (Array
.isArray(countrylist
) && countrylist
.length
> 0) {
608 this.value('', _('driver default'));
610 for (var i
= 0; i
< countrylist
.length
; i
++)
611 this.value(countrylist
[i
].iso3166
, '%s - %s'.format(countrylist
[i
].iso3166
, countrylist
[i
].country
));
614 return form
.Value
.prototype.load
.apply(this, [section_id
]);
618 validate: function(section_id
, formvalue
) {
619 if (formvalue
!= null && formvalue
!= '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue
))
620 return _('Use ISO/IEC 3166 alpha2 country codes.');
625 renderWidget: function(section_id
, option_index
, cfgvalue
) {
626 var typeClass
= (this.keylist
&& this.keylist
.length
) ? form
.ListValue
: form
.Value
;
627 return typeClass
.prototype.renderWidget
.apply(this, [section_id
, option_index
, cfgvalue
]);
632 poll_status: function(map
, data
) {
633 var rows
= map
.querySelectorAll('.cbi-section-table-row[data-sid]');
635 for (var i
= 0; i
< rows
.length
; i
++) {
636 var section_id
= rows
[i
].getAttribute('data-sid'),
637 radioDev
= data
[1].filter(function(d
) { return d
.getName() == section_id
})[0],
638 radioNet
= data
[2].filter(function(n
) { return n
.getName() == section_id
})[0],
639 badge
= rows
[i
].querySelector('[data-name="_badge"] > div'),
640 stat
= rows
[i
].querySelector('[data-name="_stat"]'),
641 btns
= rows
[i
].querySelectorAll('.cbi-section-actions button'),
642 busy
= btns
[0].classList
.contains('spinning') || btns
[1].classList
.contains('spinning') || btns
[2].classList
.contains('spinning');
645 dom
.content(badge
, render_radio_badge(radioDev
));
646 dom
.content(stat
, render_radio_status(radioDev
, data
[2].filter(function(n
) { return n
.getWifiDeviceName() == radioDev
.getName() })));
649 dom
.content(badge
, render_network_badge(radioNet
));
650 dom
.content(stat
, render_network_status(radioNet
));
653 if (stat
.hasAttribute('restart'))
654 dom
.content(stat
, E('em', _('Device is restarting…')));
656 btns
[0].disabled
= isReadonlyView
|| busy
;
657 btns
[1].disabled
= (isReadonlyView
&& radioDev
) || busy
;
658 btns
[2].disabled
= isReadonlyView
|| busy
;
661 var table
= document
.querySelector('#wifi_assoclist_table'),
665 for (var i
= 0; i
< data
[3].length
; i
++) {
666 var bss
= data
[3][i
],
667 name
= hosts
.getHostnameByMACAddr(bss
.mac
),
668 ipv4
= hosts
.getIPAddrByMACAddr(bss
.mac
),
669 ipv6
= hosts
.getIP6AddrByMACAddr(bss
.mac
);
673 if (name
&& ipv4
&& ipv6
)
674 hint
= '%s <span class="hide-xs">(%s, %s)</span>'.format(name
, ipv4
, ipv6
);
675 else if (name
&& (ipv4
|| ipv6
))
676 hint
= '%s <span class="hide-xs">(%s)</span>'.format(name
, ipv4
|| ipv6
);
678 hint
= name
|| ipv4
|| ipv6
|| '?';
682 'class': 'ifacebadge',
683 'data-ifname': bss
.network
.getIfname(),
684 'data-ssid': bss
.network
.getSSID()
687 'src': L
.resource('icons/wifi%s.png').format(bss
.network
.isUp() ? '' : '_disabled'),
688 'title': bss
.radio
.getI18n()
691 ' %s '.format(bss
.network
.getShortName()),
692 E('small', '(%s)'.format(bss
.network
.getIfname()))
697 render_signal_badge(Math
.min((bss
.signal
+ 110) / 70 * 100, 100), bss
.signal
, bss
.noise
),
699 E('span', format_wifirate(bss
.rx
)),
701 E('span', format_wifirate(bss
.tx
))
705 if (bss
.network
.isClientDisconnectSupported()) {
706 if (table
.firstElementChild
.childNodes
.length
< 6)
707 table
.firstElementChild
.appendChild(E('th', { 'class': 'th cbi-section-actions'}));
709 row
.push(E('button', {
710 'class': 'cbi-button cbi-button-remove',
711 'click': L
.bind(function(net
, mac
, ev
) {
712 dom
.parent(ev
.currentTarget
, '.tr').style
.opacity
= 0.5;
713 ev
.currentTarget
.classList
.add('spinning');
714 ev
.currentTarget
.disabled
= true;
715 ev
.currentTarget
.blur();
717 net
.disconnectClient(mac
, true, 5, 60000);
718 }, this, bss
.network
, bss
.mac
),
719 'disabled': isReadonlyView
|| null
720 }, [ _('Disconnect') ]));
729 cbi_update_table(table
, trows
, E('em', _('No information available')));
731 var stat
= document
.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large');
734 render_modal_status(stat
, data
[2].filter(function(n
) { return n
.getName() == stat
.getAttribute('data-network') })[0]);
736 return network
.flushCache();
746 checkAnonymousSections: function() {
747 var wifiIfaces
= uci
.sections('wireless', 'wifi-iface');
749 for (var i
= 0; i
< wifiIfaces
.length
; i
++)
750 if (wifiIfaces
[i
]['.anonymous'])
756 callUciRename
: rpc
.declare({
759 params
: [ 'config', 'section', 'name' ]
763 if (this.checkAnonymousSections())
764 return this.renderMigration();
766 return this.renderOverview();
769 handleMigration: function(ev
) {
770 var wifiIfaces
= uci
.sections('wireless', 'wifi-iface'),
774 for (var i
= 0; i
< wifiIfaces
.length
; i
++) {
775 if (!wifiIfaces
[i
]['.anonymous'])
778 var new_name
= next_free_sid(id_offset
);
780 tasks
.push(this.callUciRename('wireless', wifiIfaces
[i
]['.name'], new_name
));
781 id_offset
= +new_name
.substring(7) + 1;
784 return Promise
.all(tasks
)
785 .then(L
.bind(ui
.changes
.init
, ui
.changes
))
786 .then(L
.bind(ui
.changes
.apply
, ui
.changes
));
789 renderMigration: function() {
790 ui
.showModal(_('Wireless configuration migration'), [
791 E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')),
792 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.')),
793 E('div', { 'class': 'right' },
795 'class': 'btn cbi-button-action important',
796 'click': ui
.createHandlerFn(this, 'handleMigration')
801 renderOverview: function() {
804 m
= new form
.Map('wireless');
808 s
= m
.section(form
.GridSection
, 'wifi-device', _('Wireless Overview'));
812 s
.load = function() {
813 return network
.getWifiDevices().then(L
.bind(function(radios
) {
814 this.radios
= radios
.sort(function(a
, b
) {
815 return a
.getName() > b
.getName();
820 for (var i
= 0; i
< radios
.length
; i
++)
821 tasks
.push(radios
[i
].getWifiNetworks());
823 return Promise
.all(tasks
);
824 }, this)).then(L
.bind(function(data
) {
827 for (var i
= 0; i
< data
.length
; i
++)
828 this.wifis
.push
.apply(this.wifis
, data
[i
]);
832 s
.cfgsections = function() {
835 for (var i
= 0; i
< this.radios
.length
; i
++) {
836 rv
.push(this.radios
[i
].getName());
838 for (var j
= 0; j
< this.wifis
.length
; j
++)
839 if (this.wifis
[j
].getWifiDeviceName() == this.radios
[i
].getName())
840 rv
.push(this.wifis
[j
].getName());
846 s
.modaltitle = function(section_id
) {
847 var radioNet
= this.wifis
.filter(function(w
) { return w
.getName() == section_id
})[0];
848 return radioNet
? radioNet
.getI18n() : _('Edit wireless network');
851 s
.lookupRadioOrNetwork = function(section_id
) {
852 var radioDev
= this.radios
.filter(function(r
) { return r
.getName() == section_id
})[0];
856 var radioNet
= this.wifis
.filter(function(w
) { return w
.getName() == section_id
})[0];
863 s
.renderRowActions = function(section_id
) {
864 var inst
= this.lookupRadioOrNetwork(section_id
), btns
;
866 if (inst
.getWifiNetworks
) {
869 'class': 'cbi-button cbi-button-neutral',
870 'title': _('Restart radio interface'),
871 'click': ui
.createHandlerFn(this, radio_restart
, section_id
)
874 'class': 'cbi-button cbi-button-action important',
875 'title': _('Find and join network'),
876 'click': ui
.createHandlerFn(this, 'handleScan', inst
)
879 'class': 'cbi-button cbi-button-add',
880 'title': _('Provide new network'),
881 'click': ui
.createHandlerFn(this, 'handleAdd', inst
)
886 var isDisabled
= (inst
.get('disabled') == '1' ||
887 uci
.get('wireless', inst
.getWifiDeviceName(), 'disabled') == '1');
891 'class': 'cbi-button cbi-button-neutral enable-disable',
892 'title': isDisabled
? _('Enable this network') : _('Disable this network'),
893 'click': ui
.createHandlerFn(this, network_updown
, section_id
, this.map
)
894 }, isDisabled
? _('Enable') : _('Disable')),
896 'class': 'cbi-button cbi-button-action important',
897 'title': _('Edit this network'),
898 'click': ui
.createHandlerFn(this, 'renderMoreOptionsModal', section_id
)
901 'class': 'cbi-button cbi-button-negative remove',
902 'title': _('Delete this network'),
903 'click': ui
.createHandlerFn(this, 'handleRemove', section_id
)
908 return E('td', { 'class': 'td middle cbi-section-actions' }, E('div', btns
));
911 s
.addModalOptions = function(s
) {
912 return network
.getWifiNetwork(s
.section
).then(function(radioNet
) {
913 var hwtype
= uci
.get('wireless', radioNet
.getWifiDeviceName(), 'type');
916 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
920 ss
.tab('general', _('General Setup'));
921 ss
.tab('advanced', _('Advanced Settings'));
923 var isDisabled
= (radioNet
.get('disabled') == '1' ||
924 uci
.get('wireless', radioNet
.getWifiDeviceName(), 'disabled') == 1);
926 o
= ss
.taboption('general', form
.DummyValue
, '_wifistat_modal', _('Status'));
927 o
.cfgvalue
= L
.bind(function(radioNet
) {
928 return render_modal_status(null, radioNet
);
930 o
.write = function() {};
932 o
= ss
.taboption('general', form
.Button
, '_toggle', isDisabled
? _('Wireless network is disabled') : _('Wireless network is enabled'));
933 o
.inputstyle
= isDisabled
? 'apply' : 'reset';
934 o
.inputtitle
= isDisabled
? _('Enable') : _('Disable');
935 o
.onclick
= ui
.createHandlerFn(s
, network_updown
, s
.section
, s
.map
);
937 o
= ss
.taboption('general', CBIWifiFrequencyValue
, '_freq', '<br />' + _('Operating frequency'));
938 o
.ucisection
= s
.section
;
940 if (hwtype
== 'mac80211') {
941 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.'));
942 o
.depends({'_freq': '11g', '!contains': true});
944 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.'));
945 o
.wifiNetwork
= radioNet
;
947 o
= ss
.taboption('advanced', CBIWifiCountryValue
, 'country', _('Country Code'));
948 o
.wifiNetwork
= radioNet
;
950 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.'));
951 o
.value('0', _('Disabled'));
952 o
.value('1', _('Normal'));
953 o
.value('2', _('High'));
954 o
.value('3', _('Very High'));
956 o
= ss
.taboption('advanced', form
.Value
, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
957 o
.datatype
= 'or(range(0,114750),"auto")';
958 o
.placeholder
= 'auto';
960 o
= ss
.taboption('advanced', form
.Value
, 'frag', _('Fragmentation Threshold'));
961 o
.datatype
= 'min(256)';
962 o
.placeholder
= _('off');
964 o
= ss
.taboption('advanced', form
.Value
, 'rts', _('RTS/CTS Threshold'));
965 o
.datatype
= 'uinteger';
966 o
.placeholder
= _('off');
968 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!'));
971 o
= ss
.taboption('advanced', form
.Value
, 'beacon_int', _('Beacon Interval'));
972 o
.datatype
= 'range(15,65535)';
978 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getName(), 'wifi-iface', _('Interface Configuration'));
982 ss
.tab('general', _('General Setup'));
983 ss
.tab('encryption', _('Wireless Security'));
984 ss
.tab('macfilter', _('MAC-Filter'));
985 ss
.tab('advanced', _('Advanced Settings'));
987 o
= ss
.taboption('general', form
.ListValue
, 'mode', _('Mode'));
988 o
.value('ap', _('Access Point'));
989 o
.value('sta', _('Client'));
990 o
.value('adhoc', _('Ad-Hoc'));
992 o
= ss
.taboption('general', form
.Value
, 'mesh_id', _('Mesh Id'));
993 o
.depends('mode', 'mesh');
995 o
= ss
.taboption('advanced', form
.Flag
, 'mesh_fwding', _('Forward mesh peer traffic'));
998 o
.depends('mode', 'mesh');
1000 o
= ss
.taboption('advanced', form
.Value
, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
1003 o
.datatype
= 'range(-255,1)';
1004 o
.depends('mode', 'mesh');
1006 o
= ss
.taboption('general', form
.Value
, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
1007 o
.datatype
= 'maxlength(32)';
1008 o
.depends('mode', 'ap');
1009 o
.depends('mode', 'sta');
1010 o
.depends('mode', 'adhoc');
1011 o
.depends('mode', 'ahdemo');
1012 o
.depends('mode', 'monitor');
1013 o
.depends('mode', 'ap-wds');
1014 o
.depends('mode', 'sta-wds');
1015 o
.depends('mode', 'wds');
1017 o
= ss
.taboption('general', form
.Value
, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
1018 o
.datatype
= 'macaddr';
1020 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.'));
1024 o
.write = function(section_id
, value
) {
1025 return network
.getDevice(section_id
).then(L
.bind(function(dev
) {
1026 var old_networks
= dev
.getNetworks().reduce(function(o
, v
) { o
[v
.getName()] = v
; return o
}, {}),
1028 values
= L
.toArray(value
),
1031 for (var i
= 0; i
< values
.length
; i
++) {
1032 new_networks
[values
[i
]] = true;
1034 if (old_networks
[values
[i
]])
1037 tasks
.push(network
.getNetwork(values
[i
]).then(L
.bind(function(name
, net
) {
1038 return net
|| network
.addNetwork(name
, { proto
: 'none' });
1039 }, this, values
[i
])).then(L
.bind(function(dev
, net
) {
1041 if (!net
.isEmpty()) {
1042 var target_dev
= net
.getDevice();
1044 /* Resolve parent interface of vlan */
1045 while (target_dev
&& target_dev
.getType() == 'vlan')
1046 target_dev
= target_dev
.getParent();
1048 if (!target_dev
|| target_dev
.getType() != 'bridge')
1049 net
.set('type', 'bridge');
1057 for (var name
in old_networks
)
1058 if (!new_networks
[name
])
1059 tasks
.push(network
.getNetwork(name
).then(L
.bind(function(dev
, net
) {
1061 net
.deleteDevice(dev
);
1064 return Promise
.all(tasks
);
1068 if (hwtype
== 'mac80211') {
1069 var mode
= ss
.children
[0],
1070 bssid
= ss
.children
[5],
1073 mode
.value('mesh', '802.11s');
1074 mode
.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
1075 mode
.value('monitor', _('Monitor'));
1077 bssid
.depends('mode', 'adhoc');
1078 bssid
.depends('mode', 'sta');
1079 bssid
.depends('mode', 'sta-wds');
1081 o
= ss
.taboption('macfilter', form
.ListValue
, 'macfilter', _('MAC Address Filter'));
1082 o
.depends('mode', 'ap');
1083 o
.depends('mode', 'ap-wds');
1084 o
.value('', _('disable'));
1085 o
.value('allow', _('Allow listed only'));
1086 o
.value('deny', _('Allow all except listed'));
1088 o
= ss
.taboption('macfilter', form
.DynamicList
, 'maclist', _('MAC-List'));
1089 o
.datatype
= 'macaddr';
1090 o
.depends('macfilter', 'allow');
1091 o
.depends('macfilter', 'deny');
1092 o
.load = function(section_id
) {
1093 return network
.getHostHints().then(L
.bind(function(hints
) {
1094 hints
.getMACHints().map(L
.bind(function(hint
) {
1095 this.value(hint
[0], hint
[1] ? '%s (%s)'.format(hint
[0], hint
[1]) : hint
[0]);
1098 return form
.DynamicList
.prototype.load
.apply(this, [section_id
]);
1102 mode
.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
1103 mode
.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
1105 mode
.write = function(section_id
, value
) {
1108 uci
.set('wireless', section_id
, 'mode', 'ap');
1109 uci
.set('wireless', section_id
, 'wds', '1');
1113 uci
.set('wireless', section_id
, 'mode', 'sta');
1114 uci
.set('wireless', section_id
, 'wds', '1');
1118 uci
.set('wireless', section_id
, 'mode', value
);
1119 uci
.unset('wireless', section_id
, 'wds');
1124 mode
.cfgvalue = function(section_id
) {
1125 var mode
= uci
.get('wireless', section_id
, 'mode'),
1126 wds
= uci
.get('wireless', section_id
, 'wds');
1128 if (mode
== 'ap' && wds
)
1130 else if (mode
== 'sta' && wds
)
1136 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.'));
1137 o
.depends('mode', 'ap');
1138 o
.depends('mode', 'ap-wds');
1140 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.'));
1141 o
.depends('mode', 'ap');
1142 o
.depends('mode', 'ap-wds');
1143 o
.default = o
.enabled
;
1145 o
= ss
.taboption('advanced', form
.Flag
, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
1146 o
.depends('mode', 'ap');
1147 o
.depends('mode', 'ap-wds');
1149 o
= ss
.taboption('advanced', form
.Value
, 'ifname', _('Interface name'), _('Override default interface name'));
1151 o
.placeholder
= radioNet
.getIfname();
1152 if (/^radio\d+\.network/.test(o
.placeholder
))
1155 o
= ss
.taboption('advanced', form
.Flag
, 'short_preamble', _('Short Preamble'));
1156 o
.default = o
.enabled
;
1158 o
= ss
.taboption('advanced', form
.Value
, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
1161 o
.datatype
= 'range(1,255)';
1163 o
= ss
.taboption('advanced', form
.Value
, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
1165 o
.placeholder
= 600;
1166 o
.datatype
= 'uinteger';
1168 o
= ss
.taboption('advanced', form
.Flag
, 'skip_inactivity_poll', _('Disable Inactivity Polling'));
1170 o
.datatype
= 'uinteger';
1172 o
= ss
.taboption('advanced', form
.Value
, 'max_inactivity', _('Station inactivity limit'), _('sec'));
1174 o
.placeholder
= 300;
1175 o
.datatype
= 'uinteger';
1177 o
= ss
.taboption('advanced', form
.Value
, 'max_listen_interval', _('Maximum allowed Listen Interval'));
1179 o
.placeholder
= 65535;
1180 o
.datatype
= 'uinteger';
1182 o
= ss
.taboption('advanced', form
.Flag
, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
1183 o
.default = o
.enabled
;
1187 encr
= o
= ss
.taboption('encryption', form
.ListValue
, 'encryption', _('Encryption'));
1188 o
.depends('mode', 'ap');
1189 o
.depends('mode', 'sta');
1190 o
.depends('mode', 'adhoc');
1191 o
.depends('mode', 'ahdemo');
1192 o
.depends('mode', 'ap-wds');
1193 o
.depends('mode', 'sta-wds');
1194 o
.depends('mode', 'mesh');
1196 o
.cfgvalue = function(section_id
) {
1197 var v
= String(uci
.get('wireless', section_id
, 'encryption'));
1200 else if (v
.match(/\+/))
1201 return v
.replace(/\+.+$/, '');
1205 o
.write = function(section_id
, value
) {
1206 var e
= this.section
.children
.filter(function(o
) { return o
.option
== 'encryption' })[0].formvalue(section_id
),
1207 co
= this.section
.children
.filter(function(o
) { return o
.option
== 'cipher' })[0], c
= co
.formvalue(section_id
);
1209 if (value
== 'wpa' || value
== 'wpa2' || value
== 'wpa3' || value
== 'wpa3-mixed')
1210 uci
.unset('wireless', section_id
, 'key');
1212 if (co
.isActive(section_id
) && e
&& (c
== 'tkip' || c
== 'ccmp' || c
== 'tkip+ccmp'))
1215 uci
.set('wireless', section_id
, 'encryption', e
);
1218 o
= ss
.taboption('encryption', form
.ListValue
, 'cipher', _('Cipher'));
1219 o
.depends('encryption', 'wpa');
1220 o
.depends('encryption', 'wpa2');
1221 o
.depends('encryption', 'wpa3');
1222 o
.depends('encryption', 'wpa3-mixed');
1223 o
.depends('encryption', 'psk');
1224 o
.depends('encryption', 'psk2');
1225 o
.depends('encryption', 'wpa-mixed');
1226 o
.depends('encryption', 'psk-mixed');
1227 o
.value('auto', _('auto'));
1228 o
.value('ccmp', _('Force CCMP (AES)'));
1229 o
.value('tkip', _('Force TKIP'));
1230 o
.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1231 o
.write
= ss
.children
.filter(function(o
) { return o
.option
== 'encryption' })[0].write
;
1233 o
.cfgvalue = function(section_id
) {
1234 var v
= String(uci
.get('wireless', section_id
, 'encryption'));
1235 if (v
.match(/\+/)) {
1236 v
= v
.replace(/^[^+]+\+/, '');
1239 else if (v
== 'tkip+aes' || v
== 'aes+tkip' || v
== 'ccmp+tkip')
1246 var crypto_modes
= [];
1248 if (hwtype
== 'mac80211') {
1249 var has_supplicant
= L
.hasSystemFeature('wpasupplicant'),
1250 has_hostapd
= L
.hasSystemFeature('hostapd');
1252 // Probe EAP support
1253 var has_ap_eap
= L
.hasSystemFeature('hostapd', 'eap'),
1254 has_sta_eap
= L
.hasSystemFeature('wpasupplicant', 'eap');
1256 // Probe SAE support
1257 var has_ap_sae
= L
.hasSystemFeature('hostapd', 'sae'),
1258 has_sta_sae
= L
.hasSystemFeature('wpasupplicant', 'sae');
1260 // Probe OWE support
1261 var has_ap_owe
= L
.hasSystemFeature('hostapd', 'owe'),
1262 has_sta_owe
= L
.hasSystemFeature('wpasupplicant', 'owe');
1264 // Probe Suite-B support
1265 var has_ap_eap192
= L
.hasSystemFeature('hostapd', 'suiteb192'),
1266 has_sta_eap192
= L
.hasSystemFeature('wpasupplicant', 'suiteb192');
1268 // Probe WEP support
1269 var has_ap_wep
= L
.hasSystemFeature('hostapd', 'wep'),
1270 has_sta_wep
= L
.hasSystemFeature('wpasupplicant', 'wep');
1272 if (has_hostapd
|| has_supplicant
) {
1273 crypto_modes
.push(['psk2', 'WPA2-PSK', 35]);
1274 crypto_modes
.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1275 crypto_modes
.push(['psk', 'WPA-PSK', 21]);
1278 encr
.description
= _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1281 if (has_ap_sae
|| has_sta_sae
) {
1282 crypto_modes
.push(['sae', 'WPA3-SAE', 31]);
1283 crypto_modes
.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1286 if (has_ap_wep
|| has_sta_wep
) {
1287 crypto_modes
.push(['wep-open', _('WEP Open System'), 11]);
1288 crypto_modes
.push(['wep-shared', _('WEP Shared Key'), 10]);
1291 if (has_ap_eap
|| has_sta_eap
) {
1292 if (has_ap_eap192
|| has_sta_eap192
) {
1293 crypto_modes
.push(['wpa3', 'WPA3-EAP', 33]);
1294 crypto_modes
.push(['wpa3-mixed', 'WPA2-EAP/WPA3-EAP Mixed Mode', 32]);
1297 crypto_modes
.push(['wpa2', 'WPA2-EAP', 34]);
1298 crypto_modes
.push(['wpa', 'WPA-EAP', 20]);
1301 if (has_ap_owe
|| has_sta_owe
) {
1302 crypto_modes
.push(['owe', 'OWE', 1]);
1305 encr
.crypto_support
= {
1307 'wep-open': has_ap_wep
|| _('Requires hostapd with WEP support'),
1308 'wep-shared': has_ap_wep
|| _('Requires hostapd with WEP support'),
1309 'psk': has_hostapd
|| _('Requires hostapd'),
1310 'psk2': has_hostapd
|| _('Requires hostapd'),
1311 'psk-mixed': has_hostapd
|| _('Requires hostapd'),
1312 'sae': has_ap_sae
|| _('Requires hostapd with SAE support'),
1313 'sae-mixed': has_ap_sae
|| _('Requires hostapd with SAE support'),
1314 'wpa': has_ap_eap
|| _('Requires hostapd with EAP support'),
1315 'wpa2': has_ap_eap
|| _('Requires hostapd with EAP support'),
1316 'wpa3': has_ap_eap192
|| _('Requires hostapd with EAP Suite-B support'),
1317 'wpa3-mixed': has_ap_eap192
|| _('Requires hostapd with EAP Suite-B support'),
1318 'owe': has_ap_owe
|| _('Requires hostapd with OWE support')
1321 'wep-open': has_sta_wep
|| _('Requires wpa-supplicant with WEP support'),
1322 'wep-shared': has_sta_wep
|| _('Requires wpa-supplicant with WEP support'),
1323 'psk': has_supplicant
|| _('Requires wpa-supplicant'),
1324 'psk2': has_supplicant
|| _('Requires wpa-supplicant'),
1325 'psk-mixed': has_supplicant
|| _('Requires wpa-supplicant'),
1326 'sae': has_sta_sae
|| _('Requires wpa-supplicant with SAE support'),
1327 'sae-mixed': has_sta_sae
|| _('Requires wpa-supplicant with SAE support'),
1328 'wpa': has_sta_eap
|| _('Requires wpa-supplicant with EAP support'),
1329 'wpa2': has_sta_eap
|| _('Requires wpa-supplicant with EAP support'),
1330 'wpa3': has_sta_eap192
|| _('Requires wpa-supplicant with EAP Suite-B support'),
1331 'wpa3-mixed': has_sta_eap192
|| _('Requires wpa-supplicant with EAP Suite-B support'),
1332 'owe': has_sta_owe
|| _('Requires wpa-supplicant with OWE support')
1337 'psk': has_supplicant
|| _('Requires wpa-supplicant'),
1338 'psk2': has_supplicant
|| _('Requires wpa-supplicant'),
1339 'psk-mixed': has_supplicant
|| _('Requires wpa-supplicant'),
1342 'sae': has_sta_sae
|| _('Requires wpa-supplicant with SAE support')
1354 encr
.crypto_support
['ap-wds'] = encr
.crypto_support
['ap'];
1355 encr
.crypto_support
['sta-wds'] = encr
.crypto_support
['sta'];
1357 encr
.validate = function(section_id
, value
) {
1358 var modeopt
= this.section
.children
.filter(function(o
) { return o
.option
== 'mode' })[0],
1359 modeval
= modeopt
.formvalue(section_id
),
1360 modetitle
= modeopt
.vallist
[modeopt
.keylist
.indexOf(modeval
)],
1361 enctitle
= this.vallist
[this.keylist
.indexOf(value
)];
1363 if (value
== 'none')
1366 if (!L
.isObject(this.crypto_support
[modeval
]) || !this.crypto_support
[modeval
].hasOwnProperty(value
))
1367 return _('The selected %s mode is incompatible with %s encryption').format(modetitle
, enctitle
);
1369 return this.crypto_support
[modeval
][value
];
1372 else if (hwtype
== 'broadcom') {
1373 crypto_modes
.push(['psk2', 'WPA2-PSK', 33]);
1374 crypto_modes
.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1375 crypto_modes
.push(['psk', 'WPA-PSK', 21]);
1376 crypto_modes
.push(['wep-open', _('WEP Open System'), 11]);
1377 crypto_modes
.push(['wep-shared', _('WEP Shared Key'), 10]);
1380 crypto_modes
.push(['none', _('No Encryption'), 0]);
1382 crypto_modes
.sort(function(a
, b
) { return b
[2] - a
[2] });
1384 for (var i
= 0; i
< crypto_modes
.length
; i
++) {
1385 var security_level
= (crypto_modes
[i
][2] >= 30) ? _('strong security')
1386 : (crypto_modes
[i
][2] >= 20) ? _('medium security')
1387 : (crypto_modes
[i
][2] >= 10) ? _('weak security') : _('open network');
1389 encr
.value(crypto_modes
[i
][0], '%s (%s)'.format(crypto_modes
[i
][1], security_level
));
1393 o
= ss
.taboption('encryption', form
.Value
, 'auth_server', _('Radius-Authentication-Server'));
1394 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1396 o
.datatype
= 'host(0)';
1398 o
= ss
.taboption('encryption', form
.Value
, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
1399 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1401 o
.datatype
= 'port';
1403 o
= ss
.taboption('encryption', form
.Value
, 'auth_secret', _('Radius-Authentication-Secret'));
1404 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1408 o
= ss
.taboption('encryption', form
.Value
, 'acct_server', _('Radius-Accounting-Server'));
1409 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1411 o
.datatype
= 'host(0)';
1413 o
= ss
.taboption('encryption', form
.Value
, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
1414 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1416 o
.datatype
= 'port';
1418 o
= ss
.taboption('encryption', form
.Value
, 'acct_secret', _('Radius-Accounting-Secret'));
1419 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1423 o
= ss
.taboption('encryption', form
.Value
, 'dae_client', _('DAE-Client'));
1424 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1426 o
.datatype
= 'host(0)';
1428 o
= ss
.taboption('encryption', form
.Value
, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
1429 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1431 o
.datatype
= 'port';
1433 o
= ss
.taboption('encryption', form
.Value
, 'dae_secret', _('DAE-Secret'));
1434 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1439 o
= ss
.taboption('encryption', form
.Value
, '_wpa_key', _('Key'));
1440 o
.depends('encryption', 'psk');
1441 o
.depends('encryption', 'psk2');
1442 o
.depends('encryption', 'psk+psk2');
1443 o
.depends('encryption', 'psk-mixed');
1444 o
.depends('encryption', 'sae');
1445 o
.depends('encryption', 'sae-mixed');
1446 o
.datatype
= 'wpakey';
1450 o
.cfgvalue = function(section_id
) {
1451 var key
= uci
.get('wireless', section_id
, 'key');
1452 return /^[1234]$/.test(key
) ? null : key
;
1455 o
.write = function(section_id
, value
) {
1456 uci
.set('wireless', section_id
, 'key', value
);
1457 uci
.unset('wireless', section_id
, 'key1');
1458 uci
.unset('wireless', section_id
, 'key2');
1459 uci
.unset('wireless', section_id
, 'key3');
1460 uci
.unset('wireless', section_id
, 'key4');
1464 o
= ss
.taboption('encryption', form
.ListValue
, '_wep_key', _('Used Key Slot'));
1465 o
.depends('encryption', 'wep-open');
1466 o
.depends('encryption', 'wep-shared');
1467 o
.value('1', _('Key #%d').format(1));
1468 o
.value('2', _('Key #%d').format(2));
1469 o
.value('3', _('Key #%d').format(3));
1470 o
.value('4', _('Key #%d').format(4));
1472 o
.cfgvalue = function(section_id
) {
1473 var slot
= +uci
.get('wireless', section_id
, 'key');
1474 return (slot
>= 1 && slot
<= 4) ? String(slot
) : '';
1477 o
.write = function(section_id
, value
) {
1478 uci
.set('wireless', section_id
, 'key', value
);
1481 for (var slot
= 1; slot
<= 4; slot
++) {
1482 o
= ss
.taboption('encryption', form
.Value
, 'key%d'.format(slot
), _('Key #%d').format(slot
));
1483 o
.depends('encryption', 'wep-open');
1484 o
.depends('encryption', 'wep-shared');
1485 o
.datatype
= 'wepkey';
1489 o
.write = function(section_id
, value
) {
1490 if (value
!= null && (value
.length
== 5 || value
.length
== 13))
1491 value
= 's:%s'.format(value
);
1492 uci
.set('wireless', section_id
, this.option
, value
);
1497 if (hwtype
== 'mac80211') {
1498 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1499 var has_80211r
= L
.hasSystemFeature('hostapd', '11r') || L
.hasSystemFeature('hostapd', 'eap');
1501 o
= ss
.taboption('encryption', form
.Flag
, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1502 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1504 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1507 o
= ss
.taboption('encryption', form
.Value
, 'nasid', _('NAS ID'), _('Used for two different purposes: RADIUS NAS ID and 802.11r R0KH-ID. Not needed with normal WPA(2)-PSK.'));
1508 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1509 o
.depends({ ieee80211r
: '1' });
1512 o
= ss
.taboption('encryption', form
.Value
, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1513 o
.depends({ ieee80211r
: '1' });
1514 o
.placeholder
= '4f57';
1515 o
.datatype
= 'and(hexstring,length(4))';
1518 o
= ss
.taboption('encryption', form
.Value
, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1519 o
.depends({ ieee80211r
: '1' });
1520 o
.placeholder
= '1000';
1521 o
.datatype
= 'range(1000,65535)';
1524 o
= ss
.taboption('encryption', form
.ListValue
, 'ft_over_ds', _('FT protocol'));
1525 o
.depends({ ieee80211r
: '1' });
1526 o
.value('1', _('FT over DS'));
1527 o
.value('0', _('FT over the Air'));
1530 o
= ss
.taboption('encryption', form
.Flag
, 'ft_psk_generate_local', _('Generate PMK locally'), _('When using a PSK, the PMK can be automatically generated. When enabled, the R0/R1 key options below are not applied. Disable this to use the R0 and R1 key options.'));
1531 o
.depends({ ieee80211r
: '1' });
1532 o
.default = o
.enabled
;
1535 o
= ss
.taboption('encryption', form
.Value
, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1536 o
.depends({ ieee80211r
: '1' });
1537 o
.placeholder
= '10000';
1538 o
.datatype
= 'uinteger';
1541 o
= ss
.taboption('encryption', form
.Value
, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1542 o
.depends({ ieee80211r
: '1' });
1543 o
.placeholder
= '00004f577274';
1544 o
.datatype
= 'and(hexstring,length(12))';
1547 o
= ss
.taboption('encryption', form
.Flag
, 'pmk_r1_push', _('PMK R1 Push'));
1548 o
.depends({ ieee80211r
: '1' });
1549 o
.placeholder
= '0';
1552 o
= ss
.taboption('encryption', form
.DynamicList
, 'r0kh', _('External R0 Key Holder List'), _('List of R0KHs in the same Mobility Domain. <br />Format: MAC-address,NAS-Identifier,128-bit key as hex string. <br />This list is used to map R0KH-ID (NAS Identifier) to a destination MAC address when requesting PMK-R1 key from the R0KH that the STA used during the Initial Mobility Domain Association.'));
1553 o
.depends({ ieee80211r
: '1' });
1556 o
= ss
.taboption('encryption', form
.DynamicList
, 'r1kh', _('External R1 Key Holder List'), _ ('List of R1KHs in the same Mobility Domain. <br />Format: MAC-address,R1KH-ID as 6 octets with colons,128-bit key as hex string. <br />This list is used to map R1KH-ID to a destination MAC address when sending PMK-R1 key from the R0KH. This is also the list of authorized R1KHs in the MD that can request PMK-R1 keys.'));
1557 o
.depends({ ieee80211r
: '1' });
1559 // End of 802.11r options
1561 o
= ss
.taboption('encryption', form
.ListValue
, 'eap_type', _('EAP-Method'));
1562 o
.value('tls', 'TLS');
1563 o
.value('ttls', 'TTLS');
1564 o
.value('peap', 'PEAP');
1565 o
.value('fast', 'FAST');
1566 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1568 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"));
1571 o
.default = o
.disabled
;
1572 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1573 o
.validate = function(section_id
, value
) {
1574 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1575 return _("This option cannot be used because the ca-bundle package is not installed.");
1580 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert', _('Path to CA-Certificate'));
1581 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], ca_cert_usesystem
: ['0'] });
1583 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"));
1584 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1586 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"));
1587 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1589 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)"));
1590 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1592 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)"));
1593 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1595 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert', _('Path to Client-Certificate'));
1596 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1598 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key', _('Path to Private Key'));
1599 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1601 o
= ss
.taboption('encryption', form
.Value
, 'priv_key_pwd', _('Password of Private Key'));
1602 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1605 o
= ss
.taboption('encryption', form
.ListValue
, 'auth', _('Authentication'));
1606 o
.value('PAP', 'PAP');
1607 o
.value('CHAP', 'CHAP');
1608 o
.value('MSCHAP', 'MSCHAP');
1609 o
.value('MSCHAPV2', 'MSCHAPv2');
1610 o
.value('EAP-GTC', 'EAP-GTC');
1611 o
.value('EAP-MD5', 'EAP-MD5');
1612 o
.value('EAP-MSCHAPV2', 'EAP-MSCHAPv2');
1613 o
.value('EAP-TLS', 'EAP-TLS');
1614 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1616 o
.validate = function(section_id
, value
) {
1617 var eo
= this.section
.children
.filter(function(o
) { return o
.option
== 'eap_type' })[0],
1618 ev
= eo
.formvalue(section_id
);
1620 if (ev
!= 'ttls' && (value
== 'PAP' || value
== 'CHAP' || value
== 'MSCHAP' || value
== 'MSCHAPV2'))
1621 return _('This authentication type is not applicable to the selected EAP method.');
1626 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"));
1629 o
.default = o
.disabled
;
1630 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1631 o
.validate = function(section_id
, value
) {
1632 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1633 return _("This option cannot be used because the ca-bundle package is not installed.");
1638 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert2', _('Path to inner CA-Certificate'));
1639 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'], ca_cert2_usesystem
: ['0'] });
1641 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"));
1642 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1644 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"));
1645 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1647 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)"));
1648 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1650 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)"));
1651 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1653 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert2', _('Path to inner Client-Certificate'));
1654 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1656 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key2', _('Path to inner Private Key'));
1657 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1659 o
= ss
.taboption('encryption', form
.Value
, 'priv_key2_pwd', _('Password of inner Private Key'));
1660 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1663 o
= ss
.taboption('encryption', form
.Value
, 'identity', _('Identity'));
1664 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1666 o
= ss
.taboption('encryption', form
.Value
, 'anonymous_identity', _('Anonymous Identity'));
1667 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1669 o
= ss
.taboption('encryption', form
.Value
, 'password', _('Password'));
1670 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1674 if (hwtype
== 'mac80211') {
1675 // ieee802.11w options
1676 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"));
1677 o
.value('', _('Disabled'));
1678 o
.value('1', _('Optional'));
1679 o
.value('2', _('Required'));
1680 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds', 'sta', 'sta-wds'], encryption
: ['owe', 'psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1683 '2': [{ encryption
: 'sae' }, { encryption
: 'owe' }, { encryption
: 'wpa3' }, { encryption
: 'wpa3-mixed' }],
1684 '1': [{ encryption
: 'sae-mixed'}],
1688 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1689 o
.depends('ieee80211w', '1');
1690 o
.depends('ieee80211w', '2');
1691 o
.datatype
= 'uinteger';
1692 o
.placeholder
= '1000';
1695 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1696 o
.depends('ieee80211w', '1');
1697 o
.depends('ieee80211w', '2');
1698 o
.datatype
= 'uinteger';
1699 o
.placeholder
= '201';
1702 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.'));
1703 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1705 if (L
.hasSystemFeature('hostapd', 'wps') && L
.hasSystemFeature('wpasupplicant')) {
1706 o
= ss
.taboption('encryption', form
.Flag
, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1709 o
.default = o
.disabled
;
1710 o
.depends('encryption', 'psk');
1711 o
.depends('encryption', 'psk2');
1712 o
.depends('encryption', 'psk-mixed');
1713 o
.depends('encryption', 'sae');
1714 o
.depends('encryption', 'sae-mixed');
1721 s
.handleRemove = function(section_id
, ev
) {
1722 document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id
)).style
.opacity
= 0.5;
1723 return form
.TypedSection
.prototype.handleRemove
.apply(this, [section_id
, ev
]);
1726 s
.handleScan = function(radioDev
, ev
) {
1727 var table
= E('table', { 'class': 'table' }, [
1728 E('tr', { 'class': 'tr table-titles' }, [
1729 E('th', { 'class': 'th col-2 middle center' }, _('Signal')),
1730 E('th', { 'class': 'th col-4 middle left' }, _('SSID')),
1731 E('th', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1732 E('th', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1733 E('th', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1734 E('th', { 'class': 'th col-3 middle left' }, _('Encryption')),
1735 E('th', { 'class': 'th cbi-section-actions right' }, ' '),
1739 var stop
= E('button', {
1741 'click': L
.bind(this.handleScanStartStop
, this),
1742 'style': 'display:none',
1743 'data-state': 'stop'
1744 }, _('Stop refresh'));
1746 cbi_update_table(table
, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1748 var md
= ui
.showModal(_('Join Network: Wireless Scan'), [
1750 E('div', { 'class': 'right' }, [
1755 'click': L
.bind(this.handleScanAbort
, this)
1760 md
.style
.maxWidth
= '90%';
1761 md
.style
.maxHeight
= 'none';
1763 this.pollFn
= L
.bind(this.handleScanRefresh
, this, radioDev
, {}, table
, stop
);
1765 poll
.add(this.pollFn
);
1769 s
.handleScanRefresh = function(radioDev
, scanCache
, table
, stop
) {
1770 return radioDev
.getScanList().then(L
.bind(function(results
) {
1773 for (var i
= 0; i
< results
.length
; i
++)
1774 scanCache
[results
[i
].bssid
] = results
[i
];
1776 for (var k
in scanCache
)
1777 if (scanCache
[k
].stale
)
1778 results
.push(scanCache
[k
]);
1780 results
.sort(function(a
, b
) {
1781 var diff
= (b
.quality
- a
.quality
) || (a
.channel
- b
.channel
);
1786 if (a
.ssid
< b
.ssid
)
1788 else if (a
.ssid
> b
.ssid
)
1791 if (a
.bssid
< b
.bssid
)
1793 else if (a
.bssid
> b
.bssid
)
1797 for (var i
= 0; i
< results
.length
; i
++) {
1798 var res
= results
[i
],
1799 qv
= res
.quality
|| 0,
1800 qm
= res
.quality_max
|| 0,
1801 q
= (qv
> 0 && qm
> 0) ? Math
.floor((100 / qm
) * qv
) : 0,
1802 s
= res
.stale
? 'opacity:0.5' : '';
1805 E('span', { 'style': s
}, render_signal_badge(q
, res
.signal
, res
.noise
)),
1806 E('span', { 'style': s
}, (res
.ssid
!= null) ? '%h'.format(res
.ssid
) : E('em', _('hidden'))),
1807 E('span', { 'style': s
}, '%d'.format(res
.channel
)),
1808 E('span', { 'style': s
}, '%h'.format(res
.mode
)),
1809 E('span', { 'style': s
}, '%h'.format(res
.bssid
)),
1810 E('span', { 'style': s
}, '%h'.format(network
.formatWifiEncryption(res
.encryption
))),
1811 E('div', { 'class': 'right' }, E('button', {
1812 'class': 'cbi-button cbi-button-action important',
1813 'click': ui
.createHandlerFn(this, 'handleJoin', radioDev
, res
)
1814 }, _('Join Network')))
1820 cbi_update_table(table
, rows
);
1822 stop
.disabled
= false;
1823 stop
.style
.display
= '';
1824 stop
.classList
.remove('spinning');
1828 s
.handleScanStartStop = function(ev
) {
1829 var btn
= ev
.currentTarget
;
1831 if (btn
.getAttribute('data-state') == 'stop') {
1832 poll
.remove(this.pollFn
);
1833 btn
.firstChild
.data
= _('Start refresh');
1834 btn
.setAttribute('data-state', 'start');
1837 poll
.add(this.pollFn
);
1838 btn
.firstChild
.data
= _('Stop refresh');
1839 btn
.setAttribute('data-state', 'stop');
1840 btn
.classList
.add('spinning');
1841 btn
.disabled
= true;
1845 s
.handleScanAbort = function(ev
) {
1846 var md
= dom
.parent(ev
.target
, 'div[aria-modal="true"]');
1848 md
.style
.maxWidth
= '';
1849 md
.style
.maxHeight
= '';
1853 poll
.remove(this.pollFn
);
1858 s
.handleJoinConfirm = function(radioDev
, bss
, form
, ev
) {
1859 var nameopt
= L
.toArray(form
.lookupOption('name', '_new_'))[0],
1860 passopt
= L
.toArray(form
.lookupOption('password', '_new_'))[0],
1861 ssidopt
= L
.toArray(form
.lookupOption('ssid', '_new_'))[0],
1862 bssidopt
= L
.toArray(form
.lookupOption('bssid', '_new_'))[0],
1863 zoneopt
= L
.toArray(form
.lookupOption('zone', '_new_'))[0],
1864 replopt
= L
.toArray(form
.lookupOption('replace', '_new_'))[0],
1865 nameval
= (nameopt
&& nameopt
.isValid('_new_')) ? nameopt
.formvalue('_new_') : null,
1866 passval
= (passopt
&& passopt
.isValid('_new_')) ? passopt
.formvalue('_new_') : null,
1867 ssidval
= (ssidopt
&& ssidopt
.isValid('_new_')) ? ssidopt
.formvalue('_new_') : null,
1868 bssidval
= (bssidopt
&& bssidopt
.isValid('_new_')) ? bssidopt
.formvalue('_new_') : null,
1869 zoneval
= zoneopt
? zoneopt
.formvalue('_new_') : null,
1870 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
1871 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
1872 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' }).length
> 0),
1873 is_sae
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'sae' }).length
> 0);
1875 if (nameval
== null || (passopt
&& passval
== null))
1878 var section_id
= null;
1880 return this.map
.save(function() {
1881 var wifi_sections
= uci
.sections('wireless', 'wifi-iface');
1883 if (replopt
.formvalue('_new_') == '1') {
1884 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1885 if (wifi_sections
[i
].device
== radioDev
.getName())
1886 uci
.remove('wireless', wifi_sections
[i
]['.name']);
1889 if (uci
.get('wireless', radioDev
.getName(), 'disabled') == '1') {
1890 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1891 if (wifi_sections
[i
].device
== radioDev
.getName())
1892 uci
.set('wireless', wifi_sections
[i
]['.name'], 'disabled', '1');
1894 uci
.unset('wireless', radioDev
.getName(), 'disabled');
1897 section_id
= next_free_sid(wifi_sections
.length
);
1899 uci
.add('wireless', 'wifi-iface', section_id
);
1900 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
1901 uci
.set('wireless', section_id
, 'mode', (bss
.mode
== 'Ad-Hoc') ? 'adhoc' : 'sta');
1902 uci
.set('wireless', section_id
, 'network', nameval
);
1904 if (bss
.ssid
!= null) {
1905 uci
.set('wireless', section_id
, 'ssid', bss
.ssid
);
1907 if (bssidval
== '1')
1908 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
1910 else if (bss
.bssid
!= null) {
1911 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
1914 if (ssidval
!= null)
1915 uci
.set('wireless', section_id
, 'ssid', ssidval
);
1918 uci
.set('wireless', section_id
, 'encryption', 'sae');
1919 uci
.set('wireless', section_id
, 'key', passval
);
1922 for (var i
= enc
.wpa
.length
- 1; i
>= 0; i
--) {
1923 if (enc
.wpa
[i
] == 2) {
1924 uci
.set('wireless', section_id
, 'encryption', 'psk2');
1927 else if (enc
.wpa
[i
] == 1) {
1928 uci
.set('wireless', section_id
, 'encryption', 'psk');
1933 uci
.set('wireless', section_id
, 'key', passval
);
1936 uci
.set('wireless', section_id
, 'encryption', 'wep-open');
1937 uci
.set('wireless', section_id
, 'key', '1');
1938 uci
.set('wireless', section_id
, 'key1', passval
);
1941 uci
.set('wireless', section_id
, 'encryption', 'none');
1944 return network
.addNetwork(nameval
, { proto
: 'dhcp' }).then(function(net
) {
1945 firewall
.deleteNetwork(net
.getName());
1947 var zonePromise
= zoneval
1948 ? firewall
.getZone(zoneval
).then(function(zone
) { return zone
|| firewall
.addZone(zoneval
) })
1949 : Promise
.resolve();
1951 return zonePromise
.then(function(zone
) {
1953 zone
.addNetwork(net
.getName());
1956 }).then(L
.bind(function() {
1957 return this.renderMoreOptionsModal(section_id
);
1961 s
.handleJoin = function(radioDev
, bss
, ev
) {
1962 poll
.remove(this.pollFn
);
1964 var m2
= new form
.Map('wireless'),
1965 s2
= m2
.section(form
.NamedSection
, '_new_'),
1966 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
1967 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
1968 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' || a
== 'sae' })),
1969 replace
, passphrase
, name
, bssid
, zone
;
1971 var nameUsed = function(name
) {
1972 var s
= uci
.get('network', name
);
1973 if (s
!= null && s
['.type'] != 'interface')
1976 var net
= (s
!= null) ? network
.instantiateNetwork(name
) : null;
1977 return (net
!= null && !net
.isEmpty());
1980 s2
.render = function() {
1981 return Promise
.all([
1983 this.renderUCISection('_new_')
1984 ]).then(this.renderContents
.bind(this));
1987 if (bss
.ssid
== null) {
1988 name
= s2
.option(form
.Value
, 'ssid', _('Network SSID'), _('The correct SSID must be manually specified when joining a hidden wireless network'));
1989 name
.rmempty
= false;
1992 replace
= s2
.option(form
.Flag
, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
1994 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>'));
1995 name
.datatype
= 'uciname';
1996 name
.default = 'wwan';
1997 name
.rmempty
= false;
1998 name
.validate = function(section_id
, value
) {
1999 if (nameUsed(value
))
2000 return _('The network name is already used');
2005 for (var i
= 2; nameUsed(name
.default); i
++)
2006 name
.default = 'wwan%d'.format(i
);
2008 if (is_wep
|| is_psk
) {
2009 passphrase
= s2
.option(form
.Value
, 'password', is_wep
? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
2010 passphrase
.datatype
= is_wep
? 'wepkey' : 'wpakey';
2011 passphrase
.password
= true;
2012 passphrase
.rmempty
= false;
2015 if (bss
.ssid
!= null) {
2016 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
));
2017 bssid
.default = '0';
2020 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.'));
2021 zone
.default = 'wan';
2023 return m2
.render().then(L
.bind(function(nodes
) {
2024 ui
.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss
.ssid
)), [
2026 E('div', { 'class': 'right' }, [
2029 'click': ui
.hideModal
2030 }, _('Cancel')), ' ',
2032 'class': 'cbi-button cbi-button-positive important',
2033 'click': ui
.createHandlerFn(this, 'handleJoinConfirm', radioDev
, bss
, m2
)
2036 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase
|| name
).cbid('_new_'))).focus();
2040 s
.handleAdd = function(radioDev
, ev
) {
2041 var section_id
= next_free_sid(uci
.sections('wireless', 'wifi-iface').length
);
2043 uci
.unset('wireless', radioDev
.getName(), 'disabled');
2045 uci
.add('wireless', 'wifi-iface', section_id
);
2046 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
2047 uci
.set('wireless', section_id
, 'mode', 'ap');
2048 uci
.set('wireless', section_id
, 'ssid', 'OpenWrt');
2049 uci
.set('wireless', section_id
, 'encryption', 'none');
2051 this.addedSection
= section_id
;
2052 return this.renderMoreOptionsModal(section_id
);
2055 o
= s
.option(form
.DummyValue
, '_badge');
2056 o
.modalonly
= false;
2057 o
.textvalue = function(section_id
) {
2058 var inst
= this.section
.lookupRadioOrNetwork(section_id
),
2059 node
= E('div', { 'class': 'center' });
2061 if (inst
.getWifiNetworks
)
2062 node
.appendChild(render_radio_badge(inst
));
2064 node
.appendChild(render_network_badge(inst
));
2069 o
= s
.option(form
.DummyValue
, '_stat');
2070 o
.modalonly
= false;
2071 o
.textvalue = function(section_id
) {
2072 var inst
= this.section
.lookupRadioOrNetwork(section_id
);
2074 if (inst
.getWifiNetworks
)
2075 return render_radio_status(inst
, this.section
.wifis
.filter(function(e
) {
2076 return (e
.getWifiDeviceName() == inst
.getName());
2079 return render_network_status(inst
);
2082 return m
.render().then(L
.bind(function(m
, nodes
) {
2083 poll
.add(L
.bind(function() {
2084 var section_ids
= m
.children
[0].cfgsections(),
2085 tasks
= [ network
.getHostHints(), network
.getWifiDevices() ];
2087 for (var i
= 0; i
< section_ids
.length
; i
++) {
2088 var row
= nodes
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids
[i
])),
2089 dsc
= row
.querySelector('[data-name="_stat"] > div'),
2090 btns
= row
.querySelectorAll('.cbi-section-actions button');
2092 if (dsc
.getAttribute('restart') == '') {
2093 dsc
.setAttribute('restart', '1');
2094 tasks
.push(fs
.exec('/sbin/wifi', ['up', section_ids
[i
]]).catch(function(e
) {
2095 ui
.addNotification(null, E('p', e
.message
));
2098 else if (dsc
.getAttribute('restart') == '1') {
2099 dsc
.removeAttribute('restart');
2100 btns
[0].classList
.remove('spinning');
2101 btns
[0].disabled
= false;
2105 return Promise
.all(tasks
)
2106 .then(L
.bind(function(hosts_radios
) {
2109 for (var i
= 0; i
< hosts_radios
[1].length
; i
++)
2110 tasks
.push(hosts_radios
[1][i
].getWifiNetworks());
2112 return Promise
.all(tasks
).then(function(data
) {
2113 hosts_radios
[2] = [];
2115 for (var i
= 0; i
< data
.length
; i
++)
2116 hosts_radios
[2].push
.apply(hosts_radios
[2], data
[i
]);
2118 return hosts_radios
;
2121 .then(L
.bind(function(hosts_radios_wifis
) {
2124 for (var i
= 0; i
< hosts_radios_wifis
[2].length
; i
++)
2125 tasks
.push(hosts_radios_wifis
[2][i
].getAssocList());
2127 return Promise
.all(tasks
).then(function(data
) {
2128 hosts_radios_wifis
[3] = [];
2130 for (var i
= 0; i
< data
.length
; i
++) {
2131 var wifiNetwork
= hosts_radios_wifis
[2][i
],
2132 radioDev
= hosts_radios_wifis
[1].filter(function(d
) { return d
.getName() == wifiNetwork
.getWifiDeviceName() })[0];
2134 for (var j
= 0; j
< data
[i
].length
; j
++)
2135 hosts_radios_wifis
[3].push(Object
.assign({ radio
: radioDev
, network
: wifiNetwork
}, data
[i
][j
]));
2138 return hosts_radios_wifis
;
2141 .then(L
.bind(this.poll_status
, this, nodes
));
2144 var table
= E('table', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [
2145 E('tr', { 'class': 'tr table-titles' }, [
2146 E('th', { 'class': 'th nowrap' }, _('Network')),
2147 E('th', { 'class': 'th hide-xs' }, _('MAC address')),
2148 E('th', { 'class': 'th' }, _('Host')),
2149 E('th', { 'class': 'th' }, _('Signal / Noise')),
2150 E('th', { 'class': 'th' }, _('RX Rate / TX Rate'))
2154 cbi_update_table(table
, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
2156 return E([ nodes
, E('h3', _('Associated Stations')), table
]);