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
) {
991 net
.set('type', 'bridge');
997 for (var name
in old_networks
)
998 if (!new_networks
[name
])
999 tasks
.push(network
.getNetwork(name
).then(L
.bind(function(dev
, net
) {
1001 net
.deleteDevice(dev
);
1004 return Promise
.all(tasks
);
1008 if (hwtype
== 'mac80211') {
1009 var mode
= ss
.children
[0],
1010 bssid
= ss
.children
[5],
1013 mode
.value('mesh', '802.11s');
1014 mode
.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
1015 mode
.value('monitor', _('Monitor'));
1017 bssid
.depends('mode', 'adhoc');
1018 bssid
.depends('mode', 'sta');
1019 bssid
.depends('mode', 'sta-wds');
1021 o
= ss
.taboption('macfilter', form
.ListValue
, 'macfilter', _('MAC-Address Filter'));
1022 o
.depends('mode', 'ap');
1023 o
.depends('mode', 'ap-wds');
1024 o
.value('', _('disable'));
1025 o
.value('allow', _('Allow listed only'));
1026 o
.value('deny', _('Allow all except listed'));
1028 o
= ss
.taboption('macfilter', form
.DynamicList
, 'maclist', _('MAC-List'));
1029 o
.datatype
= 'macaddr';
1030 o
.depends('macfilter', 'allow');
1031 o
.depends('macfilter', 'deny');
1032 o
.load = function(section_id
) {
1033 return network
.getHostHints().then(L
.bind(function(hints
) {
1034 hints
.getMACHints().map(L
.bind(function(hint
) {
1035 this.value(hint
[0], hint
[1] ? '%s (%s)'.format(hint
[0], hint
[1]) : hint
[0]);
1038 return form
.DynamicList
.prototype.load
.apply(this, [section_id
]);
1042 mode
.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
1043 mode
.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
1045 mode
.write = function(section_id
, value
) {
1048 uci
.set('wireless', section_id
, 'mode', 'ap');
1049 uci
.set('wireless', section_id
, 'wds', '1');
1053 uci
.set('wireless', section_id
, 'mode', 'sta');
1054 uci
.set('wireless', section_id
, 'wds', '1');
1058 uci
.set('wireless', section_id
, 'mode', value
);
1059 uci
.unset('wireless', section_id
, 'wds');
1064 mode
.cfgvalue = function(section_id
) {
1065 var mode
= uci
.get('wireless', section_id
, 'mode'),
1066 wds
= uci
.get('wireless', section_id
, 'wds');
1068 if (mode
== 'ap' && wds
)
1070 else if (mode
== 'sta' && wds
)
1076 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.'));
1077 o
.depends('mode', 'ap');
1078 o
.depends('mode', 'ap-wds');
1080 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.'));
1081 o
.depends('mode', 'ap');
1082 o
.depends('mode', 'ap-wds');
1083 o
.default = o
.enabled
;
1085 o
= ss
.taboption('advanced', form
.Flag
, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
1086 o
.depends('mode', 'ap');
1087 o
.depends('mode', 'ap-wds');
1089 o
= ss
.taboption('advanced', form
.Value
, 'ifname', _('Interface name'), _('Override default interface name'));
1091 o
.placeholder
= radioNet
.getIfname();
1092 if (/^radio\d+\.network/.test(o
.placeholder
))
1095 o
= ss
.taboption('advanced', form
.Flag
, 'short_preamble', _('Short Preamble'));
1096 o
.default = o
.enabled
;
1098 o
= ss
.taboption('advanced', form
.Value
, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
1101 o
.datatype
= 'range(1,255)';
1103 o
= ss
.taboption('advanced', form
.Value
, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
1105 o
.placeholder
= 600;
1106 o
.datatype
= 'uinteger';
1108 o
= ss
.taboption('advanced', form
.Flag
, 'skip_inactivity_poll', _('Disable Inactivity Polling'));
1110 o
.datatype
= 'uinteger';
1112 o
= ss
.taboption('advanced', form
.Value
, 'max_inactivity', _('Station inactivity limit'), _('sec'));
1114 o
.placeholder
= 300;
1115 o
.datatype
= 'uinteger';
1117 o
= ss
.taboption('advanced', form
.Value
, 'max_listen_interval', _('Maximum allowed Listen Interval'));
1119 o
.placeholder
= 65535;
1120 o
.datatype
= 'uinteger';
1122 o
= ss
.taboption('advanced', form
.Flag
, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
1123 o
.default = o
.enabled
;
1127 encr
= o
= ss
.taboption('encryption', form
.ListValue
, 'encryption', _('Encryption'));
1128 o
.depends('mode', 'ap');
1129 o
.depends('mode', 'sta');
1130 o
.depends('mode', 'adhoc');
1131 o
.depends('mode', 'ahdemo');
1132 o
.depends('mode', 'ap-wds');
1133 o
.depends('mode', 'sta-wds');
1134 o
.depends('mode', 'mesh');
1136 o
.cfgvalue = function(section_id
) {
1137 var v
= String(uci
.get('wireless', section_id
, 'encryption'));
1140 else if (v
.match(/\+/))
1141 return v
.replace(/\+.+$/, '');
1145 o
.write = function(section_id
, value
) {
1146 var e
= this.section
.children
.filter(function(o
) { return o
.option
== 'encryption' })[0].formvalue(section_id
),
1147 co
= this.section
.children
.filter(function(o
) { return o
.option
== 'cipher' })[0], c
= co
.formvalue(section_id
);
1149 if (value
== 'wpa' || value
== 'wpa2' || value
== 'wpa3' || value
== 'wpa3-mixed')
1150 uci
.unset('wireless', section_id
, 'key');
1152 if (co
.isActive(section_id
) && e
&& (c
== 'tkip' || c
== 'ccmp' || c
== 'tkip+ccmp'))
1155 uci
.set('wireless', section_id
, 'encryption', e
);
1158 o
= ss
.taboption('encryption', form
.ListValue
, 'cipher', _('Cipher'));
1159 o
.depends('encryption', 'wpa');
1160 o
.depends('encryption', 'wpa2');
1161 o
.depends('encryption', 'wpa3');
1162 o
.depends('encryption', 'wpa3-mixed');
1163 o
.depends('encryption', 'psk');
1164 o
.depends('encryption', 'psk2');
1165 o
.depends('encryption', 'wpa-mixed');
1166 o
.depends('encryption', 'psk-mixed');
1167 o
.value('auto', _('auto'));
1168 o
.value('ccmp', _('Force CCMP (AES)'));
1169 o
.value('tkip', _('Force TKIP'));
1170 o
.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1171 o
.write
= ss
.children
.filter(function(o
) { return o
.option
== 'encryption' })[0].write
;
1173 o
.cfgvalue = function(section_id
) {
1174 var v
= String(uci
.get('wireless', section_id
, 'encryption'));
1175 if (v
.match(/\+/)) {
1176 v
= v
.replace(/^[^+]+\+/, '');
1179 else if (v
== 'tkip+aes' || v
== 'aes+tkip' || v
== 'ccmp+tkip')
1186 var crypto_modes
= [];
1188 if (hwtype
== 'mac80211') {
1189 var has_supplicant
= L
.hasSystemFeature('wpasupplicant'),
1190 has_hostapd
= L
.hasSystemFeature('hostapd');
1192 // Probe EAP support
1193 var has_ap_eap
= L
.hasSystemFeature('hostapd', 'eap'),
1194 has_sta_eap
= L
.hasSystemFeature('wpasupplicant', 'eap');
1196 // Probe SAE support
1197 var has_ap_sae
= L
.hasSystemFeature('hostapd', 'sae'),
1198 has_sta_sae
= L
.hasSystemFeature('wpasupplicant', 'sae');
1200 // Probe OWE support
1201 var has_ap_owe
= L
.hasSystemFeature('hostapd', 'owe'),
1202 has_sta_owe
= L
.hasSystemFeature('wpasupplicant', 'owe');
1204 // Probe Suite-B support
1205 var has_ap_eap192
= L
.hasSystemFeature('hostapd', 'suiteb192'),
1206 has_sta_eap192
= L
.hasSystemFeature('wpasupplicant', 'suiteb192');
1208 // Probe WEP support
1209 var has_ap_wep
= L
.hasSystemFeature('hostapd', 'wep'),
1210 has_sta_wep
= L
.hasSystemFeature('wpasupplicant', 'wep');
1212 if (has_hostapd
|| has_supplicant
) {
1213 crypto_modes
.push(['psk2', 'WPA2-PSK', 35]);
1214 crypto_modes
.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1215 crypto_modes
.push(['psk', 'WPA-PSK', 21]);
1218 encr
.description
= _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1221 if (has_ap_sae
|| has_sta_sae
) {
1222 crypto_modes
.push(['sae', 'WPA3-SAE', 31]);
1223 crypto_modes
.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1226 if (has_ap_wep
|| has_sta_wep
) {
1227 crypto_modes
.push(['wep-open', _('WEP Open System'), 11]);
1228 crypto_modes
.push(['wep-shared', _('WEP Shared Key'), 10]);
1231 if (has_ap_eap
|| has_sta_eap
) {
1232 if (has_ap_eap192
|| has_sta_eap192
) {
1233 crypto_modes
.push(['wpa3', 'WPA3-EAP', 33]);
1234 crypto_modes
.push(['wpa3-mixed', 'WPA2-EAP/WPA3-EAP Mixed Mode', 32]);
1237 crypto_modes
.push(['wpa2', 'WPA2-EAP', 34]);
1238 crypto_modes
.push(['wpa', 'WPA-EAP', 20]);
1241 if (has_ap_owe
|| has_sta_owe
) {
1242 crypto_modes
.push(['owe', 'OWE', 1]);
1245 encr
.crypto_support
= {
1247 'wep-open': has_ap_wep
|| _('Requires hostapd with WEP support'),
1248 'wep-shared': has_ap_wep
|| _('Requires hostapd with WEP support'),
1249 'psk': has_hostapd
|| _('Requires hostapd'),
1250 'psk2': has_hostapd
|| _('Requires hostapd'),
1251 'psk-mixed': has_hostapd
|| _('Requires hostapd'),
1252 'sae': has_ap_sae
|| _('Requires hostapd with SAE support'),
1253 'sae-mixed': has_ap_sae
|| _('Requires hostapd with SAE support'),
1254 'wpa': has_ap_eap
|| _('Requires hostapd with EAP support'),
1255 'wpa2': has_ap_eap
|| _('Requires hostapd with EAP support'),
1256 'wpa3': has_ap_eap192
|| _('Requires hostapd with EAP Suite-B support'),
1257 'wpa3-mixed': has_ap_eap192
|| _('Requires hostapd with EAP Suite-B support'),
1258 'owe': has_ap_owe
|| _('Requires hostapd with OWE support')
1261 'wep-open': has_sta_wep
|| _('Requires wpa-supplicant with WEP support'),
1262 'wep-shared': has_sta_wep
|| _('Requires wpa-supplicant with WEP support'),
1263 'psk': has_supplicant
|| _('Requires wpa-supplicant'),
1264 'psk2': has_supplicant
|| _('Requires wpa-supplicant'),
1265 'psk-mixed': has_supplicant
|| _('Requires wpa-supplicant'),
1266 'sae': has_sta_sae
|| _('Requires wpa-supplicant with SAE support'),
1267 'sae-mixed': has_sta_sae
|| _('Requires wpa-supplicant with SAE support'),
1268 'wpa': has_sta_eap
|| _('Requires wpa-supplicant with EAP support'),
1269 'wpa2': has_sta_eap
|| _('Requires wpa-supplicant with EAP support'),
1270 'wpa3': has_sta_eap192
|| _('Requires wpa-supplicant with EAP Suite-B support'),
1271 'wpa3-mixed': has_sta_eap192
|| _('Requires wpa-supplicant with EAP Suite-B support'),
1272 'owe': has_sta_owe
|| _('Requires wpa-supplicant with OWE support')
1277 'psk': has_supplicant
|| _('Requires wpa-supplicant'),
1278 'psk2': has_supplicant
|| _('Requires wpa-supplicant'),
1279 'psk-mixed': has_supplicant
|| _('Requires wpa-supplicant'),
1282 'sae': has_sta_sae
|| _('Requires wpa-supplicant with SAE support')
1294 encr
.crypto_support
['ap-wds'] = encr
.crypto_support
['ap'];
1295 encr
.crypto_support
['sta-wds'] = encr
.crypto_support
['sta'];
1297 encr
.validate = function(section_id
, value
) {
1298 var modeopt
= this.section
.children
.filter(function(o
) { return o
.option
== 'mode' })[0],
1299 modeval
= modeopt
.formvalue(section_id
),
1300 modetitle
= modeopt
.vallist
[modeopt
.keylist
.indexOf(modeval
)],
1301 enctitle
= this.vallist
[this.keylist
.indexOf(value
)];
1303 if (value
== 'none')
1306 if (!L
.isObject(this.crypto_support
[modeval
]) || !this.crypto_support
[modeval
].hasOwnProperty(value
))
1307 return _('The selected %s mode is incompatible with %s encryption').format(modetitle
, enctitle
);
1309 return this.crypto_support
[modeval
][value
];
1312 else if (hwtype
== 'broadcom') {
1313 crypto_modes
.push(['psk2', 'WPA2-PSK', 33]);
1314 crypto_modes
.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1315 crypto_modes
.push(['psk', 'WPA-PSK', 21]);
1316 crypto_modes
.push(['wep-open', _('WEP Open System'), 11]);
1317 crypto_modes
.push(['wep-shared', _('WEP Shared Key'), 10]);
1320 crypto_modes
.push(['none', _('No Encryption'), 0]);
1322 crypto_modes
.sort(function(a
, b
) { return b
[2] - a
[2] });
1324 for (var i
= 0; i
< crypto_modes
.length
; i
++) {
1325 var security_level
= (crypto_modes
[i
][2] >= 30) ? _('strong security')
1326 : (crypto_modes
[i
][2] >= 20) ? _('medium security')
1327 : (crypto_modes
[i
][2] >= 10) ? _('weak security') : _('open network');
1329 encr
.value(crypto_modes
[i
][0], '%s (%s)'.format(crypto_modes
[i
][1], security_level
));
1333 o
= ss
.taboption('encryption', form
.Value
, 'auth_server', _('Radius-Authentication-Server'));
1334 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1336 o
.datatype
= 'host(0)';
1338 o
= ss
.taboption('encryption', form
.Value
, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
1339 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1341 o
.datatype
= 'port';
1343 o
= ss
.taboption('encryption', form
.Value
, 'auth_secret', _('Radius-Authentication-Secret'));
1344 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1348 o
= ss
.taboption('encryption', form
.Value
, 'acct_server', _('Radius-Accounting-Server'));
1349 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1351 o
.datatype
= 'host(0)';
1353 o
= ss
.taboption('encryption', form
.Value
, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
1354 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1356 o
.datatype
= 'port';
1358 o
= ss
.taboption('encryption', form
.Value
, 'acct_secret', _('Radius-Accounting-Secret'));
1359 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1363 o
= ss
.taboption('encryption', form
.Value
, 'dae_client', _('DAE-Client'));
1364 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1366 o
.datatype
= 'host(0)';
1368 o
= ss
.taboption('encryption', form
.Value
, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
1369 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1371 o
.datatype
= 'port';
1373 o
= ss
.taboption('encryption', form
.Value
, 'dae_secret', _('DAE-Secret'));
1374 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1379 o
= ss
.taboption('encryption', form
.Value
, '_wpa_key', _('Key'));
1380 o
.depends('encryption', 'psk');
1381 o
.depends('encryption', 'psk2');
1382 o
.depends('encryption', 'psk+psk2');
1383 o
.depends('encryption', 'psk-mixed');
1384 o
.depends('encryption', 'sae');
1385 o
.depends('encryption', 'sae-mixed');
1386 o
.datatype
= 'wpakey';
1390 o
.cfgvalue = function(section_id
) {
1391 var key
= uci
.get('wireless', section_id
, 'key');
1392 return /^[1234]$/.test(key
) ? null : key
;
1395 o
.write = function(section_id
, value
) {
1396 uci
.set('wireless', section_id
, 'key', value
);
1397 uci
.unset('wireless', section_id
, 'key1');
1398 uci
.unset('wireless', section_id
, 'key2');
1399 uci
.unset('wireless', section_id
, 'key3');
1400 uci
.unset('wireless', section_id
, 'key4');
1404 o
= ss
.taboption('encryption', form
.ListValue
, '_wep_key', _('Used Key Slot'));
1405 o
.depends('encryption', 'wep-open');
1406 o
.depends('encryption', 'wep-shared');
1407 o
.value('1', _('Key #%d').format(1));
1408 o
.value('2', _('Key #%d').format(2));
1409 o
.value('3', _('Key #%d').format(3));
1410 o
.value('4', _('Key #%d').format(4));
1412 o
.cfgvalue = function(section_id
) {
1413 var slot
= +uci
.get('wireless', section_id
, 'key');
1414 return (slot
>= 1 && slot
<= 4) ? String(slot
) : '';
1417 o
.write = function(section_id
, value
) {
1418 uci
.set('wireless', section_id
, 'key', value
);
1421 for (var slot
= 1; slot
<= 4; slot
++) {
1422 o
= ss
.taboption('encryption', form
.Value
, 'key%d'.format(slot
), _('Key #%d').format(slot
));
1423 o
.depends('encryption', 'wep-open');
1424 o
.depends('encryption', 'wep-shared');
1425 o
.datatype
= 'wepkey';
1429 o
.write = function(section_id
, value
) {
1430 if (value
!= null && (value
.length
== 5 || value
.length
== 13))
1431 value
= 's:%s'.format(value
);
1432 uci
.set('wireless', section_id
, this.option
, value
);
1437 if (hwtype
== 'mac80211') {
1438 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1439 var has_80211r
= L
.hasSystemFeature('hostapd', '11r') || L
.hasSystemFeature('hostapd', 'eap');
1441 o
= ss
.taboption('encryption', form
.Flag
, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1442 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1444 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1447 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.'));
1448 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1449 o
.depends({ ieee80211r
: '1' });
1452 o
= ss
.taboption('encryption', form
.Value
, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1453 o
.depends({ ieee80211r
: '1' });
1454 o
.placeholder
= '4f57';
1455 o
.datatype
= 'and(hexstring,length(4))';
1458 o
= ss
.taboption('encryption', form
.Value
, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1459 o
.depends({ ieee80211r
: '1' });
1460 o
.placeholder
= '1000';
1461 o
.datatype
= 'range(1000,65535)';
1464 o
= ss
.taboption('encryption', form
.ListValue
, 'ft_over_ds', _('FT protocol'));
1465 o
.depends({ ieee80211r
: '1' });
1466 o
.value('1', _('FT over DS'));
1467 o
.value('0', _('FT over the Air'));
1470 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.'));
1471 o
.depends({ ieee80211r
: '1' });
1472 o
.default = o
.enabled
;
1475 o
= ss
.taboption('encryption', form
.Value
, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1476 o
.depends({ ieee80211r
: '1' });
1477 o
.placeholder
= '10000';
1478 o
.datatype
= 'uinteger';
1481 o
= ss
.taboption('encryption', form
.Value
, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1482 o
.depends({ ieee80211r
: '1' });
1483 o
.placeholder
= '00004f577274';
1484 o
.datatype
= 'and(hexstring,length(12))';
1487 o
= ss
.taboption('encryption', form
.Flag
, 'pmk_r1_push', _('PMK R1 Push'));
1488 o
.depends({ ieee80211r
: '1' });
1489 o
.placeholder
= '0';
1492 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.'));
1493 o
.depends({ ieee80211r
: '1' });
1496 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.'));
1497 o
.depends({ ieee80211r
: '1' });
1499 // End of 802.11r options
1501 o
= ss
.taboption('encryption', form
.ListValue
, 'eap_type', _('EAP-Method'));
1502 o
.value('tls', 'TLS');
1503 o
.value('ttls', 'TTLS');
1504 o
.value('peap', 'PEAP');
1505 o
.value('fast', 'FAST');
1506 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1508 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"));
1511 o
.default = o
.disabled
;
1512 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1513 o
.validate = function(section_id
, value
) {
1514 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1515 return _("This option cannot be used because the ca-bundle package is not installed.");
1520 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert', _('Path to CA-Certificate'));
1521 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], ca_cert_usesystem
: ['0'] });
1523 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"));
1524 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1526 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"));
1527 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1529 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)"));
1530 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1532 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)"));
1533 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1535 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert', _('Path to Client-Certificate'));
1536 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1538 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key', _('Path to Private Key'));
1539 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1541 o
= ss
.taboption('encryption', form
.Value
, 'priv_key_pwd', _('Password of Private Key'));
1542 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1545 o
= ss
.taboption('encryption', form
.ListValue
, 'auth', _('Authentication'));
1546 o
.value('PAP', 'PAP');
1547 o
.value('CHAP', 'CHAP');
1548 o
.value('MSCHAP', 'MSCHAP');
1549 o
.value('MSCHAPV2', 'MSCHAPv2');
1550 o
.value('EAP-GTC', 'EAP-GTC');
1551 o
.value('EAP-MD5', 'EAP-MD5');
1552 o
.value('EAP-MSCHAPV2', 'EAP-MSCHAPv2');
1553 o
.value('EAP-TLS', 'EAP-TLS');
1554 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1556 o
.validate = function(section_id
, value
) {
1557 var eo
= this.section
.children
.filter(function(o
) { return o
.option
== 'eap_type' })[0],
1558 ev
= eo
.formvalue(section_id
);
1560 if (ev
!= 'ttls' && (value
== 'PAP' || value
== 'CHAP' || value
== 'MSCHAP' || value
== 'MSCHAPV2'))
1561 return _('This authentication type is not applicable to the selected EAP method.');
1566 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"));
1569 o
.default = o
.disabled
;
1570 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1571 o
.validate = function(section_id
, value
) {
1572 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1573 return _("This option cannot be used because the ca-bundle package is not installed.");
1578 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert2', _('Path to inner CA-Certificate'));
1579 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'], ca_cert2_usesystem
: ['0'] });
1581 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"));
1582 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1584 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"));
1585 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1587 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)"));
1588 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1590 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)"));
1591 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1593 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert2', _('Path to inner Client-Certificate'));
1594 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1596 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key2', _('Path to inner Private Key'));
1597 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1599 o
= ss
.taboption('encryption', form
.Value
, 'priv_key2_pwd', _('Password of inner Private Key'));
1600 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1603 o
= ss
.taboption('encryption', form
.Value
, 'identity', _('Identity'));
1604 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1606 o
= ss
.taboption('encryption', form
.Value
, 'anonymous_identity', _('Anonymous Identity'));
1607 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1609 o
= ss
.taboption('encryption', form
.Value
, 'password', _('Password'));
1610 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1614 if (hwtype
== 'mac80211') {
1615 // ieee802.11w options
1616 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"));
1617 o
.value('', _('Disabled'));
1618 o
.value('1', _('Optional'));
1619 o
.value('2', _('Required'));
1620 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds', 'sta', 'sta-wds'], encryption
: ['owe', 'psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1623 '2': [{ encryption
: 'sae' }, { encryption
: 'owe' }, { encryption
: 'wpa3' }, { encryption
: 'wpa3-mixed' }],
1624 '1': [{ encryption
: 'sae-mixed'}],
1628 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1629 o
.depends('ieee80211w', '1');
1630 o
.depends('ieee80211w', '2');
1631 o
.datatype
= 'uinteger';
1632 o
.placeholder
= '1000';
1635 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1636 o
.depends('ieee80211w', '1');
1637 o
.depends('ieee80211w', '2');
1638 o
.datatype
= 'uinteger';
1639 o
.placeholder
= '201';
1642 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.'));
1643 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1645 if (L
.hasSystemFeature('hostapd', 'wps') && L
.hasSystemFeature('wpasupplicant')) {
1646 o
= ss
.taboption('encryption', form
.Flag
, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1649 o
.default = o
.disabled
;
1650 o
.depends('encryption', 'psk');
1651 o
.depends('encryption', 'psk2');
1652 o
.depends('encryption', 'psk-mixed');
1653 o
.depends('encryption', 'sae');
1654 o
.depends('encryption', 'sae-mixed');
1661 s
.handleRemove = function(section_id
, ev
) {
1662 document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id
)).style
.opacity
= 0.5;
1663 return form
.TypedSection
.prototype.handleRemove
.apply(this, [section_id
, ev
]);
1666 s
.handleScan = function(radioDev
, ev
) {
1667 var table
= E('table', { 'class': 'table' }, [
1668 E('tr', { 'class': 'tr table-titles' }, [
1669 E('th', { 'class': 'th col-2 middle center' }, _('Signal')),
1670 E('th', { 'class': 'th col-4 middle left' }, _('SSID')),
1671 E('th', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1672 E('th', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1673 E('th', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1674 E('th', { 'class': 'th col-3 middle left' }, _('Encryption')),
1675 E('th', { 'class': 'th cbi-section-actions right' }, ' '),
1679 var stop
= E('button', {
1681 'click': L
.bind(this.handleScanStartStop
, this),
1682 'style': 'display:none',
1683 'data-state': 'stop'
1684 }, _('Stop refresh'));
1686 cbi_update_table(table
, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1688 var md
= ui
.showModal(_('Join Network: Wireless Scan'), [
1690 E('div', { 'class': 'right' }, [
1695 'click': L
.bind(this.handleScanAbort
, this)
1700 md
.style
.maxWidth
= '90%';
1701 md
.style
.maxHeight
= 'none';
1703 this.pollFn
= L
.bind(this.handleScanRefresh
, this, radioDev
, {}, table
, stop
);
1705 poll
.add(this.pollFn
);
1709 s
.handleScanRefresh = function(radioDev
, scanCache
, table
, stop
) {
1710 return radioDev
.getScanList().then(L
.bind(function(results
) {
1713 for (var i
= 0; i
< results
.length
; i
++)
1714 scanCache
[results
[i
].bssid
] = results
[i
];
1716 for (var k
in scanCache
)
1717 if (scanCache
[k
].stale
)
1718 results
.push(scanCache
[k
]);
1720 results
.sort(function(a
, b
) {
1721 var diff
= (b
.quality
- a
.quality
) || (a
.channel
- b
.channel
);
1726 if (a
.ssid
< b
.ssid
)
1728 else if (a
.ssid
> b
.ssid
)
1731 if (a
.bssid
< b
.bssid
)
1733 else if (a
.bssid
> b
.bssid
)
1737 for (var i
= 0; i
< results
.length
; i
++) {
1738 var res
= results
[i
],
1739 qv
= res
.quality
|| 0,
1740 qm
= res
.quality_max
|| 0,
1741 q
= (qv
> 0 && qm
> 0) ? Math
.floor((100 / qm
) * qv
) : 0,
1742 s
= res
.stale
? 'opacity:0.5' : '';
1745 E('span', { 'style': s
}, render_signal_badge(q
, res
.signal
, res
.noise
)),
1746 E('span', { 'style': s
}, (res
.ssid
!= null) ? '%h'.format(res
.ssid
) : E('em', _('hidden'))),
1747 E('span', { 'style': s
}, '%d'.format(res
.channel
)),
1748 E('span', { 'style': s
}, '%h'.format(res
.mode
)),
1749 E('span', { 'style': s
}, '%h'.format(res
.bssid
)),
1750 E('span', { 'style': s
}, '%h'.format(network
.formatWifiEncryption(res
.encryption
))),
1751 E('div', { 'class': 'right' }, E('button', {
1752 'class': 'cbi-button cbi-button-action important',
1753 'click': ui
.createHandlerFn(this, 'handleJoin', radioDev
, res
)
1754 }, _('Join Network')))
1760 cbi_update_table(table
, rows
);
1762 stop
.disabled
= false;
1763 stop
.style
.display
= '';
1764 stop
.classList
.remove('spinning');
1768 s
.handleScanStartStop = function(ev
) {
1769 var btn
= ev
.currentTarget
;
1771 if (btn
.getAttribute('data-state') == 'stop') {
1772 poll
.remove(this.pollFn
);
1773 btn
.firstChild
.data
= _('Start refresh');
1774 btn
.setAttribute('data-state', 'start');
1777 poll
.add(this.pollFn
);
1778 btn
.firstChild
.data
= _('Stop refresh');
1779 btn
.setAttribute('data-state', 'stop');
1780 btn
.classList
.add('spinning');
1781 btn
.disabled
= true;
1785 s
.handleScanAbort = function(ev
) {
1786 var md
= dom
.parent(ev
.target
, 'div[aria-modal="true"]');
1788 md
.style
.maxWidth
= '';
1789 md
.style
.maxHeight
= '';
1793 poll
.remove(this.pollFn
);
1798 s
.handleJoinConfirm = function(radioDev
, bss
, form
, ev
) {
1799 var nameopt
= L
.toArray(form
.lookupOption('name', '_new_'))[0],
1800 passopt
= L
.toArray(form
.lookupOption('password', '_new_'))[0],
1801 ssidopt
= L
.toArray(form
.lookupOption('ssid', '_new_'))[0],
1802 bssidopt
= L
.toArray(form
.lookupOption('bssid', '_new_'))[0],
1803 zoneopt
= L
.toArray(form
.lookupOption('zone', '_new_'))[0],
1804 replopt
= L
.toArray(form
.lookupOption('replace', '_new_'))[0],
1805 nameval
= (nameopt
&& nameopt
.isValid('_new_')) ? nameopt
.formvalue('_new_') : null,
1806 passval
= (passopt
&& passopt
.isValid('_new_')) ? passopt
.formvalue('_new_') : null,
1807 ssidval
= (ssidopt
&& ssidopt
.isValid('_new_')) ? ssidopt
.formvalue('_new_') : null,
1808 bssidval
= (bssidopt
&& bssidopt
.isValid('_new_')) ? bssidopt
.formvalue('_new_') : null,
1809 zoneval
= zoneopt
? zoneopt
.formvalue('_new_') : null,
1810 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
1811 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
1812 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' }).length
> 0),
1813 is_sae
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'sae' }).length
> 0);
1815 if (nameval
== null || (passopt
&& passval
== null))
1818 var section_id
= null;
1820 return this.map
.save(function() {
1821 var wifi_sections
= uci
.sections('wireless', 'wifi-iface');
1823 if (replopt
.formvalue('_new_') == '1') {
1824 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1825 if (wifi_sections
[i
].device
== radioDev
.getName())
1826 uci
.remove('wireless', wifi_sections
[i
]['.name']);
1829 if (uci
.get('wireless', radioDev
.getName(), 'disabled') == '1') {
1830 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1831 if (wifi_sections
[i
].device
== radioDev
.getName())
1832 uci
.set('wireless', wifi_sections
[i
]['.name'], 'disabled', '1');
1834 uci
.unset('wireless', radioDev
.getName(), 'disabled');
1837 section_id
= next_free_sid(wifi_sections
.length
);
1839 uci
.add('wireless', 'wifi-iface', section_id
);
1840 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
1841 uci
.set('wireless', section_id
, 'mode', (bss
.mode
== 'Ad-Hoc') ? 'adhoc' : 'sta');
1842 uci
.set('wireless', section_id
, 'network', nameval
);
1844 if (bss
.ssid
!= null) {
1845 uci
.set('wireless', section_id
, 'ssid', bss
.ssid
);
1847 if (bssidval
== '1')
1848 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
1850 else if (bss
.bssid
!= null) {
1851 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
1854 if (ssidval
!= null)
1855 uci
.set('wireless', section_id
, 'ssid', ssidval
);
1858 uci
.set('wireless', section_id
, 'encryption', 'sae');
1859 uci
.set('wireless', section_id
, 'key', passval
);
1862 for (var i
= enc
.wpa
.length
- 1; i
>= 0; i
--) {
1863 if (enc
.wpa
[i
] == 2) {
1864 uci
.set('wireless', section_id
, 'encryption', 'psk2');
1867 else if (enc
.wpa
[i
] == 1) {
1868 uci
.set('wireless', section_id
, 'encryption', 'psk');
1873 uci
.set('wireless', section_id
, 'key', passval
);
1876 uci
.set('wireless', section_id
, 'encryption', 'wep-open');
1877 uci
.set('wireless', section_id
, 'key', '1');
1878 uci
.set('wireless', section_id
, 'key1', passval
);
1881 uci
.set('wireless', section_id
, 'encryption', 'none');
1884 return network
.addNetwork(nameval
, { proto
: 'dhcp' }).then(function(net
) {
1885 firewall
.deleteNetwork(net
.getName());
1887 var zonePromise
= zoneval
1888 ? firewall
.getZone(zoneval
).then(function(zone
) { return zone
|| firewall
.addZone(zoneval
) })
1889 : Promise
.resolve();
1891 return zonePromise
.then(function(zone
) {
1893 zone
.addNetwork(net
.getName());
1896 }).then(L
.bind(function() {
1897 return this.renderMoreOptionsModal(section_id
);
1901 s
.handleJoin = function(radioDev
, bss
, ev
) {
1902 poll
.remove(this.pollFn
);
1904 var m2
= new form
.Map('wireless'),
1905 s2
= m2
.section(form
.NamedSection
, '_new_'),
1906 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
1907 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
1908 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' || a
== 'sae' })),
1909 replace
, passphrase
, name
, bssid
, zone
;
1911 var nameUsed = function(name
) {
1912 var s
= uci
.get('network', name
);
1913 if (s
!= null && s
['.type'] != 'interface')
1916 var net
= (s
!= null) ? network
.instantiateNetwork(name
) : null;
1917 return (net
!= null && !net
.isEmpty());
1920 s2
.render = function() {
1921 return Promise
.all([
1923 this.renderUCISection('_new_')
1924 ]).then(this.renderContents
.bind(this));
1927 if (bss
.ssid
== null) {
1928 name
= s2
.option(form
.Value
, 'ssid', _('Network SSID'), _('The correct SSID must be manually specified when joining a hidden wireless network'));
1929 name
.rmempty
= false;
1932 replace
= s2
.option(form
.Flag
, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
1934 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>'));
1935 name
.datatype
= 'uciname';
1936 name
.default = 'wwan';
1937 name
.rmempty
= false;
1938 name
.validate = function(section_id
, value
) {
1939 if (nameUsed(value
))
1940 return _('The network name is already used');
1945 for (var i
= 2; nameUsed(name
.default); i
++)
1946 name
.default = 'wwan%d'.format(i
);
1948 if (is_wep
|| is_psk
) {
1949 passphrase
= s2
.option(form
.Value
, 'password', is_wep
? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
1950 passphrase
.datatype
= is_wep
? 'wepkey' : 'wpakey';
1951 passphrase
.password
= true;
1952 passphrase
.rmempty
= false;
1955 if (bss
.ssid
!= null) {
1956 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
));
1957 bssid
.default = '0';
1960 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.'));
1961 zone
.default = 'wan';
1963 return m2
.render().then(L
.bind(function(nodes
) {
1964 ui
.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss
.ssid
)), [
1966 E('div', { 'class': 'right' }, [
1969 'click': ui
.hideModal
1970 }, _('Cancel')), ' ',
1972 'class': 'cbi-button cbi-button-positive important',
1973 'click': ui
.createHandlerFn(this, 'handleJoinConfirm', radioDev
, bss
, m2
)
1976 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase
|| name
).cbid('_new_'))).focus();
1980 s
.handleAdd = function(radioDev
, ev
) {
1981 var section_id
= next_free_sid(uci
.sections('wireless', 'wifi-iface').length
);
1983 uci
.unset('wireless', radioDev
.getName(), 'disabled');
1985 uci
.add('wireless', 'wifi-iface', section_id
);
1986 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
1987 uci
.set('wireless', section_id
, 'mode', 'ap');
1988 uci
.set('wireless', section_id
, 'ssid', 'OpenWrt');
1989 uci
.set('wireless', section_id
, 'encryption', 'none');
1991 this.addedSection
= section_id
;
1992 return this.renderMoreOptionsModal(section_id
);
1995 o
= s
.option(form
.DummyValue
, '_badge');
1996 o
.modalonly
= false;
1997 o
.textvalue = function(section_id
) {
1998 var inst
= this.section
.lookupRadioOrNetwork(section_id
),
1999 node
= E('div', { 'class': 'center' });
2001 if (inst
.getWifiNetworks
)
2002 node
.appendChild(render_radio_badge(inst
));
2004 node
.appendChild(render_network_badge(inst
));
2009 o
= s
.option(form
.DummyValue
, '_stat');
2010 o
.modalonly
= false;
2011 o
.textvalue = function(section_id
) {
2012 var inst
= this.section
.lookupRadioOrNetwork(section_id
);
2014 if (inst
.getWifiNetworks
)
2015 return render_radio_status(inst
, this.section
.wifis
.filter(function(e
) {
2016 return (e
.getWifiDeviceName() == inst
.getName());
2019 return render_network_status(inst
);
2022 return m
.render().then(L
.bind(function(m
, nodes
) {
2023 poll
.add(L
.bind(function() {
2024 var section_ids
= m
.children
[0].cfgsections(),
2025 tasks
= [ network
.getHostHints(), network
.getWifiDevices() ];
2027 for (var i
= 0; i
< section_ids
.length
; i
++) {
2028 var row
= nodes
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids
[i
])),
2029 dsc
= row
.querySelector('[data-name="_stat"] > div'),
2030 btns
= row
.querySelectorAll('.cbi-section-actions button');
2032 if (dsc
.getAttribute('restart') == '') {
2033 dsc
.setAttribute('restart', '1');
2034 tasks
.push(fs
.exec('/sbin/wifi', ['up', section_ids
[i
]]).catch(function(e
) {
2035 ui
.addNotification(null, E('p', e
.message
));
2038 else if (dsc
.getAttribute('restart') == '1') {
2039 dsc
.removeAttribute('restart');
2040 btns
[0].classList
.remove('spinning');
2041 btns
[0].disabled
= false;
2045 return Promise
.all(tasks
)
2046 .then(L
.bind(function(hosts_radios
) {
2049 for (var i
= 0; i
< hosts_radios
[1].length
; i
++)
2050 tasks
.push(hosts_radios
[1][i
].getWifiNetworks());
2052 return Promise
.all(tasks
).then(function(data
) {
2053 hosts_radios
[2] = [];
2055 for (var i
= 0; i
< data
.length
; i
++)
2056 hosts_radios
[2].push
.apply(hosts_radios
[2], data
[i
]);
2058 return hosts_radios
;
2061 .then(L
.bind(function(hosts_radios_wifis
) {
2064 for (var i
= 0; i
< hosts_radios_wifis
[2].length
; i
++)
2065 tasks
.push(hosts_radios_wifis
[2][i
].getAssocList());
2067 return Promise
.all(tasks
).then(function(data
) {
2068 hosts_radios_wifis
[3] = [];
2070 for (var i
= 0; i
< data
.length
; i
++) {
2071 var wifiNetwork
= hosts_radios_wifis
[2][i
],
2072 radioDev
= hosts_radios_wifis
[1].filter(function(d
) { return d
.getName() == wifiNetwork
.getWifiDeviceName() })[0];
2074 for (var j
= 0; j
< data
[i
].length
; j
++)
2075 hosts_radios_wifis
[3].push(Object
.assign({ radio
: radioDev
, network
: wifiNetwork
}, data
[i
][j
]));
2078 return hosts_radios_wifis
;
2081 .then(L
.bind(this.poll_status
, this, nodes
));
2084 var table
= E('table', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [
2085 E('tr', { 'class': 'tr table-titles' }, [
2086 E('th', { 'class': 'th nowrap' }, _('Network')),
2087 E('th', { 'class': 'th hide-xs' }, _('MAC-Address')),
2088 E('th', { 'class': 'th' }, _('Host')),
2089 E('th', { 'class': 'th' }, _('Signal / Noise')),
2090 E('th', { 'class': 'th' }, _('RX Rate / TX Rate'))
2094 cbi_update_table(table
, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
2096 return E([ nodes
, E('h3', _('Associated Stations')), table
]);