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
);
397 toggleWifiChannel: function(elem
) {
398 var band
= elem
.querySelector('.band');
399 var chan
= elem
.querySelector('.channel');
401 this.setValues(chan
, this.channels
[band
.value
]);
404 setInitialValues: function(section_id
, elem
) {
405 var mode
= elem
.querySelector('.mode'),
406 band
= elem
.querySelector('.band'),
407 chan
= elem
.querySelector('.channel'),
408 bwdt
= elem
.querySelector('.htmode'),
409 htval
= uci
.get('wireless', section_id
, 'htmode'),
410 hwval
= uci
.get('wireless', section_id
, 'hwmode'),
411 chval
= uci
.get('wireless', section_id
, 'channel');
413 this.setValues(mode
, this.modes
);
415 if (/VHT20|VHT40|VHT80|VHT160/.test(htval
))
417 else if (/HT20|HT40/.test(htval
))
422 this.toggleWifiMode(elem
);
429 this.toggleWifiBand(elem
);
437 renderWidget: function(section_id
, option_index
, cfgvalue
) {
441 E('label', { 'style': 'float:left; margin-right:3px' }, [
445 'style': 'width:auto',
446 'change': L
.bind(this.toggleWifiMode
, this, elem
),
447 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
450 E('label', { 'style': 'float:left; margin-right:3px' }, [
454 'style': 'width:auto',
455 'change': L
.bind(this.toggleWifiBand
, this, elem
),
456 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
459 E('label', { 'style': 'float:left; margin-right:3px' }, [
460 _('Channel'), E('br'),
463 'style': 'width:auto',
464 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
467 E('label', { 'style': 'float:left; margin-right:3px' }, [
471 'style': 'width:auto',
472 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
475 E('br', { 'style': 'clear:left' })
478 return this.setInitialValues(section_id
, elem
);
481 cfgvalue: function(section_id
) {
483 uci
.get('wireless', section_id
, 'htmode'),
484 uci
.get('wireless', section_id
, 'hwmode'),
485 uci
.get('wireless', section_id
, 'channel')
489 formvalue: function(section_id
) {
490 var node
= this.map
.findElement('data-field', this.cbid(section_id
));
493 node
.querySelector('.htmode').value
,
494 node
.querySelector('.band').value
,
495 node
.querySelector('.channel').value
499 write: function(section_id
, value
) {
500 uci
.set('wireless', section_id
, 'htmode', value
[0] || null);
501 uci
.set('wireless', section_id
, 'hwmode', value
[1]);
502 uci
.set('wireless', section_id
, 'channel', value
[2]);
506 var CBIWifiTxPowerValue
= form
.ListValue
.extend({
507 callTxPowerList
: rpc
.declare({
509 method
: 'txpowerlist',
510 params
: [ 'device' ],
511 expect
: { results
: [] }
514 load: function(section_id
) {
515 return this.callTxPowerList(section_id
).then(L
.bind(function(pwrlist
) {
516 this.powerval
= this.wifiNetwork
? this.wifiNetwork
.getTXPower() : null;
517 this.poweroff
= this.wifiNetwork
? this.wifiNetwork
.getTXPowerOffset() : null;
519 this.value('', _('driver default'));
521 for (var i
= 0; i
< pwrlist
.length
; i
++)
522 this.value(pwrlist
[i
].dbm
, '%d dBm (%d mW)'.format(pwrlist
[i
].dbm
, pwrlist
[i
].mw
));
524 return form
.ListValue
.prototype.load
.apply(this, [section_id
]);
528 renderWidget: function(section_id
, option_index
, cfgvalue
) {
529 var widget
= form
.ListValue
.prototype.renderWidget
.apply(this, [section_id
, option_index
, cfgvalue
]);
530 widget
.firstElementChild
.style
.width
= 'auto';
532 dom
.append(widget
, E('span', [
533 ' - ', _('Current power'), ': ',
534 E('span', [ this.powerval
!= null ? '%d dBm'.format(this.powerval
) : E('em', _('unknown')) ]),
535 this.poweroff
? ' + %d dB offset = %s dBm'.format(this.poweroff
, this.powerval
!= null ? this.powerval
+ this.poweroff
: '?') : ''
542 var CBIWifiCountryValue
= form
.Value
.extend({
543 callCountryList
: rpc
.declare({
545 method
: 'countrylist',
546 params
: [ 'device' ],
547 expect
: { results
: [] }
550 load: function(section_id
) {
551 return this.callCountryList(section_id
).then(L
.bind(function(countrylist
) {
552 if (Array
.isArray(countrylist
) && countrylist
.length
> 0) {
553 this.value('', _('driver default'));
555 for (var i
= 0; i
< countrylist
.length
; i
++)
556 this.value(countrylist
[i
].iso3166
, '%s - %s'.format(countrylist
[i
].iso3166
, countrylist
[i
].country
));
559 return form
.Value
.prototype.load
.apply(this, [section_id
]);
563 validate: function(section_id
, formvalue
) {
564 if (formvalue
!= null && formvalue
!= '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue
))
565 return _('Use ISO/IEC 3166 alpha2 country codes.');
570 renderWidget: function(section_id
, option_index
, cfgvalue
) {
571 var typeClass
= (this.keylist
&& this.keylist
.length
) ? form
.ListValue
: form
.Value
;
572 return typeClass
.prototype.renderWidget
.apply(this, [section_id
, option_index
, cfgvalue
]);
577 poll_status: function(map
, data
) {
578 var rows
= map
.querySelectorAll('.cbi-section-table-row[data-sid]');
580 for (var i
= 0; i
< rows
.length
; i
++) {
581 var section_id
= rows
[i
].getAttribute('data-sid'),
582 radioDev
= data
[1].filter(function(d
) { return d
.getName() == section_id
})[0],
583 radioNet
= data
[2].filter(function(n
) { return n
.getName() == section_id
})[0],
584 badge
= rows
[i
].querySelector('[data-name="_badge"] > div'),
585 stat
= rows
[i
].querySelector('[data-name="_stat"]'),
586 btns
= rows
[i
].querySelectorAll('.cbi-section-actions button'),
587 busy
= btns
[0].classList
.contains('spinning') || btns
[1].classList
.contains('spinning') || btns
[2].classList
.contains('spinning');
590 dom
.content(badge
, render_radio_badge(radioDev
));
591 dom
.content(stat
, render_radio_status(radioDev
, data
[2].filter(function(n
) { return n
.getWifiDeviceName() == radioDev
.getName() })));
594 dom
.content(badge
, render_network_badge(radioNet
));
595 dom
.content(stat
, render_network_status(radioNet
));
598 if (stat
.hasAttribute('restart'))
599 dom
.content(stat
, E('em', _('Device is restarting…')));
601 btns
[0].disabled
= isReadonlyView
|| busy
;
602 btns
[1].disabled
= (isReadonlyView
&& radioDev
) || busy
;
603 btns
[2].disabled
= isReadonlyView
|| busy
;
606 var table
= document
.querySelector('#wifi_assoclist_table'),
610 for (var i
= 0; i
< data
[3].length
; i
++) {
611 var bss
= data
[3][i
],
612 name
= hosts
.getHostnameByMACAddr(bss
.mac
),
613 ipv4
= hosts
.getIPAddrByMACAddr(bss
.mac
),
614 ipv6
= hosts
.getIP6AddrByMACAddr(bss
.mac
);
618 if (name
&& ipv4
&& ipv6
)
619 hint
= '%s <span class="hide-xs">(%s, %s)</span>'.format(name
, ipv4
, ipv6
);
620 else if (name
&& (ipv4
|| ipv6
))
621 hint
= '%s <span class="hide-xs">(%s)</span>'.format(name
, ipv4
|| ipv6
);
623 hint
= name
|| ipv4
|| ipv6
|| '?';
627 'class': 'ifacebadge',
628 'data-ifname': bss
.network
.getIfname(),
629 'data-ssid': bss
.network
.getSSID()
632 'src': L
.resource('icons/wifi%s.png').format(bss
.network
.isUp() ? '' : '_disabled'),
633 'title': bss
.radio
.getI18n()
636 ' %s '.format(bss
.network
.getShortName()),
637 E('small', '(%s)'.format(bss
.network
.getIfname()))
642 render_signal_badge(Math
.min((bss
.signal
+ 110) / 70 * 100, 100), bss
.signal
, bss
.noise
),
644 E('span', format_wifirate(bss
.rx
)),
646 E('span', format_wifirate(bss
.tx
))
650 if (bss
.network
.isClientDisconnectSupported()) {
651 if (table
.firstElementChild
.childNodes
.length
< 6)
652 table
.firstElementChild
.appendChild(E('th', { 'class': 'th cbi-section-actions'}));
654 row
.push(E('button', {
655 'class': 'cbi-button cbi-button-remove',
656 'click': L
.bind(function(net
, mac
, ev
) {
657 dom
.parent(ev
.currentTarget
, '.tr').style
.opacity
= 0.5;
658 ev
.currentTarget
.classList
.add('spinning');
659 ev
.currentTarget
.disabled
= true;
660 ev
.currentTarget
.blur();
662 net
.disconnectClient(mac
, true, 5, 60000);
663 }, this, bss
.network
, bss
.mac
),
664 'disabled': isReadonlyView
|| null
665 }, [ _('Disconnect') ]));
674 cbi_update_table(table
, trows
, E('em', _('No information available')));
676 var stat
= document
.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large');
679 render_modal_status(stat
, data
[2].filter(function(n
) { return n
.getName() == stat
.getAttribute('data-network') })[0]);
681 return network
.flushCache();
691 checkAnonymousSections: function() {
692 var wifiIfaces
= uci
.sections('wireless', 'wifi-iface');
694 for (var i
= 0; i
< wifiIfaces
.length
; i
++)
695 if (wifiIfaces
[i
]['.anonymous'])
701 callUciRename
: rpc
.declare({
704 params
: [ 'config', 'section', 'name' ]
708 if (this.checkAnonymousSections())
709 return this.renderMigration();
711 return this.renderOverview();
714 handleMigration: function(ev
) {
715 var wifiIfaces
= uci
.sections('wireless', 'wifi-iface'),
719 for (var i
= 0; i
< wifiIfaces
.length
; i
++) {
720 if (!wifiIfaces
[i
]['.anonymous'])
723 var new_name
= next_free_sid(id_offset
);
725 tasks
.push(this.callUciRename('wireless', wifiIfaces
[i
]['.name'], new_name
));
726 id_offset
= +new_name
.substring(7) + 1;
729 return Promise
.all(tasks
)
730 .then(L
.bind(ui
.changes
.init
, ui
.changes
))
731 .then(L
.bind(ui
.changes
.apply
, ui
.changes
));
734 renderMigration: function() {
735 ui
.showModal(_('Wireless configuration migration'), [
736 E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')),
737 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.')),
738 E('div', { 'class': 'right' },
740 'class': 'btn cbi-button-action important',
741 'click': ui
.createHandlerFn(this, 'handleMigration')
746 renderOverview: function() {
749 m
= new form
.Map('wireless');
753 s
= m
.section(form
.GridSection
, 'wifi-device', _('Wireless Overview'));
757 s
.load = function() {
758 return network
.getWifiDevices().then(L
.bind(function(radios
) {
759 this.radios
= radios
.sort(function(a
, b
) {
760 return a
.getName() > b
.getName();
765 for (var i
= 0; i
< radios
.length
; i
++)
766 tasks
.push(radios
[i
].getWifiNetworks());
768 return Promise
.all(tasks
);
769 }, this)).then(L
.bind(function(data
) {
772 for (var i
= 0; i
< data
.length
; i
++)
773 this.wifis
.push
.apply(this.wifis
, data
[i
]);
777 s
.cfgsections = function() {
780 for (var i
= 0; i
< this.radios
.length
; i
++) {
781 rv
.push(this.radios
[i
].getName());
783 for (var j
= 0; j
< this.wifis
.length
; j
++)
784 if (this.wifis
[j
].getWifiDeviceName() == this.radios
[i
].getName())
785 rv
.push(this.wifis
[j
].getName());
791 s
.modaltitle = function(section_id
) {
792 var radioNet
= this.wifis
.filter(function(w
) { return w
.getName() == section_id
})[0];
793 return radioNet
? radioNet
.getI18n() : _('Edit wireless network');
796 s
.lookupRadioOrNetwork = function(section_id
) {
797 var radioDev
= this.radios
.filter(function(r
) { return r
.getName() == section_id
})[0];
801 var radioNet
= this.wifis
.filter(function(w
) { return w
.getName() == section_id
})[0];
808 s
.renderRowActions = function(section_id
) {
809 var inst
= this.lookupRadioOrNetwork(section_id
), btns
;
811 if (inst
.getWifiNetworks
) {
814 'class': 'cbi-button cbi-button-neutral',
815 'title': _('Restart radio interface'),
816 'click': ui
.createHandlerFn(this, radio_restart
, section_id
)
819 'class': 'cbi-button cbi-button-action important',
820 'title': _('Find and join network'),
821 'click': ui
.createHandlerFn(this, 'handleScan', inst
)
824 'class': 'cbi-button cbi-button-add',
825 'title': _('Provide new network'),
826 'click': ui
.createHandlerFn(this, 'handleAdd', inst
)
831 var isDisabled
= (inst
.get('disabled') == '1' ||
832 uci
.get('wireless', inst
.getWifiDeviceName(), 'disabled') == '1');
836 'class': 'cbi-button cbi-button-neutral enable-disable',
837 'title': isDisabled
? _('Enable this network') : _('Disable this network'),
838 'click': ui
.createHandlerFn(this, network_updown
, section_id
, this.map
)
839 }, isDisabled
? _('Enable') : _('Disable')),
841 'class': 'cbi-button cbi-button-action important',
842 'title': _('Edit this network'),
843 'click': ui
.createHandlerFn(this, 'renderMoreOptionsModal', section_id
)
846 'class': 'cbi-button cbi-button-negative remove',
847 'title': _('Delete this network'),
848 'click': ui
.createHandlerFn(this, 'handleRemove', section_id
)
853 return E('td', { 'class': 'td middle cbi-section-actions' }, E('div', btns
));
856 s
.addModalOptions = function(s
) {
857 return network
.getWifiNetwork(s
.section
).then(function(radioNet
) {
858 var hwtype
= uci
.get('wireless', radioNet
.getWifiDeviceName(), 'type');
861 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
865 ss
.tab('general', _('General Setup'));
866 ss
.tab('advanced', _('Advanced Settings'));
868 var isDisabled
= (radioNet
.get('disabled') == '1' ||
869 uci
.get('wireless', radioNet
.getWifiDeviceName(), 'disabled') == 1);
871 o
= ss
.taboption('general', form
.DummyValue
, '_wifistat_modal', _('Status'));
872 o
.cfgvalue
= L
.bind(function(radioNet
) {
873 return render_modal_status(null, radioNet
);
875 o
.write = function() {};
877 o
= ss
.taboption('general', form
.Button
, '_toggle', isDisabled
? _('Wireless network is disabled') : _('Wireless network is enabled'));
878 o
.inputstyle
= isDisabled
? 'apply' : 'reset';
879 o
.inputtitle
= isDisabled
? _('Enable') : _('Disable');
880 o
.onclick
= ui
.createHandlerFn(s
, network_updown
, s
.section
, s
.map
);
882 o
= ss
.taboption('general', CBIWifiFrequencyValue
, '_freq', '<br />' + _('Operating frequency'));
883 o
.ucisection
= s
.section
;
885 if (hwtype
== 'mac80211') {
886 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.'));
888 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.'));
889 o
.wifiNetwork
= radioNet
;
891 o
= ss
.taboption('advanced', CBIWifiCountryValue
, 'country', _('Country Code'));
892 o
.wifiNetwork
= radioNet
;
894 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.'));
895 o
.value('0', _('Disabled'));
896 o
.value('1', _('Normal'));
897 o
.value('2', _('High'));
898 o
.value('3', _('Very High'));
900 o
= ss
.taboption('advanced', form
.Value
, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
901 o
.datatype
= 'or(range(0,114750),"auto")';
902 o
.placeholder
= 'auto';
904 o
= ss
.taboption('advanced', form
.Value
, 'frag', _('Fragmentation Threshold'));
905 o
.datatype
= 'min(256)';
906 o
.placeholder
= _('off');
908 o
= ss
.taboption('advanced', form
.Value
, 'rts', _('RTS/CTS Threshold'));
909 o
.datatype
= 'uinteger';
910 o
.placeholder
= _('off');
912 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!'));
915 o
= ss
.taboption('advanced', form
.Value
, 'beacon_int', _('Beacon Interval'));
916 o
.datatype
= 'range(15,65535)';
922 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getName(), 'wifi-iface', _('Interface Configuration'));
926 ss
.tab('general', _('General Setup'));
927 ss
.tab('encryption', _('Wireless Security'));
928 ss
.tab('macfilter', _('MAC-Filter'));
929 ss
.tab('advanced', _('Advanced Settings'));
931 o
= ss
.taboption('general', form
.ListValue
, 'mode', _('Mode'));
932 o
.value('ap', _('Access Point'));
933 o
.value('sta', _('Client'));
934 o
.value('adhoc', _('Ad-Hoc'));
936 o
= ss
.taboption('general', form
.Value
, 'mesh_id', _('Mesh Id'));
937 o
.depends('mode', 'mesh');
939 o
= ss
.taboption('advanced', form
.Flag
, 'mesh_fwding', _('Forward mesh peer traffic'));
942 o
.depends('mode', 'mesh');
944 o
= ss
.taboption('advanced', form
.Value
, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
947 o
.datatype
= 'range(-255,1)';
948 o
.depends('mode', 'mesh');
950 o
= ss
.taboption('general', form
.Value
, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
951 o
.datatype
= 'maxlength(32)';
952 o
.depends('mode', 'ap');
953 o
.depends('mode', 'sta');
954 o
.depends('mode', 'adhoc');
955 o
.depends('mode', 'ahdemo');
956 o
.depends('mode', 'monitor');
957 o
.depends('mode', 'ap-wds');
958 o
.depends('mode', 'sta-wds');
959 o
.depends('mode', 'wds');
961 o
= ss
.taboption('general', form
.Value
, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
962 o
.datatype
= 'macaddr';
964 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.'));
968 o
.write = function(section_id
, value
) {
969 return network
.getDevice(section_id
).then(L
.bind(function(dev
) {
970 var old_networks
= dev
.getNetworks().reduce(function(o
, v
) { o
[v
.getName()] = v
; return o
}, {}),
972 values
= L
.toArray(value
),
975 for (var i
= 0; i
< values
.length
; i
++) {
976 new_networks
[values
[i
]] = true;
978 if (old_networks
[values
[i
]])
981 tasks
.push(network
.getNetwork(values
[i
]).then(L
.bind(function(name
, net
) {
982 return net
|| network
.addNetwork(name
, { proto
: 'none' });
983 }, this, values
[i
])).then(L
.bind(function(dev
, net
) {
986 net
.set('type', 'bridge');
992 for (var name
in old_networks
)
993 if (!new_networks
[name
])
994 tasks
.push(network
.getNetwork(name
).then(L
.bind(function(dev
, net
) {
996 net
.deleteDevice(dev
);
999 return Promise
.all(tasks
);
1003 if (hwtype
== 'mac80211') {
1004 var mode
= ss
.children
[0],
1005 bssid
= ss
.children
[5],
1008 mode
.value('mesh', '802.11s');
1009 mode
.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
1010 mode
.value('monitor', _('Monitor'));
1012 bssid
.depends('mode', 'adhoc');
1013 bssid
.depends('mode', 'sta');
1014 bssid
.depends('mode', 'sta-wds');
1016 o
= ss
.taboption('macfilter', form
.ListValue
, 'macfilter', _('MAC-Address Filter'));
1017 o
.depends('mode', 'ap');
1018 o
.depends('mode', 'ap-wds');
1019 o
.value('', _('disable'));
1020 o
.value('allow', _('Allow listed only'));
1021 o
.value('deny', _('Allow all except listed'));
1023 o
= ss
.taboption('macfilter', form
.DynamicList
, 'maclist', _('MAC-List'));
1024 o
.datatype
= 'macaddr';
1025 o
.depends('macfilter', 'allow');
1026 o
.depends('macfilter', 'deny');
1027 o
.load = function(section_id
) {
1028 return network
.getHostHints().then(L
.bind(function(hints
) {
1029 hints
.getMACHints().map(L
.bind(function(hint
) {
1030 this.value(hint
[0], hint
[1] ? '%s (%s)'.format(hint
[0], hint
[1]) : hint
[0]);
1033 return form
.DynamicList
.prototype.load
.apply(this, [section_id
]);
1037 mode
.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
1038 mode
.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
1040 mode
.write = function(section_id
, value
) {
1043 uci
.set('wireless', section_id
, 'mode', 'ap');
1044 uci
.set('wireless', section_id
, 'wds', '1');
1048 uci
.set('wireless', section_id
, 'mode', 'sta');
1049 uci
.set('wireless', section_id
, 'wds', '1');
1053 uci
.set('wireless', section_id
, 'mode', value
);
1054 uci
.unset('wireless', section_id
, 'wds');
1059 mode
.cfgvalue = function(section_id
) {
1060 var mode
= uci
.get('wireless', section_id
, 'mode'),
1061 wds
= uci
.get('wireless', section_id
, 'wds');
1063 if (mode
== 'ap' && wds
)
1065 else if (mode
== 'sta' && wds
)
1071 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.'));
1072 o
.depends('mode', 'ap');
1073 o
.depends('mode', 'ap-wds');
1075 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.'));
1076 o
.depends('mode', 'ap');
1077 o
.depends('mode', 'ap-wds');
1078 o
.default = o
.enabled
;
1080 o
= ss
.taboption('advanced', form
.Flag
, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
1081 o
.depends('mode', 'ap');
1082 o
.depends('mode', 'ap-wds');
1084 o
= ss
.taboption('advanced', form
.Value
, 'ifname', _('Interface name'), _('Override default interface name'));
1086 o
.placeholder
= radioNet
.getIfname();
1087 if (/^radio\d+\.network/.test(o
.placeholder
))
1090 o
= ss
.taboption('advanced', form
.Flag
, 'short_preamble', _('Short Preamble'));
1091 o
.default = o
.enabled
;
1093 o
= ss
.taboption('advanced', form
.Value
, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
1096 o
.datatype
= 'range(1,255)';
1098 o
= ss
.taboption('advanced', form
.Value
, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
1100 o
.placeholder
= 600;
1101 o
.datatype
= 'uinteger';
1103 o
= ss
.taboption('advanced', form
.Flag
, 'skip_inactivity_poll', _('Disable Inactivity Polling'));
1105 o
.datatype
= 'uinteger';
1107 o
= ss
.taboption('advanced', form
.Value
, 'max_inactivity', _('Station inactivity limit'), _('sec'));
1109 o
.placeholder
= 300;
1110 o
.datatype
= 'uinteger';
1112 o
= ss
.taboption('advanced', form
.Value
, 'max_listen_interval', _('Maximum allowed Listen Interval'));
1114 o
.placeholder
= 65535;
1115 o
.datatype
= 'uinteger';
1117 o
= ss
.taboption('advanced', form
.Flag
, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
1118 o
.default = o
.enabled
;
1122 encr
= o
= ss
.taboption('encryption', form
.ListValue
, 'encryption', _('Encryption'));
1123 o
.depends('mode', 'ap');
1124 o
.depends('mode', 'sta');
1125 o
.depends('mode', 'adhoc');
1126 o
.depends('mode', 'ahdemo');
1127 o
.depends('mode', 'ap-wds');
1128 o
.depends('mode', 'sta-wds');
1129 o
.depends('mode', 'mesh');
1131 o
.cfgvalue = function(section_id
) {
1132 var v
= String(uci
.get('wireless', section_id
, 'encryption'));
1135 else if (v
.match(/\+/))
1136 return v
.replace(/\+.+$/, '');
1140 o
.write = function(section_id
, value
) {
1141 var e
= this.section
.children
.filter(function(o
) { return o
.option
== 'encryption' })[0].formvalue(section_id
),
1142 co
= this.section
.children
.filter(function(o
) { return o
.option
== 'cipher' })[0], c
= co
.formvalue(section_id
);
1144 if (value
== 'wpa' || value
== 'wpa2' || value
== 'wpa3' || value
== 'wpa3-mixed')
1145 uci
.unset('wireless', section_id
, 'key');
1147 if (co
.isActive(section_id
) && e
&& (c
== 'tkip' || c
== 'ccmp' || c
== 'tkip+ccmp'))
1150 uci
.set('wireless', section_id
, 'encryption', e
);
1153 o
= ss
.taboption('encryption', form
.ListValue
, 'cipher', _('Cipher'));
1154 o
.depends('encryption', 'wpa');
1155 o
.depends('encryption', 'wpa2');
1156 o
.depends('encryption', 'wpa3');
1157 o
.depends('encryption', 'wpa3-mixed');
1158 o
.depends('encryption', 'psk');
1159 o
.depends('encryption', 'psk2');
1160 o
.depends('encryption', 'wpa-mixed');
1161 o
.depends('encryption', 'psk-mixed');
1162 o
.value('auto', _('auto'));
1163 o
.value('ccmp', _('Force CCMP (AES)'));
1164 o
.value('tkip', _('Force TKIP'));
1165 o
.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1166 o
.write
= ss
.children
.filter(function(o
) { return o
.option
== 'encryption' })[0].write
;
1168 o
.cfgvalue = function(section_id
) {
1169 var v
= String(uci
.get('wireless', section_id
, 'encryption'));
1170 if (v
.match(/\+/)) {
1171 v
= v
.replace(/^[^+]+\+/, '');
1174 else if (v
== 'tkip+aes' || v
== 'aes+tkip' || v
== 'ccmp+tkip')
1181 var crypto_modes
= [];
1183 if (hwtype
== 'mac80211') {
1184 var has_supplicant
= L
.hasSystemFeature('wpasupplicant'),
1185 has_hostapd
= L
.hasSystemFeature('hostapd');
1187 // Probe EAP support
1188 var has_ap_eap
= L
.hasSystemFeature('hostapd', 'eap'),
1189 has_sta_eap
= L
.hasSystemFeature('wpasupplicant', 'eap');
1191 // Probe SAE support
1192 var has_ap_sae
= L
.hasSystemFeature('hostapd', 'sae'),
1193 has_sta_sae
= L
.hasSystemFeature('wpasupplicant', 'sae');
1195 // Probe OWE support
1196 var has_ap_owe
= L
.hasSystemFeature('hostapd', 'owe'),
1197 has_sta_owe
= L
.hasSystemFeature('wpasupplicant', 'owe');
1199 // Probe Suite-B support
1200 var has_ap_eap192
= L
.hasSystemFeature('hostapd', 'suiteb192'),
1201 has_sta_eap192
= L
.hasSystemFeature('wpasupplicant', 'suiteb192');
1203 // Probe WEP support
1204 var has_ap_wep
= L
.hasSystemFeature('hostapd', 'wep'),
1205 has_sta_wep
= L
.hasSystemFeature('wpasupplicant', 'wep');
1207 if (has_hostapd
|| has_supplicant
) {
1208 crypto_modes
.push(['psk2', 'WPA2-PSK', 35]);
1209 crypto_modes
.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1210 crypto_modes
.push(['psk', 'WPA-PSK', 21]);
1213 encr
.description
= _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1216 if (has_ap_sae
|| has_sta_sae
) {
1217 crypto_modes
.push(['sae', 'WPA3-SAE', 31]);
1218 crypto_modes
.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1221 if (has_ap_wep
|| has_sta_wep
) {
1222 crypto_modes
.push(['wep-open', _('WEP Open System'), 11]);
1223 crypto_modes
.push(['wep-shared', _('WEP Shared Key'), 10]);
1226 if (has_ap_eap
|| has_sta_eap
) {
1227 if (has_ap_eap192
|| has_sta_eap192
) {
1228 crypto_modes
.push(['wpa3', 'WPA3-EAP', 33]);
1229 crypto_modes
.push(['wpa3-mixed', 'WPA2-EAP/WPA3-EAP Mixed Mode', 32]);
1232 crypto_modes
.push(['wpa2', 'WPA2-EAP', 34]);
1233 crypto_modes
.push(['wpa', 'WPA-EAP', 20]);
1236 if (has_ap_owe
|| has_sta_owe
) {
1237 crypto_modes
.push(['owe', 'OWE', 1]);
1240 encr
.crypto_support
= {
1242 'wep-open': has_ap_wep
|| _('Requires hostapd with WEP support'),
1243 'wep-shared': has_ap_wep
|| _('Requires hostapd with WEP support'),
1244 'psk': has_hostapd
|| _('Requires hostapd'),
1245 'psk2': has_hostapd
|| _('Requires hostapd'),
1246 'psk-mixed': has_hostapd
|| _('Requires hostapd'),
1247 'sae': has_ap_sae
|| _('Requires hostapd with SAE support'),
1248 'sae-mixed': has_ap_sae
|| _('Requires hostapd with SAE support'),
1249 'wpa': has_ap_eap
|| _('Requires hostapd with EAP support'),
1250 'wpa2': has_ap_eap
|| _('Requires hostapd with EAP support'),
1251 'wpa3': has_ap_eap192
|| _('Requires hostapd with EAP Suite-B support'),
1252 'wpa3-mixed': has_ap_eap192
|| _('Requires hostapd with EAP Suite-B support'),
1253 'owe': has_ap_owe
|| _('Requires hostapd with OWE support')
1256 'wep-open': has_sta_wep
|| _('Requires wpa-supplicant with WEP support'),
1257 'wep-shared': has_sta_wep
|| _('Requires wpa-supplicant with WEP support'),
1258 'psk': has_supplicant
|| _('Requires wpa-supplicant'),
1259 'psk2': has_supplicant
|| _('Requires wpa-supplicant'),
1260 'psk-mixed': has_supplicant
|| _('Requires wpa-supplicant'),
1261 'sae': has_sta_sae
|| _('Requires wpa-supplicant with SAE support'),
1262 'sae-mixed': has_sta_sae
|| _('Requires wpa-supplicant with SAE support'),
1263 'wpa': has_sta_eap
|| _('Requires wpa-supplicant with EAP support'),
1264 'wpa2': has_sta_eap
|| _('Requires wpa-supplicant with EAP support'),
1265 'wpa3': has_sta_eap192
|| _('Requires wpa-supplicant with EAP Suite-B support'),
1266 'wpa3-mixed': has_sta_eap192
|| _('Requires wpa-supplicant with EAP Suite-B support'),
1267 'owe': has_sta_owe
|| _('Requires wpa-supplicant with OWE support')
1272 'psk': has_supplicant
|| _('Requires wpa-supplicant'),
1273 'psk2': has_supplicant
|| _('Requires wpa-supplicant'),
1274 'psk-mixed': has_supplicant
|| _('Requires wpa-supplicant'),
1277 'sae': has_sta_sae
|| _('Requires wpa-supplicant with SAE support')
1289 encr
.crypto_support
['ap-wds'] = encr
.crypto_support
['ap'];
1290 encr
.crypto_support
['sta-wds'] = encr
.crypto_support
['sta'];
1292 encr
.validate = function(section_id
, value
) {
1293 var modeopt
= this.section
.children
.filter(function(o
) { return o
.option
== 'mode' })[0],
1294 modeval
= modeopt
.formvalue(section_id
),
1295 modetitle
= modeopt
.vallist
[modeopt
.keylist
.indexOf(modeval
)],
1296 enctitle
= this.vallist
[this.keylist
.indexOf(value
)];
1298 if (value
== 'none')
1301 if (!L
.isObject(this.crypto_support
[modeval
]) || !this.crypto_support
[modeval
].hasOwnProperty(value
))
1302 return _('The selected %s mode is incompatible with %s encryption').format(modetitle
, enctitle
);
1304 return this.crypto_support
[modeval
][value
];
1307 else if (hwtype
== 'broadcom') {
1308 crypto_modes
.push(['psk2', 'WPA2-PSK', 33]);
1309 crypto_modes
.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1310 crypto_modes
.push(['psk', 'WPA-PSK', 21]);
1311 crypto_modes
.push(['wep-open', _('WEP Open System'), 11]);
1312 crypto_modes
.push(['wep-shared', _('WEP Shared Key'), 10]);
1315 crypto_modes
.push(['none', _('No Encryption'), 0]);
1317 crypto_modes
.sort(function(a
, b
) { return b
[2] - a
[2] });
1319 for (var i
= 0; i
< crypto_modes
.length
; i
++) {
1320 var security_level
= (crypto_modes
[i
][2] >= 30) ? _('strong security')
1321 : (crypto_modes
[i
][2] >= 20) ? _('medium security')
1322 : (crypto_modes
[i
][2] >= 10) ? _('weak security') : _('open network');
1324 encr
.value(crypto_modes
[i
][0], '%s (%s)'.format(crypto_modes
[i
][1], security_level
));
1328 o
= ss
.taboption('encryption', form
.Value
, 'auth_server', _('Radius-Authentication-Server'));
1329 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1331 o
.datatype
= 'host(0)';
1333 o
= ss
.taboption('encryption', form
.Value
, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
1334 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1336 o
.datatype
= 'port';
1338 o
= ss
.taboption('encryption', form
.Value
, 'auth_secret', _('Radius-Authentication-Secret'));
1339 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1343 o
= ss
.taboption('encryption', form
.Value
, 'acct_server', _('Radius-Accounting-Server'));
1344 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1346 o
.datatype
= 'host(0)';
1348 o
= ss
.taboption('encryption', form
.Value
, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
1349 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1351 o
.datatype
= 'port';
1353 o
= ss
.taboption('encryption', form
.Value
, 'acct_secret', _('Radius-Accounting-Secret'));
1354 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1358 o
= ss
.taboption('encryption', form
.Value
, 'dae_client', _('DAE-Client'));
1359 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1361 o
.datatype
= 'host(0)';
1363 o
= ss
.taboption('encryption', form
.Value
, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
1364 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1366 o
.datatype
= 'port';
1368 o
= ss
.taboption('encryption', form
.Value
, 'dae_secret', _('DAE-Secret'));
1369 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1374 o
= ss
.taboption('encryption', form
.Value
, '_wpa_key', _('Key'));
1375 o
.depends('encryption', 'psk');
1376 o
.depends('encryption', 'psk2');
1377 o
.depends('encryption', 'psk+psk2');
1378 o
.depends('encryption', 'psk-mixed');
1379 o
.depends('encryption', 'sae');
1380 o
.depends('encryption', 'sae-mixed');
1381 o
.datatype
= 'wpakey';
1385 o
.cfgvalue = function(section_id
) {
1386 var key
= uci
.get('wireless', section_id
, 'key');
1387 return /^[1234]$/.test(key
) ? null : key
;
1390 o
.write = function(section_id
, value
) {
1391 uci
.set('wireless', section_id
, 'key', value
);
1392 uci
.unset('wireless', section_id
, 'key1');
1393 uci
.unset('wireless', section_id
, 'key2');
1394 uci
.unset('wireless', section_id
, 'key3');
1395 uci
.unset('wireless', section_id
, 'key4');
1399 o
= ss
.taboption('encryption', form
.ListValue
, '_wep_key', _('Used Key Slot'));
1400 o
.depends('encryption', 'wep-open');
1401 o
.depends('encryption', 'wep-shared');
1402 o
.value('1', _('Key #%d').format(1));
1403 o
.value('2', _('Key #%d').format(2));
1404 o
.value('3', _('Key #%d').format(3));
1405 o
.value('4', _('Key #%d').format(4));
1407 o
.cfgvalue = function(section_id
) {
1408 var slot
= +uci
.get('wireless', section_id
, 'key');
1409 return (slot
>= 1 && slot
<= 4) ? String(slot
) : '';
1412 o
.write = function(section_id
, value
) {
1413 uci
.set('wireless', section_id
, 'key', value
);
1416 for (var slot
= 1; slot
<= 4; slot
++) {
1417 o
= ss
.taboption('encryption', form
.Value
, 'key%d'.format(slot
), _('Key #%d').format(slot
));
1418 o
.depends('encryption', 'wep-open');
1419 o
.depends('encryption', 'wep-shared');
1420 o
.datatype
= 'wepkey';
1424 o
.write = function(section_id
, value
) {
1425 if (value
!= null && (value
.length
== 5 || value
.length
== 13))
1426 value
= 's:%s'.format(value
);
1427 uci
.set('wireless', section_id
, this.option
, value
);
1432 if (hwtype
== 'mac80211') {
1433 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1434 var has_80211r
= L
.hasSystemFeature('hostapd', '11r') || L
.hasSystemFeature('hostapd', 'eap');
1436 o
= ss
.taboption('encryption', form
.Flag
, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1437 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1439 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1442 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.'));
1443 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1444 o
.depends({ ieee80211r
: '1' });
1447 o
= ss
.taboption('encryption', form
.Value
, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1448 o
.depends({ ieee80211r
: '1' });
1449 o
.placeholder
= '4f57';
1450 o
.datatype
= 'and(hexstring,length(4))';
1453 o
= ss
.taboption('encryption', form
.Value
, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1454 o
.depends({ ieee80211r
: '1' });
1455 o
.placeholder
= '1000';
1456 o
.datatype
= 'range(1000,65535)';
1459 o
= ss
.taboption('encryption', form
.ListValue
, 'ft_over_ds', _('FT protocol'));
1460 o
.depends({ ieee80211r
: '1' });
1461 o
.value('1', _('FT over DS'));
1462 o
.value('0', _('FT over the Air'));
1465 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.'));
1466 o
.depends({ ieee80211r
: '1' });
1467 o
.default = o
.enabled
;
1470 o
= ss
.taboption('encryption', form
.Value
, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1471 o
.depends({ ieee80211r
: '1' });
1472 o
.placeholder
= '10000';
1473 o
.datatype
= 'uinteger';
1476 o
= ss
.taboption('encryption', form
.Value
, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1477 o
.depends({ ieee80211r
: '1' });
1478 o
.placeholder
= '00004f577274';
1479 o
.datatype
= 'and(hexstring,length(12))';
1482 o
= ss
.taboption('encryption', form
.Flag
, 'pmk_r1_push', _('PMK R1 Push'));
1483 o
.depends({ ieee80211r
: '1' });
1484 o
.placeholder
= '0';
1487 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.'));
1488 o
.depends({ ieee80211r
: '1' });
1491 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.'));
1492 o
.depends({ ieee80211r
: '1' });
1494 // End of 802.11r options
1496 o
= ss
.taboption('encryption', form
.ListValue
, 'eap_type', _('EAP-Method'));
1497 o
.value('tls', 'TLS');
1498 o
.value('ttls', 'TTLS');
1499 o
.value('peap', 'PEAP');
1500 o
.value('fast', 'FAST');
1501 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1503 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"));
1506 o
.default = o
.disabled
;
1507 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1508 o
.validate = function(section_id
, value
) {
1509 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1510 return _("This option cannot be used because the ca-bundle package is not installed.");
1515 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert', _('Path to CA-Certificate'));
1516 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], ca_cert_usesystem
: ['0'] });
1518 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"));
1519 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1521 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"));
1522 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1524 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)"));
1525 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1527 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)"));
1528 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1530 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert', _('Path to Client-Certificate'));
1531 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1533 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key', _('Path to Private Key'));
1534 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1536 o
= ss
.taboption('encryption', form
.Value
, 'priv_key_pwd', _('Password of Private Key'));
1537 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1540 o
= ss
.taboption('encryption', form
.ListValue
, 'auth', _('Authentication'));
1541 o
.value('PAP', 'PAP');
1542 o
.value('CHAP', 'CHAP');
1543 o
.value('MSCHAP', 'MSCHAP');
1544 o
.value('MSCHAPV2', 'MSCHAPv2');
1545 o
.value('EAP-GTC', 'EAP-GTC');
1546 o
.value('EAP-MD5', 'EAP-MD5');
1547 o
.value('EAP-MSCHAPV2', 'EAP-MSCHAPv2');
1548 o
.value('EAP-TLS', 'EAP-TLS');
1549 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1551 o
.validate = function(section_id
, value
) {
1552 var eo
= this.section
.children
.filter(function(o
) { return o
.option
== 'eap_type' })[0],
1553 ev
= eo
.formvalue(section_id
);
1555 if (ev
!= 'ttls' && (value
== 'PAP' || value
== 'CHAP' || value
== 'MSCHAP' || value
== 'MSCHAPV2'))
1556 return _('This authentication type is not applicable to the selected EAP method.');
1561 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"));
1564 o
.default = o
.disabled
;
1565 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1566 o
.validate = function(section_id
, value
) {
1567 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1568 return _("This option cannot be used because the ca-bundle package is not installed.");
1573 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert2', _('Path to inner CA-Certificate'));
1574 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'], ca_cert2_usesystem
: ['0'] });
1576 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"));
1577 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1579 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"));
1580 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1582 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)"));
1583 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1585 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)"));
1586 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1588 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert2', _('Path to inner Client-Certificate'));
1589 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1591 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key2', _('Path to inner Private Key'));
1592 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1594 o
= ss
.taboption('encryption', form
.Value
, 'priv_key2_pwd', _('Password of inner Private Key'));
1595 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1598 o
= ss
.taboption('encryption', form
.Value
, 'identity', _('Identity'));
1599 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1601 o
= ss
.taboption('encryption', form
.Value
, 'anonymous_identity', _('Anonymous Identity'));
1602 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1604 o
= ss
.taboption('encryption', form
.Value
, 'password', _('Password'));
1605 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1609 if (hwtype
== 'mac80211') {
1610 // ieee802.11w options
1611 if (L
.hasSystemFeature('hostapd', '11w')) {
1612 o
= ss
.taboption('encryption', form
.ListValue
, 'ieee80211w', _('802.11w Management Frame Protection'), _("Requires the 'full' version of wpad/hostapd and support from the wifi driver <br />(as of Jan 2019: ath9k, ath10k, mwlwifi and mt76)"));
1613 o
.value('', _('Disabled'));
1614 o
.value('1', _('Optional'));
1615 o
.value('2', _('Required'));
1616 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds', 'sta', 'sta-wds'], encryption
: ['owe', 'psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1619 '2': [{ encryption
: 'sae' }, { encryption
: 'owe' }, { encryption
: 'wpa3' }, { encryption
: 'wpa3-mixed' }],
1620 '1': [{ encryption
: 'sae-mixed'}],
1624 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1625 o
.depends('ieee80211w', '1');
1626 o
.depends('ieee80211w', '2');
1627 o
.datatype
= 'uinteger';
1628 o
.placeholder
= '1000';
1631 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1632 o
.depends('ieee80211w', '1');
1633 o
.depends('ieee80211w', '2');
1634 o
.datatype
= 'uinteger';
1635 o
.placeholder
= '201';
1639 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.'));
1640 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1642 if (L
.hasSystemFeature('hostapd', 'wps') && L
.hasSystemFeature('wpasupplicant')) {
1643 o
= ss
.taboption('encryption', form
.Flag
, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1646 o
.default = o
.disabled
;
1647 o
.depends('encryption', 'psk');
1648 o
.depends('encryption', 'psk2');
1649 o
.depends('encryption', 'psk-mixed');
1650 o
.depends('encryption', 'sae');
1651 o
.depends('encryption', 'sae-mixed');
1658 s
.handleRemove = function(section_id
, ev
) {
1659 document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id
)).style
.opacity
= 0.5;
1660 return form
.TypedSection
.prototype.handleRemove
.apply(this, [section_id
, ev
]);
1663 s
.handleScan = function(radioDev
, ev
) {
1664 var table
= E('table', { 'class': 'table' }, [
1665 E('tr', { 'class': 'tr table-titles' }, [
1666 E('th', { 'class': 'th col-2 middle center' }, _('Signal')),
1667 E('th', { 'class': 'th col-4 middle left' }, _('SSID')),
1668 E('th', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1669 E('th', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1670 E('th', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1671 E('th', { 'class': 'th col-3 middle left' }, _('Encryption')),
1672 E('th', { 'class': 'th cbi-section-actions right' }, ' '),
1676 var stop
= E('button', {
1678 'click': L
.bind(this.handleScanStartStop
, this),
1679 'style': 'display:none',
1680 'data-state': 'stop'
1681 }, _('Stop refresh'));
1683 cbi_update_table(table
, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1685 var md
= ui
.showModal(_('Join Network: Wireless Scan'), [
1687 E('div', { 'class': 'right' }, [
1692 'click': L
.bind(this.handleScanAbort
, this)
1697 md
.style
.maxWidth
= '90%';
1698 md
.style
.maxHeight
= 'none';
1700 this.pollFn
= L
.bind(this.handleScanRefresh
, this, radioDev
, {}, table
, stop
);
1702 poll
.add(this.pollFn
);
1706 s
.handleScanRefresh = function(radioDev
, scanCache
, table
, stop
) {
1707 return radioDev
.getScanList().then(L
.bind(function(results
) {
1710 for (var i
= 0; i
< results
.length
; i
++)
1711 scanCache
[results
[i
].bssid
] = results
[i
];
1713 for (var k
in scanCache
)
1714 if (scanCache
[k
].stale
)
1715 results
.push(scanCache
[k
]);
1717 results
.sort(function(a
, b
) {
1718 var diff
= (b
.quality
- a
.quality
) || (a
.channel
- b
.channel
);
1723 if (a
.ssid
< b
.ssid
)
1725 else if (a
.ssid
> b
.ssid
)
1728 if (a
.bssid
< b
.bssid
)
1730 else if (a
.bssid
> b
.bssid
)
1734 for (var i
= 0; i
< results
.length
; i
++) {
1735 var res
= results
[i
],
1736 qv
= res
.quality
|| 0,
1737 qm
= res
.quality_max
|| 0,
1738 q
= (qv
> 0 && qm
> 0) ? Math
.floor((100 / qm
) * qv
) : 0,
1739 s
= res
.stale
? 'opacity:0.5' : '';
1742 E('span', { 'style': s
}, render_signal_badge(q
, res
.signal
, res
.noise
)),
1743 E('span', { 'style': s
}, (res
.ssid
!= null) ? '%h'.format(res
.ssid
) : E('em', _('hidden'))),
1744 E('span', { 'style': s
}, '%d'.format(res
.channel
)),
1745 E('span', { 'style': s
}, '%h'.format(res
.mode
)),
1746 E('span', { 'style': s
}, '%h'.format(res
.bssid
)),
1747 E('span', { 'style': s
}, '%h'.format(network
.formatWifiEncryption(res
.encryption
))),
1748 E('div', { 'class': 'right' }, E('button', {
1749 'class': 'cbi-button cbi-button-action important',
1750 'click': ui
.createHandlerFn(this, 'handleJoin', radioDev
, res
)
1751 }, _('Join Network')))
1757 cbi_update_table(table
, rows
);
1759 stop
.disabled
= false;
1760 stop
.style
.display
= '';
1761 stop
.classList
.remove('spinning');
1765 s
.handleScanStartStop = function(ev
) {
1766 var btn
= ev
.currentTarget
;
1768 if (btn
.getAttribute('data-state') == 'stop') {
1769 poll
.remove(this.pollFn
);
1770 btn
.firstChild
.data
= _('Start refresh');
1771 btn
.setAttribute('data-state', 'start');
1774 poll
.add(this.pollFn
);
1775 btn
.firstChild
.data
= _('Stop refresh');
1776 btn
.setAttribute('data-state', 'stop');
1777 btn
.classList
.add('spinning');
1778 btn
.disabled
= true;
1782 s
.handleScanAbort = function(ev
) {
1783 var md
= dom
.parent(ev
.target
, 'div[aria-modal="true"]');
1785 md
.style
.maxWidth
= '';
1786 md
.style
.maxHeight
= '';
1790 poll
.remove(this.pollFn
);
1795 s
.handleJoinConfirm = function(radioDev
, bss
, form
, ev
) {
1796 var nameopt
= L
.toArray(form
.lookupOption('name', '_new_'))[0],
1797 passopt
= L
.toArray(form
.lookupOption('password', '_new_'))[0],
1798 ssidopt
= L
.toArray(form
.lookupOption('ssid', '_new_'))[0],
1799 bssidopt
= L
.toArray(form
.lookupOption('bssid', '_new_'))[0],
1800 zoneopt
= L
.toArray(form
.lookupOption('zone', '_new_'))[0],
1801 replopt
= L
.toArray(form
.lookupOption('replace', '_new_'))[0],
1802 nameval
= (nameopt
&& nameopt
.isValid('_new_')) ? nameopt
.formvalue('_new_') : null,
1803 passval
= (passopt
&& passopt
.isValid('_new_')) ? passopt
.formvalue('_new_') : null,
1804 ssidval
= (ssidopt
&& ssidopt
.isValid('_new_')) ? ssidopt
.formvalue('_new_') : null,
1805 bssidval
= (bssidopt
&& bssidopt
.isValid('_new_')) ? bssidopt
.formvalue('_new_') : null,
1806 zoneval
= zoneopt
? zoneopt
.formvalue('_new_') : null,
1807 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
1808 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
1809 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' }).length
> 0),
1810 is_sae
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'sae' }).length
> 0);
1812 if (nameval
== null || (passopt
&& passval
== null))
1815 var section_id
= null;
1817 return this.map
.save(function() {
1818 var wifi_sections
= uci
.sections('wireless', 'wifi-iface');
1820 if (replopt
.formvalue('_new_') == '1') {
1821 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1822 if (wifi_sections
[i
].device
== radioDev
.getName())
1823 uci
.remove('wireless', wifi_sections
[i
]['.name']);
1826 if (uci
.get('wireless', radioDev
.getName(), 'disabled') == '1') {
1827 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1828 if (wifi_sections
[i
].device
== radioDev
.getName())
1829 uci
.set('wireless', wifi_sections
[i
]['.name'], 'disabled', '1');
1831 uci
.unset('wireless', radioDev
.getName(), 'disabled');
1834 section_id
= next_free_sid(wifi_sections
.length
);
1836 uci
.add('wireless', 'wifi-iface', section_id
);
1837 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
1838 uci
.set('wireless', section_id
, 'mode', (bss
.mode
== 'Ad-Hoc') ? 'adhoc' : 'sta');
1839 uci
.set('wireless', section_id
, 'network', nameval
);
1841 if (bss
.ssid
!= null) {
1842 uci
.set('wireless', section_id
, 'ssid', bss
.ssid
);
1844 if (bssidval
== '1')
1845 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
1847 else if (bss
.bssid
!= null) {
1848 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
1851 if (ssidval
!= null)
1852 uci
.set('wireless', section_id
, 'ssid', ssidval
);
1855 uci
.set('wireless', section_id
, 'encryption', 'sae');
1856 uci
.set('wireless', section_id
, 'key', passval
);
1859 for (var i
= enc
.wpa
.length
- 1; i
>= 0; i
--) {
1860 if (enc
.wpa
[i
] == 2) {
1861 uci
.set('wireless', section_id
, 'encryption', 'psk2');
1864 else if (enc
.wpa
[i
] == 1) {
1865 uci
.set('wireless', section_id
, 'encryption', 'psk');
1870 uci
.set('wireless', section_id
, 'key', passval
);
1873 uci
.set('wireless', section_id
, 'encryption', 'wep-open');
1874 uci
.set('wireless', section_id
, 'key', '1');
1875 uci
.set('wireless', section_id
, 'key1', passval
);
1878 uci
.set('wireless', section_id
, 'encryption', 'none');
1881 return network
.addNetwork(nameval
, { proto
: 'dhcp' }).then(function(net
) {
1882 firewall
.deleteNetwork(net
.getName());
1884 var zonePromise
= zoneval
1885 ? firewall
.getZone(zoneval
).then(function(zone
) { return zone
|| firewall
.addZone(zoneval
) })
1886 : Promise
.resolve();
1888 return zonePromise
.then(function(zone
) {
1890 zone
.addNetwork(net
.getName());
1893 }).then(L
.bind(function() {
1894 return this.renderMoreOptionsModal(section_id
);
1898 s
.handleJoin = function(radioDev
, bss
, ev
) {
1899 poll
.remove(this.pollFn
);
1901 var m2
= new form
.Map('wireless'),
1902 s2
= m2
.section(form
.NamedSection
, '_new_'),
1903 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
1904 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
1905 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' || a
== 'sae' })),
1906 replace
, passphrase
, name
, bssid
, zone
;
1908 var nameUsed = function(name
) {
1909 var s
= uci
.get('network', name
);
1910 if (s
!= null && s
['.type'] != 'interface')
1913 var net
= (s
!= null) ? network
.instantiateNetwork(name
) : null;
1914 return (net
!= null && !net
.isEmpty());
1917 s2
.render = function() {
1918 return Promise
.all([
1920 this.renderUCISection('_new_')
1921 ]).then(this.renderContents
.bind(this));
1924 if (bss
.ssid
== null) {
1925 name
= s2
.option(form
.Value
, 'ssid', _('Network SSID'), _('The correct SSID must be manually specified when joining a hidden wireless network'));
1926 name
.rmempty
= false;
1929 replace
= s2
.option(form
.Flag
, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
1931 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>'));
1932 name
.datatype
= 'uciname';
1933 name
.default = 'wwan';
1934 name
.rmempty
= false;
1935 name
.validate = function(section_id
, value
) {
1936 if (nameUsed(value
))
1937 return _('The network name is already used');
1942 for (var i
= 2; nameUsed(name
.default); i
++)
1943 name
.default = 'wwan%d'.format(i
);
1945 if (is_wep
|| is_psk
) {
1946 passphrase
= s2
.option(form
.Value
, 'password', is_wep
? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
1947 passphrase
.datatype
= is_wep
? 'wepkey' : 'wpakey';
1948 passphrase
.password
= true;
1949 passphrase
.rmempty
= false;
1952 if (bss
.ssid
!= null) {
1953 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
));
1954 bssid
.default = '0';
1957 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.'));
1958 zone
.default = 'wan';
1960 return m2
.render().then(L
.bind(function(nodes
) {
1961 ui
.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss
.ssid
)), [
1963 E('div', { 'class': 'right' }, [
1966 'click': ui
.hideModal
1967 }, _('Cancel')), ' ',
1969 'class': 'cbi-button cbi-button-positive important',
1970 'click': ui
.createHandlerFn(this, 'handleJoinConfirm', radioDev
, bss
, m2
)
1973 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase
|| name
).cbid('_new_'))).focus();
1977 s
.handleAdd = function(radioDev
, ev
) {
1978 var section_id
= next_free_sid(uci
.sections('wireless', 'wifi-iface').length
);
1980 uci
.unset('wireless', radioDev
.getName(), 'disabled');
1982 uci
.add('wireless', 'wifi-iface', section_id
);
1983 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
1984 uci
.set('wireless', section_id
, 'mode', 'ap');
1985 uci
.set('wireless', section_id
, 'ssid', 'OpenWrt');
1986 uci
.set('wireless', section_id
, 'encryption', 'none');
1988 this.addedSection
= section_id
;
1989 return this.renderMoreOptionsModal(section_id
);
1992 o
= s
.option(form
.DummyValue
, '_badge');
1993 o
.modalonly
= false;
1994 o
.textvalue = function(section_id
) {
1995 var inst
= this.section
.lookupRadioOrNetwork(section_id
),
1996 node
= E('div', { 'class': 'center' });
1998 if (inst
.getWifiNetworks
)
1999 node
.appendChild(render_radio_badge(inst
));
2001 node
.appendChild(render_network_badge(inst
));
2006 o
= s
.option(form
.DummyValue
, '_stat');
2007 o
.modalonly
= false;
2008 o
.textvalue = function(section_id
) {
2009 var inst
= this.section
.lookupRadioOrNetwork(section_id
);
2011 if (inst
.getWifiNetworks
)
2012 return render_radio_status(inst
, this.section
.wifis
.filter(function(e
) {
2013 return (e
.getWifiDeviceName() == inst
.getName());
2016 return render_network_status(inst
);
2019 return m
.render().then(L
.bind(function(m
, nodes
) {
2020 poll
.add(L
.bind(function() {
2021 var section_ids
= m
.children
[0].cfgsections(),
2022 tasks
= [ network
.getHostHints(), network
.getWifiDevices() ];
2024 for (var i
= 0; i
< section_ids
.length
; i
++) {
2025 var row
= nodes
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids
[i
])),
2026 dsc
= row
.querySelector('[data-name="_stat"] > div'),
2027 btns
= row
.querySelectorAll('.cbi-section-actions button');
2029 if (dsc
.getAttribute('restart') == '') {
2030 dsc
.setAttribute('restart', '1');
2031 tasks
.push(fs
.exec('/sbin/wifi', ['up', section_ids
[i
]]).catch(function(e
) {
2032 ui
.addNotification(null, E('p', e
.message
));
2035 else if (dsc
.getAttribute('restart') == '1') {
2036 dsc
.removeAttribute('restart');
2037 btns
[0].classList
.remove('spinning');
2038 btns
[0].disabled
= false;
2042 return Promise
.all(tasks
)
2043 .then(L
.bind(function(hosts_radios
) {
2046 for (var i
= 0; i
< hosts_radios
[1].length
; i
++)
2047 tasks
.push(hosts_radios
[1][i
].getWifiNetworks());
2049 return Promise
.all(tasks
).then(function(data
) {
2050 hosts_radios
[2] = [];
2052 for (var i
= 0; i
< data
.length
; i
++)
2053 hosts_radios
[2].push
.apply(hosts_radios
[2], data
[i
]);
2055 return hosts_radios
;
2058 .then(L
.bind(function(hosts_radios_wifis
) {
2061 for (var i
= 0; i
< hosts_radios_wifis
[2].length
; i
++)
2062 tasks
.push(hosts_radios_wifis
[2][i
].getAssocList());
2064 return Promise
.all(tasks
).then(function(data
) {
2065 hosts_radios_wifis
[3] = [];
2067 for (var i
= 0; i
< data
.length
; i
++) {
2068 var wifiNetwork
= hosts_radios_wifis
[2][i
],
2069 radioDev
= hosts_radios_wifis
[1].filter(function(d
) { return d
.getName() == wifiNetwork
.getWifiDeviceName() })[0];
2071 for (var j
= 0; j
< data
[i
].length
; j
++)
2072 hosts_radios_wifis
[3].push(Object
.assign({ radio
: radioDev
, network
: wifiNetwork
}, data
[i
][j
]));
2075 return hosts_radios_wifis
;
2078 .then(L
.bind(this.poll_status
, this, nodes
));
2081 var table
= E('table', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [
2082 E('tr', { 'class': 'tr table-titles' }, [
2083 E('th', { 'class': 'th nowrap' }, _('Network')),
2084 E('th', { 'class': 'th hide-xs' }, _('MAC-Address')),
2085 E('th', { 'class': 'th' }, _('Host')),
2086 E('th', { 'class': 'th' }, _('Signal / Noise')),
2087 E('th', { 'class': 'th' }, _('RX Rate / TX Rate'))
2091 cbi_update_table(table
, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
2093 return E([ nodes
, E('h3', _('Associated Stations')), table
]);