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('div', { '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('div', { '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
.Flag
, 'legacy_rates', _('Allow legacy 802.11b rates'));
893 o
.default = o
.enabled
;
895 o
= ss
.taboption('advanced', form
.Value
, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
896 o
.datatype
= 'or(range(0,114750),"auto")';
897 o
.placeholder
= 'auto';
899 o
= ss
.taboption('advanced', form
.Value
, 'frag', _('Fragmentation Threshold'));
900 o
.datatype
= 'min(256)';
901 o
.placeholder
= _('off');
903 o
= ss
.taboption('advanced', form
.Value
, 'rts', _('RTS/CTS Threshold'));
904 o
.datatype
= 'uinteger';
905 o
.placeholder
= _('off');
907 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!'));
910 o
= ss
.taboption('advanced', form
.Value
, 'beacon_int', _('Beacon Interval'));
911 o
.datatype
= 'range(15,65535)';
917 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getName(), 'wifi-iface', _('Interface Configuration'));
921 ss
.tab('general', _('General Setup'));
922 ss
.tab('encryption', _('Wireless Security'));
923 ss
.tab('macfilter', _('MAC-Filter'));
924 ss
.tab('advanced', _('Advanced Settings'));
926 o
= ss
.taboption('general', form
.ListValue
, 'mode', _('Mode'));
927 o
.value('ap', _('Access Point'));
928 o
.value('sta', _('Client'));
929 o
.value('adhoc', _('Ad-Hoc'));
931 o
= ss
.taboption('general', form
.Value
, 'mesh_id', _('Mesh Id'));
932 o
.depends('mode', 'mesh');
934 o
= ss
.taboption('advanced', form
.Flag
, 'mesh_fwding', _('Forward mesh peer traffic'));
937 o
.depends('mode', 'mesh');
939 o
= ss
.taboption('advanced', form
.Value
, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
942 o
.datatype
= 'range(-255,1)';
943 o
.depends('mode', 'mesh');
945 o
= ss
.taboption('general', form
.Value
, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
946 o
.datatype
= 'maxlength(32)';
947 o
.depends('mode', 'ap');
948 o
.depends('mode', 'sta');
949 o
.depends('mode', 'adhoc');
950 o
.depends('mode', 'ahdemo');
951 o
.depends('mode', 'monitor');
952 o
.depends('mode', 'ap-wds');
953 o
.depends('mode', 'sta-wds');
954 o
.depends('mode', 'wds');
956 o
= ss
.taboption('general', form
.Value
, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
957 o
.datatype
= 'macaddr';
959 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.'));
963 o
.write = function(section_id
, value
) {
964 return network
.getDevice(section_id
).then(L
.bind(function(dev
) {
965 var old_networks
= dev
.getNetworks().reduce(function(o
, v
) { o
[v
.getName()] = v
; return o
}, {}),
967 values
= L
.toArray(value
),
970 for (var i
= 0; i
< values
.length
; i
++) {
971 new_networks
[values
[i
]] = true;
973 if (old_networks
[values
[i
]])
976 tasks
.push(network
.getNetwork(values
[i
]).then(L
.bind(function(name
, net
) {
977 return net
|| network
.addNetwork(name
, { proto
: 'none' });
978 }, this, values
[i
])).then(L
.bind(function(dev
, net
) {
981 net
.set('type', 'bridge');
987 for (var name
in old_networks
)
988 if (!new_networks
[name
])
989 tasks
.push(network
.getNetwork(name
).then(L
.bind(function(dev
, net
) {
991 net
.deleteDevice(dev
);
994 return Promise
.all(tasks
);
998 if (hwtype
== 'mac80211') {
999 var mode
= ss
.children
[0],
1000 bssid
= ss
.children
[5],
1003 mode
.value('mesh', '802.11s');
1004 mode
.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
1005 mode
.value('monitor', _('Monitor'));
1007 bssid
.depends('mode', 'adhoc');
1008 bssid
.depends('mode', 'sta');
1009 bssid
.depends('mode', 'sta-wds');
1011 o
= ss
.taboption('macfilter', form
.ListValue
, 'macfilter', _('MAC-Address Filter'));
1012 o
.depends('mode', 'ap');
1013 o
.depends('mode', 'ap-wds');
1014 o
.value('', _('disable'));
1015 o
.value('allow', _('Allow listed only'));
1016 o
.value('deny', _('Allow all except listed'));
1018 o
= ss
.taboption('macfilter', form
.DynamicList
, 'maclist', _('MAC-List'));
1019 o
.datatype
= 'macaddr';
1020 o
.depends('macfilter', 'allow');
1021 o
.depends('macfilter', 'deny');
1022 o
.load = function(section_id
) {
1023 return network
.getHostHints().then(L
.bind(function(hints
) {
1024 hints
.getMACHints().map(L
.bind(function(hint
) {
1025 this.value(hint
[0], hint
[1] ? '%s (%s)'.format(hint
[0], hint
[1]) : hint
[0]);
1028 return form
.DynamicList
.prototype.load
.apply(this, [section_id
]);
1032 mode
.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
1033 mode
.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
1035 mode
.write = function(section_id
, value
) {
1038 uci
.set('wireless', section_id
, 'mode', 'ap');
1039 uci
.set('wireless', section_id
, 'wds', '1');
1043 uci
.set('wireless', section_id
, 'mode', 'sta');
1044 uci
.set('wireless', section_id
, 'wds', '1');
1048 uci
.set('wireless', section_id
, 'mode', value
);
1049 uci
.unset('wireless', section_id
, 'wds');
1054 mode
.cfgvalue = function(section_id
) {
1055 var mode
= uci
.get('wireless', section_id
, 'mode'),
1056 wds
= uci
.get('wireless', section_id
, 'wds');
1058 if (mode
== 'ap' && wds
)
1060 else if (mode
== 'sta' && wds
)
1066 o
= ss
.taboption('general', form
.Flag
, 'hidden', _('Hide <abbr title="Extended Service Set Identifier">ESSID</abbr>'));
1067 o
.depends('mode', 'ap');
1068 o
.depends('mode', 'ap-wds');
1070 o
= ss
.taboption('general', form
.Flag
, 'wmm', _('WMM Mode'));
1071 o
.depends('mode', 'ap');
1072 o
.depends('mode', 'ap-wds');
1073 o
.default = o
.enabled
;
1075 o
= ss
.taboption('advanced', form
.Flag
, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
1076 o
.depends('mode', 'ap');
1077 o
.depends('mode', 'ap-wds');
1079 o
= ss
.taboption('advanced', form
.Value
, 'ifname', _('Interface name'), _('Override default interface name'));
1081 o
.placeholder
= radioNet
.getIfname();
1082 if (/^radio\d+\.network/.test(o
.placeholder
))
1085 o
= ss
.taboption('advanced', form
.Flag
, 'short_preamble', _('Short Preamble'));
1086 o
.default = o
.enabled
;
1088 o
= ss
.taboption('advanced', form
.Value
, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
1091 o
.datatype
= 'range(1,255)';
1093 o
= ss
.taboption('advanced', form
.Value
, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
1095 o
.placeholder
= 600;
1096 o
.datatype
= 'uinteger';
1098 o
= ss
.taboption('advanced', form
.Flag
, 'skip_inactivity_poll', _('Disable Inactivity Polling'));
1100 o
.datatype
= 'uinteger';
1102 o
= ss
.taboption('advanced', form
.Value
, 'max_inactivity', _('Station inactivity limit'), _('sec'));
1104 o
.placeholder
= 300;
1105 o
.datatype
= 'uinteger';
1107 o
= ss
.taboption('advanced', form
.Value
, 'max_listen_interval', _('Maximum allowed Listen Interval'));
1109 o
.placeholder
= 65535;
1110 o
.datatype
= 'uinteger';
1112 o
= ss
.taboption('advanced', form
.Flag
, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
1113 o
.default = o
.enabled
;
1117 encr
= o
= ss
.taboption('encryption', form
.ListValue
, 'encryption', _('Encryption'));
1118 o
.depends('mode', 'ap');
1119 o
.depends('mode', 'sta');
1120 o
.depends('mode', 'adhoc');
1121 o
.depends('mode', 'ahdemo');
1122 o
.depends('mode', 'ap-wds');
1123 o
.depends('mode', 'sta-wds');
1124 o
.depends('mode', 'mesh');
1126 o
.cfgvalue = function(section_id
) {
1127 var v
= String(uci
.get('wireless', section_id
, 'encryption'));
1130 else if (v
.match(/\+/))
1131 return v
.replace(/\+.+$/, '');
1135 o
.write = function(section_id
, value
) {
1136 var e
= this.section
.children
.filter(function(o
) { return o
.option
== 'encryption' })[0].formvalue(section_id
),
1137 co
= this.section
.children
.filter(function(o
) { return o
.option
== 'cipher' })[0], c
= co
.formvalue(section_id
);
1139 if (value
== 'wpa' || value
== 'wpa2' || value
== 'wpa3' || value
== 'wpa3-mixed')
1140 uci
.unset('wireless', section_id
, 'key');
1142 if (co
.isActive(section_id
) && e
&& (c
== 'tkip' || c
== 'ccmp' || c
== 'tkip+ccmp'))
1145 uci
.set('wireless', section_id
, 'encryption', e
);
1148 o
= ss
.taboption('encryption', form
.ListValue
, 'cipher', _('Cipher'));
1149 o
.depends('encryption', 'wpa');
1150 o
.depends('encryption', 'wpa2');
1151 o
.depends('encryption', 'wpa3');
1152 o
.depends('encryption', 'wpa3-mixed');
1153 o
.depends('encryption', 'psk');
1154 o
.depends('encryption', 'psk2');
1155 o
.depends('encryption', 'wpa-mixed');
1156 o
.depends('encryption', 'psk-mixed');
1157 o
.value('auto', _('auto'));
1158 o
.value('ccmp', _('Force CCMP (AES)'));
1159 o
.value('tkip', _('Force TKIP'));
1160 o
.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1161 o
.write
= ss
.children
.filter(function(o
) { return o
.option
== 'encryption' })[0].write
;
1163 o
.cfgvalue = function(section_id
) {
1164 var v
= String(uci
.get('wireless', section_id
, 'encryption'));
1165 if (v
.match(/\+/)) {
1166 v
= v
.replace(/^[^+]+\+/, '');
1169 else if (v
== 'tkip+aes' || v
== 'aes+tkip' || v
== 'ccmp+tkip')
1176 var crypto_modes
= [];
1178 if (hwtype
== 'mac80211') {
1179 var has_supplicant
= L
.hasSystemFeature('wpasupplicant'),
1180 has_hostapd
= L
.hasSystemFeature('hostapd');
1182 // Probe EAP support
1183 var has_ap_eap
= L
.hasSystemFeature('hostapd', 'eap'),
1184 has_sta_eap
= L
.hasSystemFeature('wpasupplicant', 'eap');
1186 // Probe SAE support
1187 var has_ap_sae
= L
.hasSystemFeature('hostapd', 'sae'),
1188 has_sta_sae
= L
.hasSystemFeature('wpasupplicant', 'sae');
1190 // Probe OWE support
1191 var has_ap_owe
= L
.hasSystemFeature('hostapd', 'owe'),
1192 has_sta_owe
= L
.hasSystemFeature('wpasupplicant', 'owe');
1194 // Probe Suite-B support
1195 var has_ap_eap192
= L
.hasSystemFeature('hostapd', 'suiteb192'),
1196 has_sta_eap192
= L
.hasSystemFeature('wpasupplicant', 'suiteb192');
1198 // Probe WEP support
1199 var has_ap_wep
= L
.hasSystemFeature('hostapd', 'wep'),
1200 has_sta_wep
= L
.hasSystemFeature('wpasupplicant', 'wep');
1202 if (has_hostapd
|| has_supplicant
) {
1203 crypto_modes
.push(['psk2', 'WPA2-PSK', 35]);
1204 crypto_modes
.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1205 crypto_modes
.push(['psk', 'WPA-PSK', 21]);
1208 encr
.description
= _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1211 if (has_ap_sae
|| has_sta_sae
) {
1212 crypto_modes
.push(['sae', 'WPA3-SAE', 31]);
1213 crypto_modes
.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1216 if (has_ap_wep
|| has_sta_wep
) {
1217 crypto_modes
.push(['wep-open', _('WEP Open System'), 11]);
1218 crypto_modes
.push(['wep-shared', _('WEP Shared Key'), 10]);
1221 if (has_ap_eap
|| has_sta_eap
) {
1222 if (has_ap_eap192
|| has_sta_eap192
) {
1223 crypto_modes
.push(['wpa3', 'WPA3-EAP', 33]);
1224 crypto_modes
.push(['wpa3-mixed', 'WPA2-EAP/WPA3-EAP Mixed Mode', 32]);
1227 crypto_modes
.push(['wpa2', 'WPA2-EAP', 34]);
1228 crypto_modes
.push(['wpa', 'WPA-EAP', 20]);
1231 if (has_ap_owe
|| has_sta_owe
) {
1232 crypto_modes
.push(['owe', 'OWE', 1]);
1235 encr
.crypto_support
= {
1237 'wep-open': has_ap_wep
|| _('Requires hostapd with WEP support'),
1238 'wep-shared': has_ap_wep
|| _('Requires hostapd with WEP support'),
1239 'psk': has_hostapd
|| _('Requires hostapd'),
1240 'psk2': has_hostapd
|| _('Requires hostapd'),
1241 'psk-mixed': has_hostapd
|| _('Requires hostapd'),
1242 'sae': has_ap_sae
|| _('Requires hostapd with SAE support'),
1243 'sae-mixed': has_ap_sae
|| _('Requires hostapd with SAE support'),
1244 'wpa': has_ap_eap
|| _('Requires hostapd with EAP support'),
1245 'wpa2': has_ap_eap
|| _('Requires hostapd with EAP support'),
1246 'wpa3': has_ap_eap192
|| _('Requires hostapd with EAP Suite-B support'),
1247 'wpa3-mixed': has_ap_eap192
|| _('Requires hostapd with EAP Suite-B support'),
1248 'owe': has_ap_owe
|| _('Requires hostapd with OWE support')
1251 'wep-open': has_sta_wep
|| _('Requires wpa-supplicant with WEP support'),
1252 'wep-shared': has_sta_wep
|| _('Requires wpa-supplicant with WEP support'),
1253 'psk': has_supplicant
|| _('Requires wpa-supplicant'),
1254 'psk2': has_supplicant
|| _('Requires wpa-supplicant'),
1255 'psk-mixed': has_supplicant
|| _('Requires wpa-supplicant'),
1256 'sae': has_sta_sae
|| _('Requires wpa-supplicant with SAE support'),
1257 'sae-mixed': has_sta_sae
|| _('Requires wpa-supplicant with SAE support'),
1258 'wpa': has_sta_eap
|| _('Requires wpa-supplicant with EAP support'),
1259 'wpa2': has_sta_eap
|| _('Requires wpa-supplicant with EAP support'),
1260 'wpa3': has_sta_eap192
|| _('Requires wpa-supplicant with EAP Suite-B support'),
1261 'wpa3-mixed': has_sta_eap192
|| _('Requires wpa-supplicant with EAP Suite-B support'),
1262 'owe': has_sta_owe
|| _('Requires wpa-supplicant with OWE support')
1267 'psk': has_supplicant
|| _('Requires wpa-supplicant'),
1268 'psk2': has_supplicant
|| _('Requires wpa-supplicant'),
1269 'psk-mixed': has_supplicant
|| _('Requires wpa-supplicant'),
1272 'sae': has_sta_sae
|| _('Requires wpa-supplicant with SAE support')
1284 encr
.crypto_support
['ap-wds'] = encr
.crypto_support
['ap'];
1285 encr
.crypto_support
['sta-wds'] = encr
.crypto_support
['sta'];
1287 encr
.validate = function(section_id
, value
) {
1288 var modeopt
= this.section
.children
.filter(function(o
) { return o
.option
== 'mode' })[0],
1289 modeval
= modeopt
.formvalue(section_id
),
1290 modetitle
= modeopt
.vallist
[modeopt
.keylist
.indexOf(modeval
)],
1291 enctitle
= this.vallist
[this.keylist
.indexOf(value
)];
1293 if (value
== 'none')
1296 if (!L
.isObject(this.crypto_support
[modeval
]) || !this.crypto_support
[modeval
].hasOwnProperty(value
))
1297 return _('The selected %s mode is incompatible with %s encryption').format(modetitle
, enctitle
);
1299 return this.crypto_support
[modeval
][value
];
1302 else if (hwtype
== 'broadcom') {
1303 crypto_modes
.push(['psk2', 'WPA2-PSK', 33]);
1304 crypto_modes
.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1305 crypto_modes
.push(['psk', 'WPA-PSK', 21]);
1306 crypto_modes
.push(['wep-open', _('WEP Open System'), 11]);
1307 crypto_modes
.push(['wep-shared', _('WEP Shared Key'), 10]);
1310 crypto_modes
.push(['none', _('No Encryption'), 0]);
1312 crypto_modes
.sort(function(a
, b
) { return b
[2] - a
[2] });
1314 for (var i
= 0; i
< crypto_modes
.length
; i
++) {
1315 var security_level
= (crypto_modes
[i
][2] >= 30) ? _('strong security')
1316 : (crypto_modes
[i
][2] >= 20) ? _('medium security')
1317 : (crypto_modes
[i
][2] >= 10) ? _('weak security') : _('open network');
1319 encr
.value(crypto_modes
[i
][0], '%s (%s)'.format(crypto_modes
[i
][1], security_level
));
1323 o
= ss
.taboption('encryption', form
.Value
, 'auth_server', _('Radius-Authentication-Server'));
1324 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1326 o
.datatype
= 'host(0)';
1328 o
= ss
.taboption('encryption', form
.Value
, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
1329 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1331 o
.datatype
= 'port';
1333 o
= ss
.taboption('encryption', form
.Value
, 'auth_secret', _('Radius-Authentication-Secret'));
1334 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1338 o
= ss
.taboption('encryption', form
.Value
, 'acct_server', _('Radius-Accounting-Server'));
1339 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1341 o
.datatype
= 'host(0)';
1343 o
= ss
.taboption('encryption', form
.Value
, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
1344 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1346 o
.datatype
= 'port';
1348 o
= ss
.taboption('encryption', form
.Value
, 'acct_secret', _('Radius-Accounting-Secret'));
1349 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1353 o
= ss
.taboption('encryption', form
.Value
, 'dae_client', _('DAE-Client'));
1354 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1356 o
.datatype
= 'host(0)';
1358 o
= ss
.taboption('encryption', form
.Value
, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
1359 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1361 o
.datatype
= 'port';
1363 o
= ss
.taboption('encryption', form
.Value
, 'dae_secret', _('DAE-Secret'));
1364 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1369 o
= ss
.taboption('encryption', form
.Value
, '_wpa_key', _('Key'));
1370 o
.depends('encryption', 'psk');
1371 o
.depends('encryption', 'psk2');
1372 o
.depends('encryption', 'psk+psk2');
1373 o
.depends('encryption', 'psk-mixed');
1374 o
.depends('encryption', 'sae');
1375 o
.depends('encryption', 'sae-mixed');
1376 o
.datatype
= 'wpakey';
1380 o
.cfgvalue = function(section_id
) {
1381 var key
= uci
.get('wireless', section_id
, 'key');
1382 return /^[1234]$/.test(key
) ? null : key
;
1385 o
.write = function(section_id
, value
) {
1386 uci
.set('wireless', section_id
, 'key', value
);
1387 uci
.unset('wireless', section_id
, 'key1');
1388 uci
.unset('wireless', section_id
, 'key2');
1389 uci
.unset('wireless', section_id
, 'key3');
1390 uci
.unset('wireless', section_id
, 'key4');
1394 o
= ss
.taboption('encryption', form
.ListValue
, '_wep_key', _('Used Key Slot'));
1395 o
.depends('encryption', 'wep-open');
1396 o
.depends('encryption', 'wep-shared');
1397 o
.value('1', _('Key #%d').format(1));
1398 o
.value('2', _('Key #%d').format(2));
1399 o
.value('3', _('Key #%d').format(3));
1400 o
.value('4', _('Key #%d').format(4));
1402 o
.cfgvalue = function(section_id
) {
1403 var slot
= +uci
.get('wireless', section_id
, 'key');
1404 return (slot
>= 1 && slot
<= 4) ? String(slot
) : '';
1407 o
.write = function(section_id
, value
) {
1408 uci
.set('wireless', section_id
, 'key', value
);
1411 for (var slot
= 1; slot
<= 4; slot
++) {
1412 o
= ss
.taboption('encryption', form
.Value
, 'key%d'.format(slot
), _('Key #%d').format(slot
));
1413 o
.depends('encryption', 'wep-open');
1414 o
.depends('encryption', 'wep-shared');
1415 o
.datatype
= 'wepkey';
1419 o
.write = function(section_id
, value
) {
1420 if (value
!= null && (value
.length
== 5 || value
.length
== 13))
1421 value
= 's:%s'.format(value
);
1422 uci
.set('wireless', section_id
, this.option
, value
);
1427 if (hwtype
== 'mac80211') {
1428 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1429 var has_80211r
= L
.hasSystemFeature('hostapd', '11r') || L
.hasSystemFeature('hostapd', 'eap');
1431 o
= ss
.taboption('encryption', form
.Flag
, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1432 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1434 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1437 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.'));
1438 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1439 o
.depends({ ieee80211r
: '1' });
1442 o
= ss
.taboption('encryption', form
.Value
, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1443 o
.depends({ ieee80211r
: '1' });
1444 o
.placeholder
= '4f57';
1445 o
.datatype
= 'and(hexstring,length(4))';
1448 o
= ss
.taboption('encryption', form
.Value
, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1449 o
.depends({ ieee80211r
: '1' });
1450 o
.placeholder
= '1000';
1451 o
.datatype
= 'range(1000,65535)';
1454 o
= ss
.taboption('encryption', form
.ListValue
, 'ft_over_ds', _('FT protocol'));
1455 o
.depends({ ieee80211r
: '1' });
1456 o
.value('1', _('FT over DS'));
1457 o
.value('0', _('FT over the Air'));
1460 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.'));
1461 o
.depends({ ieee80211r
: '1' });
1462 o
.default = o
.enabled
;
1465 o
= ss
.taboption('encryption', form
.Value
, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1466 o
.depends({ ieee80211r
: '1' });
1467 o
.placeholder
= '10000';
1468 o
.datatype
= 'uinteger';
1471 o
= ss
.taboption('encryption', form
.Value
, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1472 o
.depends({ ieee80211r
: '1' });
1473 o
.placeholder
= '00004f577274';
1474 o
.datatype
= 'and(hexstring,length(12))';
1477 o
= ss
.taboption('encryption', form
.Flag
, 'pmk_r1_push', _('PMK R1 Push'));
1478 o
.depends({ ieee80211r
: '1' });
1479 o
.placeholder
= '0';
1482 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.'));
1483 o
.depends({ ieee80211r
: '1' });
1486 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.'));
1487 o
.depends({ ieee80211r
: '1' });
1489 // End of 802.11r options
1491 o
= ss
.taboption('encryption', form
.ListValue
, 'eap_type', _('EAP-Method'));
1492 o
.value('tls', 'TLS');
1493 o
.value('ttls', 'TTLS');
1494 o
.value('peap', 'PEAP');
1495 o
.value('fast', 'FAST');
1496 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1498 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"));
1501 o
.default = o
.disabled
;
1502 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1503 o
.validate = function(section_id
, value
) {
1504 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1505 return _("This option cannot be used because the ca-bundle package is not installed.");
1510 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert', _('Path to CA-Certificate'));
1511 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], ca_cert_usesystem
: ['0'] });
1513 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"));
1514 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1516 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"));
1517 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1519 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)"));
1520 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1522 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)"));
1523 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1525 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert', _('Path to Client-Certificate'));
1526 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1528 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key', _('Path to Private Key'));
1529 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1531 o
= ss
.taboption('encryption', form
.Value
, 'priv_key_pwd', _('Password of Private Key'));
1532 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1535 o
= ss
.taboption('encryption', form
.ListValue
, 'auth', _('Authentication'));
1536 o
.value('PAP', 'PAP');
1537 o
.value('CHAP', 'CHAP');
1538 o
.value('MSCHAP', 'MSCHAP');
1539 o
.value('MSCHAPV2', 'MSCHAPv2');
1540 o
.value('EAP-GTC', 'EAP-GTC');
1541 o
.value('EAP-MD5', 'EAP-MD5');
1542 o
.value('EAP-MSCHAPV2', 'EAP-MSCHAPv2');
1543 o
.value('EAP-TLS', 'EAP-TLS');
1544 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1546 o
.validate = function(section_id
, value
) {
1547 var eo
= this.section
.children
.filter(function(o
) { return o
.option
== 'eap_type' })[0],
1548 ev
= eo
.formvalue(section_id
);
1550 if (ev
!= 'ttls' && (value
== 'PAP' || value
== 'CHAP' || value
== 'MSCHAP' || value
== 'MSCHAPV2'))
1551 return _('This authentication type is not applicable to the selected EAP method.');
1556 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"));
1559 o
.default = o
.disabled
;
1560 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1561 o
.validate = function(section_id
, value
) {
1562 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1563 return _("This option cannot be used because the ca-bundle package is not installed.");
1568 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert2', _('Path to inner CA-Certificate'));
1569 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'], ca_cert2_usesystem
: ['0'] });
1571 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"));
1572 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1574 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"));
1575 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1577 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)"));
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
, 'domain_suffix_match2', _('Inner certificate constraint (Wildcard)'), _("Certificate constraint(s) against DNS SAN values (if available)<br />or Subject CN (suffix match)"));
1581 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1583 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert2', _('Path to inner Client-Certificate'));
1584 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1586 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key2', _('Path to inner Private Key'));
1587 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1589 o
= ss
.taboption('encryption', form
.Value
, 'priv_key2_pwd', _('Password of inner Private Key'));
1590 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1593 o
= ss
.taboption('encryption', form
.Value
, 'identity', _('Identity'));
1594 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1596 o
= ss
.taboption('encryption', form
.Value
, 'anonymous_identity', _('Anonymous Identity'));
1597 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1599 o
= ss
.taboption('encryption', form
.Value
, 'password', _('Password'));
1600 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1604 if (hwtype
== 'mac80211') {
1605 // ieee802.11w options
1606 if (L
.hasSystemFeature('hostapd', '11w')) {
1607 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)"));
1608 o
.value('', _('Disabled'));
1609 o
.value('1', _('Optional'));
1610 o
.value('2', _('Required'));
1611 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds', 'sta', 'sta-wds'], encryption
: ['owe', 'psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1614 '2': [{ encryption
: 'sae' }, { encryption
: 'owe' }, { encryption
: 'wpa3' }, { encryption
: 'wpa3-mixed' }],
1615 '1': [{ encryption
: 'sae-mixed'}],
1619 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1620 o
.depends('ieee80211w', '1');
1621 o
.depends('ieee80211w', '2');
1622 o
.datatype
= 'uinteger';
1623 o
.placeholder
= '1000';
1626 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1627 o
.depends('ieee80211w', '1');
1628 o
.depends('ieee80211w', '2');
1629 o
.datatype
= 'uinteger';
1630 o
.placeholder
= '201';
1634 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.'));
1635 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1637 if (L
.hasSystemFeature('hostapd', 'cli') && L
.hasSystemFeature('wpasupplicant')) {
1638 o
= ss
.taboption('encryption', form
.Flag
, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1641 o
.default = o
.disabled
;
1642 o
.depends('encryption', 'psk');
1643 o
.depends('encryption', 'psk2');
1644 o
.depends('encryption', 'psk-mixed');
1645 o
.depends('encryption', 'sae');
1646 o
.depends('encryption', 'sae-mixed');
1653 s
.handleRemove = function(section_id
, ev
) {
1654 document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id
)).style
.opacity
= 0.5;
1655 return form
.TypedSection
.prototype.handleRemove
.apply(this, [section_id
, ev
]);
1658 s
.handleScan = function(radioDev
, ev
) {
1659 var table
= E('div', { 'class': 'table' }, [
1660 E('div', { 'class': 'tr table-titles' }, [
1661 E('div', { 'class': 'th col-2 middle center' }, _('Signal')),
1662 E('div', { 'class': 'th col-4 middle left' }, _('SSID')),
1663 E('div', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1664 E('div', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1665 E('div', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1666 E('div', { 'class': 'th col-3 middle left' }, _('Encryption')),
1667 E('div', { 'class': 'th cbi-section-actions right' }, ' '),
1671 var stop
= E('button', {
1673 'click': L
.bind(this.handleScanStartStop
, this),
1674 'style': 'display:none',
1675 'data-state': 'stop'
1676 }, _('Stop refresh'));
1678 cbi_update_table(table
, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1680 var md
= ui
.showModal(_('Join Network: Wireless Scan'), [
1682 E('div', { 'class': 'right' }, [
1687 'click': L
.bind(this.handleScanAbort
, this)
1692 md
.style
.maxWidth
= '90%';
1693 md
.style
.maxHeight
= 'none';
1695 this.pollFn
= L
.bind(this.handleScanRefresh
, this, radioDev
, {}, table
, stop
);
1697 poll
.add(this.pollFn
);
1701 s
.handleScanRefresh = function(radioDev
, scanCache
, table
, stop
) {
1702 return radioDev
.getScanList().then(L
.bind(function(results
) {
1705 for (var i
= 0; i
< results
.length
; i
++)
1706 scanCache
[results
[i
].bssid
] = results
[i
];
1708 for (var k
in scanCache
)
1709 if (scanCache
[k
].stale
)
1710 results
.push(scanCache
[k
]);
1712 results
.sort(function(a
, b
) {
1713 var diff
= (b
.quality
- a
.quality
) || (a
.channel
- b
.channel
);
1718 if (a
.ssid
< b
.ssid
)
1720 else if (a
.ssid
> b
.ssid
)
1723 if (a
.bssid
< b
.bssid
)
1725 else if (a
.bssid
> b
.bssid
)
1729 for (var i
= 0; i
< results
.length
; i
++) {
1730 var res
= results
[i
],
1731 qv
= res
.quality
|| 0,
1732 qm
= res
.quality_max
|| 0,
1733 q
= (qv
> 0 && qm
> 0) ? Math
.floor((100 / qm
) * qv
) : 0,
1734 s
= res
.stale
? 'opacity:0.5' : '';
1737 E('span', { 'style': s
}, render_signal_badge(q
, res
.signal
, res
.noise
)),
1738 E('span', { 'style': s
}, (res
.ssid
!= null) ? '%h'.format(res
.ssid
) : E('em', _('hidden'))),
1739 E('span', { 'style': s
}, '%d'.format(res
.channel
)),
1740 E('span', { 'style': s
}, '%h'.format(res
.mode
)),
1741 E('span', { 'style': s
}, '%h'.format(res
.bssid
)),
1742 E('span', { 'style': s
}, '%h'.format(network
.formatWifiEncryption(res
.encryption
))),
1743 E('div', { 'class': 'right' }, E('button', {
1744 'class': 'cbi-button cbi-button-action important',
1745 'click': L
.bind(this.handleJoin
, this, radioDev
, res
)
1746 }, _('Join Network')))
1752 cbi_update_table(table
, rows
);
1754 stop
.disabled
= false;
1755 stop
.style
.display
= '';
1756 stop
.classList
.remove('spinning');
1760 s
.handleScanStartStop = function(ev
) {
1761 var btn
= ev
.currentTarget
;
1763 if (btn
.getAttribute('data-state') == 'stop') {
1764 poll
.remove(this.pollFn
);
1765 btn
.firstChild
.data
= _('Start refresh');
1766 btn
.setAttribute('data-state', 'start');
1769 poll
.add(this.pollFn
);
1770 btn
.firstChild
.data
= _('Stop refresh');
1771 btn
.setAttribute('data-state', 'stop');
1772 btn
.classList
.add('spinning');
1773 btn
.disabled
= true;
1777 s
.handleScanAbort = function(ev
) {
1778 var md
= dom
.parent(ev
.target
, 'div[aria-modal="true"]');
1780 md
.style
.maxWidth
= '';
1781 md
.style
.maxHeight
= '';
1785 poll
.remove(this.pollFn
);
1790 s
.handleJoinConfirm = function(radioDev
, bss
, form
, ev
) {
1791 var nameopt
= L
.toArray(form
.lookupOption('name', '_new_'))[0],
1792 passopt
= L
.toArray(form
.lookupOption('password', '_new_'))[0],
1793 bssidopt
= L
.toArray(form
.lookupOption('bssid', '_new_'))[0],
1794 zoneopt
= L
.toArray(form
.lookupOption('zone', '_new_'))[0],
1795 replopt
= L
.toArray(form
.lookupOption('replace', '_new_'))[0],
1796 nameval
= (nameopt
&& nameopt
.isValid('_new_')) ? nameopt
.formvalue('_new_') : null,
1797 passval
= (passopt
&& passopt
.isValid('_new_')) ? passopt
.formvalue('_new_') : null,
1798 bssidval
= (bssidopt
&& bssidopt
.isValid('_new_')) ? bssidopt
.formvalue('_new_') : null,
1799 zoneval
= zoneopt
? zoneopt
.formvalue('_new_') : null,
1800 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
1801 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
1802 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' })),
1803 is_sae
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'sae' }));
1805 if (nameval
== null || (passopt
&& passval
== null))
1808 var section_id
= null;
1810 return this.map
.save(function() {
1811 var wifi_sections
= uci
.sections('wireless', 'wifi-iface');
1813 if (replopt
.formvalue('_new_') == '1') {
1814 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1815 if (wifi_sections
[i
].device
== radioDev
.getName())
1816 uci
.remove('wireless', wifi_sections
[i
]['.name']);
1819 if (uci
.get('wireless', radioDev
.getName(), 'disabled') == '1') {
1820 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1821 if (wifi_sections
[i
].device
== radioDev
.getName())
1822 uci
.set('wireless', wifi_sections
[i
]['.name'], 'disabled', '1');
1824 uci
.unset('wireless', radioDev
.getName(), 'disabled');
1827 section_id
= next_free_sid(wifi_sections
.length
);
1829 uci
.add('wireless', 'wifi-iface', section_id
);
1830 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
1831 uci
.set('wireless', section_id
, 'mode', (bss
.mode
== 'Ad-Hoc') ? 'adhoc' : 'sta');
1832 uci
.set('wireless', section_id
, 'network', nameval
);
1834 if (bss
.ssid
!= null) {
1835 uci
.set('wireless', section_id
, 'ssid', bss
.ssid
);
1837 if (bssidval
== '1')
1838 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
1840 else if (bss
.bssid
!= null) {
1841 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
1845 uci
.set('wireless', section_id
, 'encryption', 'sae');
1846 uci
.set('wireless', section_id
, 'key', passval
);
1849 for (var i
= enc
.wpa
.length
- 1; i
>= 0; i
--) {
1850 if (enc
.wpa
[i
] == 2) {
1851 uci
.set('wireless', section_id
, 'encryption', 'psk2');
1854 else if (enc
.wpa
[i
] == 1) {
1855 uci
.set('wireless', section_id
, 'encryption', 'psk');
1860 uci
.set('wireless', section_id
, 'key', passval
);
1863 uci
.set('wireless', section_id
, 'encryption', 'wep-open');
1864 uci
.set('wireless', section_id
, 'key', '1');
1865 uci
.set('wireless', section_id
, 'key1', passval
);
1868 uci
.set('wireless', section_id
, 'encryption', 'none');
1871 return network
.addNetwork(nameval
, { proto
: 'dhcp' }).then(function(net
) {
1872 firewall
.deleteNetwork(net
.getName());
1874 var zonePromise
= zoneval
1875 ? firewall
.getZone(zoneval
).then(function(zone
) { return zone
|| firewall
.addZone(zoneval
) })
1876 : Promise
.resolve();
1878 return zonePromise
.then(function(zone
) {
1880 zone
.addNetwork(net
.getName());
1883 }).then(L
.bind(function() {
1884 return this.renderMoreOptionsModal(section_id
);
1888 s
.handleJoin = function(radioDev
, bss
, ev
) {
1889 this.handleScanAbort(ev
);
1891 var m2
= new form
.Map('wireless'),
1892 s2
= m2
.section(form
.NamedSection
, '_new_'),
1893 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
1894 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
1895 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' || a
== 'sae' })),
1896 replace
, passphrase
, name
, bssid
, zone
;
1898 var nameUsed = function(name
) {
1899 var s
= uci
.get('network', name
);
1900 if (s
!= null && s
['.type'] != 'interface')
1903 var net
= (s
!= null) ? network
.instantiateNetwork(name
) : null;
1904 return (net
!= null && !net
.isEmpty());
1907 s2
.render = function() {
1908 return Promise
.all([
1910 this.renderUCISection('_new_')
1911 ]).then(this.renderContents
.bind(this));
1914 replace
= s2
.option(form
.Flag
, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
1916 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>'));
1917 name
.datatype
= 'uciname';
1918 name
.default = 'wwan';
1919 name
.rmempty
= false;
1920 name
.validate = function(section_id
, value
) {
1921 if (nameUsed(value
))
1922 return _('The network name is already used');
1927 for (var i
= 2; nameUsed(name
.default); i
++)
1928 name
.default = 'wwan%d'.format(i
);
1930 if (is_wep
|| is_psk
) {
1931 passphrase
= s2
.option(form
.Value
, 'password', is_wep
? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
1932 passphrase
.datatype
= is_wep
? 'wepkey' : 'wpakey';
1933 passphrase
.password
= true;
1934 passphrase
.rmempty
= false;
1937 if (bss
.ssid
!= null) {
1938 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
));
1939 bssid
.default = '0';
1942 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.'));
1943 zone
.default = 'wan';
1945 return m2
.render().then(L
.bind(function(nodes
) {
1946 ui
.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss
.ssid
)), [
1948 E('div', { 'class': 'right' }, [
1951 'click': ui
.hideModal
1952 }, _('Cancel')), ' ',
1954 'class': 'cbi-button cbi-button-positive important',
1955 'click': ui
.createHandlerFn(this, 'handleJoinConfirm', radioDev
, bss
, m2
)
1958 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase
|| name
).cbid('_new_'))).focus();
1962 s
.handleAdd = function(radioDev
, ev
) {
1963 var section_id
= next_free_sid(uci
.sections('wireless', 'wifi-iface').length
);
1965 uci
.unset('wireless', radioDev
.getName(), 'disabled');
1967 uci
.add('wireless', 'wifi-iface', section_id
);
1968 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
1969 uci
.set('wireless', section_id
, 'mode', 'ap');
1970 uci
.set('wireless', section_id
, 'ssid', 'OpenWrt');
1971 uci
.set('wireless', section_id
, 'encryption', 'none');
1973 this.addedSection
= section_id
;
1974 return this.renderMoreOptionsModal(section_id
);
1977 o
= s
.option(form
.DummyValue
, '_badge');
1978 o
.modalonly
= false;
1979 o
.textvalue = function(section_id
) {
1980 var inst
= this.section
.lookupRadioOrNetwork(section_id
),
1981 node
= E('div', { 'class': 'center' });
1983 if (inst
.getWifiNetworks
)
1984 node
.appendChild(render_radio_badge(inst
));
1986 node
.appendChild(render_network_badge(inst
));
1991 o
= s
.option(form
.DummyValue
, '_stat');
1992 o
.modalonly
= false;
1993 o
.textvalue = function(section_id
) {
1994 var inst
= this.section
.lookupRadioOrNetwork(section_id
);
1996 if (inst
.getWifiNetworks
)
1997 return render_radio_status(inst
, this.section
.wifis
.filter(function(e
) {
1998 return (e
.getWifiDeviceName() == inst
.getName());
2001 return render_network_status(inst
);
2004 return m
.render().then(L
.bind(function(m
, nodes
) {
2005 poll
.add(L
.bind(function() {
2006 var section_ids
= m
.children
[0].cfgsections(),
2007 tasks
= [ network
.getHostHints(), network
.getWifiDevices() ];
2009 for (var i
= 0; i
< section_ids
.length
; i
++) {
2010 var row
= nodes
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids
[i
])),
2011 dsc
= row
.querySelector('[data-name="_stat"] > div'),
2012 btns
= row
.querySelectorAll('.cbi-section-actions button');
2014 if (dsc
.getAttribute('restart') == '') {
2015 dsc
.setAttribute('restart', '1');
2016 tasks
.push(fs
.exec('/sbin/wifi', ['up', section_ids
[i
]]).catch(function(e
) {
2017 ui
.addNotification(null, E('p', e
.message
));
2020 else if (dsc
.getAttribute('restart') == '1') {
2021 dsc
.removeAttribute('restart');
2022 btns
[0].classList
.remove('spinning');
2023 btns
[0].disabled
= false;
2027 return Promise
.all(tasks
)
2028 .then(L
.bind(function(hosts_radios
) {
2031 for (var i
= 0; i
< hosts_radios
[1].length
; i
++)
2032 tasks
.push(hosts_radios
[1][i
].getWifiNetworks());
2034 return Promise
.all(tasks
).then(function(data
) {
2035 hosts_radios
[2] = [];
2037 for (var i
= 0; i
< data
.length
; i
++)
2038 hosts_radios
[2].push
.apply(hosts_radios
[2], data
[i
]);
2040 return hosts_radios
;
2043 .then(L
.bind(function(hosts_radios_wifis
) {
2046 for (var i
= 0; i
< hosts_radios_wifis
[2].length
; i
++)
2047 tasks
.push(hosts_radios_wifis
[2][i
].getAssocList());
2049 return Promise
.all(tasks
).then(function(data
) {
2050 hosts_radios_wifis
[3] = [];
2052 for (var i
= 0; i
< data
.length
; i
++) {
2053 var wifiNetwork
= hosts_radios_wifis
[2][i
],
2054 radioDev
= hosts_radios_wifis
[1].filter(function(d
) { return d
.getName() == wifiNetwork
.getWifiDeviceName() })[0];
2056 for (var j
= 0; j
< data
[i
].length
; j
++)
2057 hosts_radios_wifis
[3].push(Object
.assign({ radio
: radioDev
, network
: wifiNetwork
}, data
[i
][j
]));
2060 return hosts_radios_wifis
;
2063 .then(L
.bind(this.poll_status
, this, nodes
));
2066 var table
= E('div', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [
2067 E('div', { 'class': 'tr table-titles' }, [
2068 E('div', { 'class': 'th nowrap' }, _('Network')),
2069 E('div', { 'class': 'th hide-xs' }, _('MAC-Address')),
2070 E('div', { 'class': 'th' }, _('Host')),
2071 E('div', { 'class': 'th' }, _('Signal / Noise')),
2072 E('div', { 'class': 'th' }, _('RX Rate / TX Rate'))
2076 cbi_update_table(table
, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
2078 return E([ nodes
, E('h3', _('Associated Stations')), table
]);