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
;
205 if (vht
) s
+= ', VHT-MCS\xa0%d'.format(mcs
);
206 if (nss
) s
+= ', VHT-NSS\xa0%d'.format(nss
);
207 if (ht
) s
+= ', MCS\xa0%s'.format(mcs
);
208 if (sgi
) s
+= ', ' + _('Short GI').replace(/ /g
, '\xa0');
214 function radio_restart(id
, ev
) {
215 var row
= document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id
)),
216 dsc
= row
.querySelector('[data-name="_stat"] > div'),
217 btn
= row
.querySelector('.cbi-section-actions button');
220 btn
.classList
.add('spinning');
223 dsc
.setAttribute('restart', '');
224 dom
.content(dsc
, E('em', _('Device is restarting…')));
227 function network_updown(id
, map
, ev
) {
228 var radio
= uci
.get('wireless', id
, 'device'),
229 disabled
= (uci
.get('wireless', id
, 'disabled') == '1') ||
230 (uci
.get('wireless', radio
, 'disabled') == '1');
233 uci
.unset('wireless', id
, 'disabled');
234 uci
.unset('wireless', radio
, 'disabled');
237 uci
.set('wireless', id
, 'disabled', '1');
239 var all_networks_disabled
= true,
240 wifi_ifaces
= uci
.sections('wireless', 'wifi-iface');
242 for (var i
= 0; i
< wifi_ifaces
.length
; i
++) {
243 if (wifi_ifaces
[i
].device
== radio
&& wifi_ifaces
[i
].disabled
!= '1') {
244 all_networks_disabled
= false;
249 if (all_networks_disabled
)
250 uci
.set('wireless', radio
, 'disabled', '1');
253 return map
.save().then(function() {
258 function next_free_sid(offset
) {
259 var sid
= 'wifinet' + offset
;
261 while (uci
.get('wireless', sid
))
262 sid
= 'wifinet' + (++offset
);
267 function add_dependency_permutations(o
, deps
) {
270 for (var key
in deps
) {
271 if (!deps
.hasOwnProperty(key
) || !Array
.isArray(deps
[key
]))
274 var list
= deps
[key
],
277 for (var j
= 0; j
< list
.length
; j
++) {
278 for (var k
= 0; k
< (res
? res
.length
: 1); k
++) {
279 var item
= (res
? Object
.assign({}, res
[k
]) : {});
288 for (var i
= 0; i
< (res
? res
.length
: 0); i
++)
292 var CBIWifiFrequencyValue
= form
.Value
.extend({
293 callFrequencyList
: rpc
.declare({
296 params
: [ 'device' ],
297 expect
: { results
: [] }
300 load: function(section_id
) {
302 network
.getWifiDevice(section_id
),
303 this.callFrequencyList(section_id
)
304 ]).then(L
.bind(function(data
) {
306 '11g': L
.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
307 '11a': L
.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : []
310 for (var i
= 0; i
< data
[1].length
; i
++)
311 this.channels
[(data
[1][i
].mhz
> 2484) ? '11a' : '11g'].push(
313 '%d (%d Mhz)'.format(data
[1][i
].channel
, data
[1][i
].mhz
),
314 !data
[1][i
].restricted
317 var hwmodelist
= L
.toArray(data
[0] ? data
[0].getHWModes() : null)
318 .reduce(function(o
, v
) { o
[v
] = true; return o
}, {});
322 'n', 'N', hwmodelist
.n
,
323 'ac', 'AC', hwmodelist
.ac
326 var htmodelist
= L
.toArray(data
[0] ? data
[0].getHTModes() : null)
327 .reduce(function(o
, v
) { o
[v
] = true; return o
}, {});
330 '': [ '', '-', true ],
332 'HT20', '20 MHz', htmodelist
.HT20
,
333 'HT40', '40 MHz', htmodelist
.HT40
336 'VHT20', '20 MHz', htmodelist
.VHT20
,
337 'VHT40', '40 MHz', htmodelist
.VHT40
,
338 'VHT80', '80 MHz', htmodelist
.VHT80
,
339 'VHT160', '160 MHz', htmodelist
.VHT160
345 '11g', '2.4 GHz', this.channels
['11g'].length
> 3,
346 '11a', '5 GHz', this.channels
['11a'].length
> 3
349 '11g', '2.4 GHz', this.channels
['11g'].length
> 3,
350 '11a', '5 GHz', this.channels
['11a'].length
> 3
359 setValues: function(sel
, vals
) {
361 sel
.vals
.selected
= sel
.selectedIndex
;
363 while (sel
.options
[0])
366 for (var i
= 0; vals
&& i
< vals
.length
; i
+= 3)
368 sel
.add(E('option', { value
: vals
[i
+0] }, [ vals
[i
+1] ]));
370 if (vals
&& !isNaN(vals
.selected
))
371 sel
.selectedIndex
= vals
.selected
;
373 sel
.parentNode
.style
.display
= (sel
.options
.length
<= 1) ? 'none' : '';
377 toggleWifiMode: function(elem
) {
378 this.toggleWifiHTMode(elem
);
379 this.toggleWifiBand(elem
);
382 toggleWifiHTMode: function(elem
) {
383 var mode
= elem
.querySelector('.mode');
384 var bwdt
= elem
.querySelector('.htmode');
386 this.setValues(bwdt
, this.htmodes
[mode
.value
]);
389 toggleWifiBand: function(elem
) {
390 var mode
= elem
.querySelector('.mode');
391 var band
= elem
.querySelector('.band');
393 this.setValues(band
, this.bands
[mode
.value
]);
394 this.toggleWifiChannel(elem
);
396 this.map
.checkDepends();
399 toggleWifiChannel: function(elem
) {
400 var band
= elem
.querySelector('.band');
401 var chan
= elem
.querySelector('.channel');
403 this.setValues(chan
, this.channels
[band
.value
]);
406 setInitialValues: function(section_id
, elem
) {
407 var mode
= elem
.querySelector('.mode'),
408 band
= elem
.querySelector('.band'),
409 chan
= elem
.querySelector('.channel'),
410 bwdt
= elem
.querySelector('.htmode'),
411 htval
= uci
.get('wireless', section_id
, 'htmode'),
412 hwval
= uci
.get('wireless', section_id
, 'hwmode'),
413 chval
= uci
.get('wireless', section_id
, 'channel');
415 this.setValues(mode
, this.modes
);
417 if (/VHT20|VHT40|VHT80|VHT160/.test(htval
))
419 else if (/HT20|HT40/.test(htval
))
424 this.toggleWifiMode(elem
);
431 this.toggleWifiBand(elem
);
439 renderWidget: function(section_id
, option_index
, cfgvalue
) {
443 E('label', { 'style': 'float:left; margin-right:3px' }, [
447 'style': 'width:auto',
448 'change': L
.bind(this.toggleWifiMode
, this, elem
),
449 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
452 E('label', { 'style': 'float:left; margin-right:3px' }, [
456 'style': 'width:auto',
457 'change': L
.bind(this.toggleWifiBand
, this, elem
),
458 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
461 E('label', { 'style': 'float:left; margin-right:3px' }, [
462 _('Channel'), E('br'),
465 'style': 'width:auto',
466 'change': L
.bind(this.map
.checkDepends
, this.map
),
467 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
470 E('label', { 'style': 'float:left; margin-right:3px' }, [
474 'style': 'width:auto',
475 'change': L
.bind(this.map
.checkDepends
, this.map
),
476 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
479 E('br', { 'style': 'clear:left' })
482 return this.setInitialValues(section_id
, elem
);
485 cfgvalue: function(section_id
) {
487 uci
.get('wireless', section_id
, 'htmode'),
488 uci
.get('wireless', section_id
, 'hwmode'),
489 uci
.get('wireless', section_id
, 'channel')
493 formvalue: function(section_id
) {
494 var node
= this.map
.findElement('data-field', this.cbid(section_id
));
497 node
.querySelector('.htmode').value
,
498 node
.querySelector('.band').value
,
499 node
.querySelector('.channel').value
503 write: function(section_id
, value
) {
504 uci
.set('wireless', section_id
, 'htmode', value
[0] || null);
505 uci
.set('wireless', section_id
, 'hwmode', value
[1]);
506 uci
.set('wireless', section_id
, 'channel', value
[2]);
510 var CBIWifiTxPowerValue
= form
.ListValue
.extend({
511 callTxPowerList
: rpc
.declare({
513 method
: 'txpowerlist',
514 params
: [ 'device' ],
515 expect
: { results
: [] }
518 load: function(section_id
) {
519 return this.callTxPowerList(section_id
).then(L
.bind(function(pwrlist
) {
520 this.powerval
= this.wifiNetwork
? this.wifiNetwork
.getTXPower() : null;
521 this.poweroff
= this.wifiNetwork
? this.wifiNetwork
.getTXPowerOffset() : null;
523 this.value('', _('driver default'));
525 for (var i
= 0; i
< pwrlist
.length
; i
++)
526 this.value(pwrlist
[i
].dbm
, '%d dBm (%d mW)'.format(pwrlist
[i
].dbm
, pwrlist
[i
].mw
));
528 return form
.ListValue
.prototype.load
.apply(this, [section_id
]);
532 renderWidget: function(section_id
, option_index
, cfgvalue
) {
533 var widget
= form
.ListValue
.prototype.renderWidget
.apply(this, [section_id
, option_index
, cfgvalue
]);
534 widget
.firstElementChild
.style
.width
= 'auto';
536 dom
.append(widget
, E('span', [
537 ' - ', _('Current power'), ': ',
538 E('span', [ this.powerval
!= null ? '%d dBm'.format(this.powerval
) : E('em', _('unknown')) ]),
539 this.poweroff
? ' + %d dB offset = %s dBm'.format(this.poweroff
, this.powerval
!= null ? this.powerval
+ this.poweroff
: '?') : ''
546 var CBIWifiCountryValue
= form
.Value
.extend({
547 callCountryList
: rpc
.declare({
549 method
: 'countrylist',
550 params
: [ 'device' ],
551 expect
: { results
: [] }
554 load: function(section_id
) {
555 return this.callCountryList(section_id
).then(L
.bind(function(countrylist
) {
556 if (Array
.isArray(countrylist
) && countrylist
.length
> 0) {
557 this.value('', _('driver default'));
559 for (var i
= 0; i
< countrylist
.length
; i
++)
560 this.value(countrylist
[i
].iso3166
, '%s - %s'.format(countrylist
[i
].iso3166
, countrylist
[i
].country
));
563 return form
.Value
.prototype.load
.apply(this, [section_id
]);
567 validate: function(section_id
, formvalue
) {
568 if (formvalue
!= null && formvalue
!= '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue
))
569 return _('Use ISO/IEC 3166 alpha2 country codes.');
574 renderWidget: function(section_id
, option_index
, cfgvalue
) {
575 var typeClass
= (this.keylist
&& this.keylist
.length
) ? form
.ListValue
: form
.Value
;
576 return typeClass
.prototype.renderWidget
.apply(this, [section_id
, option_index
, cfgvalue
]);
581 poll_status: function(map
, data
) {
582 var rows
= map
.querySelectorAll('.cbi-section-table-row[data-sid]');
584 for (var i
= 0; i
< rows
.length
; i
++) {
585 var section_id
= rows
[i
].getAttribute('data-sid'),
586 radioDev
= data
[1].filter(function(d
) { return d
.getName() == section_id
})[0],
587 radioNet
= data
[2].filter(function(n
) { return n
.getName() == section_id
})[0],
588 badge
= rows
[i
].querySelector('[data-name="_badge"] > div'),
589 stat
= rows
[i
].querySelector('[data-name="_stat"]'),
590 btns
= rows
[i
].querySelectorAll('.cbi-section-actions button'),
591 busy
= btns
[0].classList
.contains('spinning') || btns
[1].classList
.contains('spinning') || btns
[2].classList
.contains('spinning');
594 dom
.content(badge
, render_radio_badge(radioDev
));
595 dom
.content(stat
, render_radio_status(radioDev
, data
[2].filter(function(n
) { return n
.getWifiDeviceName() == radioDev
.getName() })));
598 dom
.content(badge
, render_network_badge(radioNet
));
599 dom
.content(stat
, render_network_status(radioNet
));
602 if (stat
.hasAttribute('restart'))
603 dom
.content(stat
, E('em', _('Device is restarting…')));
605 btns
[0].disabled
= isReadonlyView
|| busy
;
606 btns
[1].disabled
= (isReadonlyView
&& radioDev
) || busy
;
607 btns
[2].disabled
= isReadonlyView
|| busy
;
610 var table
= document
.querySelector('#wifi_assoclist_table'),
614 for (var i
= 0; i
< data
[3].length
; i
++) {
615 var bss
= data
[3][i
],
616 name
= hosts
.getHostnameByMACAddr(bss
.mac
),
617 ipv4
= hosts
.getIPAddrByMACAddr(bss
.mac
),
618 ipv6
= hosts
.getIP6AddrByMACAddr(bss
.mac
);
622 if (name
&& ipv4
&& ipv6
)
623 hint
= '%s <span class="hide-xs">(%s, %s)</span>'.format(name
, ipv4
, ipv6
);
624 else if (name
&& (ipv4
|| ipv6
))
625 hint
= '%s <span class="hide-xs">(%s)</span>'.format(name
, ipv4
|| ipv6
);
627 hint
= name
|| ipv4
|| ipv6
|| '?';
631 'class': 'ifacebadge',
632 'data-ifname': bss
.network
.getIfname(),
633 'data-ssid': bss
.network
.getSSID()
636 'src': L
.resource('icons/wifi%s.png').format(bss
.network
.isUp() ? '' : '_disabled'),
637 'title': bss
.radio
.getI18n()
640 ' %s '.format(bss
.network
.getShortName()),
641 E('small', '(%s)'.format(bss
.network
.getIfname()))
646 render_signal_badge(Math
.min((bss
.signal
+ 110) / 70 * 100, 100), bss
.signal
, bss
.noise
),
648 E('span', format_wifirate(bss
.rx
)),
650 E('span', format_wifirate(bss
.tx
))
654 if (bss
.network
.isClientDisconnectSupported()) {
655 if (table
.firstElementChild
.childNodes
.length
< 6)
656 table
.firstElementChild
.appendChild(E('th', { 'class': 'th cbi-section-actions'}));
658 row
.push(E('button', {
659 'class': 'cbi-button cbi-button-remove',
660 'click': L
.bind(function(net
, mac
, ev
) {
661 dom
.parent(ev
.currentTarget
, '.tr').style
.opacity
= 0.5;
662 ev
.currentTarget
.classList
.add('spinning');
663 ev
.currentTarget
.disabled
= true;
664 ev
.currentTarget
.blur();
666 net
.disconnectClient(mac
, true, 5, 60000);
667 }, this, bss
.network
, bss
.mac
),
668 'disabled': isReadonlyView
|| null
669 }, [ _('Disconnect') ]));
678 cbi_update_table(table
, trows
, E('em', _('No information available')));
680 var stat
= document
.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large');
683 render_modal_status(stat
, data
[2].filter(function(n
) { return n
.getName() == stat
.getAttribute('data-network') })[0]);
685 return network
.flushCache();
695 checkAnonymousSections: function() {
696 var wifiIfaces
= uci
.sections('wireless', 'wifi-iface');
698 for (var i
= 0; i
< wifiIfaces
.length
; i
++)
699 if (wifiIfaces
[i
]['.anonymous'])
705 callUciRename
: rpc
.declare({
708 params
: [ 'config', 'section', 'name' ]
712 if (this.checkAnonymousSections())
713 return this.renderMigration();
715 return this.renderOverview();
718 handleMigration: function(ev
) {
719 var wifiIfaces
= uci
.sections('wireless', 'wifi-iface'),
723 for (var i
= 0; i
< wifiIfaces
.length
; i
++) {
724 if (!wifiIfaces
[i
]['.anonymous'])
727 var new_name
= next_free_sid(id_offset
);
729 tasks
.push(this.callUciRename('wireless', wifiIfaces
[i
]['.name'], new_name
));
730 id_offset
= +new_name
.substring(7) + 1;
733 return Promise
.all(tasks
)
734 .then(L
.bind(ui
.changes
.init
, ui
.changes
))
735 .then(L
.bind(ui
.changes
.apply
, ui
.changes
));
738 renderMigration: function() {
739 ui
.showModal(_('Wireless configuration migration'), [
740 E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')),
741 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.')),
742 E('div', { 'class': 'right' },
744 'class': 'btn cbi-button-action important',
745 'click': ui
.createHandlerFn(this, 'handleMigration')
750 renderOverview: function() {
753 m
= new form
.Map('wireless');
757 s
= m
.section(form
.GridSection
, 'wifi-device', _('Wireless Overview'));
761 s
.load = function() {
762 return network
.getWifiDevices().then(L
.bind(function(radios
) {
763 this.radios
= radios
.sort(function(a
, b
) {
764 return a
.getName() > b
.getName();
769 for (var i
= 0; i
< radios
.length
; i
++)
770 tasks
.push(radios
[i
].getWifiNetworks());
772 return Promise
.all(tasks
);
773 }, this)).then(L
.bind(function(data
) {
776 for (var i
= 0; i
< data
.length
; i
++)
777 this.wifis
.push
.apply(this.wifis
, data
[i
]);
781 s
.cfgsections = function() {
784 for (var i
= 0; i
< this.radios
.length
; i
++) {
785 rv
.push(this.radios
[i
].getName());
787 for (var j
= 0; j
< this.wifis
.length
; j
++)
788 if (this.wifis
[j
].getWifiDeviceName() == this.radios
[i
].getName())
789 rv
.push(this.wifis
[j
].getName());
795 s
.modaltitle = function(section_id
) {
796 var radioNet
= this.wifis
.filter(function(w
) { return w
.getName() == section_id
})[0];
797 return radioNet
? radioNet
.getI18n() : _('Edit wireless network');
800 s
.lookupRadioOrNetwork = function(section_id
) {
801 var radioDev
= this.radios
.filter(function(r
) { return r
.getName() == section_id
})[0];
805 var radioNet
= this.wifis
.filter(function(w
) { return w
.getName() == section_id
})[0];
812 s
.renderRowActions = function(section_id
) {
813 var inst
= this.lookupRadioOrNetwork(section_id
), btns
;
815 if (inst
.getWifiNetworks
) {
818 'class': 'cbi-button cbi-button-neutral',
819 'title': _('Restart radio interface'),
820 'click': ui
.createHandlerFn(this, radio_restart
, section_id
)
823 'class': 'cbi-button cbi-button-action important',
824 'title': _('Find and join network'),
825 'click': ui
.createHandlerFn(this, 'handleScan', inst
)
828 'class': 'cbi-button cbi-button-add',
829 'title': _('Provide new network'),
830 'click': ui
.createHandlerFn(this, 'handleAdd', inst
)
835 var isDisabled
= (inst
.get('disabled') == '1' ||
836 uci
.get('wireless', inst
.getWifiDeviceName(), 'disabled') == '1');
840 'class': 'cbi-button cbi-button-neutral enable-disable',
841 'title': isDisabled
? _('Enable this network') : _('Disable this network'),
842 'click': ui
.createHandlerFn(this, network_updown
, section_id
, this.map
)
843 }, isDisabled
? _('Enable') : _('Disable')),
845 'class': 'cbi-button cbi-button-action important',
846 'title': _('Edit this network'),
847 'click': ui
.createHandlerFn(this, 'renderMoreOptionsModal', section_id
)
850 'class': 'cbi-button cbi-button-negative remove',
851 'title': _('Delete this network'),
852 'click': ui
.createHandlerFn(this, 'handleRemove', section_id
)
857 return E('td', { 'class': 'td middle cbi-section-actions' }, E('div', btns
));
860 s
.addModalOptions = function(s
) {
861 return network
.getWifiNetwork(s
.section
).then(function(radioNet
) {
862 var hwtype
= uci
.get('wireless', radioNet
.getWifiDeviceName(), 'type');
865 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
869 ss
.tab('general', _('General Setup'));
870 ss
.tab('advanced', _('Advanced Settings'));
872 var isDisabled
= (radioNet
.get('disabled') == '1' ||
873 uci
.get('wireless', radioNet
.getWifiDeviceName(), 'disabled') == 1);
875 o
= ss
.taboption('general', form
.DummyValue
, '_wifistat_modal', _('Status'));
876 o
.cfgvalue
= L
.bind(function(radioNet
) {
877 return render_modal_status(null, radioNet
);
879 o
.write = function() {};
881 o
= ss
.taboption('general', form
.Button
, '_toggle', isDisabled
? _('Wireless network is disabled') : _('Wireless network is enabled'));
882 o
.inputstyle
= isDisabled
? 'apply' : 'reset';
883 o
.inputtitle
= isDisabled
? _('Enable') : _('Disable');
884 o
.onclick
= ui
.createHandlerFn(s
, network_updown
, s
.section
, s
.map
);
886 o
= ss
.taboption('general', CBIWifiFrequencyValue
, '_freq', '<br />' + _('Operating frequency'));
887 o
.ucisection
= s
.section
;
889 if (hwtype
== 'mac80211') {
890 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.'));
891 o
.depends({'_freq': '11g', '!contains': true});
893 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.'));
894 o
.wifiNetwork
= radioNet
;
896 o
= ss
.taboption('advanced', CBIWifiCountryValue
, 'country', _('Country Code'));
897 o
.wifiNetwork
= radioNet
;
899 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.'));
900 o
.value('0', _('Disabled'));
901 o
.value('1', _('Normal'));
902 o
.value('2', _('High'));
903 o
.value('3', _('Very High'));
905 o
= ss
.taboption('advanced', form
.Value
, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
906 o
.datatype
= 'or(range(0,114750),"auto")';
907 o
.placeholder
= 'auto';
909 o
= ss
.taboption('advanced', form
.Value
, 'frag', _('Fragmentation Threshold'));
910 o
.datatype
= 'min(256)';
911 o
.placeholder
= _('off');
913 o
= ss
.taboption('advanced', form
.Value
, 'rts', _('RTS/CTS Threshold'));
914 o
.datatype
= 'uinteger';
915 o
.placeholder
= _('off');
917 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!'));
920 o
= ss
.taboption('advanced', form
.Value
, 'beacon_int', _('Beacon Interval'));
921 o
.datatype
= 'range(15,65535)';
927 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getName(), 'wifi-iface', _('Interface Configuration'));
931 ss
.tab('general', _('General Setup'));
932 ss
.tab('encryption', _('Wireless Security'));
933 ss
.tab('macfilter', _('MAC-Filter'));
934 ss
.tab('advanced', _('Advanced Settings'));
936 o
= ss
.taboption('general', form
.ListValue
, 'mode', _('Mode'));
937 o
.value('ap', _('Access Point'));
938 o
.value('sta', _('Client'));
939 o
.value('adhoc', _('Ad-Hoc'));
941 o
= ss
.taboption('general', form
.Value
, 'mesh_id', _('Mesh Id'));
942 o
.depends('mode', 'mesh');
944 o
= ss
.taboption('advanced', form
.Flag
, 'mesh_fwding', _('Forward mesh peer traffic'));
947 o
.depends('mode', 'mesh');
949 o
= ss
.taboption('advanced', form
.Value
, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
952 o
.datatype
= 'range(-255,1)';
953 o
.depends('mode', 'mesh');
955 o
= ss
.taboption('general', form
.Value
, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
956 o
.datatype
= 'maxlength(32)';
957 o
.depends('mode', 'ap');
958 o
.depends('mode', 'sta');
959 o
.depends('mode', 'adhoc');
960 o
.depends('mode', 'ahdemo');
961 o
.depends('mode', 'monitor');
962 o
.depends('mode', 'ap-wds');
963 o
.depends('mode', 'sta-wds');
964 o
.depends('mode', 'wds');
966 o
= ss
.taboption('general', form
.Value
, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
967 o
.datatype
= 'macaddr';
969 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.'));
973 o
.write = function(section_id
, value
) {
974 return network
.getDevice(section_id
).then(L
.bind(function(dev
) {
975 var old_networks
= dev
.getNetworks().reduce(function(o
, v
) { o
[v
.getName()] = v
; return o
}, {}),
977 values
= L
.toArray(value
),
980 for (var i
= 0; i
< values
.length
; i
++) {
981 new_networks
[values
[i
]] = true;
983 if (old_networks
[values
[i
]])
986 tasks
.push(network
.getNetwork(values
[i
]).then(L
.bind(function(name
, net
) {
987 return net
|| network
.addNetwork(name
, { proto
: 'none' });
988 }, this, values
[i
])).then(L
.bind(function(dev
, net
) {
990 if (!net
.isEmpty()) {
991 var target_dev
= net
.getDevice();
993 /* Resolve parent interface of vlan */
994 while (target_dev
&& target_dev
.getType() == 'vlan')
995 target_dev
= target_dev
.getParent();
997 if (!target_dev
|| target_dev
.getType() != 'bridge')
998 net
.set('type', 'bridge');
1006 for (var name
in old_networks
)
1007 if (!new_networks
[name
])
1008 tasks
.push(network
.getNetwork(name
).then(L
.bind(function(dev
, net
) {
1010 net
.deleteDevice(dev
);
1013 return Promise
.all(tasks
);
1017 if (hwtype
== 'mac80211') {
1018 var mode
= ss
.children
[0],
1019 bssid
= ss
.children
[5],
1022 mode
.value('mesh', '802.11s');
1023 mode
.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
1024 mode
.value('monitor', _('Monitor'));
1026 bssid
.depends('mode', 'adhoc');
1027 bssid
.depends('mode', 'sta');
1028 bssid
.depends('mode', 'sta-wds');
1030 o
= ss
.taboption('macfilter', form
.ListValue
, 'macfilter', _('MAC-Address Filter'));
1031 o
.depends('mode', 'ap');
1032 o
.depends('mode', 'ap-wds');
1033 o
.value('', _('disable'));
1034 o
.value('allow', _('Allow listed only'));
1035 o
.value('deny', _('Allow all except listed'));
1037 o
= ss
.taboption('macfilter', form
.DynamicList
, 'maclist', _('MAC-List'));
1038 o
.datatype
= 'macaddr';
1039 o
.depends('macfilter', 'allow');
1040 o
.depends('macfilter', 'deny');
1041 o
.load = function(section_id
) {
1042 return network
.getHostHints().then(L
.bind(function(hints
) {
1043 hints
.getMACHints().map(L
.bind(function(hint
) {
1044 this.value(hint
[0], hint
[1] ? '%s (%s)'.format(hint
[0], hint
[1]) : hint
[0]);
1047 return form
.DynamicList
.prototype.load
.apply(this, [section_id
]);
1051 mode
.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
1052 mode
.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
1054 mode
.write = function(section_id
, value
) {
1057 uci
.set('wireless', section_id
, 'mode', 'ap');
1058 uci
.set('wireless', section_id
, 'wds', '1');
1062 uci
.set('wireless', section_id
, 'mode', 'sta');
1063 uci
.set('wireless', section_id
, 'wds', '1');
1067 uci
.set('wireless', section_id
, 'mode', value
);
1068 uci
.unset('wireless', section_id
, 'wds');
1073 mode
.cfgvalue = function(section_id
) {
1074 var mode
= uci
.get('wireless', section_id
, 'mode'),
1075 wds
= uci
.get('wireless', section_id
, 'wds');
1077 if (mode
== 'ap' && wds
)
1079 else if (mode
== 'sta' && wds
)
1085 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.'));
1086 o
.depends('mode', 'ap');
1087 o
.depends('mode', 'ap-wds');
1089 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.'));
1090 o
.depends('mode', 'ap');
1091 o
.depends('mode', 'ap-wds');
1092 o
.default = o
.enabled
;
1094 o
= ss
.taboption('advanced', form
.Flag
, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
1095 o
.depends('mode', 'ap');
1096 o
.depends('mode', 'ap-wds');
1098 o
= ss
.taboption('advanced', form
.Value
, 'ifname', _('Interface name'), _('Override default interface name'));
1100 o
.placeholder
= radioNet
.getIfname();
1101 if (/^radio\d+\.network/.test(o
.placeholder
))
1104 o
= ss
.taboption('advanced', form
.Flag
, 'short_preamble', _('Short Preamble'));
1105 o
.default = o
.enabled
;
1107 o
= ss
.taboption('advanced', form
.Value
, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
1110 o
.datatype
= 'range(1,255)';
1112 o
= ss
.taboption('advanced', form
.Value
, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
1114 o
.placeholder
= 600;
1115 o
.datatype
= 'uinteger';
1117 o
= ss
.taboption('advanced', form
.Flag
, 'skip_inactivity_poll', _('Disable Inactivity Polling'));
1119 o
.datatype
= 'uinteger';
1121 o
= ss
.taboption('advanced', form
.Value
, 'max_inactivity', _('Station inactivity limit'), _('sec'));
1123 o
.placeholder
= 300;
1124 o
.datatype
= 'uinteger';
1126 o
= ss
.taboption('advanced', form
.Value
, 'max_listen_interval', _('Maximum allowed Listen Interval'));
1128 o
.placeholder
= 65535;
1129 o
.datatype
= 'uinteger';
1131 o
= ss
.taboption('advanced', form
.Flag
, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
1132 o
.default = o
.enabled
;
1136 encr
= o
= ss
.taboption('encryption', form
.ListValue
, 'encryption', _('Encryption'));
1137 o
.depends('mode', 'ap');
1138 o
.depends('mode', 'sta');
1139 o
.depends('mode', 'adhoc');
1140 o
.depends('mode', 'ahdemo');
1141 o
.depends('mode', 'ap-wds');
1142 o
.depends('mode', 'sta-wds');
1143 o
.depends('mode', 'mesh');
1145 o
.cfgvalue = function(section_id
) {
1146 var v
= String(uci
.get('wireless', section_id
, 'encryption'));
1149 else if (v
.match(/\+/))
1150 return v
.replace(/\+.+$/, '');
1154 o
.write = function(section_id
, value
) {
1155 var e
= this.section
.children
.filter(function(o
) { return o
.option
== 'encryption' })[0].formvalue(section_id
),
1156 co
= this.section
.children
.filter(function(o
) { return o
.option
== 'cipher' })[0], c
= co
.formvalue(section_id
);
1158 if (value
== 'wpa' || value
== 'wpa2' || value
== 'wpa3' || value
== 'wpa3-mixed')
1159 uci
.unset('wireless', section_id
, 'key');
1161 if (co
.isActive(section_id
) && e
&& (c
== 'tkip' || c
== 'ccmp' || c
== 'tkip+ccmp'))
1164 uci
.set('wireless', section_id
, 'encryption', e
);
1167 o
= ss
.taboption('encryption', form
.ListValue
, 'cipher', _('Cipher'));
1168 o
.depends('encryption', 'wpa');
1169 o
.depends('encryption', 'wpa2');
1170 o
.depends('encryption', 'wpa3');
1171 o
.depends('encryption', 'wpa3-mixed');
1172 o
.depends('encryption', 'psk');
1173 o
.depends('encryption', 'psk2');
1174 o
.depends('encryption', 'wpa-mixed');
1175 o
.depends('encryption', 'psk-mixed');
1176 o
.value('auto', _('auto'));
1177 o
.value('ccmp', _('Force CCMP (AES)'));
1178 o
.value('tkip', _('Force TKIP'));
1179 o
.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1180 o
.write
= ss
.children
.filter(function(o
) { return o
.option
== 'encryption' })[0].write
;
1182 o
.cfgvalue = function(section_id
) {
1183 var v
= String(uci
.get('wireless', section_id
, 'encryption'));
1184 if (v
.match(/\+/)) {
1185 v
= v
.replace(/^[^+]+\+/, '');
1188 else if (v
== 'tkip+aes' || v
== 'aes+tkip' || v
== 'ccmp+tkip')
1195 var crypto_modes
= [];
1197 if (hwtype
== 'mac80211') {
1198 var has_supplicant
= L
.hasSystemFeature('wpasupplicant'),
1199 has_hostapd
= L
.hasSystemFeature('hostapd');
1201 // Probe EAP support
1202 var has_ap_eap
= L
.hasSystemFeature('hostapd', 'eap'),
1203 has_sta_eap
= L
.hasSystemFeature('wpasupplicant', 'eap');
1205 // Probe SAE support
1206 var has_ap_sae
= L
.hasSystemFeature('hostapd', 'sae'),
1207 has_sta_sae
= L
.hasSystemFeature('wpasupplicant', 'sae');
1209 // Probe OWE support
1210 var has_ap_owe
= L
.hasSystemFeature('hostapd', 'owe'),
1211 has_sta_owe
= L
.hasSystemFeature('wpasupplicant', 'owe');
1213 // Probe Suite-B support
1214 var has_ap_eap192
= L
.hasSystemFeature('hostapd', 'suiteb192'),
1215 has_sta_eap192
= L
.hasSystemFeature('wpasupplicant', 'suiteb192');
1217 // Probe WEP support
1218 var has_ap_wep
= L
.hasSystemFeature('hostapd', 'wep'),
1219 has_sta_wep
= L
.hasSystemFeature('wpasupplicant', 'wep');
1221 if (has_hostapd
|| has_supplicant
) {
1222 crypto_modes
.push(['psk2', 'WPA2-PSK', 35]);
1223 crypto_modes
.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1224 crypto_modes
.push(['psk', 'WPA-PSK', 21]);
1227 encr
.description
= _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1230 if (has_ap_sae
|| has_sta_sae
) {
1231 crypto_modes
.push(['sae', 'WPA3-SAE', 31]);
1232 crypto_modes
.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1235 if (has_ap_wep
|| has_sta_wep
) {
1236 crypto_modes
.push(['wep-open', _('WEP Open System'), 11]);
1237 crypto_modes
.push(['wep-shared', _('WEP Shared Key'), 10]);
1240 if (has_ap_eap
|| has_sta_eap
) {
1241 if (has_ap_eap192
|| has_sta_eap192
) {
1242 crypto_modes
.push(['wpa3', 'WPA3-EAP', 33]);
1243 crypto_modes
.push(['wpa3-mixed', 'WPA2-EAP/WPA3-EAP Mixed Mode', 32]);
1246 crypto_modes
.push(['wpa2', 'WPA2-EAP', 34]);
1247 crypto_modes
.push(['wpa', 'WPA-EAP', 20]);
1250 if (has_ap_owe
|| has_sta_owe
) {
1251 crypto_modes
.push(['owe', 'OWE', 1]);
1254 encr
.crypto_support
= {
1256 'wep-open': has_ap_wep
|| _('Requires hostapd with WEP support'),
1257 'wep-shared': has_ap_wep
|| _('Requires hostapd with WEP support'),
1258 'psk': has_hostapd
|| _('Requires hostapd'),
1259 'psk2': has_hostapd
|| _('Requires hostapd'),
1260 'psk-mixed': has_hostapd
|| _('Requires hostapd'),
1261 'sae': has_ap_sae
|| _('Requires hostapd with SAE support'),
1262 'sae-mixed': has_ap_sae
|| _('Requires hostapd with SAE support'),
1263 'wpa': has_ap_eap
|| _('Requires hostapd with EAP support'),
1264 'wpa2': has_ap_eap
|| _('Requires hostapd with EAP support'),
1265 'wpa3': has_ap_eap192
|| _('Requires hostapd with EAP Suite-B support'),
1266 'wpa3-mixed': has_ap_eap192
|| _('Requires hostapd with EAP Suite-B support'),
1267 'owe': has_ap_owe
|| _('Requires hostapd with OWE support')
1270 'wep-open': has_sta_wep
|| _('Requires wpa-supplicant with WEP support'),
1271 'wep-shared': has_sta_wep
|| _('Requires wpa-supplicant with WEP support'),
1272 'psk': has_supplicant
|| _('Requires wpa-supplicant'),
1273 'psk2': has_supplicant
|| _('Requires wpa-supplicant'),
1274 'psk-mixed': has_supplicant
|| _('Requires wpa-supplicant'),
1275 'sae': has_sta_sae
|| _('Requires wpa-supplicant with SAE support'),
1276 'sae-mixed': has_sta_sae
|| _('Requires wpa-supplicant with SAE support'),
1277 'wpa': has_sta_eap
|| _('Requires wpa-supplicant with EAP support'),
1278 'wpa2': has_sta_eap
|| _('Requires wpa-supplicant with EAP support'),
1279 'wpa3': has_sta_eap192
|| _('Requires wpa-supplicant with EAP Suite-B support'),
1280 'wpa3-mixed': has_sta_eap192
|| _('Requires wpa-supplicant with EAP Suite-B support'),
1281 'owe': has_sta_owe
|| _('Requires wpa-supplicant with OWE support')
1286 'psk': has_supplicant
|| _('Requires wpa-supplicant'),
1287 'psk2': has_supplicant
|| _('Requires wpa-supplicant'),
1288 'psk-mixed': has_supplicant
|| _('Requires wpa-supplicant'),
1291 'sae': has_sta_sae
|| _('Requires wpa-supplicant with SAE support')
1303 encr
.crypto_support
['ap-wds'] = encr
.crypto_support
['ap'];
1304 encr
.crypto_support
['sta-wds'] = encr
.crypto_support
['sta'];
1306 encr
.validate = function(section_id
, value
) {
1307 var modeopt
= this.section
.children
.filter(function(o
) { return o
.option
== 'mode' })[0],
1308 modeval
= modeopt
.formvalue(section_id
),
1309 modetitle
= modeopt
.vallist
[modeopt
.keylist
.indexOf(modeval
)],
1310 enctitle
= this.vallist
[this.keylist
.indexOf(value
)];
1312 if (value
== 'none')
1315 if (!L
.isObject(this.crypto_support
[modeval
]) || !this.crypto_support
[modeval
].hasOwnProperty(value
))
1316 return _('The selected %s mode is incompatible with %s encryption').format(modetitle
, enctitle
);
1318 return this.crypto_support
[modeval
][value
];
1321 else if (hwtype
== 'broadcom') {
1322 crypto_modes
.push(['psk2', 'WPA2-PSK', 33]);
1323 crypto_modes
.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1324 crypto_modes
.push(['psk', 'WPA-PSK', 21]);
1325 crypto_modes
.push(['wep-open', _('WEP Open System'), 11]);
1326 crypto_modes
.push(['wep-shared', _('WEP Shared Key'), 10]);
1329 crypto_modes
.push(['none', _('No Encryption'), 0]);
1331 crypto_modes
.sort(function(a
, b
) { return b
[2] - a
[2] });
1333 for (var i
= 0; i
< crypto_modes
.length
; i
++) {
1334 var security_level
= (crypto_modes
[i
][2] >= 30) ? _('strong security')
1335 : (crypto_modes
[i
][2] >= 20) ? _('medium security')
1336 : (crypto_modes
[i
][2] >= 10) ? _('weak security') : _('open network');
1338 encr
.value(crypto_modes
[i
][0], '%s (%s)'.format(crypto_modes
[i
][1], security_level
));
1342 o
= ss
.taboption('encryption', form
.Value
, 'auth_server', _('Radius-Authentication-Server'));
1343 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1345 o
.datatype
= 'host(0)';
1347 o
= ss
.taboption('encryption', form
.Value
, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
1348 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1350 o
.datatype
= 'port';
1352 o
= ss
.taboption('encryption', form
.Value
, 'auth_secret', _('Radius-Authentication-Secret'));
1353 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1357 o
= ss
.taboption('encryption', form
.Value
, 'acct_server', _('Radius-Accounting-Server'));
1358 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1360 o
.datatype
= 'host(0)';
1362 o
= ss
.taboption('encryption', form
.Value
, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
1363 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1365 o
.datatype
= 'port';
1367 o
= ss
.taboption('encryption', form
.Value
, 'acct_secret', _('Radius-Accounting-Secret'));
1368 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1372 o
= ss
.taboption('encryption', form
.Value
, 'dae_client', _('DAE-Client'));
1373 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1375 o
.datatype
= 'host(0)';
1377 o
= ss
.taboption('encryption', form
.Value
, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
1378 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1380 o
.datatype
= 'port';
1382 o
= ss
.taboption('encryption', form
.Value
, 'dae_secret', _('DAE-Secret'));
1383 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1388 o
= ss
.taboption('encryption', form
.Value
, '_wpa_key', _('Key'));
1389 o
.depends('encryption', 'psk');
1390 o
.depends('encryption', 'psk2');
1391 o
.depends('encryption', 'psk+psk2');
1392 o
.depends('encryption', 'psk-mixed');
1393 o
.depends('encryption', 'sae');
1394 o
.depends('encryption', 'sae-mixed');
1395 o
.datatype
= 'wpakey';
1399 o
.cfgvalue = function(section_id
) {
1400 var key
= uci
.get('wireless', section_id
, 'key');
1401 return /^[1234]$/.test(key
) ? null : key
;
1404 o
.write = function(section_id
, value
) {
1405 uci
.set('wireless', section_id
, 'key', value
);
1406 uci
.unset('wireless', section_id
, 'key1');
1407 uci
.unset('wireless', section_id
, 'key2');
1408 uci
.unset('wireless', section_id
, 'key3');
1409 uci
.unset('wireless', section_id
, 'key4');
1413 o
= ss
.taboption('encryption', form
.ListValue
, '_wep_key', _('Used Key Slot'));
1414 o
.depends('encryption', 'wep-open');
1415 o
.depends('encryption', 'wep-shared');
1416 o
.value('1', _('Key #%d').format(1));
1417 o
.value('2', _('Key #%d').format(2));
1418 o
.value('3', _('Key #%d').format(3));
1419 o
.value('4', _('Key #%d').format(4));
1421 o
.cfgvalue = function(section_id
) {
1422 var slot
= +uci
.get('wireless', section_id
, 'key');
1423 return (slot
>= 1 && slot
<= 4) ? String(slot
) : '';
1426 o
.write = function(section_id
, value
) {
1427 uci
.set('wireless', section_id
, 'key', value
);
1430 for (var slot
= 1; slot
<= 4; slot
++) {
1431 o
= ss
.taboption('encryption', form
.Value
, 'key%d'.format(slot
), _('Key #%d').format(slot
));
1432 o
.depends('encryption', 'wep-open');
1433 o
.depends('encryption', 'wep-shared');
1434 o
.datatype
= 'wepkey';
1438 o
.write = function(section_id
, value
) {
1439 if (value
!= null && (value
.length
== 5 || value
.length
== 13))
1440 value
= 's:%s'.format(value
);
1441 uci
.set('wireless', section_id
, this.option
, value
);
1446 if (hwtype
== 'mac80211') {
1447 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1448 var has_80211r
= L
.hasSystemFeature('hostapd', '11r') || L
.hasSystemFeature('hostapd', 'eap');
1450 o
= ss
.taboption('encryption', form
.Flag
, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1451 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1453 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1456 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.'));
1457 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1458 o
.depends({ ieee80211r
: '1' });
1461 o
= ss
.taboption('encryption', form
.Value
, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1462 o
.depends({ ieee80211r
: '1' });
1463 o
.placeholder
= '4f57';
1464 o
.datatype
= 'and(hexstring,length(4))';
1467 o
= ss
.taboption('encryption', form
.Value
, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1468 o
.depends({ ieee80211r
: '1' });
1469 o
.placeholder
= '1000';
1470 o
.datatype
= 'range(1000,65535)';
1473 o
= ss
.taboption('encryption', form
.ListValue
, 'ft_over_ds', _('FT protocol'));
1474 o
.depends({ ieee80211r
: '1' });
1475 o
.value('1', _('FT over DS'));
1476 o
.value('0', _('FT over the Air'));
1479 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.'));
1480 o
.depends({ ieee80211r
: '1' });
1481 o
.default = o
.enabled
;
1484 o
= ss
.taboption('encryption', form
.Value
, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1485 o
.depends({ ieee80211r
: '1' });
1486 o
.placeholder
= '10000';
1487 o
.datatype
= 'uinteger';
1490 o
= ss
.taboption('encryption', form
.Value
, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1491 o
.depends({ ieee80211r
: '1' });
1492 o
.placeholder
= '00004f577274';
1493 o
.datatype
= 'and(hexstring,length(12))';
1496 o
= ss
.taboption('encryption', form
.Flag
, 'pmk_r1_push', _('PMK R1 Push'));
1497 o
.depends({ ieee80211r
: '1' });
1498 o
.placeholder
= '0';
1501 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.'));
1502 o
.depends({ ieee80211r
: '1' });
1505 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.'));
1506 o
.depends({ ieee80211r
: '1' });
1508 // End of 802.11r options
1510 o
= ss
.taboption('encryption', form
.ListValue
, 'eap_type', _('EAP-Method'));
1511 o
.value('tls', 'TLS');
1512 o
.value('ttls', 'TTLS');
1513 o
.value('peap', 'PEAP');
1514 o
.value('fast', 'FAST');
1515 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1517 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"));
1520 o
.default = o
.disabled
;
1521 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1522 o
.validate = function(section_id
, value
) {
1523 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1524 return _("This option cannot be used because the ca-bundle package is not installed.");
1529 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert', _('Path to CA-Certificate'));
1530 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], ca_cert_usesystem
: ['0'] });
1532 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"));
1533 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1535 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"));
1536 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1538 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)"));
1539 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1541 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)"));
1542 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1544 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert', _('Path to Client-Certificate'));
1545 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1547 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key', _('Path to Private Key'));
1548 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1550 o
= ss
.taboption('encryption', form
.Value
, 'priv_key_pwd', _('Password of Private Key'));
1551 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1554 o
= ss
.taboption('encryption', form
.ListValue
, 'auth', _('Authentication'));
1555 o
.value('PAP', 'PAP');
1556 o
.value('CHAP', 'CHAP');
1557 o
.value('MSCHAP', 'MSCHAP');
1558 o
.value('MSCHAPV2', 'MSCHAPv2');
1559 o
.value('EAP-GTC', 'EAP-GTC');
1560 o
.value('EAP-MD5', 'EAP-MD5');
1561 o
.value('EAP-MSCHAPV2', 'EAP-MSCHAPv2');
1562 o
.value('EAP-TLS', 'EAP-TLS');
1563 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1565 o
.validate = function(section_id
, value
) {
1566 var eo
= this.section
.children
.filter(function(o
) { return o
.option
== 'eap_type' })[0],
1567 ev
= eo
.formvalue(section_id
);
1569 if (ev
!= 'ttls' && (value
== 'PAP' || value
== 'CHAP' || value
== 'MSCHAP' || value
== 'MSCHAPV2'))
1570 return _('This authentication type is not applicable to the selected EAP method.');
1575 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"));
1578 o
.default = o
.disabled
;
1579 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1580 o
.validate = function(section_id
, value
) {
1581 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1582 return _("This option cannot be used because the ca-bundle package is not installed.");
1587 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert2', _('Path to inner CA-Certificate'));
1588 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'], ca_cert2_usesystem
: ['0'] });
1590 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"));
1591 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1593 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"));
1594 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1596 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)"));
1597 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1599 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)"));
1600 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1602 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert2', _('Path to inner Client-Certificate'));
1603 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1605 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key2', _('Path to inner Private Key'));
1606 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1608 o
= ss
.taboption('encryption', form
.Value
, 'priv_key2_pwd', _('Password of inner Private Key'));
1609 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1612 o
= ss
.taboption('encryption', form
.Value
, 'identity', _('Identity'));
1613 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1615 o
= ss
.taboption('encryption', form
.Value
, 'anonymous_identity', _('Anonymous Identity'));
1616 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1618 o
= ss
.taboption('encryption', form
.Value
, 'password', _('Password'));
1619 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1623 if (hwtype
== 'mac80211') {
1624 // ieee802.11w options
1625 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"));
1626 o
.value('', _('Disabled'));
1627 o
.value('1', _('Optional'));
1628 o
.value('2', _('Required'));
1629 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds', 'sta', 'sta-wds'], encryption
: ['owe', 'psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1632 '2': [{ encryption
: 'sae' }, { encryption
: 'owe' }, { encryption
: 'wpa3' }, { encryption
: 'wpa3-mixed' }],
1633 '1': [{ encryption
: 'sae-mixed'}],
1637 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1638 o
.depends('ieee80211w', '1');
1639 o
.depends('ieee80211w', '2');
1640 o
.datatype
= 'uinteger';
1641 o
.placeholder
= '1000';
1644 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1645 o
.depends('ieee80211w', '1');
1646 o
.depends('ieee80211w', '2');
1647 o
.datatype
= 'uinteger';
1648 o
.placeholder
= '201';
1651 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.'));
1652 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1654 if (L
.hasSystemFeature('hostapd', 'wps') && L
.hasSystemFeature('wpasupplicant')) {
1655 o
= ss
.taboption('encryption', form
.Flag
, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1658 o
.default = o
.disabled
;
1659 o
.depends('encryption', 'psk');
1660 o
.depends('encryption', 'psk2');
1661 o
.depends('encryption', 'psk-mixed');
1662 o
.depends('encryption', 'sae');
1663 o
.depends('encryption', 'sae-mixed');
1670 s
.handleRemove = function(section_id
, ev
) {
1671 document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id
)).style
.opacity
= 0.5;
1672 return form
.TypedSection
.prototype.handleRemove
.apply(this, [section_id
, ev
]);
1675 s
.handleScan = function(radioDev
, ev
) {
1676 var table
= E('table', { 'class': 'table' }, [
1677 E('tr', { 'class': 'tr table-titles' }, [
1678 E('th', { 'class': 'th col-2 middle center' }, _('Signal')),
1679 E('th', { 'class': 'th col-4 middle left' }, _('SSID')),
1680 E('th', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1681 E('th', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1682 E('th', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1683 E('th', { 'class': 'th col-3 middle left' }, _('Encryption')),
1684 E('th', { 'class': 'th cbi-section-actions right' }, ' '),
1688 var stop
= E('button', {
1690 'click': L
.bind(this.handleScanStartStop
, this),
1691 'style': 'display:none',
1692 'data-state': 'stop'
1693 }, _('Stop refresh'));
1695 cbi_update_table(table
, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1697 var md
= ui
.showModal(_('Join Network: Wireless Scan'), [
1699 E('div', { 'class': 'right' }, [
1704 'click': L
.bind(this.handleScanAbort
, this)
1709 md
.style
.maxWidth
= '90%';
1710 md
.style
.maxHeight
= 'none';
1712 this.pollFn
= L
.bind(this.handleScanRefresh
, this, radioDev
, {}, table
, stop
);
1714 poll
.add(this.pollFn
);
1718 s
.handleScanRefresh = function(radioDev
, scanCache
, table
, stop
) {
1719 return radioDev
.getScanList().then(L
.bind(function(results
) {
1722 for (var i
= 0; i
< results
.length
; i
++)
1723 scanCache
[results
[i
].bssid
] = results
[i
];
1725 for (var k
in scanCache
)
1726 if (scanCache
[k
].stale
)
1727 results
.push(scanCache
[k
]);
1729 results
.sort(function(a
, b
) {
1730 var diff
= (b
.quality
- a
.quality
) || (a
.channel
- b
.channel
);
1735 if (a
.ssid
< b
.ssid
)
1737 else if (a
.ssid
> b
.ssid
)
1740 if (a
.bssid
< b
.bssid
)
1742 else if (a
.bssid
> b
.bssid
)
1746 for (var i
= 0; i
< results
.length
; i
++) {
1747 var res
= results
[i
],
1748 qv
= res
.quality
|| 0,
1749 qm
= res
.quality_max
|| 0,
1750 q
= (qv
> 0 && qm
> 0) ? Math
.floor((100 / qm
) * qv
) : 0,
1751 s
= res
.stale
? 'opacity:0.5' : '';
1754 E('span', { 'style': s
}, render_signal_badge(q
, res
.signal
, res
.noise
)),
1755 E('span', { 'style': s
}, (res
.ssid
!= null) ? '%h'.format(res
.ssid
) : E('em', _('hidden'))),
1756 E('span', { 'style': s
}, '%d'.format(res
.channel
)),
1757 E('span', { 'style': s
}, '%h'.format(res
.mode
)),
1758 E('span', { 'style': s
}, '%h'.format(res
.bssid
)),
1759 E('span', { 'style': s
}, '%h'.format(network
.formatWifiEncryption(res
.encryption
))),
1760 E('div', { 'class': 'right' }, E('button', {
1761 'class': 'cbi-button cbi-button-action important',
1762 'click': ui
.createHandlerFn(this, 'handleJoin', radioDev
, res
)
1763 }, _('Join Network')))
1769 cbi_update_table(table
, rows
);
1771 stop
.disabled
= false;
1772 stop
.style
.display
= '';
1773 stop
.classList
.remove('spinning');
1777 s
.handleScanStartStop = function(ev
) {
1778 var btn
= ev
.currentTarget
;
1780 if (btn
.getAttribute('data-state') == 'stop') {
1781 poll
.remove(this.pollFn
);
1782 btn
.firstChild
.data
= _('Start refresh');
1783 btn
.setAttribute('data-state', 'start');
1786 poll
.add(this.pollFn
);
1787 btn
.firstChild
.data
= _('Stop refresh');
1788 btn
.setAttribute('data-state', 'stop');
1789 btn
.classList
.add('spinning');
1790 btn
.disabled
= true;
1794 s
.handleScanAbort = function(ev
) {
1795 var md
= dom
.parent(ev
.target
, 'div[aria-modal="true"]');
1797 md
.style
.maxWidth
= '';
1798 md
.style
.maxHeight
= '';
1802 poll
.remove(this.pollFn
);
1807 s
.handleJoinConfirm = function(radioDev
, bss
, form
, ev
) {
1808 var nameopt
= L
.toArray(form
.lookupOption('name', '_new_'))[0],
1809 passopt
= L
.toArray(form
.lookupOption('password', '_new_'))[0],
1810 ssidopt
= L
.toArray(form
.lookupOption('ssid', '_new_'))[0],
1811 bssidopt
= L
.toArray(form
.lookupOption('bssid', '_new_'))[0],
1812 zoneopt
= L
.toArray(form
.lookupOption('zone', '_new_'))[0],
1813 replopt
= L
.toArray(form
.lookupOption('replace', '_new_'))[0],
1814 nameval
= (nameopt
&& nameopt
.isValid('_new_')) ? nameopt
.formvalue('_new_') : null,
1815 passval
= (passopt
&& passopt
.isValid('_new_')) ? passopt
.formvalue('_new_') : null,
1816 ssidval
= (ssidopt
&& ssidopt
.isValid('_new_')) ? ssidopt
.formvalue('_new_') : null,
1817 bssidval
= (bssidopt
&& bssidopt
.isValid('_new_')) ? bssidopt
.formvalue('_new_') : null,
1818 zoneval
= zoneopt
? zoneopt
.formvalue('_new_') : null,
1819 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
1820 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
1821 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' }).length
> 0),
1822 is_sae
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'sae' }).length
> 0);
1824 if (nameval
== null || (passopt
&& passval
== null))
1827 var section_id
= null;
1829 return this.map
.save(function() {
1830 var wifi_sections
= uci
.sections('wireless', 'wifi-iface');
1832 if (replopt
.formvalue('_new_') == '1') {
1833 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1834 if (wifi_sections
[i
].device
== radioDev
.getName())
1835 uci
.remove('wireless', wifi_sections
[i
]['.name']);
1838 if (uci
.get('wireless', radioDev
.getName(), 'disabled') == '1') {
1839 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1840 if (wifi_sections
[i
].device
== radioDev
.getName())
1841 uci
.set('wireless', wifi_sections
[i
]['.name'], 'disabled', '1');
1843 uci
.unset('wireless', radioDev
.getName(), 'disabled');
1846 section_id
= next_free_sid(wifi_sections
.length
);
1848 uci
.add('wireless', 'wifi-iface', section_id
);
1849 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
1850 uci
.set('wireless', section_id
, 'mode', (bss
.mode
== 'Ad-Hoc') ? 'adhoc' : 'sta');
1851 uci
.set('wireless', section_id
, 'network', nameval
);
1853 if (bss
.ssid
!= null) {
1854 uci
.set('wireless', section_id
, 'ssid', bss
.ssid
);
1856 if (bssidval
== '1')
1857 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
1859 else if (bss
.bssid
!= null) {
1860 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
1863 if (ssidval
!= null)
1864 uci
.set('wireless', section_id
, 'ssid', ssidval
);
1867 uci
.set('wireless', section_id
, 'encryption', 'sae');
1868 uci
.set('wireless', section_id
, 'key', passval
);
1871 for (var i
= enc
.wpa
.length
- 1; i
>= 0; i
--) {
1872 if (enc
.wpa
[i
] == 2) {
1873 uci
.set('wireless', section_id
, 'encryption', 'psk2');
1876 else if (enc
.wpa
[i
] == 1) {
1877 uci
.set('wireless', section_id
, 'encryption', 'psk');
1882 uci
.set('wireless', section_id
, 'key', passval
);
1885 uci
.set('wireless', section_id
, 'encryption', 'wep-open');
1886 uci
.set('wireless', section_id
, 'key', '1');
1887 uci
.set('wireless', section_id
, 'key1', passval
);
1890 uci
.set('wireless', section_id
, 'encryption', 'none');
1893 return network
.addNetwork(nameval
, { proto
: 'dhcp' }).then(function(net
) {
1894 firewall
.deleteNetwork(net
.getName());
1896 var zonePromise
= zoneval
1897 ? firewall
.getZone(zoneval
).then(function(zone
) { return zone
|| firewall
.addZone(zoneval
) })
1898 : Promise
.resolve();
1900 return zonePromise
.then(function(zone
) {
1902 zone
.addNetwork(net
.getName());
1905 }).then(L
.bind(function() {
1906 return this.renderMoreOptionsModal(section_id
);
1910 s
.handleJoin = function(radioDev
, bss
, ev
) {
1911 poll
.remove(this.pollFn
);
1913 var m2
= new form
.Map('wireless'),
1914 s2
= m2
.section(form
.NamedSection
, '_new_'),
1915 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
1916 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
1917 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' || a
== 'sae' })),
1918 replace
, passphrase
, name
, bssid
, zone
;
1920 var nameUsed = function(name
) {
1921 var s
= uci
.get('network', name
);
1922 if (s
!= null && s
['.type'] != 'interface')
1925 var net
= (s
!= null) ? network
.instantiateNetwork(name
) : null;
1926 return (net
!= null && !net
.isEmpty());
1929 s2
.render = function() {
1930 return Promise
.all([
1932 this.renderUCISection('_new_')
1933 ]).then(this.renderContents
.bind(this));
1936 if (bss
.ssid
== null) {
1937 name
= s2
.option(form
.Value
, 'ssid', _('Network SSID'), _('The correct SSID must be manually specified when joining a hidden wireless network'));
1938 name
.rmempty
= false;
1941 replace
= s2
.option(form
.Flag
, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
1943 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>'));
1944 name
.datatype
= 'uciname';
1945 name
.default = 'wwan';
1946 name
.rmempty
= false;
1947 name
.validate = function(section_id
, value
) {
1948 if (nameUsed(value
))
1949 return _('The network name is already used');
1954 for (var i
= 2; nameUsed(name
.default); i
++)
1955 name
.default = 'wwan%d'.format(i
);
1957 if (is_wep
|| is_psk
) {
1958 passphrase
= s2
.option(form
.Value
, 'password', is_wep
? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
1959 passphrase
.datatype
= is_wep
? 'wepkey' : 'wpakey';
1960 passphrase
.password
= true;
1961 passphrase
.rmempty
= false;
1964 if (bss
.ssid
!= null) {
1965 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
));
1966 bssid
.default = '0';
1969 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.'));
1970 zone
.default = 'wan';
1972 return m2
.render().then(L
.bind(function(nodes
) {
1973 ui
.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss
.ssid
)), [
1975 E('div', { 'class': 'right' }, [
1978 'click': ui
.hideModal
1979 }, _('Cancel')), ' ',
1981 'class': 'cbi-button cbi-button-positive important',
1982 'click': ui
.createHandlerFn(this, 'handleJoinConfirm', radioDev
, bss
, m2
)
1985 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase
|| name
).cbid('_new_'))).focus();
1989 s
.handleAdd = function(radioDev
, ev
) {
1990 var section_id
= next_free_sid(uci
.sections('wireless', 'wifi-iface').length
);
1992 uci
.unset('wireless', radioDev
.getName(), 'disabled');
1994 uci
.add('wireless', 'wifi-iface', section_id
);
1995 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
1996 uci
.set('wireless', section_id
, 'mode', 'ap');
1997 uci
.set('wireless', section_id
, 'ssid', 'OpenWrt');
1998 uci
.set('wireless', section_id
, 'encryption', 'none');
2000 this.addedSection
= section_id
;
2001 return this.renderMoreOptionsModal(section_id
);
2004 o
= s
.option(form
.DummyValue
, '_badge');
2005 o
.modalonly
= false;
2006 o
.textvalue = function(section_id
) {
2007 var inst
= this.section
.lookupRadioOrNetwork(section_id
),
2008 node
= E('div', { 'class': 'center' });
2010 if (inst
.getWifiNetworks
)
2011 node
.appendChild(render_radio_badge(inst
));
2013 node
.appendChild(render_network_badge(inst
));
2018 o
= s
.option(form
.DummyValue
, '_stat');
2019 o
.modalonly
= false;
2020 o
.textvalue = function(section_id
) {
2021 var inst
= this.section
.lookupRadioOrNetwork(section_id
);
2023 if (inst
.getWifiNetworks
)
2024 return render_radio_status(inst
, this.section
.wifis
.filter(function(e
) {
2025 return (e
.getWifiDeviceName() == inst
.getName());
2028 return render_network_status(inst
);
2031 return m
.render().then(L
.bind(function(m
, nodes
) {
2032 poll
.add(L
.bind(function() {
2033 var section_ids
= m
.children
[0].cfgsections(),
2034 tasks
= [ network
.getHostHints(), network
.getWifiDevices() ];
2036 for (var i
= 0; i
< section_ids
.length
; i
++) {
2037 var row
= nodes
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids
[i
])),
2038 dsc
= row
.querySelector('[data-name="_stat"] > div'),
2039 btns
= row
.querySelectorAll('.cbi-section-actions button');
2041 if (dsc
.getAttribute('restart') == '') {
2042 dsc
.setAttribute('restart', '1');
2043 tasks
.push(fs
.exec('/sbin/wifi', ['up', section_ids
[i
]]).catch(function(e
) {
2044 ui
.addNotification(null, E('p', e
.message
));
2047 else if (dsc
.getAttribute('restart') == '1') {
2048 dsc
.removeAttribute('restart');
2049 btns
[0].classList
.remove('spinning');
2050 btns
[0].disabled
= false;
2054 return Promise
.all(tasks
)
2055 .then(L
.bind(function(hosts_radios
) {
2058 for (var i
= 0; i
< hosts_radios
[1].length
; i
++)
2059 tasks
.push(hosts_radios
[1][i
].getWifiNetworks());
2061 return Promise
.all(tasks
).then(function(data
) {
2062 hosts_radios
[2] = [];
2064 for (var i
= 0; i
< data
.length
; i
++)
2065 hosts_radios
[2].push
.apply(hosts_radios
[2], data
[i
]);
2067 return hosts_radios
;
2070 .then(L
.bind(function(hosts_radios_wifis
) {
2073 for (var i
= 0; i
< hosts_radios_wifis
[2].length
; i
++)
2074 tasks
.push(hosts_radios_wifis
[2][i
].getAssocList());
2076 return Promise
.all(tasks
).then(function(data
) {
2077 hosts_radios_wifis
[3] = [];
2079 for (var i
= 0; i
< data
.length
; i
++) {
2080 var wifiNetwork
= hosts_radios_wifis
[2][i
],
2081 radioDev
= hosts_radios_wifis
[1].filter(function(d
) { return d
.getName() == wifiNetwork
.getWifiDeviceName() })[0];
2083 for (var j
= 0; j
< data
[i
].length
; j
++)
2084 hosts_radios_wifis
[3].push(Object
.assign({ radio
: radioDev
, network
: wifiNetwork
}, data
[i
][j
]));
2087 return hosts_radios_wifis
;
2090 .then(L
.bind(this.poll_status
, this, nodes
));
2093 var table
= E('table', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [
2094 E('tr', { 'class': 'tr table-titles' }, [
2095 E('th', { 'class': 'th nowrap' }, _('Network')),
2096 E('th', { 'class': 'th hide-xs' }, _('MAC-Address')),
2097 E('th', { 'class': 'th' }, _('Host')),
2098 E('th', { 'class': 'th' }, _('Signal / Noise')),
2099 E('th', { 'class': 'th' }, _('RX Rate / TX Rate'))
2103 cbi_update_table(table
, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
2105 return E([ nodes
, E('h3', _('Associated Stations')), table
]);