9 'require tools.widgets as widgets';
11 function count_changes(section_id
) {
12 var changes
= ui
.changes
.changes
, n
= 0;
14 if (!L
.isObject(changes
))
17 if (Array
.isArray(changes
.wireless
))
18 for (var i
= 0; i
< changes
.wireless
.length
; i
++)
19 n
+= (changes
.wireless
[i
][1] == section_id
);
24 function render_radio_badge(radioDev
) {
25 return E('span', { 'class': 'ifacebadge' }, [
26 E('img', { 'src': L
.resource('icons/wifi%s.png').format(radioDev
.isUp() ? '' : '_disabled') }),
32 function render_signal_badge(signalPercent
, signalValue
, noiseValue
, wrap
) {
33 var icon
, title
, value
;
35 if (signalPercent
< 0)
36 icon
= L
.resource('icons/signal-none.png');
37 else if (signalPercent
== 0)
38 icon
= L
.resource('icons/signal-0.png');
39 else if (signalPercent
< 25)
40 icon
= L
.resource('icons/signal-0-25.png');
41 else if (signalPercent
< 50)
42 icon
= L
.resource('icons/signal-25-50.png');
43 else if (signalPercent
< 75)
44 icon
= L
.resource('icons/signal-50-75.png');
46 icon
= L
.resource('icons/signal-75-100.png');
48 if (signalValue
!= null && signalValue
!= 0 && noiseValue
!= null && noiseValue
!= 0) {
49 value
= '%d/%d\xa0%s'.format(signalValue
, noiseValue
, _('dBm'));
50 title
= '%s: %d %s / %s: %d %s / %s %d'.format(
51 _('Signal'), signalValue
, _('dBm'),
52 _('Noise'), noiseValue
, _('dBm'),
53 _('SNR'), signalValue
- noiseValue
);
55 else if (signalValue
!= null && signalValue
!= 0) {
56 value
= '%d %s'.format(signalValue
, _('dBm'));
57 title
= '%s: %d %s'.format(_('Signal'), signalValue
, _('dBm'));
59 else if (signalPercent
> -1) {
60 value
= '\xa0---\xa0';
61 title
= _('No signal');
64 value
= E('em', {}, E('small', {}, [ _('disabled') ]));
65 title
= _('Interface is disabled');
69 'class': wrap
? 'center' : 'ifacebadge',
71 'data-signal': signalValue
,
72 'data-noise': noiseValue
74 E('img', { 'src': icon
}),
82 function render_network_badge(radioNet
) {
83 return render_signal_badge(radioNet
.isUp() ? radioNet
.getSignalPercent() : -1, radioNet
.getSignal(), radioNet
.getNoise());
86 function render_radio_status(radioDev
, wifiNets
) {
87 var name
= radioDev
.getI18n().replace(/ Wireless Controller
.+$/, ''),
88 node
= E('div', [ E('big', {}, E('strong', {}, name
)), E('div') ]),
89 channel
, frequency
, bitrate
;
91 for (var i
= 0; i
< wifiNets
.length
; i
++) {
92 channel
= channel
|| wifiNets
[i
].getChannel();
93 frequency
= frequency
|| wifiNets
[i
].getFrequency();
94 bitrate
= bitrate
|| wifiNets
[i
].getBitRate();
98 L
.itemlist(node
.lastElementChild
, [
99 _('Channel'), '%s (%s %s)'.format(channel
|| '?', frequency
|| '?', _('GHz')),
100 _('Bitrate'), '%s %s'.format(bitrate
|| '?', _('Mbit/s'))
103 node
.lastElementChild
.appendChild(E('em', _('Device is not active')));
108 function render_network_status(radioNet
) {
109 var mode
= radioNet
.getActiveMode(),
110 bssid
= radioNet
.getActiveBSSID(),
111 channel
= radioNet
.getChannel(),
112 disabled
= (radioNet
.get('disabled') == '1' || uci
.get('wireless', radioNet
.getWifiDeviceName(), 'disabled') == '1'),
113 is_assoc
= (bssid
&& bssid
!= '00:00:00:00:00:00' && channel
&& mode
!= 'Unknown' && !disabled
),
114 is_mesh
= (radioNet
.getMode() == 'mesh'),
115 changecount
= count_changes(radioNet
.getName()),
119 status_text
= E('a', {
121 click
: L
.bind(ui
.changes
.displayChanges
, ui
.changes
)
122 }, _('Interface has %d pending changes').format(changecount
));
124 status_text
= E('em', disabled
? _('Wireless is disabled') : _('Wireless is not associated'));
126 return L
.itemlist(E('div'), [
127 is_mesh
? _('Mesh ID') : _('SSID'), (is_mesh
? radioNet
.getMeshID() : radioNet
.getSSID()) || '?',
129 _('BSSID'), (!changecount
&& is_assoc
) ? bssid
: null,
130 _('Encryption'), (!changecount
&& is_assoc
) ? radioNet
.getActiveEncryption() || _('None') : null,
132 ], [ ' | ', E('br') ]);
135 function render_modal_status(node
, radioNet
) {
136 var mode
= radioNet
.getActiveMode(),
137 noise
= radioNet
.getNoise(),
138 bssid
= radioNet
.getActiveBSSID(),
139 channel
= radioNet
.getChannel(),
140 disabled
= (radioNet
.get('disabled') == '1'),
141 is_assoc
= (bssid
&& bssid
!= '00:00:00:00:00:00' && channel
&& mode
!= 'Unknown' && !disabled
);
144 node
= E('span', { 'class': 'ifacebadge large', 'data-network': radioNet
.getName() }, [ E('small'), E('span') ]);
146 L
.dom
.content(node
.firstElementChild
, render_signal_badge(disabled
? -1 : radioNet
.getSignalPercent(), radioNet
.getSignal(), noise
, true));
148 L
.itemlist(node
.lastElementChild
, [
150 _('SSID'), radioNet
.getSSID() || '?',
151 _('BSSID'), is_assoc
? bssid
: null,
152 _('Encryption'), is_assoc
? radioNet
.getActiveEncryption() || _('None') : null,
153 _('Channel'), is_assoc
? '%d (%.3f %s)'.format(radioNet
.getChannel(), radioNet
.getFrequency() || 0, _('GHz')) : null,
154 _('Tx-Power'), is_assoc
? '%d %s'.format(radioNet
.getTXPower(), _('dBm')) : null,
155 _('Signal'), is_assoc
? '%d %s'.format(radioNet
.getSignal(), _('dBm')) : null,
156 _('Noise'), (is_assoc
&& noise
!= null) ? '%d %s'.format(noise
, _('dBm')) : null,
157 _('Bitrate'), is_assoc
? '%.1f %s'.format(radioNet
.getBitRate() || 0, _('Mbit/s')) : null,
158 _('Country'), is_assoc
? radioNet
.getCountryCode() : null
159 ], [ ' | ', E('br'), E('br'), E('br'), E('br'), E('br'), ' | ', E('br'), ' | ' ]);
162 L
.dom
.append(node
.lastElementChild
, E('em', disabled
? _('Wireless is disabled') : _('Wireless is not associated')));
167 function format_wifirate(rate
) {
168 var s
= '%.1f\xa0%s, %d\xa0%s'.format(rate
.rate
/ 1000, _('Mbit/s'), rate
.mhz
, _('MHz')),
169 ht
= rate
.ht
, vht
= rate
.vht
,
170 mhz
= rate
.mhz
, nss
= rate
.nss
,
171 mcs
= rate
.mcs
, sgi
= rate
.short_gi
;
174 if (vht
) s
+= ', VHT-MCS\xa0%d'.format(mcs
);
175 if (nss
) s
+= ', VHT-NSS\xa0%d'.format(nss
);
176 if (ht
) s
+= ', MCS\xa0%s'.format(mcs
);
177 if (sgi
) s
+= ', ' + _('Short GI').replace(/ /g
, '\xa0');
183 function radio_restart(id
, ev
) {
184 var row
= document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id
)),
185 dsc
= row
.querySelector('[data-name="_stat"] > div'),
186 btn
= row
.querySelector('.cbi-section-actions button');
189 btn
.classList
.add('spinning');
192 dsc
.setAttribute('restart', '');
193 L
.dom
.content(dsc
, E('em', _('Device is restarting…')));
196 function network_updown(id
, map
, ev
) {
197 var radio
= uci
.get('wireless', id
, 'device'),
198 disabled
= (uci
.get('wireless', id
, 'disabled') == '1') ||
199 (uci
.get('wireless', radio
, 'disabled') == '1');
202 uci
.unset('wireless', id
, 'disabled');
203 uci
.unset('wireless', radio
, 'disabled');
206 uci
.set('wireless', id
, 'disabled', '1');
208 var all_networks_disabled
= true,
209 wifi_ifaces
= uci
.sections('wireless', 'wifi-iface');
211 for (var i
= 0; i
< wifi_ifaces
.length
; i
++) {
212 if (wifi_ifaces
[i
].device
== radio
&& wifi_ifaces
[i
].disabled
!= '1') {
213 all_networks_disabled
= false;
218 if (all_networks_disabled
)
219 uci
.set('wireless', radio
, 'disabled', '1');
222 return map
.save().then(function() {
227 function next_free_sid(offset
) {
228 var sid
= 'wifinet' + offset
;
230 while (uci
.get('wireless', sid
))
231 sid
= 'wifinet' + (++offset
);
236 function add_dependency_permutations(o
, deps
) {
239 for (var key
in deps
) {
240 if (!deps
.hasOwnProperty(key
) || !Array
.isArray(deps
[key
]))
243 var list
= deps
[key
],
246 for (var j
= 0; j
< list
.length
; j
++) {
247 for (var k
= 0; k
< (res
? res
.length
: 1); k
++) {
248 var item
= (res
? Object
.assign({}, res
[k
]) : {});
257 for (var i
= 0; i
< (res
? res
.length
: 0); i
++)
261 var CBIWifiFrequencyValue
= form
.Value
.extend({
262 callFrequencyList
: rpc
.declare({
265 params
: [ 'device' ],
266 expect
: { results
: [] }
269 load: function(section_id
) {
271 network
.getWifiDevice(section_id
),
272 this.callFrequencyList(section_id
)
273 ]).then(L
.bind(function(data
) {
275 '11g': L
.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
276 '11a': L
.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : []
279 for (var i
= 0; i
< data
[1].length
; i
++)
280 this.channels
[(data
[1][i
].mhz
> 2484) ? '11a' : '11g'].push(
282 '%d (%d Mhz)'.format(data
[1][i
].channel
, data
[1][i
].mhz
),
283 !data
[1][i
].restricted
286 var hwmodelist
= L
.toArray(data
[0] ? data
[0].getHWModes() : null)
287 .reduce(function(o
, v
) { o
[v
] = true; return o
}, {});
291 'n', 'N', hwmodelist
.n
,
292 'ac', 'AC', hwmodelist
.ac
295 var htmodelist
= L
.toArray(data
[0] ? data
[0].getHTModes() : null)
296 .reduce(function(o
, v
) { o
[v
] = true; return o
}, {});
299 '': [ '', '-', true ],
301 'HT20', '20 MHz', htmodelist
.HT20
,
302 'HT40', '40 MHz', htmodelist
.HT40
305 'VHT20', '20 MHz', htmodelist
.VHT20
,
306 'VHT40', '40 MHz', htmodelist
.VHT40
,
307 'VHT80', '80 MHz', htmodelist
.VHT80
,
308 'VHT160', '160 MHz', htmodelist
.VHT160
314 '11g', '2.4 GHz', this.channels
['11g'].length
> 3,
315 '11a', '5 GHz', this.channels
['11a'].length
> 3
318 '11g', '2.4 GHz', this.channels
['11g'].length
> 3,
319 '11a', '5 GHz', this.channels
['11a'].length
> 3
328 setValues: function(sel
, vals
) {
330 sel
.vals
.selected
= sel
.selectedIndex
;
332 while (sel
.options
[0])
335 for (var i
= 0; vals
&& i
< vals
.length
; i
+= 3)
337 sel
.add(E('option', { value
: vals
[i
+0] }, [ vals
[i
+1] ]));
339 if (vals
&& !isNaN(vals
.selected
))
340 sel
.selectedIndex
= vals
.selected
;
342 sel
.parentNode
.style
.display
= (sel
.options
.length
<= 1) ? 'none' : '';
346 toggleWifiMode: function(elem
) {
347 this.toggleWifiHTMode(elem
);
348 this.toggleWifiBand(elem
);
351 toggleWifiHTMode: function(elem
) {
352 var mode
= elem
.querySelector('.mode');
353 var bwdt
= elem
.querySelector('.htmode');
355 this.setValues(bwdt
, this.htmodes
[mode
.value
]);
358 toggleWifiBand: function(elem
) {
359 var mode
= elem
.querySelector('.mode');
360 var band
= elem
.querySelector('.band');
362 this.setValues(band
, this.bands
[mode
.value
]);
363 this.toggleWifiChannel(elem
);
366 toggleWifiChannel: function(elem
) {
367 var band
= elem
.querySelector('.band');
368 var chan
= elem
.querySelector('.channel');
370 this.setValues(chan
, this.channels
[band
.value
]);
373 setInitialValues: function(section_id
, elem
) {
374 var mode
= elem
.querySelector('.mode'),
375 band
= elem
.querySelector('.band'),
376 chan
= elem
.querySelector('.channel'),
377 bwdt
= elem
.querySelector('.htmode'),
378 htval
= uci
.get('wireless', section_id
, 'htmode'),
379 hwval
= uci
.get('wireless', section_id
, 'hwmode'),
380 chval
= uci
.get('wireless', section_id
, 'channel');
382 this.setValues(mode
, this.modes
);
384 if (/VHT20|VHT40|VHT80|VHT160/.test(htval
))
386 else if (/HT20|HT40/.test(htval
))
391 this.toggleWifiMode(elem
);
398 this.toggleWifiBand(elem
);
406 renderWidget: function(section_id
, option_index
, cfgvalue
) {
409 L
.dom
.content(elem
, [
410 E('label', { 'style': 'float:left; margin-right:3px' }, [
414 'style': 'width:auto',
415 'change': L
.bind(this.toggleWifiMode
, this, elem
)
418 E('label', { 'style': 'float:left; margin-right:3px' }, [
422 'style': 'width:auto',
423 'change': L
.bind(this.toggleWifiBand
, this, elem
)
426 E('label', { 'style': 'float:left; margin-right:3px' }, [
427 _('Channel'), E('br'),
430 'style': 'width:auto'
433 E('label', { 'style': 'float:left; margin-right:3px' }, [
437 'style': 'width:auto'
440 E('br', { 'style': 'clear:left' })
443 return this.setInitialValues(section_id
, elem
);
446 cfgvalue: function(section_id
) {
448 uci
.get('wireless', section_id
, 'htmode'),
449 uci
.get('wireless', section_id
, 'hwmode'),
450 uci
.get('wireless', section_id
, 'channel')
454 formvalue: function(section_id
) {
455 var node
= this.map
.findElement('data-field', this.cbid(section_id
));
458 node
.querySelector('.htmode').value
,
459 node
.querySelector('.band').value
,
460 node
.querySelector('.channel').value
464 write: function(section_id
, value
) {
465 uci
.set('wireless', section_id
, 'htmode', value
[0] || null);
466 uci
.set('wireless', section_id
, 'hwmode', value
[1]);
467 uci
.set('wireless', section_id
, 'channel', value
[2]);
471 var CBIWifiTxPowerValue
= form
.ListValue
.extend({
472 callTxPowerList
: rpc
.declare({
474 method
: 'txpowerlist',
475 params
: [ 'device' ],
476 expect
: { results
: [] }
479 load: function(section_id
) {
480 return this.callTxPowerList(section_id
).then(L
.bind(function(pwrlist
) {
481 this.powerval
= this.wifiNetwork
? this.wifiNetwork
.getTXPower() : null;
482 this.poweroff
= this.wifiNetwork
? this.wifiNetwork
.getTXPowerOffset() : null;
484 this.value('', _('driver default'));
486 for (var i
= 0; i
< pwrlist
.length
; i
++)
487 this.value(pwrlist
[i
].dbm
, '%d dBm (%d mW)'.format(pwrlist
[i
].dbm
, pwrlist
[i
].mw
));
489 return form
.ListValue
.prototype.load
.apply(this, [section_id
]);
493 renderWidget: function(section_id
, option_index
, cfgvalue
) {
494 var widget
= form
.ListValue
.prototype.renderWidget
.apply(this, [section_id
, option_index
, cfgvalue
]);
495 widget
.firstElementChild
.style
.width
= 'auto';
497 L
.dom
.append(widget
, E('span', [
498 ' - ', _('Current power'), ': ',
499 E('span', [ this.powerval
!= null ? '%d dBm'.format(this.powerval
) : E('em', _('unknown')) ]),
500 this.poweroff
? ' + %d dB offset = %s dBm'.format(this.poweroff
, this.powerval
!= null ? this.powerval
+ this.poweroff
: '?') : ''
507 var CBIWifiCountryValue
= form
.Value
.extend({
508 callCountryList
: rpc
.declare({
510 method
: 'countrylist',
511 params
: [ 'device' ],
512 expect
: { results
: [] }
515 load: function(section_id
) {
516 return this.callCountryList(section_id
).then(L
.bind(function(countrylist
) {
517 if (Array
.isArray(countrylist
) && countrylist
.length
> 0) {
518 this.value('', _('driver default'));
520 for (var i
= 0; i
< countrylist
.length
; i
++)
521 this.value(countrylist
[i
].iso3166
, '%s - %s'.format(countrylist
[i
].iso3166
, countrylist
[i
].country
));
524 return form
.Value
.prototype.load
.apply(this, [section_id
]);
528 validate: function(section_id
, formvalue
) {
529 if (formvalue
!= null && formvalue
!= '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue
))
530 return _('Use ISO/IEC 3166 alpha2 country codes.');
535 renderWidget: function(section_id
, option_index
, cfgvalue
) {
536 var typeClass
= (this.keylist
&& this.keylist
.length
) ? form
.ListValue
: form
.Value
;
537 return typeClass
.prototype.renderWidget
.apply(this, [section_id
, option_index
, cfgvalue
]);
541 return L
.view
.extend({
542 poll_status: function(map
, data
) {
543 var rows
= map
.querySelectorAll('.cbi-section-table-row[data-sid]');
545 for (var i
= 0; i
< rows
.length
; i
++) {
546 var section_id
= rows
[i
].getAttribute('data-sid'),
547 radioDev
= data
[1].filter(function(d
) { return d
.getName() == section_id
})[0],
548 radioNet
= data
[2].filter(function(n
) { return n
.getName() == section_id
})[0],
549 badge
= rows
[i
].querySelector('[data-name="_badge"] > div'),
550 stat
= rows
[i
].querySelector('[data-name="_stat"]'),
551 btns
= rows
[i
].querySelectorAll('.cbi-section-actions button'),
552 busy
= btns
[0].classList
.contains('spinning') || btns
[1].classList
.contains('spinning') || btns
[2].classList
.contains('spinning');
555 L
.dom
.content(badge
, render_radio_badge(radioDev
));
556 L
.dom
.content(stat
, render_radio_status(radioDev
, data
[2].filter(function(n
) { return n
.getWifiDeviceName() == radioDev
.getName() })));
559 L
.dom
.content(badge
, render_network_badge(radioNet
));
560 L
.dom
.content(stat
, render_network_status(radioNet
));
563 if (stat
.hasAttribute('restart'))
564 L
.dom
.content(stat
, E('em', _('Device is restarting…')));
566 btns
[0].disabled
= busy
;
567 btns
[1].disabled
= busy
;
568 btns
[2].disabled
= busy
;
571 var table
= document
.querySelector('#wifi_assoclist_table'),
575 for (var i
= 0; i
< data
[3].length
; i
++) {
576 var bss
= data
[3][i
],
577 name
= hosts
.getHostnameByMACAddr(bss
.mac
),
578 ipv4
= hosts
.getIPAddrByMACAddr(bss
.mac
),
579 ipv6
= hosts
.getIP6AddrByMACAddr(bss
.mac
);
583 if (name
&& ipv4
&& ipv6
)
584 hint
= '%s <span class="hide-xs">(%s, %s)</span>'.format(name
, ipv4
, ipv6
);
585 else if (name
&& (ipv4
|| ipv6
))
586 hint
= '%s <span class="hide-xs">(%s)</span>'.format(name
, ipv4
|| ipv6
);
588 hint
= name
|| ipv4
|| ipv6
|| '?';
592 'class': 'ifacebadge',
593 'data-ifname': bss
.network
.getIfname(),
594 'data-ssid': bss
.network
.getSSID()
597 'src': L
.resource('icons/wifi%s.png').format(bss
.network
.isUp() ? '' : '_disabled'),
598 'title': bss
.radio
.getI18n()
601 ' %s '.format(bss
.network
.getShortName()),
602 E('small', '(%s)'.format(bss
.network
.getIfname()))
607 render_signal_badge(Math
.min((bss
.signal
+ 110) / 70 * 100, 100), bss
.signal
, bss
.noise
),
609 E('span', format_wifirate(bss
.rx
)),
611 E('span', format_wifirate(bss
.tx
))
615 if (bss
.network
.isClientDisconnectSupported()) {
616 if (table
.firstElementChild
.childNodes
.length
< 6)
617 table
.firstElementChild
.appendChild(E('div', { 'class': 'th cbi-section-actions'}));
619 row
.push(E('button', {
620 'class': 'cbi-button cbi-button-remove',
621 'click': L
.bind(function(net
, mac
, ev
) {
622 L
.dom
.parent(ev
.currentTarget
, '.tr').style
.opacity
= 0.5;
623 ev
.currentTarget
.classList
.add('spinning');
624 ev
.currentTarget
.disabled
= true;
625 ev
.currentTarget
.blur();
627 net
.disconnectClient(mac
, true, 5, 60000);
628 }, this, bss
.network
, bss
.mac
)
629 }, [ _('Disconnect') ]));
638 cbi_update_table(table
, trows
, E('em', _('No information available')));
640 var stat
= document
.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large');
643 render_modal_status(stat
, data
[2].filter(function(n
) { return n
.getName() == stat
.getAttribute('data-network') })[0]);
645 return network
.flushCache();
655 checkAnonymousSections: function() {
656 var wifiIfaces
= uci
.sections('wireless', 'wifi-iface');
658 for (var i
= 0; i
< wifiIfaces
.length
; i
++)
659 if (wifiIfaces
[i
]['.anonymous'])
665 callUciRename
: rpc
.declare({
668 params
: [ 'config', 'section', 'name' ]
672 if (this.checkAnonymousSections())
673 return this.renderMigration();
675 return this.renderOverview();
678 handleMigration: function(ev
) {
679 var wifiIfaces
= uci
.sections('wireless', 'wifi-iface'),
683 for (var i
= 0; i
< wifiIfaces
.length
; i
++) {
684 if (!wifiIfaces
[i
]['.anonymous'])
687 var new_name
= next_free_sid(id_offset
);
689 tasks
.push(this.callUciRename('wireless', wifiIfaces
[i
]['.name'], new_name
));
690 id_offset
= +new_name
.substring(7) + 1;
693 return Promise
.all(tasks
)
694 .then(L
.bind(ui
.changes
.init
, ui
.changes
))
695 .then(L
.bind(ui
.changes
.apply
, ui
.changes
));
698 renderMigration: function() {
699 ui
.showModal(_('Wireless configuration migration'), [
700 E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')),
701 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.')),
702 E('div', { 'class': 'right' },
704 'class': 'btn cbi-button-action important',
705 'click': ui
.createHandlerFn(this, 'handleMigration')
710 renderOverview: function() {
713 m
= new form
.Map('wireless');
717 s
= m
.section(form
.GridSection
, 'wifi-device', _('Wireless Overview'));
721 s
.load = function() {
722 return network
.getWifiDevices().then(L
.bind(function(radios
) {
723 this.radios
= radios
.sort(function(a
, b
) {
724 return a
.getName() > b
.getName();
729 for (var i
= 0; i
< radios
.length
; i
++)
730 tasks
.push(radios
[i
].getWifiNetworks());
732 return Promise
.all(tasks
);
733 }, this)).then(L
.bind(function(data
) {
736 for (var i
= 0; i
< data
.length
; i
++)
737 this.wifis
.push
.apply(this.wifis
, data
[i
]);
741 s
.cfgsections = function() {
744 for (var i
= 0; i
< this.radios
.length
; i
++) {
745 rv
.push(this.radios
[i
].getName());
747 for (var j
= 0; j
< this.wifis
.length
; j
++)
748 if (this.wifis
[j
].getWifiDeviceName() == this.radios
[i
].getName())
749 rv
.push(this.wifis
[j
].getName());
755 s
.modaltitle = function(section_id
) {
756 var radioNet
= this.wifis
.filter(function(w
) { return w
.getName() == section_id
})[0];
757 return radioNet
? radioNet
.getI18n() : _('Edit wireless network');
760 s
.lookupRadioOrNetwork = function(section_id
) {
761 var radioDev
= this.radios
.filter(function(r
) { return r
.getName() == section_id
})[0];
765 var radioNet
= this.wifis
.filter(function(w
) { return w
.getName() == section_id
})[0];
772 s
.renderRowActions = function(section_id
) {
773 var inst
= this.lookupRadioOrNetwork(section_id
), btns
;
775 if (inst
.getWifiNetworks
) {
778 'class': 'cbi-button cbi-button-neutral',
779 'title': _('Restart radio interface'),
780 'click': ui
.createHandlerFn(this, radio_restart
, section_id
)
783 'class': 'cbi-button cbi-button-action important',
784 'title': _('Find and join network'),
785 'click': ui
.createHandlerFn(this, 'handleScan', inst
)
788 'class': 'cbi-button cbi-button-add',
789 'title': _('Provide new network'),
790 'click': ui
.createHandlerFn(this, 'handleAdd', inst
)
795 var isDisabled
= (inst
.get('disabled') == '1' ||
796 uci
.get('wireless', inst
.getWifiDeviceName(), 'disabled') == '1');
800 'class': 'cbi-button cbi-button-neutral enable-disable',
801 'title': isDisabled
? _('Enable this network') : _('Disable this network'),
802 'click': ui
.createHandlerFn(this, network_updown
, section_id
, this.map
)
803 }, isDisabled
? _('Enable') : _('Disable')),
805 'class': 'cbi-button cbi-button-action important',
806 'title': _('Edit this network'),
807 'click': ui
.createHandlerFn(this, 'renderMoreOptionsModal', section_id
)
810 'class': 'cbi-button cbi-button-negative remove',
811 'title': _('Delete this network'),
812 'click': ui
.createHandlerFn(this, 'handleRemove', section_id
)
817 return E('div', { 'class': 'td middle cbi-section-actions' }, E('div', btns
));
820 s
.addModalOptions = function(s
) {
821 return network
.getWifiNetwork(s
.section
).then(function(radioNet
) {
822 var hwtype
= uci
.get('wireless', radioNet
.getWifiDeviceName(), 'type');
825 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
829 ss
.tab('general', _('General Setup'));
830 ss
.tab('advanced', _('Advanced Settings'));
832 var isDisabled
= (radioNet
.get('disabled') == '1' ||
833 uci
.get('wireless', radioNet
.getWifiDeviceName(), 'disabled') == 1);
835 o
= ss
.taboption('general', form
.DummyValue
, '_wifistat_modal', _('Status'));
836 o
.cfgvalue
= L
.bind(function(radioNet
) {
837 return render_modal_status(null, radioNet
);
839 o
.write = function() {};
841 o
= ss
.taboption('general', form
.Button
, '_toggle', isDisabled
? _('Wireless network is disabled') : _('Wireless network is enabled'));
842 o
.inputstyle
= isDisabled
? 'apply' : 'reset';
843 o
.inputtitle
= isDisabled
? _('Enable') : _('Disable');
844 o
.onclick
= ui
.createHandlerFn(s
, network_updown
, s
.section
, s
.map
);
846 o
= ss
.taboption('general', CBIWifiFrequencyValue
, '_freq', '<br />' + _('Operating frequency'));
847 o
.ucisection
= s
.section
;
849 if (hwtype
== 'mac80211') {
850 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.'));
851 o
.wifiNetwork
= radioNet
;
853 o
= ss
.taboption('advanced', CBIWifiCountryValue
, 'country', _('Country Code'));
854 o
.wifiNetwork
= radioNet
;
856 o
= ss
.taboption('advanced', form
.Flag
, 'legacy_rates', _('Allow legacy 802.11b rates'));
857 o
.default = o
.enabled
;
859 o
= ss
.taboption('advanced', form
.Value
, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
860 o
.datatype
= 'range(0,114750)';
861 o
.placeholder
= 'auto';
863 o
= ss
.taboption('advanced', form
.Value
, 'frag', _('Fragmentation Threshold'));
864 o
.datatype
= 'min(256)';
865 o
.placeholder
= _('off');
867 o
= ss
.taboption('advanced', form
.Value
, 'rts', _('RTS/CTS Threshold'));
868 o
.datatype
= 'uinteger';
869 o
.placeholder
= _('off');
871 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!'));
874 o
= ss
.taboption('advanced', form
.Value
, 'beacon_int', _('Beacon Interval'));
875 o
.datatype
= 'range(15,65535)';
881 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getName(), 'wifi-iface', _('Interface Configuration'));
885 ss
.tab('general', _('General Setup'));
886 ss
.tab('encryption', _('Wireless Security'));
887 ss
.tab('macfilter', _('MAC-Filter'));
888 ss
.tab('advanced', _('Advanced Settings'));
890 o
= ss
.taboption('general', form
.ListValue
, 'mode', _('Mode'));
891 o
.value('ap', _('Access Point'));
892 o
.value('sta', _('Client'));
893 o
.value('adhoc', _('Ad-Hoc'));
895 o
= ss
.taboption('general', form
.Value
, 'mesh_id', _('Mesh Id'));
896 o
.depends('mode', 'mesh');
898 o
= ss
.taboption('advanced', form
.Flag
, 'mesh_fwding', _('Forward mesh peer traffic'));
901 o
.depends('mode', 'mesh');
903 o
= ss
.taboption('advanced', form
.Value
, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
906 o
.datatype
= 'range(-255,1)';
907 o
.depends('mode', 'mesh');
909 o
= ss
.taboption('general', form
.Value
, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
910 o
.datatype
= 'maxlength(32)';
911 o
.depends('mode', 'ap');
912 o
.depends('mode', 'sta');
913 o
.depends('mode', 'adhoc');
914 o
.depends('mode', 'ahdemo');
915 o
.depends('mode', 'monitor');
916 o
.depends('mode', 'ap-wds');
917 o
.depends('mode', 'sta-wds');
918 o
.depends('mode', 'wds');
920 o
= ss
.taboption('general', form
.Value
, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
921 o
.datatype
= 'macaddr';
923 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>create</em> field to define a new network.'));
927 o
.write = function(section_id
, value
) {
928 return network
.getDevice(section_id
).then(L
.bind(function(dev
) {
929 var old_networks
= dev
.getNetworks().reduce(function(o
, v
) { o
[v
.getName()] = v
; return o
}, {}),
931 values
= L
.toArray(value
),
934 for (var i
= 0; i
< values
.length
; i
++) {
935 new_networks
[values
[i
]] = true;
937 if (old_networks
[values
[i
]])
940 tasks
.push(network
.getNetwork(values
[i
]).then(L
.bind(function(name
, net
) {
941 return net
|| network
.addNetwork(name
, { proto
: 'none' });
942 }, this, values
[i
])).then(L
.bind(function(dev
, net
) {
945 net
.set('type', 'bridge');
951 for (var name
in old_networks
)
952 if (!new_networks
[name
])
953 tasks
.push(network
.getNetwork(name
).then(L
.bind(function(dev
, net
) {
955 net
.deleteDevice(dev
);
958 return Promise
.all(tasks
);
962 if (hwtype
== 'mac80211') {
963 var mode
= ss
.children
[0],
964 bssid
= ss
.children
[5],
967 mode
.value('mesh', '802.11s');
968 mode
.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
969 mode
.value('monitor', _('Monitor'));
971 bssid
.depends('mode', 'adhoc');
972 bssid
.depends('mode', 'sta');
973 bssid
.depends('mode', 'sta-wds');
975 o
= ss
.taboption('macfilter', form
.ListValue
, 'macfilter', _('MAC-Address Filter'));
976 o
.depends('mode', 'ap');
977 o
.depends('mode', 'ap-wds');
978 o
.value('', _('disable'));
979 o
.value('allow', _('Allow listed only'));
980 o
.value('deny', _('Allow all except listed'));
982 o
= ss
.taboption('macfilter', form
.DynamicList
, 'maclist', _('MAC-List'));
983 o
.datatype
= 'macaddr';
984 o
.depends('macfilter', 'allow');
985 o
.depends('macfilter', 'deny');
986 o
.load = function(section_id
) {
987 return network
.getHostHints().then(L
.bind(function(hints
) {
988 hints
.getMACHints().map(L
.bind(function(hint
) {
989 this.value(hint
[0], hint
[1] ? '%s (%s)'.format(hint
[0], hint
[1]) : hint
[0]);
992 return form
.DynamicList
.prototype.load
.apply(this, [section_id
]);
996 mode
.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
997 mode
.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
999 mode
.write = function(section_id
, value
) {
1002 uci
.set('wireless', section_id
, 'mode', 'ap');
1003 uci
.set('wireless', section_id
, 'wds', '1');
1007 uci
.set('wireless', section_id
, 'mode', 'sta');
1008 uci
.set('wireless', section_id
, 'wds', '1');
1012 uci
.set('wireless', section_id
, 'mode', value
);
1013 uci
.unset('wireless', section_id
, 'wds');
1018 mode
.cfgvalue = function(section_id
) {
1019 var mode
= uci
.get('wireless', section_id
, 'mode'),
1020 wds
= uci
.get('wireless', section_id
, 'wds');
1022 if (mode
== 'ap' && wds
)
1024 else if (mode
== 'sta' && wds
)
1030 o
= ss
.taboption('general', form
.Flag
, 'hidden', _('Hide <abbr title="Extended Service Set Identifier">ESSID</abbr>'));
1031 o
.depends('mode', 'ap');
1032 o
.depends('mode', 'ap-wds');
1034 o
= ss
.taboption('general', form
.Flag
, 'wmm', _('WMM Mode'));
1035 o
.depends('mode', 'ap');
1036 o
.depends('mode', 'ap-wds');
1037 o
.default = o
.enabled
;
1039 o
= ss
.taboption('advanced', form
.Flag
, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
1040 o
.depends('mode', 'ap');
1041 o
.depends('mode', 'ap-wds');
1043 o
= ss
.taboption('advanced', form
.Value
, 'ifname', _('Interface name'), _('Override default interface name'));
1045 o
.placeholder
= radioNet
.getIfname();
1046 if (/^radio\d+\.network/.test(o
.placeholder
))
1049 o
= ss
.taboption('advanced', form
.Flag
, 'short_preamble', _('Short Preamble'));
1050 o
.default = o
.enabled
;
1052 o
= ss
.taboption('advanced', form
.Value
, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
1055 o
.datatype
= 'range(1,255)';
1057 o
= ss
.taboption('advanced', form
.Value
, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
1059 o
.placeholder
= 600;
1060 o
.datatype
= 'uinteger';
1062 o
= ss
.taboption('advanced', form
.Flag
, 'skip_inactivity_poll', _('Disable Inactivity Polling'));
1064 o
.datatype
= 'uinteger';
1066 o
= ss
.taboption('advanced', form
.Value
, 'max_inactivity', _('Station inactivity limit'), _('sec'));
1068 o
.placeholder
= 300;
1069 o
.datatype
= 'uinteger';
1071 o
= ss
.taboption('advanced', form
.Value
, 'max_listen_interval', _('Maximum allowed Listen Interval'));
1073 o
.placeholder
= 65535;
1074 o
.datatype
= 'uinteger';
1076 o
= ss
.taboption('advanced', form
.Flag
, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
1077 o
.default = o
.enabled
;
1081 encr
= o
= ss
.taboption('encryption', form
.ListValue
, 'encryption', _('Encryption'));
1082 o
.depends('mode', 'ap');
1083 o
.depends('mode', 'sta');
1084 o
.depends('mode', 'adhoc');
1085 o
.depends('mode', 'ahdemo');
1086 o
.depends('mode', 'ap-wds');
1087 o
.depends('mode', 'sta-wds');
1088 o
.depends('mode', 'mesh');
1090 o
.cfgvalue = function(section_id
) {
1091 var v
= String(uci
.get('wireless', section_id
, 'encryption'));
1094 else if (v
.match(/\+/))
1095 return v
.replace(/\+.+$/, '');
1099 o
.write = function(section_id
, value
) {
1100 var e
= this.section
.children
.filter(function(o
) { return o
.option
== 'encryption' })[0].formvalue(section_id
),
1101 co
= this.section
.children
.filter(function(o
) { return o
.option
== 'cipher' })[0], c
= co
.formvalue(section_id
);
1103 if (value
== 'wpa' || value
== 'wpa2' || value
== 'wpa3' || value
== 'wpa3-mixed')
1104 uci
.unset('wireless', section_id
, 'key');
1106 if (co
.isActive(section_id
) && e
&& (c
== 'tkip' || c
== 'ccmp' || c
== 'tkip+ccmp'))
1109 uci
.set('wireless', section_id
, 'encryption', e
);
1112 o
= ss
.taboption('encryption', form
.ListValue
, 'cipher', _('Cipher'));
1113 o
.depends('encryption', 'wpa');
1114 o
.depends('encryption', 'wpa2');
1115 o
.depends('encryption', 'wpa3');
1116 o
.depends('encryption', 'wpa3-mixed');
1117 o
.depends('encryption', 'psk');
1118 o
.depends('encryption', 'psk2');
1119 o
.depends('encryption', 'wpa-mixed');
1120 o
.depends('encryption', 'psk-mixed');
1121 o
.value('auto', _('auto'));
1122 o
.value('ccmp', _('Force CCMP (AES)'));
1123 o
.value('tkip', _('Force TKIP'));
1124 o
.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1125 o
.write
= ss
.children
.filter(function(o
) { return o
.option
== 'encryption' })[0].write
;
1127 o
.cfgvalue = function(section_id
) {
1128 var v
= String(uci
.get('wireless', section_id
, 'encryption'));
1129 if (v
.match(/\+/)) {
1130 v
= v
.replace(/^[^+]+\+/, '');
1133 else if (v
== 'tkip+aes' || v
== 'aes+tkip' || v
== 'ccmp+tkip')
1140 var crypto_modes
= [];
1142 if (hwtype
== 'mac80211') {
1143 var has_supplicant
= L
.hasSystemFeature('wpasupplicant'),
1144 has_hostapd
= L
.hasSystemFeature('hostapd');
1146 // Probe EAP support
1147 var has_ap_eap
= L
.hasSystemFeature('hostapd', 'eap'),
1148 has_sta_eap
= L
.hasSystemFeature('wpasupplicant', 'eap');
1150 // Probe SAE support
1151 var has_ap_sae
= L
.hasSystemFeature('hostapd', 'sae'),
1152 has_sta_sae
= L
.hasSystemFeature('wpasupplicant', 'sae');
1154 // Probe OWE support
1155 var has_ap_owe
= L
.hasSystemFeature('hostapd', 'owe'),
1156 has_sta_owe
= L
.hasSystemFeature('wpasupplicant', 'owe');
1158 // Probe Suite-B support
1159 var has_ap_eap192
= L
.hasSystemFeature('hostapd', 'suiteb192'),
1160 has_sta_eap192
= L
.hasSystemFeature('wpasupplicant', 'suiteb192');
1163 if (has_hostapd
|| has_supplicant
) {
1164 crypto_modes
.push(['psk2', 'WPA2-PSK', 35]);
1165 crypto_modes
.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1166 crypto_modes
.push(['psk', 'WPA-PSK', 21]);
1169 encr
.description
= _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1172 if (has_ap_sae
|| has_sta_sae
) {
1173 crypto_modes
.push(['sae', 'WPA3-SAE', 31]);
1174 crypto_modes
.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1177 if (has_ap_eap
|| has_sta_eap
) {
1178 if (has_ap_eap192
|| has_sta_eap192
) {
1179 crypto_modes
.push(['wpa3', 'WPA3-EAP', 33]);
1180 crypto_modes
.push(['wpa3-mixed', 'WPA2-EAP/WPA3-EAP Mixed Mode', 32]);
1183 crypto_modes
.push(['wpa2', 'WPA2-EAP', 34]);
1184 crypto_modes
.push(['wpa', 'WPA-EAP', 20]);
1187 if (has_ap_owe
|| has_sta_owe
) {
1188 crypto_modes
.push(['owe', 'OWE', 1]);
1191 encr
.crypto_support
= {
1195 'psk': has_hostapd
|| _('Requires hostapd'),
1196 'psk2': has_hostapd
|| _('Requires hostapd'),
1197 'psk-mixed': has_hostapd
|| _('Requires hostapd'),
1198 'sae': has_ap_sae
|| _('Requires hostapd with SAE support'),
1199 'sae-mixed': has_ap_sae
|| _('Requires hostapd with SAE support'),
1200 'wpa': has_ap_eap
|| _('Requires hostapd with EAP support'),
1201 'wpa2': has_ap_eap
|| _('Requires hostapd with EAP support'),
1202 'wpa3': has_ap_eap192
|| _('Requires hostapd with EAP Suite-B support'),
1203 'wpa3-mixed': has_ap_eap192
|| _('Requires hostapd with EAP Suite-B support'),
1204 'owe': has_ap_owe
|| _('Requires hostapd with OWE support')
1209 'psk': has_supplicant
|| _('Requires wpa-supplicant'),
1210 'psk2': has_supplicant
|| _('Requires wpa-supplicant'),
1211 'psk-mixed': has_supplicant
|| _('Requires wpa-supplicant'),
1212 'sae': has_sta_sae
|| _('Requires wpa-supplicant with SAE support'),
1213 'sae-mixed': has_sta_sae
|| _('Requires wpa-supplicant with SAE support'),
1214 'wpa': has_sta_eap
|| _('Requires wpa-supplicant with EAP support'),
1215 'wpa2': has_sta_eap
|| _('Requires wpa-supplicant with EAP support'),
1216 'wpa3': has_sta_eap192
|| _('Requires wpa-supplicant with EAP Suite-B support'),
1217 'wpa3-mixed': has_sta_eap192
|| _('Requires wpa-supplicant with EAP Suite-B support'),
1218 'owe': has_sta_owe
|| _('Requires wpa-supplicant with OWE support')
1223 'psk': has_supplicant
|| _('Requires wpa-supplicant'),
1224 'psk2': has_supplicant
|| _('Requires wpa-supplicant'),
1225 'psk-mixed': has_supplicant
|| _('Requires wpa-supplicant'),
1228 'sae': has_sta_sae
|| _('Requires wpa-supplicant with SAE support')
1240 encr
.crypto_support
['ap-wds'] = encr
.crypto_support
['ap'];
1241 encr
.crypto_support
['sta-wds'] = encr
.crypto_support
['sta'];
1243 encr
.validate = function(section_id
, value
) {
1244 var modeopt
= this.section
.children
.filter(function(o
) { return o
.option
== 'mode' })[0],
1245 modeval
= modeopt
.formvalue(section_id
),
1246 modetitle
= modeopt
.vallist
[modeopt
.keylist
.indexOf(modeval
)],
1247 enctitle
= this.vallist
[this.keylist
.indexOf(value
)];
1249 if (value
== 'none')
1252 if (!L
.isObject(this.crypto_support
[modeval
]) || !this.crypto_support
[modeval
].hasOwnProperty(value
))
1253 return _('The selected %s mode is incompatible with %s encryption').format(modetitle
, enctitle
);
1255 return this.crypto_support
[modeval
][value
];
1258 else if (hwtype
== 'broadcom') {
1259 crypto_modes
.push(['psk2', 'WPA2-PSK', 33]);
1260 crypto_modes
.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1261 crypto_modes
.push(['psk', 'WPA-PSK', 21]);
1264 crypto_modes
.push(['wep-open', _('WEP Open System'), 11]);
1265 crypto_modes
.push(['wep-shared', _('WEP Shared Key'), 10]);
1266 crypto_modes
.push(['none', _('No Encryption'), 0]);
1268 crypto_modes
.sort(function(a
, b
) { return b
[2] - a
[2] });
1270 for (var i
= 0; i
< crypto_modes
.length
; i
++) {
1271 var security_level
= (crypto_modes
[i
][2] >= 30) ? _('strong security')
1272 : (crypto_modes
[i
][2] >= 20) ? _('medium security')
1273 : (crypto_modes
[i
][2] >= 10) ? _('weak security') : _('open network');
1275 encr
.value(crypto_modes
[i
][0], '%s (%s)'.format(crypto_modes
[i
][1], security_level
));
1279 o
= ss
.taboption('encryption', form
.Value
, 'auth_server', _('Radius-Authentication-Server'));
1280 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1282 o
.datatype
= 'host(0)';
1284 o
= ss
.taboption('encryption', form
.Value
, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
1285 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1287 o
.datatype
= 'port';
1289 o
= ss
.taboption('encryption', form
.Value
, 'auth_secret', _('Radius-Authentication-Secret'));
1290 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1294 o
= ss
.taboption('encryption', form
.Value
, 'acct_server', _('Radius-Accounting-Server'));
1295 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1297 o
.datatype
= 'host(0)';
1299 o
= ss
.taboption('encryption', form
.Value
, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
1300 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1302 o
.datatype
= 'port';
1304 o
= ss
.taboption('encryption', form
.Value
, 'acct_secret', _('Radius-Accounting-Secret'));
1305 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1309 o
= ss
.taboption('encryption', form
.Value
, 'dae_client', _('DAE-Client'));
1310 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1312 o
.datatype
= 'host(0)';
1314 o
= ss
.taboption('encryption', form
.Value
, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
1315 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1317 o
.datatype
= 'port';
1319 o
= ss
.taboption('encryption', form
.Value
, 'dae_secret', _('DAE-Secret'));
1320 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1325 o
= ss
.taboption('encryption', form
.Value
, '_wpa_key', _('Key'));
1326 o
.depends('encryption', 'psk');
1327 o
.depends('encryption', 'psk2');
1328 o
.depends('encryption', 'psk+psk2');
1329 o
.depends('encryption', 'psk-mixed');
1330 o
.depends('encryption', 'sae');
1331 o
.depends('encryption', 'sae-mixed');
1332 o
.datatype
= 'wpakey';
1336 o
.cfgvalue = function(section_id
) {
1337 var key
= uci
.get('wireless', section_id
, 'key');
1338 return /^[1234]$/.test(key
) ? null : key
;
1341 o
.write = function(section_id
, value
) {
1342 uci
.set('wireless', section_id
, 'key', value
);
1343 uci
.unset('wireless', section_id
, 'key1');
1344 uci
.unset('wireless', section_id
, 'key2');
1345 uci
.unset('wireless', section_id
, 'key3');
1346 uci
.unset('wireless', section_id
, 'key4');
1350 o
= ss
.taboption('encryption', form
.ListValue
, '_wep_key', _('Used Key Slot'));
1351 o
.depends('encryption', 'wep-open');
1352 o
.depends('encryption', 'wep-shared');
1353 o
.value('1', _('Key #%d').format(1));
1354 o
.value('2', _('Key #%d').format(2));
1355 o
.value('3', _('Key #%d').format(3));
1356 o
.value('4', _('Key #%d').format(4));
1358 o
.cfgvalue = function(section_id
) {
1359 var slot
= +uci
.get('wireless', section_id
, 'key');
1360 return (slot
>= 1 && slot
<= 4) ? String(slot
) : '';
1363 o
.write = function(section_id
, value
) {
1364 uci
.set('wireless', section_id
, 'key', value
);
1367 for (var slot
= 1; slot
<= 4; slot
++) {
1368 o
= ss
.taboption('encryption', form
.Value
, 'key%d'.format(slot
), _('Key #%d').format(slot
));
1369 o
.depends('encryption', 'wep-open');
1370 o
.depends('encryption', 'wep-shared');
1371 o
.datatype
= 'wepkey';
1375 o
.write = function(section_id
, value
) {
1376 if (value
!= null && (value
.length
== 5 || value
.length
== 13))
1377 value
= 's:%s'.format(value
);
1378 uci
.set('wireless', section_id
, this.option
, value
);
1383 if (hwtype
== 'mac80211') {
1384 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1385 var has_80211r
= L
.hasSystemFeature('hostapd', '11r') || L
.hasSystemFeature('hostapd', 'eap');
1387 o
= ss
.taboption('encryption', form
.Flag
, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1388 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1390 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1393 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.'));
1394 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1395 o
.depends({ ieee80211r
: '1' });
1398 o
= ss
.taboption('encryption', form
.Value
, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1399 o
.depends({ ieee80211r
: '1' });
1400 o
.placeholder
= '4f57';
1401 o
.datatype
= 'and(hexstring,length(4))';
1404 o
= ss
.taboption('encryption', form
.Value
, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1405 o
.depends({ ieee80211r
: '1' });
1406 o
.placeholder
= '1000';
1407 o
.datatype
= 'range(1000,65535)';
1410 o
= ss
.taboption('encryption', form
.ListValue
, 'ft_over_ds', _('FT protocol'));
1411 o
.depends({ ieee80211r
: '1' });
1412 o
.value('1', _('FT over DS'));
1413 o
.value('0', _('FT over the Air'));
1416 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.'));
1417 o
.depends({ ieee80211r
: '1' });
1418 o
.default = o
.enabled
;
1421 o
= ss
.taboption('encryption', form
.Value
, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1422 o
.depends({ ieee80211r
: '1' });
1423 o
.placeholder
= '10000';
1424 o
.datatype
= 'uinteger';
1427 o
= ss
.taboption('encryption', form
.Value
, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1428 o
.depends({ ieee80211r
: '1' });
1429 o
.placeholder
= '00004f577274';
1430 o
.datatype
= 'and(hexstring,length(12))';
1433 o
= ss
.taboption('encryption', form
.Flag
, 'pmk_r1_push', _('PMK R1 Push'));
1434 o
.depends({ ieee80211r
: '1' });
1435 o
.placeholder
= '0';
1438 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.'));
1439 o
.depends({ ieee80211r
: '1' });
1442 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.'));
1443 o
.depends({ ieee80211r
: '1' });
1445 // End of 802.11r options
1447 o
= ss
.taboption('encryption', form
.ListValue
, 'eap_type', _('EAP-Method'));
1448 o
.value('tls', 'TLS');
1449 o
.value('ttls', 'TTLS');
1450 o
.value('peap', 'PEAP');
1451 o
.value('fast', 'FAST');
1452 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1454 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"));
1457 o
.default = o
.disabled
;
1458 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1459 o
.validate = function(section_id
, value
) {
1460 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1461 return _("This option cannot be used because the ca-bundle package is not installed.");
1466 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert', _('Path to CA-Certificate'));
1467 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], ca_cert_usesystem
: ['0'] });
1469 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"));
1470 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1472 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"));
1473 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1475 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)"));
1476 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1478 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)"));
1479 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1481 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert', _('Path to Client-Certificate'));
1482 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1484 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key', _('Path to Private Key'));
1485 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1487 o
= ss
.taboption('encryption', form
.Value
, 'priv_key_pwd', _('Password of Private Key'));
1488 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['tls'] });
1491 o
= ss
.taboption('encryption', form
.ListValue
, 'auth', _('Authentication'));
1492 o
.value('PAP', 'PAP');
1493 o
.value('CHAP', 'CHAP');
1494 o
.value('MSCHAP', 'MSCHAP');
1495 o
.value('MSCHAPV2', 'MSCHAPv2');
1496 o
.value('EAP-GTC', 'EAP-GTC');
1497 o
.value('EAP-MD5', 'EAP-MD5');
1498 o
.value('EAP-MSCHAPV2', 'EAP-MSCHAPv2');
1499 o
.value('EAP-TLS', 'EAP-TLS');
1500 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1502 o
.validate = function(section_id
, value
) {
1503 var eo
= this.section
.children
.filter(function(o
) { return o
.option
== 'eap_type' })[0],
1504 ev
= eo
.formvalue(section_id
);
1506 if (ev
!= 'ttls' && (value
== 'PAP' || value
== 'CHAP' || value
== 'MSCHAP' || value
== 'MSCHAPV2'))
1507 return _('This authentication type is not applicable to the selected EAP method.');
1512 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"));
1515 o
.default = o
.disabled
;
1516 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1517 o
.validate = function(section_id
, value
) {
1518 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1519 return _("This option cannot be used because the ca-bundle package is not installed.");
1524 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert2', _('Path to inner CA-Certificate'));
1525 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'], ca_cert2_usesystem
: ['0'] });
1527 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"));
1528 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1530 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"));
1531 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1533 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)"));
1534 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1536 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)"));
1537 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1539 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert2', _('Path to inner Client-Certificate'));
1540 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1542 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key2', _('Path to inner Private Key'));
1543 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1545 o
= ss
.taboption('encryption', form
.Value
, 'priv_key2_pwd', _('Password of inner Private Key'));
1546 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth
: ['EAP-TLS'] });
1549 o
= ss
.taboption('encryption', form
.Value
, 'identity', _('Identity'));
1550 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1552 o
= ss
.taboption('encryption', form
.Value
, 'anonymous_identity', _('Anonymous Identity'));
1553 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1555 o
= ss
.taboption('encryption', form
.Value
, 'password', _('Password'));
1556 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type
: ['fast', 'peap', 'ttls'] });
1560 if (hwtype
== 'mac80211') {
1561 // ieee802.11w options
1562 if (L
.hasSystemFeature('hostapd', '11w')) {
1563 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)"));
1564 o
.value('', _('Disabled'));
1565 o
.value('1', _('Optional'));
1566 o
.value('2', _('Required'));
1567 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds', 'sta', 'sta-wds'], encryption
: ['owe', 'psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1570 '2': [{ encryption
: 'sae' }, { encryption
: 'owe' }, { encryption
: 'wpa3' }, { encryption
: 'wpa3-mixed' }],
1571 '1': [{ encryption
: 'sae-mixed'}],
1575 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1576 o
.depends('ieee80211w', '1');
1577 o
.depends('ieee80211w', '2');
1578 o
.datatype
= 'uinteger';
1579 o
.placeholder
= '1000';
1582 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1583 o
.depends('ieee80211w', '1');
1584 o
.depends('ieee80211w', '2');
1585 o
.datatype
= 'uinteger';
1586 o
.placeholder
= '201';
1590 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.'));
1591 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1593 if (L
.hasSystemFeature('hostapd', 'cli') && L
.hasSystemFeature('wpasupplicant')) {
1594 o
= ss
.taboption('encryption', form
.Flag
, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1597 o
.default = o
.disabled
;
1598 o
.depends('encryption', 'psk');
1599 o
.depends('encryption', 'psk2');
1600 o
.depends('encryption', 'psk-mixed');
1601 o
.depends('encryption', 'sae');
1602 o
.depends('encryption', 'sae-mixed');
1609 s
.handleRemove = function(section_id
, ev
) {
1610 document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id
)).style
.opacity
= 0.5;
1611 return form
.TypedSection
.prototype.handleRemove
.apply(this, [section_id
, ev
]);
1614 s
.handleScan = function(radioDev
, ev
) {
1615 var table
= E('div', { 'class': 'table' }, [
1616 E('div', { 'class': 'tr table-titles' }, [
1617 E('div', { 'class': 'th col-2 middle center' }, _('Signal')),
1618 E('div', { 'class': 'th col-4 middle left' }, _('SSID')),
1619 E('div', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1620 E('div', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1621 E('div', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1622 E('div', { 'class': 'th col-3 middle left' }, _('Encryption')),
1623 E('div', { 'class': 'th cbi-section-actions right' }, ' '),
1627 var stop
= E('button', {
1629 'click': L
.bind(this.handleScanStartStop
, this),
1630 'style': 'display:none',
1631 'data-state': 'stop'
1632 }, _('Stop refresh'));
1634 cbi_update_table(table
, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1636 var md
= ui
.showModal(_('Join Network: Wireless Scan'), [
1638 E('div', { 'class': 'right' }, [
1643 'click': L
.bind(this.handleScanAbort
, this)
1648 md
.style
.maxWidth
= '90%';
1649 md
.style
.maxHeight
= 'none';
1651 this.pollFn
= L
.bind(this.handleScanRefresh
, this, radioDev
, {}, table
, stop
);
1653 L
.Poll
.add(this.pollFn
);
1657 s
.handleScanRefresh = function(radioDev
, scanCache
, table
, stop
) {
1658 return radioDev
.getScanList().then(L
.bind(function(results
) {
1661 for (var i
= 0; i
< results
.length
; i
++)
1662 scanCache
[results
[i
].bssid
] = results
[i
];
1664 for (var k
in scanCache
)
1665 if (scanCache
[k
].stale
)
1666 results
.push(scanCache
[k
]);
1668 results
.sort(function(a
, b
) {
1669 var diff
= (b
.quality
- a
.quality
) || (a
.channel
- b
.channel
);
1674 if (a
.ssid
< b
.ssid
)
1676 else if (a
.ssid
> b
.ssid
)
1679 if (a
.bssid
< b
.bssid
)
1681 else if (a
.bssid
> b
.bssid
)
1685 for (var i
= 0; i
< results
.length
; i
++) {
1686 var res
= results
[i
],
1687 qv
= res
.quality
|| 0,
1688 qm
= res
.quality_max
|| 0,
1689 q
= (qv
> 0 && qm
> 0) ? Math
.floor((100 / qm
) * qv
) : 0,
1690 s
= res
.stale
? 'opacity:0.5' : '';
1693 E('span', { 'style': s
}, render_signal_badge(q
, res
.signal
, res
.noise
)),
1694 E('span', { 'style': s
}, (res
.ssid
!= null) ? '%h'.format(res
.ssid
) : E('em', _('hidden'))),
1695 E('span', { 'style': s
}, '%d'.format(res
.channel
)),
1696 E('span', { 'style': s
}, '%h'.format(res
.mode
)),
1697 E('span', { 'style': s
}, '%h'.format(res
.bssid
)),
1698 E('span', { 'style': s
}, '%h'.format(network
.formatWifiEncryption(res
.encryption
))),
1699 E('div', { 'class': 'right' }, E('button', {
1700 'class': 'cbi-button cbi-button-action important',
1701 'click': L
.bind(this.handleJoin
, this, radioDev
, res
)
1702 }, _('Join Network')))
1708 cbi_update_table(table
, rows
);
1710 stop
.disabled
= false;
1711 stop
.style
.display
= '';
1712 stop
.classList
.remove('spinning');
1716 s
.handleScanStartStop = function(ev
) {
1717 var btn
= ev
.currentTarget
;
1719 if (btn
.getAttribute('data-state') == 'stop') {
1720 L
.Poll
.remove(this.pollFn
);
1721 btn
.firstChild
.data
= _('Start refresh');
1722 btn
.setAttribute('data-state', 'start');
1725 L
.Poll
.add(this.pollFn
);
1726 btn
.firstChild
.data
= _('Stop refresh');
1727 btn
.setAttribute('data-state', 'stop');
1728 btn
.classList
.add('spinning');
1729 btn
.disabled
= true;
1733 s
.handleScanAbort = function(ev
) {
1734 var md
= L
.dom
.parent(ev
.target
, 'div[aria-modal="true"]');
1736 md
.style
.maxWidth
= '';
1737 md
.style
.maxHeight
= '';
1741 L
.Poll
.remove(this.pollFn
);
1746 s
.handleJoinConfirm = function(radioDev
, bss
, form
, ev
) {
1747 var nameopt
= L
.toArray(form
.lookupOption('name', '_new_'))[0],
1748 passopt
= L
.toArray(form
.lookupOption('password', '_new_'))[0],
1749 bssidopt
= L
.toArray(form
.lookupOption('bssid', '_new_'))[0],
1750 zoneopt
= L
.toArray(form
.lookupOption('zone', '_new_'))[0],
1751 replopt
= L
.toArray(form
.lookupOption('replace', '_new_'))[0],
1752 nameval
= (nameopt
&& nameopt
.isValid('_new_')) ? nameopt
.formvalue('_new_') : null,
1753 passval
= (passopt
&& passopt
.isValid('_new_')) ? passopt
.formvalue('_new_') : null,
1754 bssidval
= (bssidopt
&& bssidopt
.isValid('_new_')) ? bssidopt
.formvalue('_new_') : null,
1755 zoneval
= zoneopt
? zoneopt
.formvalue('_new_') : null,
1756 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
1757 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
1758 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' })),
1759 is_sae
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'sae' }));
1761 if (nameval
== null || (passopt
&& passval
== null))
1764 var section_id
= null;
1766 return this.map
.save(function() {
1767 var wifi_sections
= uci
.sections('wireless', 'wifi-iface');
1769 if (replopt
.formvalue('_new_') == '1') {
1770 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1771 if (wifi_sections
[i
].device
== radioDev
.getName())
1772 uci
.remove('wireless', wifi_sections
[i
]['.name']);
1775 if (uci
.get('wireless', radioDev
.getName(), 'disabled') == '1') {
1776 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1777 if (wifi_sections
[i
].device
== radioDev
.getName())
1778 uci
.set('wireless', wifi_sections
[i
]['.name'], 'disabled', '1');
1780 uci
.unset('wireless', radioDev
.getName(), 'disabled');
1783 section_id
= next_free_sid(wifi_sections
.length
);
1785 uci
.add('wireless', 'wifi-iface', section_id
);
1786 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
1787 uci
.set('wireless', section_id
, 'mode', (bss
.mode
== 'Ad-Hoc') ? 'adhoc' : 'sta');
1788 uci
.set('wireless', section_id
, 'network', nameval
);
1790 if (bss
.ssid
!= null) {
1791 uci
.set('wireless', section_id
, 'ssid', bss
.ssid
);
1793 if (bssidval
== '1')
1794 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
1796 else if (bss
.bssid
!= null) {
1797 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
1801 uci
.set('wireless', section_id
, 'encryption', 'sae');
1802 uci
.set('wireless', section_id
, 'key', passval
);
1805 for (var i
= enc
.wpa
.length
- 1; i
>= 0; i
--) {
1806 if (enc
.wpa
[i
] == 2) {
1807 uci
.set('wireless', section_id
, 'encryption', 'psk2');
1810 else if (enc
.wpa
[i
] == 1) {
1811 uci
.set('wireless', section_id
, 'encryption', 'psk');
1816 uci
.set('wireless', section_id
, 'key', passval
);
1819 uci
.set('wireless', section_id
, 'encryption', 'wep-open');
1820 uci
.set('wireless', section_id
, 'key', '1');
1821 uci
.set('wireless', section_id
, 'key1', passval
);
1824 uci
.set('wireless', section_id
, 'encryption', 'none');
1827 return network
.addNetwork(nameval
, { proto
: 'dhcp' }).then(function(net
) {
1828 firewall
.deleteNetwork(net
.getName());
1830 var zonePromise
= zoneval
1831 ? firewall
.getZone(zoneval
).then(function(zone
) { return zone
|| firewall
.addZone(zoneval
) })
1832 : Promise
.resolve();
1834 return zonePromise
.then(function(zone
) {
1836 zone
.addNetwork(net
.getName());
1839 }).then(L
.bind(function() {
1840 return this.renderMoreOptionsModal(section_id
);
1844 s
.handleJoin = function(radioDev
, bss
, ev
) {
1845 this.handleScanAbort(ev
);
1847 var m2
= new form
.Map('wireless'),
1848 s2
= m2
.section(form
.NamedSection
, '_new_'),
1849 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
1850 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
1851 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' || a
== 'sae' })),
1852 replace
, passphrase
, name
, bssid
, zone
;
1854 var nameUsed = function(name
) {
1855 var s
= uci
.get('network', name
);
1856 if (s
!= null && s
['.type'] != 'interface')
1859 var net
= (s
!= null) ? network
.instantiateNetwork(name
) : null;
1860 return (net
!= null && !net
.isEmpty());
1863 s2
.render = function() {
1864 return Promise
.all([
1866 this.renderUCISection('_new_')
1867 ]).then(this.renderContents
.bind(this));
1870 replace
= s2
.option(form
.Flag
, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
1872 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>'));
1873 name
.datatype
= 'uciname';
1874 name
.default = 'wwan';
1875 name
.rmempty
= false;
1876 name
.validate = function(section_id
, value
) {
1877 if (nameUsed(value
))
1878 return _('The network name is already used');
1883 for (var i
= 2; nameUsed(name
.default); i
++)
1884 name
.default = 'wwan%d'.format(i
);
1886 if (is_wep
|| is_psk
) {
1887 passphrase
= s2
.option(form
.Value
, 'password', is_wep
? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
1888 passphrase
.datatype
= is_wep
? 'wepkey' : 'wpakey';
1889 passphrase
.password
= true;
1890 passphrase
.rmempty
= false;
1893 if (bss
.ssid
!= null) {
1894 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
));
1895 bssid
.default = '0';
1898 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>create</em> field to define a new zone and attach the interface to it.'));
1899 zone
.default = 'wan';
1901 return m2
.render().then(L
.bind(function(nodes
) {
1902 ui
.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss
.ssid
)), [
1904 E('div', { 'class': 'right' }, [
1907 'click': ui
.hideModal
1908 }, _('Cancel')), ' ',
1910 'class': 'cbi-button cbi-button-positive important',
1911 'click': ui
.createHandlerFn(this, 'handleJoinConfirm', radioDev
, bss
, m2
)
1914 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase
|| name
).cbid('_new_'))).focus();
1918 s
.handleAdd = function(radioDev
, ev
) {
1919 var section_id
= next_free_sid(uci
.sections('wireless', 'wifi-iface').length
);
1921 uci
.unset('wireless', radioDev
.getName(), 'disabled');
1923 uci
.add('wireless', 'wifi-iface', section_id
);
1924 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
1925 uci
.set('wireless', section_id
, 'mode', 'ap');
1926 uci
.set('wireless', section_id
, 'ssid', 'OpenWrt');
1927 uci
.set('wireless', section_id
, 'encryption', 'none');
1929 this.addedSection
= section_id
;
1930 return this.renderMoreOptionsModal(section_id
);
1933 o
= s
.option(form
.DummyValue
, '_badge');
1934 o
.modalonly
= false;
1935 o
.textvalue = function(section_id
) {
1936 var inst
= this.section
.lookupRadioOrNetwork(section_id
),
1937 node
= E('div', { 'class': 'center' });
1939 if (inst
.getWifiNetworks
)
1940 node
.appendChild(render_radio_badge(inst
));
1942 node
.appendChild(render_network_badge(inst
));
1947 o
= s
.option(form
.DummyValue
, '_stat');
1948 o
.modalonly
= false;
1949 o
.textvalue = function(section_id
) {
1950 var inst
= this.section
.lookupRadioOrNetwork(section_id
);
1952 if (inst
.getWifiNetworks
)
1953 return render_radio_status(inst
, this.section
.wifis
.filter(function(e
) {
1954 return (e
.getWifiDeviceName() == inst
.getName());
1957 return render_network_status(inst
);
1960 return m
.render().then(L
.bind(function(m
, nodes
) {
1961 L
.Poll
.add(L
.bind(function() {
1962 var section_ids
= m
.children
[0].cfgsections(),
1963 tasks
= [ network
.getHostHints(), network
.getWifiDevices() ];
1965 for (var i
= 0; i
< section_ids
.length
; i
++) {
1966 var row
= nodes
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids
[i
])),
1967 dsc
= row
.querySelector('[data-name="_stat"] > div'),
1968 btns
= row
.querySelectorAll('.cbi-section-actions button');
1970 if (dsc
.getAttribute('restart') == '') {
1971 dsc
.setAttribute('restart', '1');
1972 tasks
.push(fs
.exec('/sbin/wifi', ['up', section_ids
[i
]]).catch(function(e
) {
1973 ui
.addNotification(null, E('p', e
.message
));
1976 else if (dsc
.getAttribute('restart') == '1') {
1977 dsc
.removeAttribute('restart');
1978 btns
[0].classList
.remove('spinning');
1979 btns
[0].disabled
= false;
1983 return Promise
.all(tasks
)
1984 .then(L
.bind(function(hosts_radios
) {
1987 for (var i
= 0; i
< hosts_radios
[1].length
; i
++)
1988 tasks
.push(hosts_radios
[1][i
].getWifiNetworks());
1990 return Promise
.all(tasks
).then(function(data
) {
1991 hosts_radios
[2] = [];
1993 for (var i
= 0; i
< data
.length
; i
++)
1994 hosts_radios
[2].push
.apply(hosts_radios
[2], data
[i
]);
1996 return hosts_radios
;
1999 .then(L
.bind(function(hosts_radios_wifis
) {
2002 for (var i
= 0; i
< hosts_radios_wifis
[2].length
; i
++)
2003 tasks
.push(hosts_radios_wifis
[2][i
].getAssocList());
2005 return Promise
.all(tasks
).then(function(data
) {
2006 hosts_radios_wifis
[3] = [];
2008 for (var i
= 0; i
< data
.length
; i
++) {
2009 var wifiNetwork
= hosts_radios_wifis
[2][i
],
2010 radioDev
= hosts_radios_wifis
[1].filter(function(d
) { return d
.getName() == wifiNetwork
.getWifiDeviceName() })[0];
2012 for (var j
= 0; j
< data
[i
].length
; j
++)
2013 hosts_radios_wifis
[3].push(Object
.assign({ radio
: radioDev
, network
: wifiNetwork
}, data
[i
][j
]));
2016 return hosts_radios_wifis
;
2019 .then(L
.bind(this.poll_status
, this, nodes
));
2022 var table
= E('div', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [
2023 E('div', { 'class': 'tr table-titles' }, [
2024 E('div', { 'class': 'th nowrap' }, _('Network')),
2025 E('div', { 'class': 'th hide-xs' }, _('MAC-Address')),
2026 E('div', { 'class': 'th' }, _('Host')),
2027 E('div', { 'class': 'th' }, _('Signal / Noise')),
2028 E('div', { 'class': 'th' }, _('RX Rate / TX Rate'))
2032 cbi_update_table(table
, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
2034 return E([ nodes
, E('h3', _('Associated Stations')), table
]);