12 'require tools.widgets as widgets';
14 var isReadonlyView
= !L
.hasViewPermission();
16 function count_changes(section_id
) {
17 var changes
= ui
.changes
.changes
, n
= 0;
19 if (!L
.isObject(changes
))
22 if (Array
.isArray(changes
.wireless
))
23 for (var i
= 0; i
< changes
.wireless
.length
; i
++)
24 n
+= (changes
.wireless
[i
][1] == section_id
);
29 function render_radio_badge(radioDev
) {
30 return E('span', { 'class': 'ifacebadge' }, [
31 E('img', { 'src': L
.resource('icons/wifi%s.png').format(radioDev
.isUp() ? '' : '_disabled') }),
37 function render_signal_badge(signalPercent
, signalValue
, noiseValue
, wrap
, mode
) {
38 var icon
, title
, value
;
40 if (signalPercent
< 0)
41 icon
= L
.resource('icons/signal-none.png');
42 else if (signalPercent
== 0)
43 icon
= L
.resource('icons/signal-0.png');
44 else if (signalPercent
< 25)
45 icon
= L
.resource('icons/signal-0-25.png');
46 else if (signalPercent
< 50)
47 icon
= L
.resource('icons/signal-25-50.png');
48 else if (signalPercent
< 75)
49 icon
= L
.resource('icons/signal-50-75.png');
51 icon
= L
.resource('icons/signal-75-100.png');
53 if (signalValue
!= null && signalValue
!= 0) {
54 if (noiseValue
!= null && noiseValue
!= 0) {
55 value
= '%d/%d\xa0%s'.format(signalValue
, noiseValue
, _('dBm'));
56 title
= '%s: %d %s / %s: %d %s / %s %d'.format(
57 _('Signal'), signalValue
, _('dBm'),
58 _('Noise'), noiseValue
, _('dBm'),
59 _('SNR'), signalValue
- noiseValue
);
62 value
= '%d\xa0%s'.format(signalValue
, _('dBm'));
63 title
= '%s: %d %s'.format(_('Signal'), signalValue
, _('dBm'));
66 else if (signalPercent
> -1) {
69 title
= _('No client associated');
75 title
= _('Not associated');
79 title
= _('No RX signal');
82 if (noiseValue
!= null && noiseValue
!= 0) {
83 value
= '---/%d\x0a%s'.format(noiseValue
, _('dBm'));
84 title
= '%s / %s: %d %s'.format(title
, _('Noise'), noiseValue
, _('dBm'));
87 value
= '---\xa0%s'.format(_('dBm'));
91 value
= E('em', {}, E('small', {}, [ _('disabled') ]));
92 title
= _('Interface is disabled');
96 'class': wrap
? 'center' : 'ifacebadge',
98 'data-signal': signalValue
,
99 'data-noise': noiseValue
101 E('img', { 'src': icon
}),
103 wrap
? E('br') : ' ',
109 function render_network_badge(radioNet
) {
110 return render_signal_badge(
111 radioNet
.isUp() ? radioNet
.getSignalPercent() : -1,
112 radioNet
.getSignal(), radioNet
.getNoise(), false, radioNet
.getMode());
115 function render_radio_status(radioDev
, wifiNets
) {
116 var name
= radioDev
.getI18n().replace(/ Wireless Controller
.+$/, ''),
117 node
= E('div', [ E('big', {}, E('strong', {}, name
)), E('div') ]),
118 channel
, frequency
, bitrate
;
120 for (var i
= 0; i
< wifiNets
.length
; i
++) {
121 channel
= channel
|| wifiNets
[i
].getChannel();
122 frequency
= frequency
|| wifiNets
[i
].getFrequency();
123 bitrate
= bitrate
|| wifiNets
[i
].getBitRate();
127 L
.itemlist(node
.lastElementChild
, [
128 _('Channel'), '%s (%s %s)'.format(channel
|| '?', frequency
|| '?', _('GHz')),
129 _('Bitrate'), '%s %s'.format(bitrate
|| '?', _('Mbit/s'))
132 node
.lastElementChild
.appendChild(E('em', _('Device is not active')));
137 function render_network_status(radioNet
) {
138 var mode
= radioNet
.getActiveMode(),
139 bssid
= radioNet
.getActiveBSSID(),
140 channel
= radioNet
.getChannel(),
141 disabled
= (radioNet
.get('disabled') == '1' || uci
.get('wireless', radioNet
.getWifiDeviceName(), 'disabled') == '1'),
142 is_assoc
= (bssid
&& bssid
!= '00:00:00:00:00:00' && channel
&& mode
!= 'Unknown' && !disabled
),
143 is_mesh
= (radioNet
.getMode() == 'mesh'),
144 changecount
= count_changes(radioNet
.getName()),
148 status_text
= E('a', {
150 click
: L
.bind(ui
.changes
.displayChanges
, ui
.changes
)
151 }, _('Interface has %d pending changes').format(changecount
));
153 status_text
= E('em', disabled
? _('Wireless is disabled') : _('Wireless is not associated'));
155 return L
.itemlist(E('div'), [
156 is_mesh
? _('Mesh ID') : _('SSID'), (is_mesh
? radioNet
.getMeshID() : radioNet
.getSSID()) || '?',
158 _('BSSID'), (!changecount
&& is_assoc
) ? bssid
: null,
159 _('Encryption'), (!changecount
&& is_assoc
) ? radioNet
.getActiveEncryption() || _('None') : null,
161 ], [ ' | ', E('br') ]);
164 function render_modal_status(node
, radioNet
) {
165 var mode
= radioNet
.getActiveMode(),
166 noise
= radioNet
.getNoise(),
167 bssid
= radioNet
.getActiveBSSID(),
168 channel
= radioNet
.getChannel(),
169 disabled
= (radioNet
.get('disabled') == '1'),
170 is_assoc
= (bssid
&& bssid
!= '00:00:00:00:00:00' && channel
&& mode
!= 'Unknown' && !disabled
);
173 node
= E('span', { 'class': 'ifacebadge large', 'data-network': radioNet
.getName() }, [ E('small'), E('span') ]);
175 dom
.content(node
.firstElementChild
, render_signal_badge(
176 disabled
? -1 : radioNet
.getSignalPercent(),
177 radioNet
.getSignal(), noise
, true, radioNet
.getMode()));
179 L
.itemlist(node
.lastElementChild
, [
181 _('SSID'), radioNet
.getSSID() || '?',
182 _('BSSID'), is_assoc
? bssid
: null,
183 _('Encryption'), is_assoc
? radioNet
.getActiveEncryption() || _('None') : null,
184 _('Channel'), is_assoc
? '%d (%.3f %s)'.format(radioNet
.getChannel(), radioNet
.getFrequency() || 0, _('GHz')) : null,
185 _('Tx-Power'), is_assoc
? '%d %s'.format(radioNet
.getTXPower(), _('dBm')) : null,
186 _('Signal'), is_assoc
? '%d %s'.format(radioNet
.getSignal(), _('dBm')) : null,
187 _('Noise'), (is_assoc
&& noise
!= null) ? '%d %s'.format(noise
, _('dBm')) : null,
188 _('Bitrate'), is_assoc
? '%.1f %s'.format(radioNet
.getBitRate() || 0, _('Mbit/s')) : null,
189 _('Country'), is_assoc
? radioNet
.getCountryCode() : null
190 ], [ ' | ', E('br'), E('br'), E('br'), E('br'), E('br'), ' | ', E('br'), ' | ' ]);
193 dom
.append(node
.lastElementChild
, E('em', disabled
? _('Wireless is disabled') : _('Wireless is not associated')));
198 function format_wifirate(rate
) {
199 var s
= '%.1f\xa0%s, %d\xa0%s'.format(rate
.rate
/ 1000, _('Mbit/s'), rate
.mhz
, _('MHz')),
200 ht
= rate
.ht
, vht
= rate
.vht
,
201 mhz
= rate
.mhz
, nss
= rate
.nss
,
202 mcs
= rate
.mcs
, sgi
= rate
.short_gi
,
203 he
= rate
.he
, he_gi
= rate
.he_gi
,
204 he_dcm
= rate
.he_dcm
;
207 if (vht
) s
+= ', VHT-MCS\xa0%d'.format(mcs
);
208 if (nss
) s
+= ', VHT-NSS\xa0%d'.format(nss
);
209 if (ht
) s
+= ', MCS\xa0%s'.format(mcs
);
210 if (sgi
) s
+= ', ' + _('Short GI').replace(/ /g
, '\xa0');
214 s
+= ', HE-MCS\xa0%d'.format(mcs
);
215 if (nss
) s
+= ', HE-NSS\xa0%d'.format(nss
);
216 if (he_gi
) s
+= ', HE-GI\xa0%d'.format(he_gi
);
217 if (he_dcm
) s
+= ', HE-DCM\xa0%d'.format(he_dcm
);
223 function radio_restart(id
, ev
) {
224 var row
= document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id
)),
225 dsc
= row
.querySelector('[data-name="_stat"] > div'),
226 btn
= row
.querySelector('.cbi-section-actions button');
229 btn
.classList
.add('spinning');
232 dsc
.setAttribute('restart', '');
233 dom
.content(dsc
, E('em', _('Device is restarting…')));
236 function network_updown(id
, map
, ev
) {
237 var radio
= uci
.get('wireless', id
, 'device'),
238 disabled
= (uci
.get('wireless', id
, 'disabled') == '1') ||
239 (uci
.get('wireless', radio
, 'disabled') == '1');
242 uci
.unset('wireless', id
, 'disabled');
243 uci
.unset('wireless', radio
, 'disabled');
246 uci
.set('wireless', id
, 'disabled', '1');
248 var all_networks_disabled
= true,
249 wifi_ifaces
= uci
.sections('wireless', 'wifi-iface');
251 for (var i
= 0; i
< wifi_ifaces
.length
; i
++) {
252 if (wifi_ifaces
[i
].device
== radio
&& wifi_ifaces
[i
].disabled
!= '1') {
253 all_networks_disabled
= false;
258 if (all_networks_disabled
)
259 uci
.set('wireless', radio
, 'disabled', '1');
262 return map
.save().then(function() {
267 function next_free_sid(offset
) {
268 var sid
= 'wifinet' + offset
;
270 while (uci
.get('wireless', sid
))
271 sid
= 'wifinet' + (++offset
);
276 function add_dependency_permutations(o
, deps
) {
279 for (var key
in deps
) {
280 if (!deps
.hasOwnProperty(key
) || !Array
.isArray(deps
[key
]))
283 var list
= deps
[key
],
286 for (var j
= 0; j
< list
.length
; j
++) {
287 for (var k
= 0; k
< (res
? res
.length
: 1); k
++) {
288 var item
= (res
? Object
.assign({}, res
[k
]) : {});
297 for (var i
= 0; i
< (res
? res
.length
: 0); i
++)
301 var CBIWifiFrequencyValue
= form
.Value
.extend({
302 callFrequencyList
: rpc
.declare({
305 params
: [ 'device' ],
306 expect
: { results
: [] }
309 load: function(section_id
) {
311 network
.getWifiDevice(section_id
),
312 this.callFrequencyList(section_id
)
313 ]).then(L
.bind(function(data
) {
315 '2g': L
.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
316 '5g': L
.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
321 for (var i
= 0; i
< data
[1].length
; i
++) {
324 if (data
[1][i
].mhz
>= 2412 && data
[1][i
].mhz
<= 2484)
326 else if (data
[1][i
].mhz
>= 5160 && data
[1][i
].mhz
<= 5885)
328 else if (data
[1][i
].mhz
>= 5925 && data
[1][i
].mhz
<= 7125)
330 else if (data
[1][i
].mhz
>= 58320 && data
[1][i
].mhz
<= 69120)
335 this.channels
[band
].push(
337 '%d (%d Mhz)'.format(data
[1][i
].channel
, data
[1][i
].mhz
),
338 !data
[1][i
].restricted
342 var hwmodelist
= L
.toArray(data
[0] ? data
[0].getHWModes() : null)
343 .reduce(function(o
, v
) { o
[v
] = true; return o
}, {});
347 'n', 'N', hwmodelist
.n
,
348 'ac', 'AC', hwmodelist
.ac
,
349 'ax', 'AX', hwmodelist
.ax
352 var htmodelist
= L
.toArray(data
[0] ? data
[0].getHTModes() : null)
353 .reduce(function(o
, v
) { o
[v
] = true; return o
}, {});
356 '': [ '', '-', true ],
358 'HT20', '20 MHz', htmodelist
.HT20
,
359 'HT40', '40 MHz', htmodelist
.HT40
362 'VHT20', '20 MHz', htmodelist
.VHT20
,
363 'VHT40', '40 MHz', htmodelist
.VHT40
,
364 'VHT80', '80 MHz', htmodelist
.VHT80
,
365 'VHT160', '160 MHz', htmodelist
.VHT160
368 'HE20', '20 MHz', htmodelist
.HE20
,
369 'HE40', '40 MHz', htmodelist
.HE40
,
370 'HE80', '80 MHz', htmodelist
.HE80
,
371 'HE160', '160 MHz', htmodelist
.HE160
377 '2g', '2.4 GHz', this.channels
['2g'].length
> 3,
378 '5g', '5 GHz', this.channels
['5g'].length
> 3,
379 '60g', '60 GHz', this.channels
['60g'].length
> 0
382 '2g', '2.4 GHz', this.channels
['2g'].length
> 3,
383 '5g', '5 GHz', this.channels
['5g'].length
> 3
389 '2g', '2.4 GHz', this.channels
['2g'].length
> 3,
390 '5g', '5 GHz', this.channels
['5g'].length
> 3
396 setValues: function(sel
, vals
) {
398 sel
.vals
.selected
= sel
.selectedIndex
;
400 while (sel
.options
[0])
403 for (var i
= 0; vals
&& i
< vals
.length
; i
+= 3)
405 sel
.add(E('option', { value
: vals
[i
+0] }, [ vals
[i
+1] ]));
407 if (vals
&& !isNaN(vals
.selected
))
408 sel
.selectedIndex
= vals
.selected
;
410 sel
.parentNode
.style
.display
= (sel
.options
.length
<= 1) ? 'none' : '';
414 toggleWifiMode: function(elem
) {
415 this.toggleWifiHTMode(elem
);
416 this.toggleWifiBand(elem
);
419 toggleWifiHTMode: function(elem
) {
420 var mode
= elem
.querySelector('.mode');
421 var bwdt
= elem
.querySelector('.htmode');
423 this.setValues(bwdt
, this.htmodes
[mode
.value
]);
426 toggleWifiBand: function(elem
) {
427 var mode
= elem
.querySelector('.mode');
428 var band
= elem
.querySelector('.band');
430 this.setValues(band
, this.bands
[mode
.value
]);
431 this.toggleWifiChannel(elem
);
433 this.map
.checkDepends();
436 toggleWifiChannel: function(elem
) {
437 var band
= elem
.querySelector('.band');
438 var chan
= elem
.querySelector('.channel');
440 this.setValues(chan
, this.channels
[band
.value
]);
443 setInitialValues: function(section_id
, elem
) {
444 var mode
= elem
.querySelector('.mode'),
445 band
= elem
.querySelector('.band'),
446 chan
= elem
.querySelector('.channel'),
447 bwdt
= elem
.querySelector('.htmode'),
448 htval
= uci
.get('wireless', section_id
, 'htmode'),
449 hwval
= uci
.get('wireless', section_id
, 'hwmode'),
450 chval
= uci
.get('wireless', section_id
, 'channel'),
451 bandval
= uci
.get('wireless', section_id
, 'band');
453 this.setValues(mode
, this.modes
);
455 if (/HE20|HE40|HE80|HE160/.test(htval
))
457 else if (/VHT20|VHT40|VHT80|VHT160/.test(htval
))
459 else if (/HT20|HT40/.test(htval
))
464 this.toggleWifiMode(elem
);
467 this.useBandOption
= false;
475 this.useBandOption
= true;
477 band
.value
= bandval
;
480 this.toggleWifiBand(elem
);
483 chan
.value
= chval
|| (chan
.options
[0] ? chan
.options
[0].value
: 'auto');
488 renderWidget: function(section_id
, option_index
, cfgvalue
) {
492 E('label', { 'style': 'float:left; margin-right:3px' }, [
496 'style': 'width:auto',
497 'change': L
.bind(this.toggleWifiMode
, this, elem
),
498 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
501 E('label', { 'style': 'float:left; margin-right:3px' }, [
505 'style': 'width:auto',
506 'change': L
.bind(this.toggleWifiBand
, this, elem
),
507 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
510 E('label', { 'style': 'float:left; margin-right:3px' }, [
511 _('Channel'), E('br'),
514 'style': 'width:auto',
515 'change': L
.bind(this.map
.checkDepends
, this.map
),
516 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
519 E('label', { 'style': 'float:left; margin-right:3px' }, [
523 'style': 'width:auto',
524 'change': L
.bind(this.map
.checkDepends
, this.map
),
525 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
528 E('br', { 'style': 'clear:left' })
531 return this.setInitialValues(section_id
, elem
);
534 cfgvalue: function(section_id
) {
536 uci
.get('wireless', section_id
, 'htmode'),
537 uci
.get('wireless', section_id
, 'hwmode') || uci
.get('wireless', section_id
, 'band'),
538 uci
.get('wireless', section_id
, 'channel')
542 formvalue: function(section_id
) {
543 var node
= this.map
.findElement('data-field', this.cbid(section_id
));
546 node
.querySelector('.htmode').value
,
547 node
.querySelector('.band').value
,
548 node
.querySelector('.channel').value
552 write: function(section_id
, value
) {
553 uci
.set('wireless', section_id
, 'htmode', value
[0] || null);
555 if (this.useBandOption
)
556 uci
.set('wireless', section_id
, 'band', value
[1]);
558 uci
.set('wireless', section_id
, 'hwmode', (value
[1] == '2g') ? '11g' : '11a');
560 uci
.set('wireless', section_id
, 'channel', value
[2]);
564 var CBIWifiTxPowerValue
= form
.ListValue
.extend({
565 callTxPowerList
: rpc
.declare({
567 method
: 'txpowerlist',
568 params
: [ 'device' ],
569 expect
: { results
: [] }
572 load: function(section_id
) {
573 return this.callTxPowerList(section_id
).then(L
.bind(function(pwrlist
) {
574 this.powerval
= this.wifiNetwork
? this.wifiNetwork
.getTXPower() : null;
575 this.poweroff
= this.wifiNetwork
? this.wifiNetwork
.getTXPowerOffset() : null;
577 this.value('', _('driver default'));
579 for (var i
= 0; i
< pwrlist
.length
; i
++)
580 this.value(pwrlist
[i
].dbm
, '%d dBm (%d mW)'.format(pwrlist
[i
].dbm
, pwrlist
[i
].mw
));
582 return form
.ListValue
.prototype.load
.apply(this, [section_id
]);
586 renderWidget: function(section_id
, option_index
, cfgvalue
) {
587 var widget
= form
.ListValue
.prototype.renderWidget
.apply(this, [section_id
, option_index
, cfgvalue
]);
588 widget
.firstElementChild
.style
.width
= 'auto';
590 dom
.append(widget
, E('span', [
591 ' - ', _('Current power'), ': ',
592 E('span', [ this.powerval
!= null ? '%d dBm'.format(this.powerval
) : E('em', _('unknown')) ]),
593 this.poweroff
? ' + %d dB offset = %s dBm'.format(this.poweroff
, this.powerval
!= null ? this.powerval
+ this.poweroff
: '?') : ''
600 var CBIWifiCountryValue
= form
.Value
.extend({
601 callCountryList
: rpc
.declare({
603 method
: 'countrylist',
604 params
: [ 'device' ],
605 expect
: { results
: [] }
608 load: function(section_id
) {
609 return this.callCountryList(section_id
).then(L
.bind(function(countrylist
) {
610 if (Array
.isArray(countrylist
) && countrylist
.length
> 0) {
611 this.value('', _('driver default'));
613 for (var i
= 0; i
< countrylist
.length
; i
++)
614 this.value(countrylist
[i
].iso3166
, '%s - %s'.format(countrylist
[i
].iso3166
, countrylist
[i
].country
));
617 return form
.Value
.prototype.load
.apply(this, [section_id
]);
621 validate: function(section_id
, formvalue
) {
622 if (formvalue
!= null && formvalue
!= '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue
))
623 return _('Use ISO/IEC 3166 alpha2 country codes.');
628 renderWidget: function(section_id
, option_index
, cfgvalue
) {
629 var typeClass
= (this.keylist
&& this.keylist
.length
) ? form
.ListValue
: form
.Value
;
630 return typeClass
.prototype.renderWidget
.apply(this, [section_id
, option_index
, cfgvalue
]);
635 poll_status: function(map
, data
) {
636 var rows
= map
.querySelectorAll('.cbi-section-table-row[data-sid]');
638 for (var i
= 0; i
< rows
.length
; i
++) {
639 var section_id
= rows
[i
].getAttribute('data-sid'),
640 radioDev
= data
[1].filter(function(d
) { return d
.getName() == section_id
})[0],
641 radioNet
= data
[2].filter(function(n
) { return n
.getName() == section_id
})[0],
642 badge
= rows
[i
].querySelector('[data-name="_badge"] > div'),
643 stat
= rows
[i
].querySelector('[data-name="_stat"]'),
644 btns
= rows
[i
].querySelectorAll('.cbi-section-actions button'),
645 busy
= btns
[0].classList
.contains('spinning') || btns
[1].classList
.contains('spinning') || btns
[2].classList
.contains('spinning');
648 dom
.content(badge
, render_radio_badge(radioDev
));
649 dom
.content(stat
, render_radio_status(radioDev
, data
[2].filter(function(n
) { return n
.getWifiDeviceName() == radioDev
.getName() })));
652 dom
.content(badge
, render_network_badge(radioNet
));
653 dom
.content(stat
, render_network_status(radioNet
));
656 if (stat
.hasAttribute('restart'))
657 dom
.content(stat
, E('em', _('Device is restarting…')));
659 btns
[0].disabled
= isReadonlyView
|| busy
;
660 btns
[1].disabled
= (isReadonlyView
&& radioDev
) || busy
;
661 btns
[2].disabled
= isReadonlyView
|| busy
;
664 var table
= document
.querySelector('#wifi_assoclist_table'),
668 for (var i
= 0; i
< data
[3].length
; i
++) {
669 var bss
= data
[3][i
],
670 name
= hosts
.getHostnameByMACAddr(bss
.mac
),
671 ipv4
= hosts
.getIPAddrByMACAddr(bss
.mac
),
672 ipv6
= hosts
.getIP6AddrByMACAddr(bss
.mac
);
676 if (name
&& ipv4
&& ipv6
)
677 hint
= '%s <span class="hide-xs">(%s, %s)</span>'.format(name
, ipv4
, ipv6
);
678 else if (name
&& (ipv4
|| ipv6
))
679 hint
= '%s <span class="hide-xs">(%s)</span>'.format(name
, ipv4
|| ipv6
);
681 hint
= name
|| ipv4
|| ipv6
|| '?';
685 'class': 'ifacebadge',
686 'data-ifname': bss
.network
.getIfname(),
687 'data-ssid': bss
.network
.getSSID()
690 'src': L
.resource('icons/wifi%s.png').format(bss
.network
.isUp() ? '' : '_disabled'),
691 'title': bss
.radio
.getI18n()
694 ' %s '.format(bss
.network
.getShortName()),
695 E('small', '(%s)'.format(bss
.network
.getIfname()))
700 render_signal_badge(Math
.min((bss
.signal
+ 110) / 70 * 100, 100), bss
.signal
, bss
.noise
),
702 E('span', format_wifirate(bss
.rx
)),
704 E('span', format_wifirate(bss
.tx
))
708 if (bss
.network
.isClientDisconnectSupported()) {
709 if (table
.firstElementChild
.childNodes
.length
< 6)
710 table
.firstElementChild
.appendChild(E('th', { 'class': 'th cbi-section-actions'}));
712 row
.push(E('button', {
713 'class': 'cbi-button cbi-button-remove',
714 'click': L
.bind(function(net
, mac
, ev
) {
715 dom
.parent(ev
.currentTarget
, '.tr').style
.opacity
= 0.5;
716 ev
.currentTarget
.classList
.add('spinning');
717 ev
.currentTarget
.disabled
= true;
718 ev
.currentTarget
.blur();
720 net
.disconnectClient(mac
, true, 5, 60000);
721 }, this, bss
.network
, bss
.mac
),
722 'disabled': isReadonlyView
|| null
723 }, [ _('Disconnect') ]));
732 cbi_update_table(table
, trows
, E('em', _('No information available')));
734 var stat
= document
.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large');
737 render_modal_status(stat
, data
[2].filter(function(n
) { return n
.getName() == stat
.getAttribute('data-network') })[0]);
739 return network
.flushCache();
749 checkAnonymousSections: function() {
750 var wifiIfaces
= uci
.sections('wireless', 'wifi-iface');
752 for (var i
= 0; i
< wifiIfaces
.length
; i
++)
753 if (wifiIfaces
[i
]['.anonymous'])
759 callUciRename
: rpc
.declare({
762 params
: [ 'config', 'section', 'name' ]
766 if (this.checkAnonymousSections())
767 return this.renderMigration();
769 return this.renderOverview();
772 handleMigration: function(ev
) {
773 var wifiIfaces
= uci
.sections('wireless', 'wifi-iface'),
777 for (var i
= 0; i
< wifiIfaces
.length
; i
++) {
778 if (!wifiIfaces
[i
]['.anonymous'])
781 var new_name
= next_free_sid(id_offset
);
783 tasks
.push(this.callUciRename('wireless', wifiIfaces
[i
]['.name'], new_name
));
784 id_offset
= +new_name
.substring(7) + 1;
787 return Promise
.all(tasks
)
788 .then(L
.bind(ui
.changes
.init
, ui
.changes
))
789 .then(L
.bind(ui
.changes
.apply
, ui
.changes
));
792 renderMigration: function() {
793 ui
.showModal(_('Wireless configuration migration'), [
794 E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')),
795 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.')),
796 E('div', { 'class': 'right' },
798 'class': 'btn cbi-button-action important',
799 'click': ui
.createHandlerFn(this, 'handleMigration')
804 renderOverview: function() {
807 m
= new form
.Map('wireless');
811 s
= m
.section(form
.GridSection
, 'wifi-device', _('Wireless Overview'));
815 s
.load = function() {
816 return network
.getWifiDevices().then(L
.bind(function(radios
) {
817 this.radios
= radios
.sort(function(a
, b
) {
818 return a
.getName() > b
.getName();
823 for (var i
= 0; i
< radios
.length
; i
++)
824 tasks
.push(radios
[i
].getWifiNetworks());
826 return Promise
.all(tasks
);
827 }, this)).then(L
.bind(function(data
) {
830 for (var i
= 0; i
< data
.length
; i
++)
831 this.wifis
.push
.apply(this.wifis
, data
[i
]);
835 s
.cfgsections = function() {
838 for (var i
= 0; i
< this.radios
.length
; i
++) {
839 rv
.push(this.radios
[i
].getName());
841 for (var j
= 0; j
< this.wifis
.length
; j
++)
842 if (this.wifis
[j
].getWifiDeviceName() == this.radios
[i
].getName())
843 rv
.push(this.wifis
[j
].getName());
849 s
.modaltitle = function(section_id
) {
850 var radioNet
= this.wifis
.filter(function(w
) { return w
.getName() == section_id
})[0];
851 return radioNet
? radioNet
.getI18n() : _('Edit wireless network');
854 s
.lookupRadioOrNetwork = function(section_id
) {
855 var radioDev
= this.radios
.filter(function(r
) { return r
.getName() == section_id
})[0];
859 var radioNet
= this.wifis
.filter(function(w
) { return w
.getName() == section_id
})[0];
866 s
.renderRowActions = function(section_id
) {
867 var inst
= this.lookupRadioOrNetwork(section_id
), btns
;
869 if (inst
.getWifiNetworks
) {
872 'class': 'cbi-button cbi-button-neutral',
873 'title': _('Restart radio interface'),
874 'click': ui
.createHandlerFn(this, radio_restart
, section_id
)
877 'class': 'cbi-button cbi-button-action important',
878 'title': _('Find and join network'),
879 'click': ui
.createHandlerFn(this, 'handleScan', inst
)
882 'class': 'cbi-button cbi-button-add',
883 'title': _('Provide new network'),
884 'click': ui
.createHandlerFn(this, 'handleAdd', inst
)
889 var isDisabled
= (inst
.get('disabled') == '1' ||
890 uci
.get('wireless', inst
.getWifiDeviceName(), 'disabled') == '1');
894 'class': 'cbi-button cbi-button-neutral enable-disable',
895 'title': isDisabled
? _('Enable this network') : _('Disable this network'),
896 'click': ui
.createHandlerFn(this, network_updown
, section_id
, this.map
)
897 }, isDisabled
? _('Enable') : _('Disable')),
899 'class': 'cbi-button cbi-button-action important',
900 'title': _('Edit this network'),
901 'click': ui
.createHandlerFn(this, 'renderMoreOptionsModal', section_id
)
904 'class': 'cbi-button cbi-button-negative remove',
905 'title': _('Delete this network'),
906 'click': ui
.createHandlerFn(this, 'handleRemove', section_id
)
911 return E('td', { 'class': 'td middle cbi-section-actions' }, E('div', btns
));
914 s
.addModalOptions = function(s
) {
915 return network
.getWifiNetwork(s
.section
).then(function(radioNet
) {
916 var hwtype
= uci
.get('wireless', radioNet
.getWifiDeviceName(), 'type');
919 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
923 ss
.tab('general', _('General Setup'));
924 ss
.tab('advanced', _('Advanced Settings'));
926 var isDisabled
= (radioNet
.get('disabled') == '1' ||
927 uci
.get('wireless', radioNet
.getWifiDeviceName(), 'disabled') == 1);
929 o
= ss
.taboption('general', form
.DummyValue
, '_wifistat_modal', _('Status'));
930 o
.cfgvalue
= L
.bind(function(radioNet
) {
931 return render_modal_status(null, radioNet
);
933 o
.write = function() {};
935 o
= ss
.taboption('general', form
.Button
, '_toggle', isDisabled
? _('Wireless network is disabled') : _('Wireless network is enabled'));
936 o
.inputstyle
= isDisabled
? 'apply' : 'reset';
937 o
.inputtitle
= isDisabled
? _('Enable') : _('Disable');
938 o
.onclick
= ui
.createHandlerFn(s
, network_updown
, s
.section
, s
.map
);
940 o
= ss
.taboption('general', CBIWifiFrequencyValue
, '_freq', '<br />' + _('Operating frequency'));
941 o
.ucisection
= s
.section
;
943 if (hwtype
== 'mac80211') {
944 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.'));
945 o
.depends({'_freq': '2g', '!contains': true});
947 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.'));
948 o
.wifiNetwork
= radioNet
;
950 o
= ss
.taboption('advanced', CBIWifiCountryValue
, 'country', _('Country Code'));
951 o
.wifiNetwork
= radioNet
;
953 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.'));
954 o
.value('0', _('Disabled'));
955 o
.value('1', _('Normal'));
956 o
.value('2', _('High'));
957 o
.value('3', _('Very High'));
959 o
= ss
.taboption('advanced', form
.Value
, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
960 o
.datatype
= 'or(range(0,114750),"auto")';
961 o
.placeholder
= 'auto';
963 o
= ss
.taboption('advanced', form
.Value
, 'frag', _('Fragmentation Threshold'));
964 o
.datatype
= 'min(256)';
965 o
.placeholder
= _('off');
967 o
= ss
.taboption('advanced', form
.Value
, 'rts', _('RTS/CTS Threshold'));
968 o
.datatype
= 'uinteger';
969 o
.placeholder
= _('off');
971 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!'));
974 o
= ss
.taboption('advanced', form
.Value
, 'beacon_int', _('Beacon Interval'));
975 o
.datatype
= 'range(15,65535)';
981 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getName(), 'wifi-iface', _('Interface Configuration'));
985 ss
.tab('general', _('General Setup'));
986 ss
.tab('encryption', _('Wireless Security'));
987 ss
.tab('macfilter', _('MAC-Filter'));
988 ss
.tab('advanced', _('Advanced Settings'));
990 o
= ss
.taboption('general', form
.ListValue
, 'mode', _('Mode'));
991 o
.value('ap', _('Access Point'));
992 o
.value('sta', _('Client'));
993 o
.value('adhoc', _('Ad-Hoc'));
995 o
= ss
.taboption('general', form
.Value
, 'mesh_id', _('Mesh Id'));
996 o
.depends('mode', 'mesh');
998 o
= ss
.taboption('advanced', form
.Flag
, 'mesh_fwding', _('Forward mesh peer traffic'));
1001 o
.depends('mode', 'mesh');
1003 o
= ss
.taboption('advanced', form
.Value
, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
1006 o
.datatype
= 'range(-255,1)';
1007 o
.depends('mode', 'mesh');
1009 o
= ss
.taboption('general', form
.Value
, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
1010 o
.datatype
= 'maxlength(32)';
1011 o
.depends('mode', 'ap');
1012 o
.depends('mode', 'sta');
1013 o
.depends('mode', 'adhoc');
1014 o
.depends('mode', 'ahdemo');
1015 o
.depends('mode', 'monitor');
1016 o
.depends('mode', 'ap-wds');
1017 o
.depends('mode', 'sta-wds');
1018 o
.depends('mode', 'wds');
1020 o
= ss
.taboption('general', form
.Value
, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
1021 o
.datatype
= 'macaddr';
1023 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.'));
1027 o
.write = function(section_id
, value
) {
1028 return network
.getDevice(section_id
).then(L
.bind(function(dev
) {
1029 var old_networks
= dev
.getNetworks().reduce(function(o
, v
) { o
[v
.getName()] = v
; return o
}, {}),
1031 values
= L
.toArray(value
),
1034 for (var i
= 0; i
< values
.length
; i
++) {
1035 new_networks
[values
[i
]] = true;
1037 if (old_networks
[values
[i
]])
1040 tasks
.push(network
.getNetwork(values
[i
]).then(L
.bind(function(name
, net
) {
1041 return net
|| network
.addNetwork(name
, { proto
: 'none' });
1042 }, this, values
[i
])).then(L
.bind(function(dev
, net
) {
1044 if (!net
.isEmpty()) {
1045 var target_dev
= net
.getDevice();
1047 /* Resolve parent interface of vlan */
1048 while (target_dev
&& target_dev
.getType() == 'vlan')
1049 target_dev
= target_dev
.getParent();
1051 if (!target_dev
|| target_dev
.getType() != 'bridge')
1052 net
.set('type', 'bridge');
1060 for (var name
in old_networks
)
1061 if (!new_networks
[name
])
1062 tasks
.push(network
.getNetwork(name
).then(L
.bind(function(dev
, net
) {
1064 net
.deleteDevice(dev
);
1067 return Promise
.all(tasks
);
1071 if (hwtype
== 'mac80211') {
1072 var mode
= ss
.children
[0],
1073 bssid
= ss
.children
[5],
1076 mode
.value('mesh', '802.11s');
1077 mode
.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
1078 mode
.value('monitor', _('Monitor'));
1080 bssid
.depends('mode', 'adhoc');
1081 bssid
.depends('mode', 'sta');
1082 bssid
.depends('mode', 'sta-wds');
1084 o
= ss
.taboption('macfilter', form
.ListValue
, 'macfilter', _('MAC Address Filter'));
1085 o
.depends('mode', 'ap');
1086 o
.depends('mode', 'ap-wds');
1087 o
.value('', _('disable'));
1088 o
.value('allow', _('Allow listed only'));
1089 o
.value('deny', _('Allow all except listed'));
1091 o
= ss
.taboption('macfilter', form
.DynamicList
, 'maclist', _('MAC-List'));
1092 o
.datatype
= 'macaddr';
1094 o
.depends('macfilter', 'allow');
1095 o
.depends('macfilter', 'deny');
1096 o
.load = function(section_id
) {
1097 return network
.getHostHints().then(L
.bind(function(hints
) {
1098 hints
.getMACHints().map(L
.bind(function(hint
) {
1099 this.value(hint
[0], hint
[1] ? '%s (%s)'.format(hint
[0], hint
[1]) : hint
[0]);
1102 return form
.DynamicList
.prototype.load
.apply(this, [section_id
]);
1106 mode
.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
1107 mode
.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
1109 mode
.write = function(section_id
, value
) {
1112 uci
.set('wireless', section_id
, 'mode', 'ap');
1113 uci
.set('wireless', section_id
, 'wds', '1');
1117 uci
.set('wireless', section_id
, 'mode', 'sta');
1118 uci
.set('wireless', section_id
, 'wds', '1');
1122 uci
.set('wireless', section_id
, 'mode', value
);
1123 uci
.unset('wireless', section_id
, 'wds');
1128 mode
.cfgvalue = function(section_id
) {
1129 var mode
= uci
.get('wireless', section_id
, 'mode'),
1130 wds
= uci
.get('wireless', section_id
, 'wds');
1132 if (mode
== 'ap' && wds
)
1134 else if (mode
== 'sta' && wds
)
1140 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.'));
1141 o
.depends('mode', 'ap');
1142 o
.depends('mode', 'ap-wds');
1144 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.'));
1145 o
.depends('mode', 'ap');
1146 o
.depends('mode', 'ap-wds');
1147 o
.default = o
.enabled
;
1149 /* https://w1.fi/cgit/hostap/commit/?id=34f7c699a6bcb5c45f82ceb6743354ad79296078 */
1150 /* multicast_to_unicast https://github.com/openwrt/openwrt/commit/7babb978ad9d7fc29acb1ff86afb1eb343af303a */
1151 o = ss.taboption('advanced', form.Flag, 'multicast_to_unicast', _('Multi To Unicast'), _('ARP, IPv4 and IPv6 (even 802.1Q) with multicast destination MACs are unicast to the STA MAC address. Note: This is not Directed Multicast Service (DMS) in 802.11v. Note: might break receiver STA multicast expectations.'));
1154 o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
1155 o.depends('mode', 'ap');
1156 o.depends('mode', 'ap-wds');
1158 o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name'));
1160 o.placeholder = radioNet.getIfname();
1161 if (/^radio\d+\.network/.test(o.placeholder))
1164 o = ss.taboption('advanced', form.Value, 'macaddr', _('MAC address'), _('Override default MAC address - the range of usable addresses might be limited by the driver'));
1166 o.placeholder = radioNet.getActiveBSSID();
1167 o.datatype = 'macaddr';
1169 o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble'));
1170 o.default = o.enabled;
1172 o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
1175 o.datatype = 'range(1,255)';
1177 o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
1179 o.placeholder = 600;
1180 o.datatype = 'uinteger';
1182 o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling'));
1184 o.datatype = 'uinteger';
1186 o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('sec'));
1188 o.placeholder = 300;
1189 o.datatype = 'uinteger';
1191 o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval'));
1193 o.placeholder = 65535;
1194 o.datatype = 'uinteger';
1196 o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
1197 o.default = o.enabled;
1201 encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption'));
1202 o.depends('mode', 'ap');
1203 o.depends('mode', 'sta');
1204 o.depends('mode', 'adhoc');
1205 o.depends('mode', 'ahdemo');
1206 o.depends('mode', 'ap-wds');
1207 o.depends('mode', 'sta-wds');
1208 o.depends('mode', 'mesh');
1210 o.cfgvalue = function(section_id) {
1211 var v = String(uci.get('wireless', section_id, 'encryption'));
1214 else if (v.match(/\+/))
1215 return v.replace(/\+.+$/, '');
1219 o.write = function(section_id, value) {
1220 var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id),
1221 co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id);
1223 if (value == 'wpa' || value == 'wpa2' || value == 'wpa3' || value == 'wpa3-mixed')
1224 uci.unset('wireless', section_id, 'key');
1226 if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp'))
1229 uci.set('wireless', section_id, 'encryption', e);
1232 o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher'));
1233 o.depends('encryption', 'wpa');
1234 o.depends('encryption', 'wpa2');
1235 o.depends('encryption', 'wpa3');
1236 o.depends('encryption', 'wpa3-mixed');
1237 o.depends('encryption', 'psk');
1238 o.depends('encryption', 'psk2');
1239 o.depends('encryption', 'wpa-mixed');
1240 o.depends('encryption', 'psk-mixed');
1241 o.value('auto', _('auto'));
1242 o.value('ccmp', _('Force CCMP (AES)'));
1243 o.value('tkip', _('Force TKIP'));
1244 o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1245 o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write;
1247 o.cfgvalue = function(section_id) {
1248 var v = String(uci.get('wireless', section_id, 'encryption'));
1249 if (v.match(/\+/)) {
1250 v = v.replace(/^[^+]+\+/, '');
1253 else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip')
1260 var crypto_modes = [];
1262 if (hwtype == 'mac80211') {
1263 var has_supplicant = L.hasSystemFeature('wpasupplicant'),
1264 has_hostapd = L.hasSystemFeature('hostapd');
1266 // Probe EAP support
1267 var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'),
1268 has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap');
1270 // Probe SAE support
1271 var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'),
1272 has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae');
1274 // Probe OWE support
1275 var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
1276 has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
1278 // Probe Suite-B support
1279 var has_ap_eap192 = L.hasSystemFeature('hostapd', 'suiteb192'),
1280 has_sta_eap192 = L.hasSystemFeature('wpasupplicant', 'suiteb192');
1282 // Probe WEP support
1283 var has_ap_wep = L.hasSystemFeature('hostapd', 'wep'),
1284 has_sta_wep = L.hasSystemFeature('wpasupplicant', 'wep');
1286 if (has_hostapd || has_supplicant) {
1287 crypto_modes.push(['psk2', 'WPA2-PSK', 35]);
1288 crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1289 crypto_modes.push(['psk', 'WPA-PSK', 12]);
1292 encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1295 if (has_ap_sae || has_sta_sae) {
1296 crypto_modes.push(['sae', 'WPA3-SAE', 31]);
1297 crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1300 if (has_ap_wep || has_sta_wep) {
1301 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1302 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1305 if (has_ap_eap || has_sta_eap) {
1306 if (has_ap_eap192 || has_sta_eap192) {
1307 crypto_modes.push(['wpa3', 'WPA3-EAP', 33]);
1308 crypto_modes.push(['wpa3-mixed', 'WPA2-EAP/WPA3-EAP Mixed Mode', 32]);
1311 crypto_modes.push(['wpa2', 'WPA2-EAP', 34]);
1312 crypto_modes.push(['wpa', 'WPA-EAP', 20]);
1315 if (has_ap_owe || has_sta_owe) {
1316 crypto_modes.push(['owe', 'OWE', 1]);
1319 encr.crypto_support = {
1321 'wep-open': has_ap_wep || _('Requires hostapd with WEP support'),
1322 'wep-shared': has_ap_wep || _('Requires hostapd with WEP support'),
1323 'psk': has_hostapd || _('Requires hostapd'),
1324 'psk2': has_hostapd || _('Requires hostapd'),
1325 'psk-mixed': has_hostapd || _('Requires hostapd'),
1326 'sae': has_ap_sae || _('Requires hostapd with SAE support'),
1327 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
1328 'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
1329 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
1330 'wpa3': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
1331 'wpa3-mixed': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
1332 'owe': has_ap_owe || _('Requires hostapd with OWE support')
1335 'wep-open': has_sta_wep || _('Requires wpa-supplicant with WEP support'),
1336 'wep-shared': has_sta_wep || _('Requires wpa-supplicant with WEP support'),
1337 'psk': has_supplicant || _('Requires wpa-supplicant'),
1338 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1339 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1340 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1341 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1342 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1343 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1344 'wpa3': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
1345 'wpa3-mixed': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
1346 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
1351 'psk': has_supplicant || _('Requires wpa-supplicant'),
1352 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1353 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1356 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support')
1368 encr.crypto_support['ap-wds'] = encr.crypto_support['ap'];
1369 encr.crypto_support['sta-wds'] = encr.crypto_support['sta'];
1371 encr.validate = function(section_id, value) {
1372 var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
1373 modeval = modeopt.formvalue(section_id),
1374 modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)],
1375 enctitle = this.vallist[this.keylist.indexOf(value)];
1377 if (value == 'none')
1380 if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value))
1381 return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle);
1383 return this.crypto_support[modeval][value];
1386 else if (hwtype == 'broadcom') {
1387 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1388 crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1389 crypto_modes.push(['psk', 'WPA-PSK', 12]);
1390 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1391 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1394 crypto_modes.push(['none', _('No Encryption'), 0]);
1396 crypto_modes.sort(function(a, b) { return b[2] - a[2] });
1398 for (var i = 0; i < crypto_modes.length; i++) {
1399 var security_level = (crypto_modes[i][2] >= 30) ? _('strong security')
1400 : (crypto_modes[i][2] >= 20) ? _('medium security')
1401 : (crypto_modes[i][2] >= 10) ? _('weak security') : _('open network');
1403 encr.value(crypto_modes[i][0], '%s (%s)'.format(crypto_modes[i][1], security_level));
1407 o = ss.taboption('encryption', form.Value, 'auth_server', _('RADIUS Authentication Server'));
1408 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1410 o.datatype = 'host(0)';
1412 o = ss.taboption('encryption', form.Value, 'auth_port', _('RADIUS Authentication Port'));
1413 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1415 o.datatype = 'port';
1416 o.placeholder = '1812';
1418 o = ss.taboption('encryption', form.Value, 'auth_secret', _('RADIUS Authentication Secret'));
1419 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1423 o = ss.taboption('encryption', form.Value, 'acct_server', _('RADIUS Accounting Server'));
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, 'acct_port', _('RADIUS Accounting Port'));
1429 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1431 o.datatype = 'port';
1432 o.placeholder = '1813';
1434 o = ss.taboption('encryption', form.Value, 'acct_secret', _('RADIUS Accounting Secret'));
1435 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1439 /* extra RADIUS settings start */
1440 o
= ss
.taboption('encryption', form
.ListValue
, 'dynamic_vlan', _('RADIUS Dynamic VLAN Assignment'), _('Required: Rejects auth if RADIUS server does not provide appropriate VLAN attributes.'));
1441 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1442 o
.value('0', _('Disabled'));
1443 o
.value('1', _('Optional'));
1444 o
.value('2', _('Required'));
1445 o
.write = function (section_id
, value
) {
1446 return this.super('write', [section_id
, (value
== 0) ? null: value
]);
1449 o
= ss
.taboption('encryption', form
.Flag
, 'per_sta_vif', _('RADIUS Per STA VLAN'), _('Each STA is assigned its own AP_VLAN interface.'));
1450 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1452 //hostapd internally defaults to vlan_naming=1 even with dynamic VLAN off
1453 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>.'));
1454 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1456 o
= ss
.taboption('encryption', widgets
.DeviceSelect
, 'vlan_tagged_interface', _('RADIUS VLAN Tagged Interface'), _('E.g. eth0, eth1'));
1457 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1464 o
.noinactive
= true;
1466 o
= ss
.taboption('encryption', form
.Value
, 'vlan_bridge', _('RADIUS VLAN Bridge Naming Scheme'), _('E.g. <code>br-vlan</code> or <code>brvlan</code>.'));
1467 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1469 /* extra RADIUS settings end */
1471 o
= ss
.taboption('encryption', form
.Value
, 'dae_client', _('DAE-Client'), _('Dynamic Authorization Extension client.'));
1472 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1474 o
.datatype
= 'host(0)';
1476 o
= ss
.taboption('encryption', form
.Value
, 'dae_port', _('DAE-Port'), _('Dynamic Authorization Extension port.'));
1477 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1479 o
.datatype
= 'port';
1480 o
.placeholder
= '3799';
1482 o
= ss
.taboption('encryption', form
.Value
, 'dae_secret', _('DAE-Secret'), _('Dynamic Authorization Extension secret.'));
1483 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1487 //WPA(1) has only WPA IE. Only >= WPA2 has RSN IE Preauth frames.
1488 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.'));
1489 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa2', 'wpa3', 'wpa3-mixed'] });
1492 o
= ss
.taboption('encryption', form
.Value
, '_wpa_key', _('Key'));
1493 o
.depends('encryption', 'psk');
1494 o
.depends('encryption', 'psk2');
1495 o
.depends('encryption', 'psk+psk2');
1496 o
.depends('encryption', 'psk-mixed');
1497 o
.depends('encryption', 'sae');
1498 o
.depends('encryption', 'sae-mixed');
1499 o
.datatype
= 'wpakey';
1503 o
.cfgvalue = function(section_id
) {
1504 var key
= uci
.get('wireless', section_id
, 'key');
1505 return /^[1234]$/.test(key
) ? null : key
;
1508 o
.write = function(section_id
, value
) {
1509 uci
.set('wireless', section_id
, 'key', value
);
1510 uci
.unset('wireless', section_id
, 'key1');
1511 uci
.unset('wireless', section_id
, 'key2');
1512 uci
.unset('wireless', section_id
, 'key3');
1513 uci
.unset('wireless', section_id
, 'key4');
1517 o
= ss
.taboption('encryption', form
.ListValue
, '_wep_key', _('Used Key Slot'));
1518 o
.depends('encryption', 'wep-open');
1519 o
.depends('encryption', 'wep-shared');
1520 o
.value('1', _('Key #%d').format(1));
1521 o
.value('2', _('Key #%d').format(2));
1522 o
.value('3', _('Key #%d').format(3));
1523 o
.value('4', _('Key #%d').format(4));
1525 o
.cfgvalue = function(section_id
) {
1526 var slot
= +uci
.get('wireless', section_id
, 'key');
1527 return (slot
>= 1 && slot
<= 4) ? String(slot
) : '';
1530 o
.write = function(section_id
, value
) {
1531 uci
.set('wireless', section_id
, 'key', value
);
1534 for (var slot
= 1; slot
<= 4; slot
++) {
1535 o
= ss
.taboption('encryption', form
.Value
, 'key%d'.format(slot
), _('Key #%d').format(slot
));
1536 o
.depends('encryption', 'wep-open');
1537 o
.depends('encryption', 'wep-shared');
1538 o
.datatype
= 'wepkey';
1542 o
.write = function(section_id
, value
) {
1543 if (value
!= null && (value
.length
== 5 || value
.length
== 13))
1544 value
= 's:%s'.format(value
);
1545 uci
.set('wireless', section_id
, this.option
, value
);
1550 if (hwtype
== 'mac80211') {
1551 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1552 var has_80211r
= L
.hasSystemFeature('hostapd', '11r') || L
.hasSystemFeature('hostapd', 'eap');
1554 o
= ss
.taboption('encryption', form
.Flag
, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1555 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1557 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1560 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.'));
1561 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1562 o
.depends({ ieee80211r
: '1' });
1565 o
= ss
.taboption('encryption', form
.Value
, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1566 o
.depends({ ieee80211r
: '1' });
1567 o
.placeholder
= '4f57';
1568 o
.datatype
= 'and(hexstring,length(4))';
1571 o
= ss
.taboption('encryption', form
.Value
, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1572 o
.depends({ ieee80211r
: '1' });
1573 o
.placeholder
= '1000';
1574 o
.datatype
= 'range(1000,65535)';
1577 o
= ss
.taboption('encryption', form
.ListValue
, 'ft_over_ds', _('FT protocol'));
1578 o
.depends({ ieee80211r
: '1' });
1579 o
.value('0', _('FT over the Air'));
1580 o
.value('1', _('FT over DS'));
1583 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.'));
1584 o
.depends({ ieee80211r
: '1' });
1585 o
.default = o
.enabled
;
1588 o
= ss
.taboption('encryption', form
.Value
, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1589 o
.depends({ ieee80211r
: '1' });
1590 o
.placeholder
= '10000';
1591 o
.datatype
= 'uinteger';
1594 o
= ss
.taboption('encryption', form
.Value
, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1595 o
.depends({ ieee80211r
: '1' });
1596 o
.placeholder
= '00004f577274';
1597 o
.datatype
= 'and(hexstring,length(12))';
1600 o
= ss
.taboption('encryption', form
.Flag
, 'pmk_r1_push', _('PMK R1 Push'));
1601 o
.depends({ ieee80211r
: '1' });
1602 o
.placeholder
= '0';
1605 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.'));
1606 o
.depends({ ieee80211r
: '1' });
1609 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.'));
1610 o
.depends({ ieee80211r
: '1' });
1612 // End of 802.11r options
1614 o
= ss
.taboption('encryption', form
.ListValue
, 'eap_type', _('EAP-Method'));
1615 o
.value('tls', 'TLS');
1616 o
.value('ttls', 'TTLS');
1617 o
.value('peap', 'PEAP');
1618 o
.value('fast', 'FAST');
1619 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1621 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"));
1624 o
.default = o
.disabled
;
1625 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1626 o
.validate = function(section_id
, value
) {
1627 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1628 return _("This option cannot be used because the ca-bundle package is not installed.");
1633 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert', _('Path to CA-Certificate'));
1634 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], ca_cert_usesystem
: ['0'] });
1636 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"));
1637 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1639 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"));
1640 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1642 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)"));
1643 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1645 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)"));
1646 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1648 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert', _('Path to Client-Certificate'));
1649 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1651 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key', _('Path to Private Key'));
1652 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1654 o
= ss
.taboption('encryption', form
.Value
, 'priv_key_pwd', _('Password of Private Key'));
1655 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1658 o
= ss
.taboption('encryption', form
.ListValue
, 'auth', _('Authentication'));
1659 o
.value('PAP', 'PAP');
1660 o
.value('CHAP', 'CHAP');
1661 o
.value('MSCHAP', 'MSCHAP');
1662 o
.value('MSCHAPV2', 'MSCHAPv2');
1663 o
.value('EAP-GTC', 'EAP-GTC');
1664 o
.value('EAP-MD5', 'EAP-MD5');
1665 o
.value('EAP-MSCHAPV2', 'EAP-MSCHAPv2');
1666 o
.value('EAP-TLS', 'EAP-TLS');
1667 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1669 o
.validate = function(section_id
, value
) {
1670 var eo
= this.section
.children
.filter(function(o
) { return o
.option
== 'eap_type' })[0],
1671 ev
= eo
.formvalue(section_id
);
1673 if (ev
!= 'ttls' && (value
== 'PAP' || value
== 'CHAP' || value
== 'MSCHAP' || value
== 'MSCHAPV2'))
1674 return _('This authentication type is not applicable to the selected EAP method.');
1679 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"));
1682 o
.default = o
.disabled
;
1683 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1684 o
.validate = function(section_id
, value
) {
1685 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1686 return _("This option cannot be used because the ca-bundle package is not installed.");
1691 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert2', _('Path to inner CA-Certificate'));
1692 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'], ca_cert2_usesystem
: ['0'] });
1694 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"));
1695 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1697 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"));
1698 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1700 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)"));
1701 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1703 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)"));
1704 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1706 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert2', _('Path to inner Client-Certificate'));
1707 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1709 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key2', _('Path to inner Private Key'));
1710 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1712 o
= ss
.taboption('encryption', form
.Value
, 'priv_key2_pwd', _('Password of inner Private Key'));
1713 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1716 o
= ss
.taboption('encryption', form
.Value
, 'identity', _('Identity'));
1717 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1719 o
= ss
.taboption('encryption', form
.Value
, 'anonymous_identity', _('Anonymous Identity'));
1720 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1722 o
= ss
.taboption('encryption', form
.Value
, 'password', _('Password'));
1723 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1727 if (hwtype
== 'mac80211') {
1728 // ieee802.11w options
1729 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"));
1730 o
.value('0', _('Disabled'));
1731 o
.value('1', _('Optional'));
1732 o
.value('2', _('Required'));
1733 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds', 'sta', 'sta-wds'], encryption
: ['owe', 'psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1736 '2': [{ encryption
: 'sae' }, { encryption
: 'owe' }, { encryption
: 'wpa3' }, { encryption
: 'wpa3-mixed' }],
1737 '1': [{ encryption
: 'sae-mixed'}],
1741 o
.write = function(section_id
, value
) {
1742 if (value
!= this.default)
1743 return form
.ListValue
.prototype.write
.call(this, section_id
, value
);
1745 return form
.ListValue
.prototype.remove
.call(this, section_id
);
1748 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1749 o
.depends('ieee80211w', '1');
1750 o
.depends('ieee80211w', '2');
1751 o
.datatype
= 'uinteger';
1752 o
.placeholder
= '1000';
1755 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1756 o
.depends('ieee80211w', '1');
1757 o
.depends('ieee80211w', '2');
1758 o
.datatype
= 'uinteger';
1759 o
.placeholder
= '201';
1762 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.'));
1763 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1765 if (L
.hasSystemFeature('hostapd', 'wps') && L
.hasSystemFeature('wpasupplicant')) {
1766 o
= ss
.taboption('encryption', form
.Flag
, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1769 o
.default = o
.disabled
;
1770 o
.depends('encryption', 'psk');
1771 o
.depends('encryption', 'psk2');
1772 o
.depends('encryption', 'psk-mixed');
1773 o
.depends('encryption', 'sae');
1774 o
.depends('encryption', 'sae-mixed');
1781 s
.handleRemove = function(section_id
, ev
) {
1782 document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id
)).style
.opacity
= 0.5;
1783 return form
.TypedSection
.prototype.handleRemove
.apply(this, [section_id
, ev
]);
1786 s
.handleScan = function(radioDev
, ev
) {
1787 var table
= E('table', { 'class': 'table' }, [
1788 E('tr', { 'class': 'tr table-titles' }, [
1789 E('th', { 'class': 'th col-2 middle center' }, _('Signal')),
1790 E('th', { 'class': 'th col-4 middle left' }, _('SSID')),
1791 E('th', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1792 E('th', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1793 E('th', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1794 E('th', { 'class': 'th col-3 middle left' }, _('Encryption')),
1795 E('th', { 'class': 'th cbi-section-actions right' }, ' '),
1799 var stop
= E('button', {
1801 'click': L
.bind(this.handleScanStartStop
, this),
1802 'style': 'display:none',
1803 'data-state': 'stop'
1804 }, _('Stop refresh'));
1806 cbi_update_table(table
, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1808 var md
= ui
.showModal(_('Join Network: Wireless Scan'), [
1810 E('div', { 'class': 'right' }, [
1815 'click': L
.bind(this.handleScanAbort
, this)
1820 md
.style
.maxWidth
= '90%';
1821 md
.style
.maxHeight
= 'none';
1823 this.pollFn
= L
.bind(this.handleScanRefresh
, this, radioDev
, {}, table
, stop
);
1825 poll
.add(this.pollFn
);
1829 s
.handleScanRefresh = function(radioDev
, scanCache
, table
, stop
) {
1830 return radioDev
.getScanList().then(L
.bind(function(results
) {
1833 for (var i
= 0; i
< results
.length
; i
++)
1834 scanCache
[results
[i
].bssid
] = results
[i
];
1836 for (var k
in scanCache
)
1837 if (scanCache
[k
].stale
)
1838 results
.push(scanCache
[k
]);
1840 results
.sort(function(a
, b
) {
1841 var diff
= (b
.quality
- a
.quality
) || (a
.channel
- b
.channel
);
1846 if (a
.ssid
< b
.ssid
)
1848 else if (a
.ssid
> b
.ssid
)
1851 if (a
.bssid
< b
.bssid
)
1853 else if (a
.bssid
> b
.bssid
)
1857 for (var i
= 0; i
< results
.length
; i
++) {
1858 var res
= results
[i
],
1859 qv
= res
.quality
|| 0,
1860 qm
= res
.quality_max
|| 0,
1861 q
= (qv
> 0 && qm
> 0) ? Math
.floor((100 / qm
) * qv
) : 0,
1862 s
= res
.stale
? 'opacity:0.5' : '';
1865 E('span', { 'style': s
}, render_signal_badge(q
, res
.signal
, res
.noise
)),
1866 E('span', { 'style': s
}, (res
.ssid
!= null) ? '%h'.format(res
.ssid
) : E('em', _('hidden'))),
1867 E('span', { 'style': s
}, '%d'.format(res
.channel
)),
1868 E('span', { 'style': s
}, '%h'.format(res
.mode
)),
1869 E('span', { 'style': s
}, '%h'.format(res
.bssid
)),
1870 E('span', { 'style': s
}, '%h'.format(network
.formatWifiEncryption(res
.encryption
))),
1871 E('div', { 'class': 'right' }, E('button', {
1872 'class': 'cbi-button cbi-button-action important',
1873 'click': ui
.createHandlerFn(this, 'handleJoin', radioDev
, res
)
1874 }, _('Join Network')))
1880 cbi_update_table(table
, rows
);
1882 stop
.disabled
= false;
1883 stop
.style
.display
= '';
1884 stop
.classList
.remove('spinning');
1888 s
.handleScanStartStop = function(ev
) {
1889 var btn
= ev
.currentTarget
;
1891 if (btn
.getAttribute('data-state') == 'stop') {
1892 poll
.remove(this.pollFn
);
1893 btn
.firstChild
.data
= _('Start refresh');
1894 btn
.setAttribute('data-state', 'start');
1897 poll
.add(this.pollFn
);
1898 btn
.firstChild
.data
= _('Stop refresh');
1899 btn
.setAttribute('data-state', 'stop');
1900 btn
.classList
.add('spinning');
1901 btn
.disabled
= true;
1905 s
.handleScanAbort = function(ev
) {
1906 var md
= dom
.parent(ev
.target
, 'div[aria-modal="true"]');
1908 md
.style
.maxWidth
= '';
1909 md
.style
.maxHeight
= '';
1913 poll
.remove(this.pollFn
);
1918 s
.handleJoinConfirm = function(radioDev
, bss
, form
, ev
) {
1919 var nameopt
= L
.toArray(form
.lookupOption('name', '_new_'))[0],
1920 passopt
= L
.toArray(form
.lookupOption('password', '_new_'))[0],
1921 ssidopt
= L
.toArray(form
.lookupOption('ssid', '_new_'))[0],
1922 bssidopt
= L
.toArray(form
.lookupOption('bssid', '_new_'))[0],
1923 zoneopt
= L
.toArray(form
.lookupOption('zone', '_new_'))[0],
1924 replopt
= L
.toArray(form
.lookupOption('replace', '_new_'))[0],
1925 nameval
= (nameopt
&& nameopt
.isValid('_new_')) ? nameopt
.formvalue('_new_') : null,
1926 passval
= (passopt
&& passopt
.isValid('_new_')) ? passopt
.formvalue('_new_') : null,
1927 ssidval
= (ssidopt
&& ssidopt
.isValid('_new_')) ? ssidopt
.formvalue('_new_') : null,
1928 bssidval
= (bssidopt
&& bssidopt
.isValid('_new_')) ? bssidopt
.formvalue('_new_') : null,
1929 zoneval
= zoneopt
? zoneopt
.formvalue('_new_') : null,
1930 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
1931 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
1932 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' }).length
> 0),
1933 is_sae
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'sae' }).length
> 0);
1935 if (nameval
== null || (passopt
&& passval
== null))
1938 var section_id
= null;
1940 return this.map
.save(function() {
1941 var wifi_sections
= uci
.sections('wireless', 'wifi-iface');
1943 if (replopt
.formvalue('_new_') == '1') {
1944 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1945 if (wifi_sections
[i
].device
== radioDev
.getName())
1946 uci
.remove('wireless', wifi_sections
[i
]['.name']);
1949 if (uci
.get('wireless', radioDev
.getName(), 'disabled') == '1') {
1950 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1951 if (wifi_sections
[i
].device
== radioDev
.getName())
1952 uci
.set('wireless', wifi_sections
[i
]['.name'], 'disabled', '1');
1954 uci
.unset('wireless', radioDev
.getName(), 'disabled');
1957 section_id
= next_free_sid(wifi_sections
.length
);
1959 uci
.add('wireless', 'wifi-iface', section_id
);
1960 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
1961 uci
.set('wireless', section_id
, 'mode', (bss
.mode
== 'Ad-Hoc') ? 'adhoc' : 'sta');
1962 uci
.set('wireless', section_id
, 'network', nameval
);
1964 if (bss
.ssid
!= null) {
1965 uci
.set('wireless', section_id
, 'ssid', bss
.ssid
);
1967 if (bssidval
== '1')
1968 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
1970 else if (bss
.bssid
!= null) {
1971 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
1974 if (ssidval
!= null)
1975 uci
.set('wireless', section_id
, 'ssid', ssidval
);
1978 uci
.set('wireless', section_id
, 'encryption', 'sae');
1979 uci
.set('wireless', section_id
, 'key', passval
);
1982 for (var i
= enc
.wpa
.length
- 1; i
>= 0; i
--) {
1983 if (enc
.wpa
[i
] == 2) {
1984 uci
.set('wireless', section_id
, 'encryption', 'psk2');
1987 else if (enc
.wpa
[i
] == 1) {
1988 uci
.set('wireless', section_id
, 'encryption', 'psk');
1993 uci
.set('wireless', section_id
, 'key', passval
);
1996 uci
.set('wireless', section_id
, 'encryption', 'wep-open');
1997 uci
.set('wireless', section_id
, 'key', '1');
1998 uci
.set('wireless', section_id
, 'key1', passval
);
2001 uci
.set('wireless', section_id
, 'encryption', 'none');
2004 return network
.addNetwork(nameval
, { proto
: 'dhcp' }).then(function(net
) {
2005 firewall
.deleteNetwork(net
.getName());
2007 var zonePromise
= zoneval
2008 ? firewall
.getZone(zoneval
).then(function(zone
) { return zone
|| firewall
.addZone(zoneval
) })
2009 : Promise
.resolve();
2011 return zonePromise
.then(function(zone
) {
2013 zone
.addNetwork(net
.getName());
2016 }).then(L
.bind(function() {
2017 ui
.showModal(null, E('p', { 'class': 'spinning' }, [ _('Loading data…') ]));
2019 return this.renderMoreOptionsModal(section_id
);
2023 s
.handleJoin = function(radioDev
, bss
, ev
) {
2024 poll
.remove(this.pollFn
);
2026 var m2
= new form
.Map('wireless'),
2027 s2
= m2
.section(form
.NamedSection
, '_new_'),
2028 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
2029 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
2030 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' || a
== 'sae' })),
2031 replace
, passphrase
, name
, bssid
, zone
;
2033 var nameUsed = function(name
) {
2034 var s
= uci
.get('network', name
);
2035 if (s
!= null && s
['.type'] != 'interface')
2038 var net
= (s
!= null) ? network
.instantiateNetwork(name
) : null;
2039 return (net
!= null && !net
.isEmpty());
2042 s2
.render = function() {
2043 return Promise
.all([
2045 this.renderUCISection('_new_')
2046 ]).then(this.renderContents
.bind(this));
2049 if (bss
.ssid
== null) {
2050 name
= s2
.option(form
.Value
, 'ssid', _('Network SSID'), _('The correct SSID must be manually specified when joining a hidden wireless network'));
2051 name
.rmempty
= false;
2054 replace
= s2
.option(form
.Flag
, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
2056 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>'));
2057 name
.datatype
= 'uciname';
2058 name
.default = 'wwan';
2059 name
.rmempty
= false;
2060 name
.validate = function(section_id
, value
) {
2061 if (nameUsed(value
))
2062 return _('The network name is already used');
2067 for (var i
= 2; nameUsed(name
.default); i
++)
2068 name
.default = 'wwan%d'.format(i
);
2070 if (is_wep
|| is_psk
) {
2071 passphrase
= s2
.option(form
.Value
, 'password', is_wep
? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
2072 passphrase
.datatype
= is_wep
? 'wepkey' : 'wpakey';
2073 passphrase
.password
= true;
2074 passphrase
.rmempty
= false;
2077 if (bss
.ssid
!= null) {
2078 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
));
2079 bssid
.default = '0';
2082 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.'));
2083 zone
.default = 'wan';
2085 return m2
.render().then(L
.bind(function(nodes
) {
2086 ui
.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss
.ssid
)), [
2088 E('div', { 'class': 'right' }, [
2091 'click': ui
.hideModal
2092 }, _('Cancel')), ' ',
2094 'class': 'cbi-button cbi-button-positive important',
2095 'click': ui
.createHandlerFn(this, 'handleJoinConfirm', radioDev
, bss
, m2
)
2098 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase
|| name
).cbid('_new_'))).focus();
2102 s
.handleAdd = function(radioDev
, ev
) {
2103 var section_id
= next_free_sid(uci
.sections('wireless', 'wifi-iface').length
);
2105 uci
.unset('wireless', radioDev
.getName(), 'disabled');
2107 uci
.add('wireless', 'wifi-iface', section_id
);
2108 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
2109 uci
.set('wireless', section_id
, 'mode', 'ap');
2110 uci
.set('wireless', section_id
, 'ssid', 'OpenWrt');
2111 uci
.set('wireless', section_id
, 'encryption', 'none');
2113 this.addedSection
= section_id
;
2114 return this.renderMoreOptionsModal(section_id
);
2117 o
= s
.option(form
.DummyValue
, '_badge');
2118 o
.modalonly
= false;
2119 o
.textvalue = function(section_id
) {
2120 var inst
= this.section
.lookupRadioOrNetwork(section_id
),
2121 node
= E('div', { 'class': 'center' });
2123 if (inst
.getWifiNetworks
)
2124 node
.appendChild(render_radio_badge(inst
));
2126 node
.appendChild(render_network_badge(inst
));
2131 o
= s
.option(form
.DummyValue
, '_stat');
2132 o
.modalonly
= false;
2133 o
.textvalue = function(section_id
) {
2134 var inst
= this.section
.lookupRadioOrNetwork(section_id
);
2136 if (inst
.getWifiNetworks
)
2137 return render_radio_status(inst
, this.section
.wifis
.filter(function(e
) {
2138 return (e
.getWifiDeviceName() == inst
.getName());
2141 return render_network_status(inst
);
2144 return m
.render().then(L
.bind(function(m
, nodes
) {
2145 poll
.add(L
.bind(function() {
2146 var section_ids
= m
.children
[0].cfgsections(),
2147 tasks
= [ network
.getHostHints(), network
.getWifiDevices() ];
2149 for (var i
= 0; i
< section_ids
.length
; i
++) {
2150 var row
= nodes
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids
[i
])),
2151 dsc
= row
.querySelector('[data-name="_stat"] > div'),
2152 btns
= row
.querySelectorAll('.cbi-section-actions button');
2154 if (dsc
.getAttribute('restart') == '') {
2155 dsc
.setAttribute('restart', '1');
2156 tasks
.push(fs
.exec('/sbin/wifi', ['up', section_ids
[i
]]).catch(function(e
) {
2157 ui
.addNotification(null, E('p', e
.message
));
2160 else if (dsc
.getAttribute('restart') == '1') {
2161 dsc
.removeAttribute('restart');
2162 btns
[0].classList
.remove('spinning');
2163 btns
[0].disabled
= false;
2167 return Promise
.all(tasks
)
2168 .then(L
.bind(function(hosts_radios
) {
2171 for (var i
= 0; i
< hosts_radios
[1].length
; i
++)
2172 tasks
.push(hosts_radios
[1][i
].getWifiNetworks());
2174 return Promise
.all(tasks
).then(function(data
) {
2175 hosts_radios
[2] = [];
2177 for (var i
= 0; i
< data
.length
; i
++)
2178 hosts_radios
[2].push
.apply(hosts_radios
[2], data
[i
]);
2180 return hosts_radios
;
2183 .then(L
.bind(function(hosts_radios_wifis
) {
2186 for (var i
= 0; i
< hosts_radios_wifis
[2].length
; i
++)
2187 tasks
.push(hosts_radios_wifis
[2][i
].getAssocList());
2189 return Promise
.all(tasks
).then(function(data
) {
2190 hosts_radios_wifis
[3] = [];
2192 for (var i
= 0; i
< data
.length
; i
++) {
2193 var wifiNetwork
= hosts_radios_wifis
[2][i
],
2194 radioDev
= hosts_radios_wifis
[1].filter(function(d
) { return d
.getName() == wifiNetwork
.getWifiDeviceName() })[0];
2196 for (var j
= 0; j
< data
[i
].length
; j
++)
2197 hosts_radios_wifis
[3].push(Object
.assign({ radio
: radioDev
, network
: wifiNetwork
}, data
[i
][j
]));
2200 return hosts_radios_wifis
;
2203 .then(L
.bind(this.poll_status
, this, nodes
));
2206 var table
= E('table', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [
2207 E('tr', { 'class': 'tr table-titles' }, [
2208 E('th', { 'class': 'th nowrap' }, _('Network')),
2209 E('th', { 'class': 'th hide-xs' }, _('MAC address')),
2210 E('th', { 'class': 'th' }, _('Host')),
2211 E('th', { 'class': 'th' }, _('Signal / Noise')),
2212 E('th', { 'class': 'th' }, _('RX Rate / TX Rate'))
2216 cbi_update_table(table
, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
2218 return E([ nodes
, E('h3', _('Associated Stations')), table
]);