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', 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.'));
887 o
.wifiNetwork
= radioNet
;
889 o
= ss
.taboption('advanced', CBIWifiCountryValue
, 'country', _('Country Code'));
890 o
.wifiNetwork
= radioNet
;
892 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.'));
893 o
.value('0', _('Disabled'));
894 o
.value('1', _('Normal'));
895 o
.value('2', _('High'));
896 o
.value('3', _('Very High'));
898 o
= ss
.taboption('advanced', form
.Flag
, 'legacy_rates', _('Allow legacy 802.11b rates'));
899 o
.default = o
.enabled
;
901 o
= ss
.taboption('advanced', form
.Value
, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
902 o
.datatype
= 'or(range(0,114750),"auto")';
903 o
.placeholder
= 'auto';
905 o
= ss
.taboption('advanced', form
.Value
, 'frag', _('Fragmentation Threshold'));
906 o
.datatype
= 'min(256)';
907 o
.placeholder
= _('off');
909 o
= ss
.taboption('advanced', form
.Value
, 'rts', _('RTS/CTS Threshold'));
910 o
.datatype
= 'uinteger';
911 o
.placeholder
= _('off');
913 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!'));
916 o
= ss
.taboption('advanced', form
.Value
, 'beacon_int', _('Beacon Interval'));
917 o
.datatype
= 'range(15,65535)';
923 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getName(), 'wifi-iface', _('Interface Configuration'));
927 ss
.tab('general', _('General Setup'));
928 ss
.tab('encryption', _('Wireless Security'));
929 ss
.tab('macfilter', _('MAC-Filter'));
930 ss
.tab('advanced', _('Advanced Settings'));
932 o
= ss
.taboption('general', form
.ListValue
, 'mode', _('Mode'));
933 o
.value('ap', _('Access Point'));
934 o
.value('sta', _('Client'));
935 o
.value('adhoc', _('Ad-Hoc'));
937 o
= ss
.taboption('general', form
.Value
, 'mesh_id', _('Mesh Id'));
938 o
.depends('mode', 'mesh');
940 o
= ss
.taboption('advanced', form
.Flag
, 'mesh_fwding', _('Forward mesh peer traffic'));
943 o
.depends('mode', 'mesh');
945 o
= ss
.taboption('advanced', form
.Value
, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
948 o
.datatype
= 'range(-255,1)';
949 o
.depends('mode', 'mesh');
951 o
= ss
.taboption('general', form
.Value
, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
952 o
.datatype
= 'maxlength(32)';
953 o
.depends('mode', 'ap');
954 o
.depends('mode', 'sta');
955 o
.depends('mode', 'adhoc');
956 o
.depends('mode', 'ahdemo');
957 o
.depends('mode', 'monitor');
958 o
.depends('mode', 'ap-wds');
959 o
.depends('mode', 'sta-wds');
960 o
.depends('mode', 'wds');
962 o
= ss
.taboption('general', form
.Value
, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
963 o
.datatype
= 'macaddr';
965 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.'));
969 o
.write = function(section_id
, value
) {
970 return network
.getDevice(section_id
).then(L
.bind(function(dev
) {
971 var old_networks
= dev
.getNetworks().reduce(function(o
, v
) { o
[v
.getName()] = v
; return o
}, {}),
973 values
= L
.toArray(value
),
976 for (var i
= 0; i
< values
.length
; i
++) {
977 new_networks
[values
[i
]] = true;
979 if (old_networks
[values
[i
]])
982 tasks
.push(network
.getNetwork(values
[i
]).then(L
.bind(function(name
, net
) {
983 return net
|| network
.addNetwork(name
, { proto
: 'none' });
984 }, this, values
[i
])).then(L
.bind(function(dev
, net
) {
987 net
.set('type', 'bridge');
993 for (var name
in old_networks
)
994 if (!new_networks
[name
])
995 tasks
.push(network
.getNetwork(name
).then(L
.bind(function(dev
, net
) {
997 net
.deleteDevice(dev
);
1000 return Promise
.all(tasks
);
1004 if (hwtype
== 'mac80211') {
1005 var mode
= ss
.children
[0],
1006 bssid
= ss
.children
[5],
1009 mode
.value('mesh', '802.11s');
1010 mode
.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
1011 mode
.value('monitor', _('Monitor'));
1013 bssid
.depends('mode', 'adhoc');
1014 bssid
.depends('mode', 'sta');
1015 bssid
.depends('mode', 'sta-wds');
1017 o
= ss
.taboption('macfilter', form
.ListValue
, 'macfilter', _('MAC-Address Filter'));
1018 o
.depends('mode', 'ap');
1019 o
.depends('mode', 'ap-wds');
1020 o
.value('', _('disable'));
1021 o
.value('allow', _('Allow listed only'));
1022 o
.value('deny', _('Allow all except listed'));
1024 o
= ss
.taboption('macfilter', form
.DynamicList
, 'maclist', _('MAC-List'));
1025 o
.datatype
= 'macaddr';
1026 o
.depends('macfilter', 'allow');
1027 o
.depends('macfilter', 'deny');
1028 o
.load = function(section_id
) {
1029 return network
.getHostHints().then(L
.bind(function(hints
) {
1030 hints
.getMACHints().map(L
.bind(function(hint
) {
1031 this.value(hint
[0], hint
[1] ? '%s (%s)'.format(hint
[0], hint
[1]) : hint
[0]);
1034 return form
.DynamicList
.prototype.load
.apply(this, [section_id
]);
1038 mode
.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
1039 mode
.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
1041 mode
.write = function(section_id
, value
) {
1044 uci
.set('wireless', section_id
, 'mode', 'ap');
1045 uci
.set('wireless', section_id
, 'wds', '1');
1049 uci
.set('wireless', section_id
, 'mode', 'sta');
1050 uci
.set('wireless', section_id
, 'wds', '1');
1054 uci
.set('wireless', section_id
, 'mode', value
);
1055 uci
.unset('wireless', section_id
, 'wds');
1060 mode
.cfgvalue = function(section_id
) {
1061 var mode
= uci
.get('wireless', section_id
, 'mode'),
1062 wds
= uci
.get('wireless', section_id
, 'wds');
1064 if (mode
== 'ap' && wds
)
1066 else if (mode
== 'sta' && wds
)
1072 o
= ss
.taboption('general', form
.Flag
, 'hidden', _('Hide <abbr title="Extended Service Set Identifier">ESSID</abbr>'));
1073 o
.depends('mode', 'ap');
1074 o
.depends('mode', 'ap-wds');
1076 o
= ss
.taboption('general', form
.Flag
, 'wmm', _('WMM Mode'));
1077 o
.depends('mode', 'ap');
1078 o
.depends('mode', 'ap-wds');
1079 o
.default = o
.enabled
;
1081 o
= ss
.taboption('advanced', form
.Flag
, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
1082 o
.depends('mode', 'ap');
1083 o
.depends('mode', 'ap-wds');
1085 o
= ss
.taboption('advanced', form
.Value
, 'ifname', _('Interface name'), _('Override default interface name'));
1087 o
.placeholder
= radioNet
.getIfname();
1088 if (/^radio\d+\.network/.test(o
.placeholder
))
1091 o
= ss
.taboption('advanced', form
.Flag
, 'short_preamble', _('Short Preamble'));
1092 o
.default = o
.enabled
;
1094 o
= ss
.taboption('advanced', form
.Value
, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
1097 o
.datatype
= 'range(1,255)';
1099 o
= ss
.taboption('advanced', form
.Value
, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
1101 o
.placeholder
= 600;
1102 o
.datatype
= 'uinteger';
1104 o
= ss
.taboption('advanced', form
.Flag
, 'skip_inactivity_poll', _('Disable Inactivity Polling'));
1106 o
.datatype
= 'uinteger';
1108 o
= ss
.taboption('advanced', form
.Value
, 'max_inactivity', _('Station inactivity limit'), _('sec'));
1110 o
.placeholder
= 300;
1111 o
.datatype
= 'uinteger';
1113 o
= ss
.taboption('advanced', form
.Value
, 'max_listen_interval', _('Maximum allowed Listen Interval'));
1115 o
.placeholder
= 65535;
1116 o
.datatype
= 'uinteger';
1118 o
= ss
.taboption('advanced', form
.Flag
, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
1119 o
.default = o
.enabled
;
1123 encr
= o
= ss
.taboption('encryption', form
.ListValue
, 'encryption', _('Encryption'));
1124 o
.depends('mode', 'ap');
1125 o
.depends('mode', 'sta');
1126 o
.depends('mode', 'adhoc');
1127 o
.depends('mode', 'ahdemo');
1128 o
.depends('mode', 'ap-wds');
1129 o
.depends('mode', 'sta-wds');
1130 o
.depends('mode', 'mesh');
1132 o
.cfgvalue = function(section_id
) {
1133 var v
= String(uci
.get('wireless', section_id
, 'encryption'));
1136 else if (v
.match(/\+/))
1137 return v
.replace(/\+.+$/, '');
1141 o
.write = function(section_id
, value
) {
1142 var e
= this.section
.children
.filter(function(o
) { return o
.option
== 'encryption' })[0].formvalue(section_id
),
1143 co
= this.section
.children
.filter(function(o
) { return o
.option
== 'cipher' })[0], c
= co
.formvalue(section_id
);
1145 if (value
== 'wpa' || value
== 'wpa2' || value
== 'wpa3' || value
== 'wpa3-mixed')
1146 uci
.unset('wireless', section_id
, 'key');
1148 if (co
.isActive(section_id
) && e
&& (c
== 'tkip' || c
== 'ccmp' || c
== 'tkip+ccmp'))
1151 uci
.set('wireless', section_id
, 'encryption', e
);
1154 o
= ss
.taboption('encryption', form
.ListValue
, 'cipher', _('Cipher'));
1155 o
.depends('encryption', 'wpa');
1156 o
.depends('encryption', 'wpa2');
1157 o
.depends('encryption', 'wpa3');
1158 o
.depends('encryption', 'wpa3-mixed');
1159 o
.depends('encryption', 'psk');
1160 o
.depends('encryption', 'psk2');
1161 o
.depends('encryption', 'wpa-mixed');
1162 o
.depends('encryption', 'psk-mixed');
1163 o
.value('auto', _('auto'));
1164 o
.value('ccmp', _('Force CCMP (AES)'));
1165 o
.value('tkip', _('Force TKIP'));
1166 o
.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1167 o
.write
= ss
.children
.filter(function(o
) { return o
.option
== 'encryption' })[0].write
;
1169 o
.cfgvalue = function(section_id
) {
1170 var v
= String(uci
.get('wireless', section_id
, 'encryption'));
1171 if (v
.match(/\+/)) {
1172 v
= v
.replace(/^[^+]+\+/, '');
1175 else if (v
== 'tkip+aes' || v
== 'aes+tkip' || v
== 'ccmp+tkip')
1182 var crypto_modes
= [];
1184 if (hwtype
== 'mac80211') {
1185 var has_supplicant
= L
.hasSystemFeature('wpasupplicant'),
1186 has_hostapd
= L
.hasSystemFeature('hostapd');
1188 // Probe EAP support
1189 var has_ap_eap
= L
.hasSystemFeature('hostapd', 'eap'),
1190 has_sta_eap
= L
.hasSystemFeature('wpasupplicant', 'eap');
1192 // Probe SAE support
1193 var has_ap_sae
= L
.hasSystemFeature('hostapd', 'sae'),
1194 has_sta_sae
= L
.hasSystemFeature('wpasupplicant', 'sae');
1196 // Probe OWE support
1197 var has_ap_owe
= L
.hasSystemFeature('hostapd', 'owe'),
1198 has_sta_owe
= L
.hasSystemFeature('wpasupplicant', 'owe');
1200 // Probe Suite-B support
1201 var has_ap_eap192
= L
.hasSystemFeature('hostapd', 'suiteb192'),
1202 has_sta_eap192
= L
.hasSystemFeature('wpasupplicant', 'suiteb192');
1204 // Probe WEP support
1205 var has_ap_wep
= L
.hasSystemFeature('hostapd', 'wep'),
1206 has_sta_wep
= L
.hasSystemFeature('wpasupplicant', 'wep');
1208 if (has_hostapd
|| has_supplicant
) {
1209 crypto_modes
.push(['psk2', 'WPA2-PSK', 35]);
1210 crypto_modes
.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1211 crypto_modes
.push(['psk', 'WPA-PSK', 21]);
1214 encr
.description
= _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1217 if (has_ap_sae
|| has_sta_sae
) {
1218 crypto_modes
.push(['sae', 'WPA3-SAE', 31]);
1219 crypto_modes
.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1222 if (has_ap_wep
|| has_sta_wep
) {
1223 crypto_modes
.push(['wep-open', _('WEP Open System'), 11]);
1224 crypto_modes
.push(['wep-shared', _('WEP Shared Key'), 10]);
1227 if (has_ap_eap
|| has_sta_eap
) {
1228 if (has_ap_eap192
|| has_sta_eap192
) {
1229 crypto_modes
.push(['wpa3', 'WPA3-EAP', 33]);
1230 crypto_modes
.push(['wpa3-mixed', 'WPA2-EAP/WPA3-EAP Mixed Mode', 32]);
1233 crypto_modes
.push(['wpa2', 'WPA2-EAP', 34]);
1234 crypto_modes
.push(['wpa', 'WPA-EAP', 20]);
1237 if (has_ap_owe
|| has_sta_owe
) {
1238 crypto_modes
.push(['owe', 'OWE', 1]);
1241 encr
.crypto_support
= {
1243 'wep-open': has_ap_wep
|| _('Requires hostapd with WEP support'),
1244 'wep-shared': has_ap_wep
|| _('Requires hostapd with WEP support'),
1245 'psk': has_hostapd
|| _('Requires hostapd'),
1246 'psk2': has_hostapd
|| _('Requires hostapd'),
1247 'psk-mixed': has_hostapd
|| _('Requires hostapd'),
1248 'sae': has_ap_sae
|| _('Requires hostapd with SAE support'),
1249 'sae-mixed': has_ap_sae
|| _('Requires hostapd with SAE support'),
1250 'wpa': has_ap_eap
|| _('Requires hostapd with EAP support'),
1251 'wpa2': has_ap_eap
|| _('Requires hostapd with EAP support'),
1252 'wpa3': has_ap_eap192
|| _('Requires hostapd with EAP Suite-B support'),
1253 'wpa3-mixed': has_ap_eap192
|| _('Requires hostapd with EAP Suite-B support'),
1254 'owe': has_ap_owe
|| _('Requires hostapd with OWE support')
1257 'wep-open': has_sta_wep
|| _('Requires wpa-supplicant with WEP support'),
1258 'wep-shared': has_sta_wep
|| _('Requires wpa-supplicant with WEP support'),
1259 'psk': has_supplicant
|| _('Requires wpa-supplicant'),
1260 'psk2': has_supplicant
|| _('Requires wpa-supplicant'),
1261 'psk-mixed': has_supplicant
|| _('Requires wpa-supplicant'),
1262 'sae': has_sta_sae
|| _('Requires wpa-supplicant with SAE support'),
1263 'sae-mixed': has_sta_sae
|| _('Requires wpa-supplicant with SAE support'),
1264 'wpa': has_sta_eap
|| _('Requires wpa-supplicant with EAP support'),
1265 'wpa2': has_sta_eap
|| _('Requires wpa-supplicant with EAP support'),
1266 'wpa3': has_sta_eap192
|| _('Requires wpa-supplicant with EAP Suite-B support'),
1267 'wpa3-mixed': has_sta_eap192
|| _('Requires wpa-supplicant with EAP Suite-B support'),
1268 'owe': has_sta_owe
|| _('Requires wpa-supplicant with OWE support')
1273 'psk': has_supplicant
|| _('Requires wpa-supplicant'),
1274 'psk2': has_supplicant
|| _('Requires wpa-supplicant'),
1275 'psk-mixed': has_supplicant
|| _('Requires wpa-supplicant'),
1278 'sae': has_sta_sae
|| _('Requires wpa-supplicant with SAE support')
1290 encr
.crypto_support
['ap-wds'] = encr
.crypto_support
['ap'];
1291 encr
.crypto_support
['sta-wds'] = encr
.crypto_support
['sta'];
1293 encr
.validate = function(section_id
, value
) {
1294 var modeopt
= this.section
.children
.filter(function(o
) { return o
.option
== 'mode' })[0],
1295 modeval
= modeopt
.formvalue(section_id
),
1296 modetitle
= modeopt
.vallist
[modeopt
.keylist
.indexOf(modeval
)],
1297 enctitle
= this.vallist
[this.keylist
.indexOf(value
)];
1299 if (value
== 'none')
1302 if (!L
.isObject(this.crypto_support
[modeval
]) || !this.crypto_support
[modeval
].hasOwnProperty(value
))
1303 return _('The selected %s mode is incompatible with %s encryption').format(modetitle
, enctitle
);
1305 return this.crypto_support
[modeval
][value
];
1308 else if (hwtype
== 'broadcom') {
1309 crypto_modes
.push(['psk2', 'WPA2-PSK', 33]);
1310 crypto_modes
.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1311 crypto_modes
.push(['psk', 'WPA-PSK', 21]);
1312 crypto_modes
.push(['wep-open', _('WEP Open System'), 11]);
1313 crypto_modes
.push(['wep-shared', _('WEP Shared Key'), 10]);
1316 crypto_modes
.push(['none', _('No Encryption'), 0]);
1318 crypto_modes
.sort(function(a
, b
) { return b
[2] - a
[2] });
1320 for (var i
= 0; i
< crypto_modes
.length
; i
++) {
1321 var security_level
= (crypto_modes
[i
][2] >= 30) ? _('strong security')
1322 : (crypto_modes
[i
][2] >= 20) ? _('medium security')
1323 : (crypto_modes
[i
][2] >= 10) ? _('weak security') : _('open network');
1325 encr
.value(crypto_modes
[i
][0], '%s (%s)'.format(crypto_modes
[i
][1], security_level
));
1329 o
= ss
.taboption('encryption', form
.Value
, 'auth_server', _('Radius-Authentication-Server'));
1330 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1332 o
.datatype
= 'host(0)';
1334 o
= ss
.taboption('encryption', form
.Value
, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
1335 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1337 o
.datatype
= 'port';
1339 o
= ss
.taboption('encryption', form
.Value
, 'auth_secret', _('Radius-Authentication-Secret'));
1340 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1344 o
= ss
.taboption('encryption', form
.Value
, 'acct_server', _('Radius-Accounting-Server'));
1345 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1347 o
.datatype
= 'host(0)';
1349 o
= ss
.taboption('encryption', form
.Value
, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
1350 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1352 o
.datatype
= 'port';
1354 o
= ss
.taboption('encryption', form
.Value
, 'acct_secret', _('Radius-Accounting-Secret'));
1355 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1359 o
= ss
.taboption('encryption', form
.Value
, 'dae_client', _('DAE-Client'));
1360 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1362 o
.datatype
= 'host(0)';
1364 o
= ss
.taboption('encryption', form
.Value
, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
1365 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1367 o
.datatype
= 'port';
1369 o
= ss
.taboption('encryption', form
.Value
, 'dae_secret', _('DAE-Secret'));
1370 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1375 o
= ss
.taboption('encryption', form
.Value
, '_wpa_key', _('Key'));
1376 o
.depends('encryption', 'psk');
1377 o
.depends('encryption', 'psk2');
1378 o
.depends('encryption', 'psk+psk2');
1379 o
.depends('encryption', 'psk-mixed');
1380 o
.depends('encryption', 'sae');
1381 o
.depends('encryption', 'sae-mixed');
1382 o
.datatype
= 'wpakey';
1386 o
.cfgvalue = function(section_id
) {
1387 var key
= uci
.get('wireless', section_id
, 'key');
1388 return /^[1234]$/.test(key
) ? null : key
;
1391 o
.write = function(section_id
, value
) {
1392 uci
.set('wireless', section_id
, 'key', value
);
1393 uci
.unset('wireless', section_id
, 'key1');
1394 uci
.unset('wireless', section_id
, 'key2');
1395 uci
.unset('wireless', section_id
, 'key3');
1396 uci
.unset('wireless', section_id
, 'key4');
1400 o
= ss
.taboption('encryption', form
.ListValue
, '_wep_key', _('Used Key Slot'));
1401 o
.depends('encryption', 'wep-open');
1402 o
.depends('encryption', 'wep-shared');
1403 o
.value('1', _('Key #%d').format(1));
1404 o
.value('2', _('Key #%d').format(2));
1405 o
.value('3', _('Key #%d').format(3));
1406 o
.value('4', _('Key #%d').format(4));
1408 o
.cfgvalue = function(section_id
) {
1409 var slot
= +uci
.get('wireless', section_id
, 'key');
1410 return (slot
>= 1 && slot
<= 4) ? String(slot
) : '';
1413 o
.write = function(section_id
, value
) {
1414 uci
.set('wireless', section_id
, 'key', value
);
1417 for (var slot
= 1; slot
<= 4; slot
++) {
1418 o
= ss
.taboption('encryption', form
.Value
, 'key%d'.format(slot
), _('Key #%d').format(slot
));
1419 o
.depends('encryption', 'wep-open');
1420 o
.depends('encryption', 'wep-shared');
1421 o
.datatype
= 'wepkey';
1425 o
.write = function(section_id
, value
) {
1426 if (value
!= null && (value
.length
== 5 || value
.length
== 13))
1427 value
= 's:%s'.format(value
);
1428 uci
.set('wireless', section_id
, this.option
, value
);
1433 if (hwtype
== 'mac80211') {
1434 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1435 var has_80211r
= L
.hasSystemFeature('hostapd', '11r') || L
.hasSystemFeature('hostapd', 'eap');
1437 o
= ss
.taboption('encryption', form
.Flag
, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1438 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1440 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1443 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.'));
1444 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1445 o
.depends({ ieee80211r
: '1' });
1448 o
= ss
.taboption('encryption', form
.Value
, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1449 o
.depends({ ieee80211r
: '1' });
1450 o
.placeholder
= '4f57';
1451 o
.datatype
= 'and(hexstring,length(4))';
1454 o
= ss
.taboption('encryption', form
.Value
, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1455 o
.depends({ ieee80211r
: '1' });
1456 o
.placeholder
= '1000';
1457 o
.datatype
= 'range(1000,65535)';
1460 o
= ss
.taboption('encryption', form
.ListValue
, 'ft_over_ds', _('FT protocol'));
1461 o
.depends({ ieee80211r
: '1' });
1462 o
.value('1', _('FT over DS'));
1463 o
.value('0', _('FT over the Air'));
1466 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.'));
1467 o
.depends({ ieee80211r
: '1' });
1468 o
.default = o
.enabled
;
1471 o
= ss
.taboption('encryption', form
.Value
, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1472 o
.depends({ ieee80211r
: '1' });
1473 o
.placeholder
= '10000';
1474 o
.datatype
= 'uinteger';
1477 o
= ss
.taboption('encryption', form
.Value
, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1478 o
.depends({ ieee80211r
: '1' });
1479 o
.placeholder
= '00004f577274';
1480 o
.datatype
= 'and(hexstring,length(12))';
1483 o
= ss
.taboption('encryption', form
.Flag
, 'pmk_r1_push', _('PMK R1 Push'));
1484 o
.depends({ ieee80211r
: '1' });
1485 o
.placeholder
= '0';
1488 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.'));
1489 o
.depends({ ieee80211r
: '1' });
1492 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.'));
1493 o
.depends({ ieee80211r
: '1' });
1495 // End of 802.11r options
1497 o
= ss
.taboption('encryption', form
.ListValue
, 'eap_type', _('EAP-Method'));
1498 o
.value('tls', 'TLS');
1499 o
.value('ttls', 'TTLS');
1500 o
.value('peap', 'PEAP');
1501 o
.value('fast', 'FAST');
1502 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1504 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"));
1507 o
.default = o
.disabled
;
1508 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1509 o
.validate = function(section_id
, value
) {
1510 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1511 return _("This option cannot be used because the ca-bundle package is not installed.");
1516 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert', _('Path to CA-Certificate'));
1517 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], ca_cert_usesystem
: ['0'] });
1519 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"));
1520 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1522 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"));
1523 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1525 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)"));
1526 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1528 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)"));
1529 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1531 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert', _('Path to Client-Certificate'));
1532 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1534 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key', _('Path to Private Key'));
1535 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1537 o
= ss
.taboption('encryption', form
.Value
, 'priv_key_pwd', _('Password of Private Key'));
1538 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1541 o
= ss
.taboption('encryption', form
.ListValue
, 'auth', _('Authentication'));
1542 o
.value('PAP', 'PAP');
1543 o
.value('CHAP', 'CHAP');
1544 o
.value('MSCHAP', 'MSCHAP');
1545 o
.value('MSCHAPV2', 'MSCHAPv2');
1546 o
.value('EAP-GTC', 'EAP-GTC');
1547 o
.value('EAP-MD5', 'EAP-MD5');
1548 o
.value('EAP-MSCHAPV2', 'EAP-MSCHAPv2');
1549 o
.value('EAP-TLS', 'EAP-TLS');
1550 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1552 o
.validate = function(section_id
, value
) {
1553 var eo
= this.section
.children
.filter(function(o
) { return o
.option
== 'eap_type' })[0],
1554 ev
= eo
.formvalue(section_id
);
1556 if (ev
!= 'ttls' && (value
== 'PAP' || value
== 'CHAP' || value
== 'MSCHAP' || value
== 'MSCHAPV2'))
1557 return _('This authentication type is not applicable to the selected EAP method.');
1562 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"));
1565 o
.default = o
.disabled
;
1566 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1567 o
.validate = function(section_id
, value
) {
1568 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1569 return _("This option cannot be used because the ca-bundle package is not installed.");
1574 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert2', _('Path to inner CA-Certificate'));
1575 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'], ca_cert2_usesystem
: ['0'] });
1577 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"));
1578 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1580 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"));
1581 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1583 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)"));
1584 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1586 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)"));
1587 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1589 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert2', _('Path to inner Client-Certificate'));
1590 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1592 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key2', _('Path to inner Private Key'));
1593 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1595 o
= ss
.taboption('encryption', form
.Value
, 'priv_key2_pwd', _('Password of inner Private Key'));
1596 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1599 o
= ss
.taboption('encryption', form
.Value
, 'identity', _('Identity'));
1600 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1602 o
= ss
.taboption('encryption', form
.Value
, 'anonymous_identity', _('Anonymous Identity'));
1603 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1605 o
= ss
.taboption('encryption', form
.Value
, 'password', _('Password'));
1606 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1610 if (hwtype
== 'mac80211') {
1611 // ieee802.11w options
1612 if (L
.hasSystemFeature('hostapd', '11w')) {
1613 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)"));
1614 o
.value('', _('Disabled'));
1615 o
.value('1', _('Optional'));
1616 o
.value('2', _('Required'));
1617 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds', 'sta', 'sta-wds'], encryption
: ['owe', 'psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1620 '2': [{ encryption
: 'sae' }, { encryption
: 'owe' }, { encryption
: 'wpa3' }, { encryption
: 'wpa3-mixed' }],
1621 '1': [{ encryption
: 'sae-mixed'}],
1625 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1626 o
.depends('ieee80211w', '1');
1627 o
.depends('ieee80211w', '2');
1628 o
.datatype
= 'uinteger';
1629 o
.placeholder
= '1000';
1632 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1633 o
.depends('ieee80211w', '1');
1634 o
.depends('ieee80211w', '2');
1635 o
.datatype
= 'uinteger';
1636 o
.placeholder
= '201';
1640 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.'));
1641 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1643 if (L
.hasSystemFeature('hostapd', 'wps') && L
.hasSystemFeature('wpasupplicant')) {
1644 o
= ss
.taboption('encryption', form
.Flag
, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1647 o
.default = o
.disabled
;
1648 o
.depends('encryption', 'psk');
1649 o
.depends('encryption', 'psk2');
1650 o
.depends('encryption', 'psk-mixed');
1651 o
.depends('encryption', 'sae');
1652 o
.depends('encryption', 'sae-mixed');
1659 s
.handleRemove = function(section_id
, ev
) {
1660 document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id
)).style
.opacity
= 0.5;
1661 return form
.TypedSection
.prototype.handleRemove
.apply(this, [section_id
, ev
]);
1664 s
.handleScan = function(radioDev
, ev
) {
1665 var table
= E('table', { 'class': 'table' }, [
1666 E('tr', { 'class': 'tr table-titles' }, [
1667 E('th', { 'class': 'th col-2 middle center' }, _('Signal')),
1668 E('th', { 'class': 'th col-4 middle left' }, _('SSID')),
1669 E('th', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1670 E('th', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1671 E('th', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1672 E('th', { 'class': 'th col-3 middle left' }, _('Encryption')),
1673 E('th', { 'class': 'th cbi-section-actions right' }, ' '),
1677 var stop
= E('button', {
1679 'click': L
.bind(this.handleScanStartStop
, this),
1680 'style': 'display:none',
1681 'data-state': 'stop'
1682 }, _('Stop refresh'));
1684 cbi_update_table(table
, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1686 var md
= ui
.showModal(_('Join Network: Wireless Scan'), [
1688 E('div', { 'class': 'right' }, [
1693 'click': L
.bind(this.handleScanAbort
, this)
1698 md
.style
.maxWidth
= '90%';
1699 md
.style
.maxHeight
= 'none';
1701 this.pollFn
= L
.bind(this.handleScanRefresh
, this, radioDev
, {}, table
, stop
);
1703 poll
.add(this.pollFn
);
1707 s
.handleScanRefresh = function(radioDev
, scanCache
, table
, stop
) {
1708 return radioDev
.getScanList().then(L
.bind(function(results
) {
1711 for (var i
= 0; i
< results
.length
; i
++)
1712 scanCache
[results
[i
].bssid
] = results
[i
];
1714 for (var k
in scanCache
)
1715 if (scanCache
[k
].stale
)
1716 results
.push(scanCache
[k
]);
1718 results
.sort(function(a
, b
) {
1719 var diff
= (b
.quality
- a
.quality
) || (a
.channel
- b
.channel
);
1724 if (a
.ssid
< b
.ssid
)
1726 else if (a
.ssid
> b
.ssid
)
1729 if (a
.bssid
< b
.bssid
)
1731 else if (a
.bssid
> b
.bssid
)
1735 for (var i
= 0; i
< results
.length
; i
++) {
1736 var res
= results
[i
],
1737 qv
= res
.quality
|| 0,
1738 qm
= res
.quality_max
|| 0,
1739 q
= (qv
> 0 && qm
> 0) ? Math
.floor((100 / qm
) * qv
) : 0,
1740 s
= res
.stale
? 'opacity:0.5' : '';
1743 E('span', { 'style': s
}, render_signal_badge(q
, res
.signal
, res
.noise
)),
1744 E('span', { 'style': s
}, (res
.ssid
!= null) ? '%h'.format(res
.ssid
) : E('em', _('hidden'))),
1745 E('span', { 'style': s
}, '%d'.format(res
.channel
)),
1746 E('span', { 'style': s
}, '%h'.format(res
.mode
)),
1747 E('span', { 'style': s
}, '%h'.format(res
.bssid
)),
1748 E('span', { 'style': s
}, '%h'.format(network
.formatWifiEncryption(res
.encryption
))),
1749 E('div', { 'class': 'right' }, E('button', {
1750 'class': 'cbi-button cbi-button-action important',
1751 'click': ui
.createHandlerFn(this, 'handleJoin', radioDev
, res
)
1752 }, _('Join Network')))
1758 cbi_update_table(table
, rows
);
1760 stop
.disabled
= false;
1761 stop
.style
.display
= '';
1762 stop
.classList
.remove('spinning');
1766 s
.handleScanStartStop = function(ev
) {
1767 var btn
= ev
.currentTarget
;
1769 if (btn
.getAttribute('data-state') == 'stop') {
1770 poll
.remove(this.pollFn
);
1771 btn
.firstChild
.data
= _('Start refresh');
1772 btn
.setAttribute('data-state', 'start');
1775 poll
.add(this.pollFn
);
1776 btn
.firstChild
.data
= _('Stop refresh');
1777 btn
.setAttribute('data-state', 'stop');
1778 btn
.classList
.add('spinning');
1779 btn
.disabled
= true;
1783 s
.handleScanAbort = function(ev
) {
1784 var md
= dom
.parent(ev
.target
, 'div[aria-modal="true"]');
1786 md
.style
.maxWidth
= '';
1787 md
.style
.maxHeight
= '';
1791 poll
.remove(this.pollFn
);
1796 s
.handleJoinConfirm = function(radioDev
, bss
, form
, ev
) {
1797 var nameopt
= L
.toArray(form
.lookupOption('name', '_new_'))[0],
1798 passopt
= L
.toArray(form
.lookupOption('password', '_new_'))[0],
1799 ssidopt
= L
.toArray(form
.lookupOption('ssid', '_new_'))[0],
1800 bssidopt
= L
.toArray(form
.lookupOption('bssid', '_new_'))[0],
1801 zoneopt
= L
.toArray(form
.lookupOption('zone', '_new_'))[0],
1802 replopt
= L
.toArray(form
.lookupOption('replace', '_new_'))[0],
1803 nameval
= (nameopt
&& nameopt
.isValid('_new_')) ? nameopt
.formvalue('_new_') : null,
1804 passval
= (passopt
&& passopt
.isValid('_new_')) ? passopt
.formvalue('_new_') : null,
1805 ssidval
= (ssidopt
&& ssidopt
.isValid('_new_')) ? ssidopt
.formvalue('_new_') : null,
1806 bssidval
= (bssidopt
&& bssidopt
.isValid('_new_')) ? bssidopt
.formvalue('_new_') : null,
1807 zoneval
= zoneopt
? zoneopt
.formvalue('_new_') : null,
1808 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
1809 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
1810 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' }).length
> 0),
1811 is_sae
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'sae' }).length
> 0);
1813 if (nameval
== null || (passopt
&& passval
== null))
1816 var section_id
= null;
1818 return this.map
.save(function() {
1819 var wifi_sections
= uci
.sections('wireless', 'wifi-iface');
1821 if (replopt
.formvalue('_new_') == '1') {
1822 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1823 if (wifi_sections
[i
].device
== radioDev
.getName())
1824 uci
.remove('wireless', wifi_sections
[i
]['.name']);
1827 if (uci
.get('wireless', radioDev
.getName(), 'disabled') == '1') {
1828 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1829 if (wifi_sections
[i
].device
== radioDev
.getName())
1830 uci
.set('wireless', wifi_sections
[i
]['.name'], 'disabled', '1');
1832 uci
.unset('wireless', radioDev
.getName(), 'disabled');
1835 section_id
= next_free_sid(wifi_sections
.length
);
1837 uci
.add('wireless', 'wifi-iface', section_id
);
1838 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
1839 uci
.set('wireless', section_id
, 'mode', (bss
.mode
== 'Ad-Hoc') ? 'adhoc' : 'sta');
1840 uci
.set('wireless', section_id
, 'network', nameval
);
1842 if (bss
.ssid
!= null) {
1843 uci
.set('wireless', section_id
, 'ssid', bss
.ssid
);
1845 if (bssidval
== '1')
1846 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
1848 else if (bss
.bssid
!= null) {
1849 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
1852 if (ssidval
!= null)
1853 uci
.set('wireless', section_id
, 'ssid', ssidval
);
1856 uci
.set('wireless', section_id
, 'encryption', 'sae');
1857 uci
.set('wireless', section_id
, 'key', passval
);
1860 for (var i
= enc
.wpa
.length
- 1; i
>= 0; i
--) {
1861 if (enc
.wpa
[i
] == 2) {
1862 uci
.set('wireless', section_id
, 'encryption', 'psk2');
1865 else if (enc
.wpa
[i
] == 1) {
1866 uci
.set('wireless', section_id
, 'encryption', 'psk');
1871 uci
.set('wireless', section_id
, 'key', passval
);
1874 uci
.set('wireless', section_id
, 'encryption', 'wep-open');
1875 uci
.set('wireless', section_id
, 'key', '1');
1876 uci
.set('wireless', section_id
, 'key1', passval
);
1879 uci
.set('wireless', section_id
, 'encryption', 'none');
1882 return network
.addNetwork(nameval
, { proto
: 'dhcp' }).then(function(net
) {
1883 firewall
.deleteNetwork(net
.getName());
1885 var zonePromise
= zoneval
1886 ? firewall
.getZone(zoneval
).then(function(zone
) { return zone
|| firewall
.addZone(zoneval
) })
1887 : Promise
.resolve();
1889 return zonePromise
.then(function(zone
) {
1891 zone
.addNetwork(net
.getName());
1894 }).then(L
.bind(function() {
1895 return this.renderMoreOptionsModal(section_id
);
1899 s
.handleJoin = function(radioDev
, bss
, ev
) {
1900 poll
.remove(this.pollFn
);
1902 var m2
= new form
.Map('wireless'),
1903 s2
= m2
.section(form
.NamedSection
, '_new_'),
1904 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
1905 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
1906 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' || a
== 'sae' })),
1907 replace
, passphrase
, name
, bssid
, zone
;
1909 var nameUsed = function(name
) {
1910 var s
= uci
.get('network', name
);
1911 if (s
!= null && s
['.type'] != 'interface')
1914 var net
= (s
!= null) ? network
.instantiateNetwork(name
) : null;
1915 return (net
!= null && !net
.isEmpty());
1918 s2
.render = function() {
1919 return Promise
.all([
1921 this.renderUCISection('_new_')
1922 ]).then(this.renderContents
.bind(this));
1925 if (bss
.ssid
== null) {
1926 name
= s2
.option(form
.Value
, 'ssid', _('Network SSID'), _('The correct SSID must be manually specified when joining a hidden wireless network'));
1927 name
.rmempty
= false;
1930 replace
= s2
.option(form
.Flag
, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
1932 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>'));
1933 name
.datatype
= 'uciname';
1934 name
.default = 'wwan';
1935 name
.rmempty
= false;
1936 name
.validate = function(section_id
, value
) {
1937 if (nameUsed(value
))
1938 return _('The network name is already used');
1943 for (var i
= 2; nameUsed(name
.default); i
++)
1944 name
.default = 'wwan%d'.format(i
);
1946 if (is_wep
|| is_psk
) {
1947 passphrase
= s2
.option(form
.Value
, 'password', is_wep
? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
1948 passphrase
.datatype
= is_wep
? 'wepkey' : 'wpakey';
1949 passphrase
.password
= true;
1950 passphrase
.rmempty
= false;
1953 if (bss
.ssid
!= null) {
1954 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
));
1955 bssid
.default = '0';
1958 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.'));
1959 zone
.default = 'wan';
1961 return m2
.render().then(L
.bind(function(nodes
) {
1962 ui
.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss
.ssid
)), [
1964 E('div', { 'class': 'right' }, [
1967 'click': ui
.hideModal
1968 }, _('Cancel')), ' ',
1970 'class': 'cbi-button cbi-button-positive important',
1971 'click': ui
.createHandlerFn(this, 'handleJoinConfirm', radioDev
, bss
, m2
)
1974 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase
|| name
).cbid('_new_'))).focus();
1978 s
.handleAdd = function(radioDev
, ev
) {
1979 var section_id
= next_free_sid(uci
.sections('wireless', 'wifi-iface').length
);
1981 uci
.unset('wireless', radioDev
.getName(), 'disabled');
1983 uci
.add('wireless', 'wifi-iface', section_id
);
1984 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
1985 uci
.set('wireless', section_id
, 'mode', 'ap');
1986 uci
.set('wireless', section_id
, 'ssid', 'OpenWrt');
1987 uci
.set('wireless', section_id
, 'encryption', 'none');
1989 this.addedSection
= section_id
;
1990 return this.renderMoreOptionsModal(section_id
);
1993 o
= s
.option(form
.DummyValue
, '_badge');
1994 o
.modalonly
= false;
1995 o
.textvalue = function(section_id
) {
1996 var inst
= this.section
.lookupRadioOrNetwork(section_id
),
1997 node
= E('div', { 'class': 'center' });
1999 if (inst
.getWifiNetworks
)
2000 node
.appendChild(render_radio_badge(inst
));
2002 node
.appendChild(render_network_badge(inst
));
2007 o
= s
.option(form
.DummyValue
, '_stat');
2008 o
.modalonly
= false;
2009 o
.textvalue = function(section_id
) {
2010 var inst
= this.section
.lookupRadioOrNetwork(section_id
);
2012 if (inst
.getWifiNetworks
)
2013 return render_radio_status(inst
, this.section
.wifis
.filter(function(e
) {
2014 return (e
.getWifiDeviceName() == inst
.getName());
2017 return render_network_status(inst
);
2020 return m
.render().then(L
.bind(function(m
, nodes
) {
2021 poll
.add(L
.bind(function() {
2022 var section_ids
= m
.children
[0].cfgsections(),
2023 tasks
= [ network
.getHostHints(), network
.getWifiDevices() ];
2025 for (var i
= 0; i
< section_ids
.length
; i
++) {
2026 var row
= nodes
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids
[i
])),
2027 dsc
= row
.querySelector('[data-name="_stat"] > div'),
2028 btns
= row
.querySelectorAll('.cbi-section-actions button');
2030 if (dsc
.getAttribute('restart') == '') {
2031 dsc
.setAttribute('restart', '1');
2032 tasks
.push(fs
.exec('/sbin/wifi', ['up', section_ids
[i
]]).catch(function(e
) {
2033 ui
.addNotification(null, E('p', e
.message
));
2036 else if (dsc
.getAttribute('restart') == '1') {
2037 dsc
.removeAttribute('restart');
2038 btns
[0].classList
.remove('spinning');
2039 btns
[0].disabled
= false;
2043 return Promise
.all(tasks
)
2044 .then(L
.bind(function(hosts_radios
) {
2047 for (var i
= 0; i
< hosts_radios
[1].length
; i
++)
2048 tasks
.push(hosts_radios
[1][i
].getWifiNetworks());
2050 return Promise
.all(tasks
).then(function(data
) {
2051 hosts_radios
[2] = [];
2053 for (var i
= 0; i
< data
.length
; i
++)
2054 hosts_radios
[2].push
.apply(hosts_radios
[2], data
[i
]);
2056 return hosts_radios
;
2059 .then(L
.bind(function(hosts_radios_wifis
) {
2062 for (var i
= 0; i
< hosts_radios_wifis
[2].length
; i
++)
2063 tasks
.push(hosts_radios_wifis
[2][i
].getAssocList());
2065 return Promise
.all(tasks
).then(function(data
) {
2066 hosts_radios_wifis
[3] = [];
2068 for (var i
= 0; i
< data
.length
; i
++) {
2069 var wifiNetwork
= hosts_radios_wifis
[2][i
],
2070 radioDev
= hosts_radios_wifis
[1].filter(function(d
) { return d
.getName() == wifiNetwork
.getWifiDeviceName() })[0];
2072 for (var j
= 0; j
< data
[i
].length
; j
++)
2073 hosts_radios_wifis
[3].push(Object
.assign({ radio
: radioDev
, network
: wifiNetwork
}, data
[i
][j
]));
2076 return hosts_radios_wifis
;
2079 .then(L
.bind(this.poll_status
, this, nodes
));
2082 var table
= E('table', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [
2083 E('tr', { 'class': 'tr table-titles' }, [
2084 E('th', { 'class': 'th nowrap' }, _('Network')),
2085 E('th', { 'class': 'th hide-xs' }, _('MAC-Address')),
2086 E('th', { 'class': 'th' }, _('Host')),
2087 E('th', { 'class': 'th' }, _('Signal / Noise')),
2088 E('th', { 'class': 'th' }, _('RX Rate / TX Rate'))
2092 cbi_update_table(table
, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
2094 return E([ nodes
, E('h3', _('Associated Stations')), table
]);