8 'require tools.widgets as widgets';
10 function count_changes(section_id
) {
11 var changes
= L
.ui
.changes
.changes
, n
= 0;
13 if (!L
.isObject(changes
))
16 if (Array
.isArray(changes
.wireless
))
17 for (var i
= 0; i
< changes
.wireless
.length
; i
++)
18 n
+= (changes
.wireless
[i
][1] == section_id
);
23 function render_radio_badge(radioDev
) {
24 return E('span', { 'class': 'ifacebadge' }, [
25 E('img', { 'src': L
.resource('icons/wifi%s.png').format(radioDev
.isUp() ? '' : '_disabled') }),
31 function render_signal_badge(signalPercent
, signalValue
, noiseValue
, wrap
) {
34 if (signalPercent
< 0)
35 icon
= L
.resource('icons/signal-none.png');
36 else if (signalPercent
== 0)
37 icon
= L
.resource('icons/signal-0.png');
38 else if (signalPercent
< 25)
39 icon
= L
.resource('icons/signal-0-25.png');
40 else if (signalPercent
< 50)
41 icon
= L
.resource('icons/signal-25-50.png');
42 else if (signalPercent
< 75)
43 icon
= L
.resource('icons/signal-50-75.png');
45 icon
= L
.resource('icons/signal-75-100.png');
47 if (signalValue
!= null && signalValue
!= 0) {
48 title
= '%s %d %s'.format(_('Signal'), signalValue
, _('dBm'));
50 if (noiseValue
!= null && noiseValue
!= 0)
51 title
+= ' / %s: %d %s'.format(_('Noise'), noiseValue
, _('dBm'));
54 title
= _('No signal');
57 return E('div', { 'class': wrap
? 'center' : 'ifacebadge', 'title': title
},
58 [ E('img', { 'src': icon
}), wrap
? E('br') : ' ', '%d%%'.format(Math
.max(signalPercent
, 0)) ]);
61 function render_network_badge(radioNet
) {
62 return render_signal_badge(radioNet
.isUp() ? radioNet
.getSignalPercent() : -1, radioNet
.getSignal(), radioNet
.getNoise());
65 function render_radio_status(radioDev
, wifiNets
) {
66 var name
= radioDev
.getI18n().replace(/ Wireless Controller
.+$/, ''),
67 node
= E('div', [ E('big', {}, E('strong', {}, name
)), E('div') ]),
68 channel
, frequency
, bitrate
;
70 for (var i
= 0; i
< wifiNets
.length
; i
++) {
71 channel
= channel
|| wifiNets
[i
].getChannel();
72 frequency
= frequency
|| wifiNets
[i
].getFrequency();
73 bitrate
= bitrate
|| wifiNets
[i
].getBitRate();
77 L
.itemlist(node
.lastElementChild
, [
78 _('Channel'), '%s (%s %s)'.format(channel
|| '?', frequency
|| '?', _('GHz')),
79 _('Bitrate'), '%s %s'.format(bitrate
|| '?', _('Mbit/s'))
82 node
.lastElementChild
.appendChild(E('em', _('Device is not active')));
87 function render_network_status(radioNet
) {
88 var mode
= radioNet
.getActiveMode(),
89 bssid
= radioNet
.getActiveBSSID(),
90 channel
= radioNet
.getChannel(),
91 disabled
= (radioNet
.get('disabled') == '1' || uci
.get('wireless', radioNet
.getWifiDeviceName(), 'disabled') == '1'),
92 is_assoc
= (bssid
&& bssid
!= '00:00:00:00:00:00' && channel
&& mode
!= 'Unknown' && !disabled
),
93 is_mesh
= (radioNet
.getMode() == 'mesh'),
94 changecount
= count_changes(radioNet
.getName()),
98 status_text
= E('a', {
100 click
: L
.bind(L
.ui
.changes
.displayChanges
, L
.ui
.changes
)
101 }, _('Interface has %d pending changes').format(changecount
));
103 status_text
= E('em', disabled
? _('Wireless is disabled') : _('Wireless is not associated'));
105 return L
.itemlist(E('div'), [
106 is_mesh
? _('Mesh ID') : _('SSID'), (is_mesh
? radioNet
.getMeshID() : radioNet
.getSSID()) || '?',
108 _('BSSID'), (!changecount
&& is_assoc
) ? bssid
: null,
109 _('Encryption'), (!changecount
&& is_assoc
) ? radioNet
.getActiveEncryption() || _('None') : null,
111 ], [ ' | ', E('br') ]);
114 function render_modal_status(node
, radioNet
) {
115 var mode
= radioNet
.getActiveMode(),
116 noise
= radioNet
.getNoise(),
117 bssid
= radioNet
.getActiveBSSID(),
118 channel
= radioNet
.getChannel(),
119 disabled
= (radioNet
.get('disabled') == '1'),
120 is_assoc
= (bssid
&& bssid
!= '00:00:00:00:00:00' && channel
&& mode
!= 'Unknown' && !disabled
);
123 node
= E('span', { 'class': 'ifacebadge large', 'data-network': radioNet
.getName() }, [ E('small'), E('span') ]);
125 L
.dom
.content(node
.firstElementChild
, render_signal_badge(disabled
? -1 : radioNet
.getSignalPercent(), radioNet
.getSignal(), noise
, true));
127 L
.itemlist(node
.lastElementChild
, [
129 _('SSID'), radioNet
.getSSID() || '?',
130 _('BSSID'), is_assoc
? bssid
: null,
131 _('Encryption'), is_assoc
? radioNet
.getActiveEncryption() || _('None') : null,
132 _('Channel'), is_assoc
? '%d (%.3f %s)'.format(radioNet
.getChannel(), radioNet
.getFrequency() || 0, _('GHz')) : null,
133 _('Tx-Power'), is_assoc
? '%d %s'.format(radioNet
.getTXPower(), _('dBm')) : null,
134 _('Signal'), is_assoc
? '%d %s'.format(radioNet
.getSignal(), _('dBm')) : null,
135 _('Noise'), (is_assoc
&& noise
!= null) ? '%d %s'.format(noise
, _('dBm')) : null,
136 _('Bitrate'), is_assoc
? '%.1f %s'.format(radioNet
.getBitRate() || 0, _('Mbit/s')) : null,
137 _('Country'), is_assoc
? radioNet
.getCountryCode() : null
138 ], [ ' | ', E('br'), E('br'), E('br'), E('br'), E('br'), ' | ', E('br'), ' | ' ]);
141 L
.dom
.append(node
.lastElementChild
, E('em', disabled
? _('Wireless is disabled') : _('Wireless is not associated')));
146 function format_wifirate(rate
) {
147 var s
= '%.1f Mbit/s, %dMHz'.format(rate
.rate
/ 1000, rate
.mhz
);
149 if (rate
.ht
|| rate
.vht
) {
150 if (rate
.vht
) s
+= ', VHT-MCS %d'.format(rate
.mcs
);
151 if (rate
.nss
) s
+= ', VHT-NSS %d'.format(rate
.nss
);
152 if (rate
.ht
) s
+= ', MCS %s'.format(rate
.mcs
);
153 if (rate
.short_gi
) s
+= ', Short GI';
159 function radio_restart(id
, ev
) {
160 var row
= document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id
)),
161 dsc
= row
.querySelector('[data-name="_stat"] > div'),
162 btn
= row
.querySelector('.cbi-section-actions button');
165 btn
.classList
.add('spinning');
168 dsc
.setAttribute('restart', '');
169 L
.dom
.content(dsc
, E('em', _('Device is restarting…')));
172 function network_updown(id
, map
, ev
) {
173 var radio
= uci
.get('wireless', id
, 'device'),
174 disabled
= (uci
.get('wireless', id
, 'disabled') == '1') ||
175 (uci
.get('wireless', radio
, 'disabled') == '1');
178 uci
.unset('wireless', id
, 'disabled');
179 uci
.unset('wireless', radio
, 'disabled');
182 uci
.set('wireless', id
, 'disabled', '1');
184 var all_networks_disabled
= true,
185 wifi_ifaces
= uci
.sections('wireless', 'wifi-iface');
187 for (var i
= 0; i
< wifi_ifaces
.length
; i
++) {
188 if (wifi_ifaces
[i
].device
== radio
&& wifi_ifaces
[i
].disabled
!= '1') {
189 all_networks_disabled
= false;
194 if (all_networks_disabled
)
195 uci
.set('wireless', radio
, 'disabled', '1');
198 return map
.save().then(function() {
203 function next_free_sid(offset
) {
204 var sid
= 'wifinet' + offset
;
206 while (uci
.get('wireless', sid
))
207 sid
= 'wifinet' + (++offset
);
212 var CBIWifiFrequencyValue
= form
.Value
.extend({
213 callFrequencyList
: rpc
.declare({
216 params
: [ 'device' ],
217 expect
: { results
: [] }
220 load: function(section_id
) {
222 network
.getWifiDevice(section_id
),
223 this.callFrequencyList(section_id
)
224 ]).then(L
.bind(function(data
) {
226 '11g': L
.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
227 '11a': L
.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : []
230 for (var i
= 0; i
< data
[1].length
; i
++)
231 this.channels
[(data
[1][i
].mhz
> 2484) ? '11a' : '11g'].push(
233 '%d (%d Mhz)'.format(data
[1][i
].channel
, data
[1][i
].mhz
),
234 !data
[1][i
].restricted
237 var hwmodelist
= L
.toArray(data
[0] ? data
[0].getHWModes() : null)
238 .reduce(function(o
, v
) { o
[v
] = true; return o
}, {});
242 'n', 'N', hwmodelist
.n
,
243 'ac', 'AC', hwmodelist
.ac
246 var htmodelist
= L
.toArray(data
[0] ? data
[0].getHTModes() : null)
247 .reduce(function(o
, v
) { o
[v
] = true; return o
}, {});
250 '': [ '', '-', true ],
252 'HT20', '20 MHz', htmodelist
.HT20
,
253 'HT40', '40 MHz', htmodelist
.HT40
256 'VHT20', '20 MHz', htmodelist
.VHT20
,
257 'VHT40', '40 MHz', htmodelist
.VHT40
,
258 'VHT80', '80 MHz', htmodelist
.VHT80
,
259 'VHT160', '160 MHz', htmodelist
.VHT160
265 '11g', '2.4 GHz', this.channels
['11g'].length
> 3,
266 '11a', '5 GHz', this.channels
['11a'].length
> 3
269 '11g', '2.4 GHz', this.channels
['11g'].length
> 3,
270 '11a', '5 GHz', this.channels
['11a'].length
> 3
279 setValues: function(sel
, vals
) {
281 sel
.vals
.selected
= sel
.selectedIndex
;
283 while (sel
.options
[0])
286 for (var i
= 0; vals
&& i
< vals
.length
; i
+= 3)
288 sel
.add(E('option', { value
: vals
[i
+0] }, [ vals
[i
+1] ]));
290 if (vals
&& !isNaN(vals
.selected
))
291 sel
.selectedIndex
= vals
.selected
;
293 sel
.parentNode
.style
.display
= (sel
.options
.length
<= 1) ? 'none' : '';
297 toggleWifiMode: function(elem
) {
298 this.toggleWifiHTMode(elem
);
299 this.toggleWifiBand(elem
);
302 toggleWifiHTMode: function(elem
) {
303 var mode
= elem
.querySelector('.mode');
304 var bwdt
= elem
.querySelector('.htmode');
306 this.setValues(bwdt
, this.htmodes
[mode
.value
]);
309 toggleWifiBand: function(elem
) {
310 var mode
= elem
.querySelector('.mode');
311 var band
= elem
.querySelector('.band');
313 this.setValues(band
, this.bands
[mode
.value
]);
314 this.toggleWifiChannel(elem
);
317 toggleWifiChannel: function(elem
) {
318 var band
= elem
.querySelector('.band');
319 var chan
= elem
.querySelector('.channel');
321 this.setValues(chan
, this.channels
[band
.value
]);
324 setInitialValues: function(section_id
, elem
) {
325 var mode
= elem
.querySelector('.mode'),
326 band
= elem
.querySelector('.band'),
327 chan
= elem
.querySelector('.channel'),
328 bwdt
= elem
.querySelector('.htmode'),
329 htval
= uci
.get('wireless', section_id
, 'htmode'),
330 hwval
= uci
.get('wireless', section_id
, 'hwmode'),
331 chval
= uci
.get('wireless', section_id
, 'channel');
333 this.setValues(mode
, this.modes
);
335 if (/VHT20|VHT40|VHT80|VHT160/.test(htval
))
337 else if (/HT20|HT40/.test(htval
))
342 this.toggleWifiMode(elem
);
349 this.toggleWifiBand(elem
);
357 renderWidget: function(section_id
, option_index
, cfgvalue
) {
360 L
.dom
.content(elem
, [
361 E('label', { 'style': 'float:left; margin-right:3px' }, [
365 'style': 'width:auto',
366 'change': L
.bind(this.toggleWifiMode
, this, elem
)
369 E('label', { 'style': 'float:left; margin-right:3px' }, [
373 'style': 'width:auto',
374 'change': L
.bind(this.toggleWifiBand
, this, elem
)
377 E('label', { 'style': 'float:left; margin-right:3px' }, [
378 _('Channel'), E('br'),
381 'style': 'width:auto'
384 E('label', { 'style': 'float:left; margin-right:3px' }, [
388 'style': 'width:auto'
391 E('br', { 'style': 'clear:left' })
394 return this.setInitialValues(section_id
, elem
);
397 cfgvalue: function(section_id
) {
399 uci
.get('wireless', section_id
, 'htmode'),
400 uci
.get('wireless', section_id
, 'hwmode'),
401 uci
.get('wireless', section_id
, 'channel')
405 formvalue: function(section_id
) {
406 var node
= this.map
.findElement('data-field', this.cbid(section_id
));
409 node
.querySelector('.htmode').value
,
410 node
.querySelector('.band').value
,
411 node
.querySelector('.channel').value
415 write: function(section_id
, value
) {
416 uci
.set('wireless', section_id
, 'htmode', value
[0] || null);
417 uci
.set('wireless', section_id
, 'hwmode', value
[1]);
418 uci
.set('wireless', section_id
, 'channel', value
[2]);
422 var CBIWifiTxPowerValue
= form
.ListValue
.extend({
423 callTxPowerList
: rpc
.declare({
425 method
: 'txpowerlist',
426 params
: [ 'device' ],
427 expect
: { results
: [] }
430 load: function(section_id
) {
431 return this.callTxPowerList(section_id
).then(L
.bind(function(pwrlist
) {
432 this.powerval
= this.wifiNetwork
? this.wifiNetwork
.getTXPower() : null;
433 this.poweroff
= this.wifiNetwork
? this.wifiNetwork
.getTXPowerOffset() : null;
435 this.value('', _('driver default'));
437 for (var i
= 0; i
< pwrlist
.length
; i
++)
438 this.value(pwrlist
[i
].dbm
, '%d dBm (%d mW)'.format(pwrlist
[i
].dbm
, pwrlist
[i
].mw
));
440 return form
.ListValue
.prototype.load
.apply(this, [section_id
]);
444 renderWidget: function(section_id
, option_index
, cfgvalue
) {
445 var widget
= form
.ListValue
.prototype.renderWidget
.apply(this, [section_id
, option_index
, cfgvalue
]);
446 widget
.firstElementChild
.style
.width
= 'auto';
448 L
.dom
.append(widget
, E('span', [
449 ' - ', _('Current power'), ': ',
450 E('span', [ this.powerval
!= null ? '%d dBm'.format(this.powerval
) : E('em', _('unknown')) ]),
451 this.poweroff
? ' + %d dB offset = %s dBm'.format(this.poweroff
, this.powerval
!= null ? this.powerval
+ this.poweroff
: '?') : ''
458 var CBIWifiCountryValue
= form
.Value
.extend({
459 callCountryList
: rpc
.declare({
461 method
: 'countrylist',
462 params
: [ 'device' ],
463 expect
: { results
: [] }
466 load: function(section_id
) {
467 return this.callCountryList(section_id
).then(L
.bind(function(countrylist
) {
468 if (Array
.isArray(countrylist
) && countrylist
.length
> 0) {
469 this.value('', _('driver default'));
471 for (var i
= 0; i
< countrylist
.length
; i
++)
472 this.value(countrylist
[i
].iso3166
, '%s - %s'.format(countrylist
[i
].iso3166
, countrylist
[i
].country
));
475 return form
.Value
.prototype.load
.apply(this, [section_id
]);
479 validate: function(section_id
, formvalue
) {
480 if (formvalue
!= null && formvalue
!= '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue
))
481 return _('Use ISO/IEC 3166 alpha2 country codes.');
486 renderWidget: function(section_id
, option_index
, cfgvalue
) {
487 var typeClass
= (this.keylist
&& this.keylist
.length
) ? form
.ListValue
: form
.Value
;
488 return typeClass
.prototype.renderWidget
.apply(this, [section_id
, option_index
, cfgvalue
]);
492 return L
.view
.extend({
493 poll_status: function(map
, data
) {
494 var rows
= map
.querySelectorAll('.cbi-section-table-row[data-sid]');
496 for (var i
= 0; i
< rows
.length
; i
++) {
497 var section_id
= rows
[i
].getAttribute('data-sid'),
498 radioDev
= data
[1].filter(function(d
) { return d
.getName() == section_id
})[0],
499 radioNet
= data
[2].filter(function(n
) { return n
.getName() == section_id
})[0],
500 badge
= rows
[i
].querySelector('[data-name="_badge"] > div'),
501 stat
= rows
[i
].querySelector('[data-name="_stat"]'),
502 btns
= rows
[i
].querySelectorAll('.cbi-section-actions button'),
503 busy
= btns
[0].classList
.contains('spinning') || btns
[1].classList
.contains('spinning') || btns
[2].classList
.contains('spinning');
506 L
.dom
.content(badge
, render_radio_badge(radioDev
));
507 L
.dom
.content(stat
, render_radio_status(radioDev
, data
[2].filter(function(n
) { return n
.getWifiDeviceName() == radioDev
.getName() })));
510 L
.dom
.content(badge
, render_network_badge(radioNet
));
511 L
.dom
.content(stat
, render_network_status(radioNet
));
514 if (stat
.hasAttribute('restart'))
515 L
.dom
.content(stat
, E('em', _('Device is restarting…')));
517 btns
[0].disabled
= busy
;
518 btns
[1].disabled
= busy
;
519 btns
[2].disabled
= busy
;
522 var table
= document
.querySelector('wifi_assoclist_table'),
526 for (var i
= 0; i
< data
[3].length
; i
++) {
527 var bss
= data
[3][i
],
528 name
= hosts
.getHostnameByMACAddr(bss
.mac
),
529 ipv4
= hosts
.getIPAddrByMACAddr(bss
.mac
),
530 ipv6
= hosts
.getIP6AddrByMACAddr(bss
.mac
);
533 E('span', { 'class': 'ifacebadge' }, [
535 'src': L
.resource('icons/wifi%s.png').format(bss
.network
.isUp() ? '' : '_disabled'),
536 'title': bss
.radio
.getI18n()
538 ' %s '.format(bss
.network
.getShortName()),
539 E('small', '(%s)'.format(bss
.network
.getIfname()))
542 name
? '%s (%s)'.format(name
, ipv4
|| ipv6
|| '?') : ipv4
|| ipv6
|| '?',
543 render_signal_badge(Math
.min((bss
.signal
+ 110) / 70 * 100, 100), bss
.signal
, bss
.noise
),
545 E('span', format_wifirate(bss
.rx
)),
547 E('span', format_wifirate(bss
.tx
))
552 cbi_update_table('#wifi_assoclist_table', trows
, E('em', _('No information available')));
554 var stat
= document
.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large');
557 render_modal_status(stat
, data
[2].filter(function(n
) { return n
.getName() == stat
.getAttribute('data-network') })[0]);
559 return network
.flushCache();
569 checkAnonymousSections: function() {
570 var wifiIfaces
= uci
.sections('wireless', 'wifi-iface');
572 for (var i
= 0; i
< wifiIfaces
.length
; i
++)
573 if (wifiIfaces
[i
]['.anonymous'])
579 callUciRename
: rpc
.declare({
582 params
: [ 'config', 'section', 'name' ]
586 if (this.checkAnonymousSections())
587 return this.renderMigration();
589 return this.renderOverview();
592 handleMigration: function(ev
) {
593 var wifiIfaces
= uci
.sections('wireless', 'wifi-iface'),
597 for (var i
= 0; i
< wifiIfaces
.length
; i
++) {
598 if (!wifiIfaces
[i
]['.anonymous'])
601 var new_name
= next_free_sid(id_offset
);
603 tasks
.push(this.callUciRename('wireless', wifiIfaces
[i
]['.name'], new_name
));
604 id_offset
= +new_name
.substring(7) + 1;
607 return Promise
.all(tasks
)
608 .then(L
.bind(L
.ui
.changes
.init
, L
.ui
.changes
))
609 .then(L
.bind(L
.ui
.changes
.apply
, L
.ui
.changes
));
612 renderMigration: function() {
613 L
.ui
.showModal(_('Wireless configuration migration'), [
614 E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')),
615 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.')),
616 E('div', { 'class': 'right' },
618 'class': 'btn cbi-button-action important',
619 'click': L
.ui
.createHandlerFn(this, 'handleMigration')
624 renderOverview: function() {
627 m
= new form
.Map('wireless');
631 s
= m
.section(form
.GridSection
, 'wifi-device', _('Wireless Overview'));
635 s
.load = function() {
636 return network
.getWifiDevices().then(L
.bind(function(radios
) {
637 this.radios
= radios
.sort(function(a
, b
) {
638 return a
.getName() > b
.getName();
643 for (var i
= 0; i
< radios
.length
; i
++)
644 tasks
.push(radios
[i
].getWifiNetworks());
646 return Promise
.all(tasks
);
647 }, this)).then(L
.bind(function(data
) {
650 for (var i
= 0; i
< data
.length
; i
++)
651 this.wifis
.push
.apply(this.wifis
, data
[i
]);
655 s
.cfgsections = function() {
658 for (var i
= 0; i
< this.radios
.length
; i
++) {
659 rv
.push(this.radios
[i
].getName());
661 for (var j
= 0; j
< this.wifis
.length
; j
++)
662 if (this.wifis
[j
].getWifiDeviceName() == this.radios
[i
].getName())
663 rv
.push(this.wifis
[j
].getName());
669 s
.modaltitle = function(section_id
) {
670 var radioNet
= this.wifis
.filter(function(w
) { return w
.getName() == section_id
})[0];
671 return radioNet
? radioNet
.getI18n() : _('Edit wireless network');
674 s
.lookupRadioOrNetwork = function(section_id
) {
675 var radioDev
= this.radios
.filter(function(r
) { return r
.getName() == section_id
})[0];
679 var radioNet
= this.wifis
.filter(function(w
) { return w
.getName() == section_id
})[0];
686 s
.renderRowActions = function(section_id
) {
687 var inst
= this.lookupRadioOrNetwork(section_id
), btns
;
689 if (inst
.getWifiNetworks
) {
692 'class': 'cbi-button cbi-button-neutral',
693 'title': _('Restart radio interface'),
694 'click': L
.ui
.createHandlerFn(this, radio_restart
, section_id
)
697 'class': 'cbi-button cbi-button-action important',
698 'title': _('Find and join network'),
699 'click': L
.ui
.createHandlerFn(this, 'handleScan', inst
)
702 'class': 'cbi-button cbi-button-add',
703 'title': _('Provide new network'),
704 'click': L
.ui
.createHandlerFn(this, 'handleAdd', inst
)
709 var isDisabled
= (inst
.get('disabled') == '1' ||
710 uci
.get('wireless', inst
.getWifiDeviceName(), 'disabled') == '1');
714 'class': 'cbi-button cbi-button-neutral enable-disable',
715 'title': isDisabled
? _('Enable this network') : _('Disable this network'),
716 'click': L
.ui
.createHandlerFn(this, network_updown
, section_id
, this.map
)
717 }, isDisabled
? _('Enable') : _('Disable')),
719 'class': 'cbi-button cbi-button-action important',
720 'title': _('Edit this network'),
721 'click': L
.ui
.createHandlerFn(this, 'renderMoreOptionsModal', section_id
)
724 'class': 'cbi-button cbi-button-negative remove',
725 'title': _('Delete this network'),
726 'click': L
.ui
.createHandlerFn(this, 'handleRemove', section_id
)
731 return E('div', { 'class': 'td middle cbi-section-actions' }, E('div', btns
));
734 s
.addModalOptions = function(s
) {
735 return network
.getWifiNetwork(s
.section
).then(function(radioNet
) {
736 var hwtype
= uci
.get('wireless', radioNet
.getWifiDeviceName(), 'type');
739 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
743 ss
.tab('general', _('General Setup'));
744 ss
.tab('advanced', _('Advanced Settings'));
746 var isDisabled
= (radioNet
.get('disabled') == '1' ||
747 uci
.get('wireless', radioNet
.getWifiDeviceName(), 'disabled') == 1);
749 o
= ss
.taboption('general', form
.DummyValue
, '_wifistat_modal', _('Status'));
750 o
.cfgvalue
= L
.bind(function(radioNet
) {
751 return render_modal_status(null, radioNet
);
753 o
.write = function() {};
755 o
= ss
.taboption('general', form
.Button
, '_toggle', isDisabled
? _('Wireless network is disabled') : _('Wireless network is enabled'));
756 o
.inputstyle
= isDisabled
? 'apply' : 'reset';
757 o
.inputtitle
= isDisabled
? _('Enable') : _('Disable');
758 o
.onclick
= L
.ui
.createHandlerFn(s
, network_updown
, s
.section
, s
.map
);
760 o
= ss
.taboption('general', CBIWifiFrequencyValue
, '_freq', '<br />' + _('Operating frequency'));
761 o
.ucisection
= s
.section
;
763 if (hwtype
== 'mac80211') {
764 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.'));
765 o
.wifiNetwork
= radioNet
;
767 o
= ss
.taboption('advanced', CBIWifiCountryValue
, 'country', _('Country Code'));
768 o
.wifiNetwork
= radioNet
;
770 o
= ss
.taboption('advanced', form
.Flag
, 'legacy_rates', _('Allow legacy 802.11b rates'));
771 o
.default = o
.enabled
;
773 o
= ss
.taboption('advanced', form
.Value
, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
774 o
.datatype
= 'range(0,114750)';
775 o
.placeholder
= 'auto';
777 o
= ss
.taboption('advanced', form
.Value
, 'frag', _('Fragmentation Threshold'));
778 o
.datatype
= 'min(256)';
779 o
.placeholder
= _('off');
781 o
= ss
.taboption('advanced', form
.Value
, 'rts', _('RTS/CTS Threshold'));
782 o
.datatype
= 'uinteger';
783 o
.placeholder
= _('off');
785 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!'));
788 o
= ss
.taboption('advanced', form
.Value
, 'beacon_int', _('Beacon Interval'));
789 o
.datatype
= 'range(15,65535)';
795 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getName(), 'wifi-iface', _('Interface Configuration'));
799 ss
.tab('general', _('General Setup'));
800 ss
.tab('encryption', _('Wireless Security'));
801 ss
.tab('macfilter', _('MAC-Filter'));
802 ss
.tab('advanced', _('Advanced Settings'));
804 o
= ss
.taboption('general', form
.ListValue
, 'mode', _('Mode'));
805 o
.value('ap', _('Access Point'));
806 o
.value('sta', _('Client'));
807 o
.value('adhoc', _('Ad-Hoc'));
809 o
= ss
.taboption('general', form
.Value
, 'mesh_id', _('Mesh Id'));
810 o
.depends('mode', 'mesh');
812 o
= ss
.taboption('advanced', form
.Flag
, 'mesh_fwding', _('Forward mesh peer traffic'));
815 o
.depends('mode', 'mesh');
817 o
= ss
.taboption('advanced', form
.Value
, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
820 o
.datatype
= 'range(-255,1)';
821 o
.depends('mode', 'mesh');
823 o
= ss
.taboption('general', form
.Value
, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
824 o
.datatype
= 'maxlength(32)';
825 o
.depends('mode', 'ap');
826 o
.depends('mode', 'sta');
827 o
.depends('mode', 'adhoc');
828 o
.depends('mode', 'ahdemo');
829 o
.depends('mode', 'monitor');
830 o
.depends('mode', 'ap-wds');
831 o
.depends('mode', 'sta-wds');
832 o
.depends('mode', 'wds');
834 o
= ss
.taboption('general', form
.Value
, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
835 o
.datatype
= 'macaddr';
837 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.'));
841 o
.write = function(section_id
, value
) {
842 return network
.getDevice(section_id
).then(L
.bind(function(dev
) {
843 var old_networks
= dev
.getNetworks().reduce(function(o
, v
) { o
[v
.getName()] = v
; return o
}, {}),
845 values
= L
.toArray(value
),
848 for (var i
= 0; i
< values
.length
; i
++) {
849 new_networks
[values
[i
]] = true;
851 if (old_networks
[values
[i
]])
854 tasks
.push(network
.getNetwork(values
[i
]).then(L
.bind(function(name
, net
) {
855 return net
|| network
.addNetwork(name
, { proto
: 'none' });
856 }, this, values
[i
])).then(L
.bind(function(dev
, net
) {
859 net
.set('type', 'bridge');
865 for (var name
in old_networks
)
866 if (!new_networks
[name
])
867 tasks
.push(network
.getNetwork(name
).then(L
.bind(function(dev
, net
) {
869 net
.deleteDevice(dev
);
872 return Promise
.all(tasks
);
876 if (hwtype
== 'mac80211') {
877 var mode
= ss
.children
[0],
878 bssid
= ss
.children
[5],
881 mode
.value('mesh', '802.11s');
882 mode
.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
883 mode
.value('monitor', _('Monitor'));
885 bssid
.depends('mode', 'adhoc');
886 bssid
.depends('mode', 'sta');
887 bssid
.depends('mode', 'sta-wds');
889 o
= ss
.taboption('macfilter', form
.ListValue
, 'macfilter', _('MAC-Address Filter'));
890 o
.depends('mode', 'ap');
891 o
.depends('mode', 'ap-wds');
892 o
.value('', _('disable'));
893 o
.value('allow', _('Allow listed only'));
894 o
.value('deny', _('Allow all except listed'));
896 o
= ss
.taboption('macfilter', form
.DynamicList
, 'maclist', _('MAC-List'));
897 o
.datatype
= 'macaddr';
898 o
.depends('macfilter', 'allow');
899 o
.depends('macfilter', 'deny');
900 o
.load = function(section_id
) {
901 return network
.getHostHints().then(L
.bind(function(hints
) {
902 hints
.getMACHints().map(L
.bind(function(hint
) {
903 this.value(hint
[0], hint
[1] ? '%s (%s)'.format(hint
[0], hint
[1]) : hint
[0]);
906 return form
.DynamicList
.prototype.load
.apply(this, [section_id
]);
910 mode
.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
911 mode
.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
913 mode
.write = function(section_id
, value
) {
916 uci
.set('wireless', section_id
, 'mode', 'ap');
917 uci
.set('wireless', section_id
, 'wds', '1');
921 uci
.set('wireless', section_id
, 'mode', 'sta');
922 uci
.set('wireless', section_id
, 'wds', '1');
926 uci
.set('wireless', section_id
, 'mode', value
);
927 uci
.unset('wireless', section_id
, 'wds');
932 mode
.cfgvalue = function(section_id
) {
933 var mode
= uci
.get('wireless', section_id
, 'mode'),
934 wds
= uci
.get('wireless', section_id
, 'wds');
936 if (mode
== 'ap' && wds
)
938 else if (mode
== 'sta' && wds
)
944 o
= ss
.taboption('general', form
.Flag
, 'hidden', _('Hide <abbr title="Extended Service Set Identifier">ESSID</abbr>'));
945 o
.depends('mode', 'ap');
946 o
.depends('mode', 'ap-wds');
948 o
= ss
.taboption('general', form
.Flag
, 'wmm', _('WMM Mode'));
949 o
.depends('mode', 'ap');
950 o
.depends('mode', 'ap-wds');
951 o
.default = o
.enabled
;
953 o
= ss
.taboption('advanced', form
.Flag
, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
954 o
.depends('mode', 'ap');
955 o
.depends('mode', 'ap-wds');
957 o
= ss
.taboption('advanced', form
.Value
, 'ifname', _('Interface name'), _('Override default interface name'));
959 o
.placeholder
= radioNet
.getIfname();
960 if (/^radio\d+\.network/.test(o
.placeholder
))
963 o
= ss
.taboption('advanced', form
.Flag
, 'short_preamble', _('Short Preamble'));
964 o
.default = o
.enabled
;
966 o
= ss
.taboption('advanced', form
.Value
, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
969 o
.datatype
= 'range(1,255)';
971 o
= ss
.taboption('advanced', form
.Value
, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
974 o
.datatype
= 'uinteger';
976 o
= ss
.taboption('advanced', form
.Flag
, 'skip_inactivity_poll', _('Disable Inactivity Polling'));
978 o
.datatype
= 'uinteger';
980 o
= ss
.taboption('advanced', form
.Value
, 'max_inactivity', _('Station inactivity limit'), _('sec'));
983 o
.datatype
= 'uinteger';
985 o
= ss
.taboption('advanced', form
.Value
, 'max_listen_interval', _('Maximum allowed Listen Interval'));
987 o
.placeholder
= 65535;
988 o
.datatype
= 'uinteger';
990 o
= ss
.taboption('advanced', form
.Flag
, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
991 o
.default = o
.enabled
;
995 encr
= o
= ss
.taboption('encryption', form
.ListValue
, 'encryption', _('Encryption'));
996 o
.depends('mode', 'ap');
997 o
.depends('mode', 'sta');
998 o
.depends('mode', 'adhoc');
999 o
.depends('mode', 'ahdemo');
1000 o
.depends('mode', 'ap-wds');
1001 o
.depends('mode', 'sta-wds');
1002 o
.depends('mode', 'mesh');
1004 o
.cfgvalue = function(section_id
) {
1005 var v
= String(uci
.get('wireless', section_id
, 'encryption'));
1008 else if (v
.match(/\+/))
1009 return v
.replace(/\+.+$/, '');
1013 o
.write = function(section_id
, value
) {
1014 var e
= this.section
.children
.filter(function(o
) { return o
.option
== 'encryption' })[0].formvalue(section_id
),
1015 co
= this.section
.children
.filter(function(o
) { return o
.option
== 'cipher' })[0], c
= co
.formvalue(section_id
);
1017 if (value
== 'wpa' || value
== 'wpa2')
1018 uci
.unset('wireless', section_id
, 'key');
1020 if (co
.isActive(section_id
) && e
&& (c
== 'tkip' || c
== 'ccmp' || c
== 'tkip+ccmp'))
1023 uci
.set('wireless', section_id
, 'encryption', e
);
1026 o
= ss
.taboption('encryption', form
.ListValue
, 'cipher', _('Cipher'));
1027 o
.depends('encryption', 'wpa');
1028 o
.depends('encryption', 'wpa2');
1029 o
.depends('encryption', 'psk');
1030 o
.depends('encryption', 'psk2');
1031 o
.depends('encryption', 'wpa-mixed');
1032 o
.depends('encryption', 'psk-mixed');
1033 o
.value('auto', _('auto'));
1034 o
.value('ccmp', _('Force CCMP (AES)'));
1035 o
.value('tkip', _('Force TKIP'));
1036 o
.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1037 o
.write
= ss
.children
.filter(function(o
) { return o
.option
== 'encryption' })[0].write
;
1039 o
.cfgvalue = function(section_id
) {
1040 var v
= String(uci
.get('wireless', section_id
, 'encryption'));
1041 if (v
.match(/\+/)) {
1042 v
= v
.replace(/^[^+]+\+/, '');
1045 else if (v
== 'tkip+aes' || v
== 'aes+tkip' || v
== 'ccmp+tkip')
1052 var crypto_modes
= [];
1054 if (hwtype
== 'mac80211') {
1055 var has_supplicant
= L
.hasSystemFeature('wpasupplicant'),
1056 has_hostapd
= L
.hasSystemFeature('hostapd');
1058 // Probe EAP support
1059 var has_ap_eap
= L
.hasSystemFeature('hostapd', 'eap'),
1060 has_sta_eap
= L
.hasSystemFeature('wpasupplicant', 'eap');
1062 // Probe SAE support
1063 var has_ap_sae
= L
.hasSystemFeature('hostapd', 'sae'),
1064 has_sta_sae
= L
.hasSystemFeature('wpasupplicant', 'sae');
1066 // Probe OWE support
1067 var has_ap_owe
= L
.hasSystemFeature('hostapd', 'owe'),
1068 has_sta_owe
= L
.hasSystemFeature('wpasupplicant', 'owe');
1071 if (has_hostapd
|| has_supplicant
) {
1072 crypto_modes
.push(['psk2', 'WPA2-PSK', 33]);
1073 crypto_modes
.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1074 crypto_modes
.push(['psk', 'WPA-PSK', 21]);
1077 encr
.description
= _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1080 if (has_ap_sae
|| has_sta_sae
) {
1081 crypto_modes
.push(['sae', 'WPA3-SAE', 31]);
1082 crypto_modes
.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1085 if (has_ap_eap
|| has_sta_eap
) {
1086 crypto_modes
.push(['wpa2', 'WPA2-EAP', 32]);
1087 crypto_modes
.push(['wpa', 'WPA-EAP', 20]);
1090 if (has_ap_owe
|| has_sta_owe
) {
1091 crypto_modes
.push(['owe', 'OWE', 1]);
1094 encr
.crypto_support
= {
1098 'psk': has_hostapd
|| _('Requires hostapd'),
1099 'psk2': has_hostapd
|| _('Requires hostapd'),
1100 'psk-mixed': has_hostapd
|| _('Requires hostapd'),
1101 'sae': has_ap_sae
|| _('Requires hostapd with SAE support'),
1102 'sae-mixed': has_ap_sae
|| _('Requires hostapd with SAE support'),
1103 'wpa': has_ap_eap
|| _('Requires hostapd with EAP support'),
1104 'wpa2': has_ap_eap
|| _('Requires hostapd with EAP support'),
1105 'owe': has_ap_owe
|| _('Requires hostapd with OWE support')
1110 'psk': has_supplicant
|| _('Requires wpa-supplicant'),
1111 'psk2': has_supplicant
|| _('Requires wpa-supplicant'),
1112 'psk-mixed': has_supplicant
|| _('Requires wpa-supplicant'),
1113 'sae': has_sta_sae
|| _('Requires wpa-supplicant with SAE support'),
1114 'sae-mixed': has_sta_sae
|| _('Requires wpa-supplicant with SAE support'),
1115 'wpa': has_sta_eap
|| _('Requires wpa-supplicant with EAP support'),
1116 'wpa2': has_sta_eap
|| _('Requires wpa-supplicant with EAP support'),
1117 'owe': has_sta_owe
|| _('Requires wpa-supplicant with OWE support')
1122 'psk': has_supplicant
|| _('Requires wpa-supplicant'),
1123 'psk2': has_supplicant
|| _('Requires wpa-supplicant'),
1124 'psk-mixed': has_supplicant
|| _('Requires wpa-supplicant'),
1127 'sae': has_sta_sae
|| _('Requires wpa-supplicant with SAE support')
1139 encr
.crypto_support
['ap-wds'] = encr
.crypto_support
['ap'];
1140 encr
.crypto_support
['sta-wds'] = encr
.crypto_support
['sta'];
1142 encr
.validate = function(section_id
, value
) {
1143 var modeopt
= this.section
.children
.filter(function(o
) { return o
.option
== 'mode' })[0],
1144 modeval
= modeopt
.formvalue(section_id
),
1145 modetitle
= modeopt
.vallist
[modeopt
.keylist
.indexOf(modeval
)],
1146 enctitle
= this.vallist
[this.keylist
.indexOf(value
)];
1148 if (value
== 'none')
1151 if (!L
.isObject(this.crypto_support
[modeval
]) || !this.crypto_support
[modeval
].hasOwnProperty(value
))
1152 return _('The selected %s mode is incompatible with %s encryption').format(modetitle
, enctitle
);
1154 return this.crypto_support
[modeval
][value
];
1157 else if (hwtype
== 'broadcom') {
1158 crypto_modes
.push(['psk2', 'WPA2-PSK', 33]);
1159 crypto_modes
.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1160 crypto_modes
.push(['psk', 'WPA-PSK', 21]);
1163 crypto_modes
.push(['wep-open', _('WEP Open System'), 11]);
1164 crypto_modes
.push(['wep-shared', _('WEP Shared Key'), 10]);
1165 crypto_modes
.push(['none', _('No Encryption'), 0]);
1167 crypto_modes
.sort(function(a
, b
) { return b
[2] - a
[2] });
1169 for (var i
= 0; i
< crypto_modes
.length
; i
++) {
1170 var security_level
= (crypto_modes
[i
][2] >= 30) ? _('strong security')
1171 : (crypto_modes
[i
][2] >= 20) ? _('medium security')
1172 : (crypto_modes
[i
][2] >= 10) ? _('weak security') : _('open network');
1174 encr
.value(crypto_modes
[i
][0], '%s (%s)'.format(crypto_modes
[i
][1], security_level
));
1178 o
= ss
.taboption('encryption', form
.Value
, 'auth_server', _('Radius-Authentication-Server'));
1179 o
.depends({ mode
: 'ap', encryption
: 'wpa' });
1180 o
.depends({ mode
: 'ap', encryption
: 'wpa2' });
1181 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa' });
1182 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa2' });
1184 o
.datatype
= 'host(0)';
1186 o
= ss
.taboption('encryption', form
.Value
, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
1187 o
.depends({ mode
: 'ap', encryption
: 'wpa' });
1188 o
.depends({ mode
: 'ap', encryption
: 'wpa2' });
1189 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa' });
1190 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa2' });
1192 o
.datatype
= 'port';
1194 o
= ss
.taboption('encryption', form
.Value
, 'auth_secret', _('Radius-Authentication-Secret'));
1195 o
.depends({ mode
: 'ap', encryption
: 'wpa' });
1196 o
.depends({ mode
: 'ap', encryption
: 'wpa2' });
1197 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa' });
1198 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa2' });
1202 o
= ss
.taboption('encryption', form
.Value
, 'acct_server', _('Radius-Accounting-Server'));
1203 o
.depends({ mode
: 'ap', encryption
: 'wpa' });
1204 o
.depends({ mode
: 'ap', encryption
: 'wpa2' });
1205 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa' });
1206 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa2' });
1208 o
.datatype
= 'host(0)';
1210 o
= ss
.taboption('encryption', form
.Value
, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
1211 o
.depends({ mode
: 'ap', encryption
: 'wpa' });
1212 o
.depends({ mode
: 'ap', encryption
: 'wpa2' });
1213 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa' });
1214 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa2' });
1216 o
.datatype
= 'port';
1218 o
= ss
.taboption('encryption', form
.Value
, 'acct_secret', _('Radius-Accounting-Secret'));
1219 o
.depends({ mode
: 'ap', encryption
: 'wpa' });
1220 o
.depends({ mode
: 'ap', encryption
: 'wpa2' });
1221 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa' });
1222 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa2' });
1226 o
= ss
.taboption('encryption', form
.Value
, 'dae_client', _('DAE-Client'));
1227 o
.depends({ mode
: 'ap', encryption
: 'wpa' });
1228 o
.depends({ mode
: 'ap', encryption
: 'wpa2' });
1229 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa' });
1230 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa2' });
1232 o
.datatype
= 'host(0)';
1234 o
= ss
.taboption('encryption', form
.Value
, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
1235 o
.depends({ mode
: 'ap', encryption
: 'wpa' });
1236 o
.depends({ mode
: 'ap', encryption
: 'wpa2' });
1237 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa' });
1238 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa2' });
1240 o
.datatype
= 'port';
1242 o
= ss
.taboption('encryption', form
.Value
, 'dae_secret', _('DAE-Secret'));
1243 o
.depends({ mode
: 'ap', encryption
: 'wpa' });
1244 o
.depends({ mode
: 'ap', encryption
: 'wpa2' });
1245 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa' });
1246 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa2' });
1251 o
= ss
.taboption('encryption', form
.Value
, '_wpa_key', _('Key'));
1252 o
.depends('encryption', 'psk');
1253 o
.depends('encryption', 'psk2');
1254 o
.depends('encryption', 'psk+psk2');
1255 o
.depends('encryption', 'psk-mixed');
1256 o
.depends('encryption', 'sae');
1257 o
.depends('encryption', 'sae-mixed');
1258 o
.datatype
= 'wpakey';
1262 o
.cfgvalue = function(section_id
) {
1263 var key
= uci
.get('wireless', section_id
, 'key');
1264 return /^[1234]$/.test(key
) ? null : key
;
1267 o
.write = function(section_id
, value
) {
1268 uci
.set('wireless', section_id
, 'key', value
);
1269 uci
.unset('wireless', section_id
, 'key1');
1270 uci
.unset('wireless', section_id
, 'key2');
1271 uci
.unset('wireless', section_id
, 'key3');
1272 uci
.unset('wireless', section_id
, 'key4');
1276 o
= ss
.taboption('encryption', form
.ListValue
, '_wep_key', _('Used Key Slot'));
1277 o
.depends('encryption', 'wep-open');
1278 o
.depends('encryption', 'wep-shared');
1279 o
.value('1', _('Key #%d').format(1));
1280 o
.value('2', _('Key #%d').format(2));
1281 o
.value('3', _('Key #%d').format(3));
1282 o
.value('4', _('Key #%d').format(4));
1284 o
.cfgvalue = function(section_id
) {
1285 var slot
= +uci
.get('wireless', section_id
, 'key');
1286 return (slot
>= 1 && slot
<= 4) ? String(slot
) : '';
1289 o
.write = function(section_id
, value
) {
1290 uci
.set('wireless', section_id
, 'key', value
);
1293 for (var slot
= 1; slot
<= 4; slot
++) {
1294 o
= ss
.taboption('encryption', form
.Value
, 'key%d'.format(slot
), _('Key #%d').format(slot
));
1295 o
.depends('encryption', 'wep-open');
1296 o
.depends('encryption', 'wep-shared');
1297 o
.datatype
= 'wepkey';
1301 o
.write = function(section_id
, value
) {
1302 if (value
!= null && (value
.length
== 5 || value
.length
== 13))
1303 value
= 's:%s'.format(value
);
1304 uci
.set('wireless', section_id
, this.option
, value
);
1309 if (hwtype
== 'mac80211') {
1310 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1311 var has_80211r
= L
.hasSystemFeature('hostapd', '11r') || L
.hasSystemFeature('hostapd', 'eap');
1313 o
= ss
.taboption('encryption', form
.Flag
, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1314 o
.depends({ mode
: 'ap', encryption
: 'wpa' });
1315 o
.depends({ mode
: 'ap', encryption
: 'wpa2' });
1316 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa' });
1317 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa2' });
1319 o
.depends({ mode
: 'ap', encryption
: 'psk' });
1320 o
.depends({ mode
: 'ap', encryption
: 'psk2' });
1321 o
.depends({ mode
: 'ap', encryption
: 'psk-mixed' });
1322 o
.depends({ mode
: 'ap', encryption
: 'sae' });
1323 o
.depends({ mode
: 'ap', encryption
: 'sae-mixed' });
1324 o
.depends({ mode
: 'ap-wds', encryption
: 'psk' });
1325 o
.depends({ mode
: 'ap-wds', encryption
: 'psk2' });
1326 o
.depends({ mode
: 'ap-wds', encryption
: 'psk-mixed' });
1327 o
.depends({ mode
: 'ap-wds', encryption
: 'sae' });
1328 o
.depends({ mode
: 'ap-wds', encryption
: 'sae-mixed' });
1332 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.'));
1333 o
.depends({ mode
: 'ap', encryption
: 'wpa' });
1334 o
.depends({ mode
: 'ap', encryption
: 'wpa2' });
1335 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa' });
1336 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa2' });
1337 o
.depends({ ieee80211r
: '1' });
1340 o
= ss
.taboption('encryption', form
.Value
, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1341 o
.depends({ ieee80211r
: '1' });
1342 o
.placeholder
= '4f57';
1343 o
.datatype
= 'and(hexstring,length(4))';
1346 o
= ss
.taboption('encryption', form
.Value
, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1347 o
.depends({ ieee80211r
: '1' });
1348 o
.placeholder
= '1000';
1349 o
.datatype
= 'range(1000,65535)';
1352 o
= ss
.taboption('encryption', form
.ListValue
, 'ft_over_ds', _('FT protocol'));
1353 o
.depends({ ieee80211r
: '1' });
1354 o
.value('1', _('FT over DS'));
1355 o
.value('0', _('FT over the Air'));
1358 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.'));
1359 o
.depends({ ieee80211r
: '1' });
1360 o
.default = o
.enabled
;
1363 o
= ss
.taboption('encryption', form
.Value
, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1364 o
.depends({ ieee80211r
: '1' });
1365 o
.placeholder
= '10000';
1366 o
.datatype
= 'uinteger';
1369 o
= ss
.taboption('encryption', form
.Value
, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1370 o
.depends({ ieee80211r
: '1' });
1371 o
.placeholder
= '00004f577274';
1372 o
.datatype
= 'and(hexstring,length(12))';
1375 o
= ss
.taboption('encryption', form
.Flag
, 'pmk_r1_push', _('PMK R1 Push'));
1376 o
.depends({ ieee80211r
: '1' });
1377 o
.placeholder
= '0';
1380 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.'));
1381 o
.depends({ ieee80211r
: '1' });
1384 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.'));
1385 o
.depends({ ieee80211r
: '1' });
1387 // End of 802.11r options
1389 o
= ss
.taboption('encryption', form
.ListValue
, 'eap_type', _('EAP-Method'));
1390 o
.value('tls', 'TLS');
1391 o
.value('ttls', 'TTLS');
1392 o
.value('peap', 'PEAP');
1393 o
.value('fast', 'FAST');
1394 o
.depends({ mode
: 'sta', encryption
: 'wpa' });
1395 o
.depends({ mode
: 'sta', encryption
: 'wpa2' });
1396 o
.depends({ mode
: 'sta-wds', encryption
: 'wpa' });
1397 o
.depends({ mode
: 'sta-wds', encryption
: 'wpa2' });
1399 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert', _('Path to CA-Certificate'));
1400 o
.depends({ mode
: 'sta', encryption
: 'wpa' });
1401 o
.depends({ mode
: 'sta', encryption
: 'wpa2' });
1402 o
.depends({ mode
: 'sta-wds', encryption
: 'wpa' });
1403 o
.depends({ mode
: 'sta-wds', encryption
: 'wpa2' });
1405 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert', _('Path to Client-Certificate'));
1406 o
.depends({ mode
: 'sta', eap_type
: 'tls', encryption
: 'wpa' });
1407 o
.depends({ mode
: 'sta', eap_type
: 'tls', encryption
: 'wpa2' });
1408 o
.depends({ mode
: 'sta-wds', eap_type
: 'tls', encryption
: 'wpa' });
1409 o
.depends({ mode
: 'sta-wds', eap_type
: 'tls', encryption
: 'wpa2' });
1411 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key', _('Path to Private Key'));
1412 o
.depends({ mode
: 'sta', eap_type
: 'tls', encryption
: 'wpa2' });
1413 o
.depends({ mode
: 'sta', eap_type
: 'tls', encryption
: 'wpa' });
1414 o
.depends({ mode
: 'sta-wds', eap_type
: 'tls', encryption
: 'wpa2' });
1415 o
.depends({ mode
: 'sta-wds', eap_type
: 'tls', encryption
: 'wpa' });
1417 o
= ss
.taboption('encryption', form
.Value
, 'priv_key_pwd', _('Password of Private Key'));
1418 o
.depends({ mode
: 'sta', eap_type
: 'tls', encryption
: 'wpa2' });
1419 o
.depends({ mode
: 'sta', eap_type
: 'tls', encryption
: 'wpa' });
1420 o
.depends({ mode
: 'sta-wds', eap_type
: 'tls', encryption
: 'wpa2' });
1421 o
.depends({ mode
: 'sta-wds', eap_type
: 'tls', encryption
: 'wpa' });
1424 o
= ss
.taboption('encryption', form
.ListValue
, 'auth', _('Authentication'));
1425 o
.value('PAP', 'PAP');
1426 o
.value('CHAP', 'CHAP');
1427 o
.value('MSCHAP', 'MSCHAP');
1428 o
.value('MSCHAPV2', 'MSCHAPv2');
1431 o
.value('EAP-MSCHAPV2');
1433 o
.depends({ mode
: 'sta', eap_type
: 'fast', encryption
: 'wpa2' });
1434 o
.depends({ mode
: 'sta', eap_type
: 'fast', encryption
: 'wpa' });
1435 o
.depends({ mode
: 'sta', eap_type
: 'peap', encryption
: 'wpa2' });
1436 o
.depends({ mode
: 'sta', eap_type
: 'peap', encryption
: 'wpa' });
1437 o
.depends({ mode
: 'sta', eap_type
: 'ttls', encryption
: 'wpa2' });
1438 o
.depends({ mode
: 'sta', eap_type
: 'ttls', encryption
: 'wpa' });
1439 o
.depends({ mode
: 'sta-wds', eap_type
: 'fast', encryption
: 'wpa2' });
1440 o
.depends({ mode
: 'sta-wds', eap_type
: 'fast', encryption
: 'wpa' });
1441 o
.depends({ mode
: 'sta-wds', eap_type
: 'peap', encryption
: 'wpa2' });
1442 o
.depends({ mode
: 'sta-wds', eap_type
: 'peap', encryption
: 'wpa' });
1443 o
.depends({ mode
: 'sta-wds', eap_type
: 'ttls', encryption
: 'wpa2' });
1444 o
.depends({ mode
: 'sta-wds', eap_type
: 'ttls', encryption
: 'wpa' });
1446 o
.validate = function(section_id
, value
) {
1447 var eo
= this.section
.children
.filter(function(o
) { return o
.option
== 'eap_type' })[0],
1448 ev
= eo
.formvalue(section_id
);
1450 if (ev
!= 'ttls' && (value
== 'PAP' || value
== 'CHAP' || value
== 'MSCHAP' || value
== 'MSCHAPV2'))
1451 return _('This authentication type is not applicable to the selected EAP method.');
1456 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert2', _('Path to inner CA-Certificate'));
1457 o
.depends({ mode
: 'sta', auth
: 'EAP-TLS', encryption
: 'wpa' });
1458 o
.depends({ mode
: 'sta', auth
: 'EAP-TLS', encryption
: 'wpa2' });
1459 o
.depends({ mode
: 'sta-wds', auth
: 'EAP-TLS', encryption
: 'wpa' });
1460 o
.depends({ mode
: 'sta-wds', auth
: 'EAP-TLS', encryption
: 'wpa2' });
1462 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert2', _('Path to inner Client-Certificate'));
1463 o
.depends({ mode
: 'sta', auth
: 'EAP-TLS', encryption
: 'wpa' });
1464 o
.depends({ mode
: 'sta', auth
: 'EAP-TLS', encryption
: 'wpa2' });
1465 o
.depends({ mode
: 'sta-wds', auth
: 'EAP-TLS', encryption
: 'wpa' });
1466 o
.depends({ mode
: 'sta-wds', auth
: 'EAP-TLS', encryption
: 'wpa2' });
1468 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key2', _('Path to inner Private Key'));
1469 o
.depends({ mode
: 'sta', auth
: 'EAP-TLS', encryption
: 'wpa' });
1470 o
.depends({ mode
: 'sta', auth
: 'EAP-TLS', encryption
: 'wpa2' });
1471 o
.depends({ mode
: 'sta-wds', auth
: 'EAP-TLS', encryption
: 'wpa' });
1472 o
.depends({ mode
: 'sta-wds', auth
: 'EAP-TLS', encryption
: 'wpa2' });
1474 o
= ss
.taboption('encryption', form
.Value
, 'priv_key2_pwd', _('Password of inner Private Key'));
1475 o
.depends({ mode
: 'sta', auth
: 'EAP-TLS', encryption
: 'wpa' });
1476 o
.depends({ mode
: 'sta', auth
: 'EAP-TLS', encryption
: 'wpa2' });
1477 o
.depends({ mode
: 'sta-wds', auth
: 'EAP-TLS', encryption
: 'wpa' });
1478 o
.depends({ mode
: 'sta-wds', auth
: 'EAP-TLS', encryption
: 'wpa2' });
1481 o
= ss
.taboption('encryption', form
.Value
, 'identity', _('Identity'));
1482 o
.depends({ mode
: 'sta', eap_type
: 'fast', encryption
: 'wpa2' });
1483 o
.depends({ mode
: 'sta', eap_type
: 'fast', encryption
: 'wpa' });
1484 o
.depends({ mode
: 'sta', eap_type
: 'peap', encryption
: 'wpa2' });
1485 o
.depends({ mode
: 'sta', eap_type
: 'peap', encryption
: 'wpa' });
1486 o
.depends({ mode
: 'sta', eap_type
: 'ttls', encryption
: 'wpa2' });
1487 o
.depends({ mode
: 'sta', eap_type
: 'ttls', encryption
: 'wpa' });
1488 o
.depends({ mode
: 'sta-wds', eap_type
: 'fast', encryption
: 'wpa2' });
1489 o
.depends({ mode
: 'sta-wds', eap_type
: 'fast', encryption
: 'wpa' });
1490 o
.depends({ mode
: 'sta-wds', eap_type
: 'peap', encryption
: 'wpa2' });
1491 o
.depends({ mode
: 'sta-wds', eap_type
: 'peap', encryption
: 'wpa' });
1492 o
.depends({ mode
: 'sta-wds', eap_type
: 'ttls', encryption
: 'wpa2' });
1493 o
.depends({ mode
: 'sta-wds', eap_type
: 'ttls', encryption
: 'wpa' });
1494 o
.depends({ mode
: 'sta', eap_type
: 'tls', encryption
: 'wpa2' });
1495 o
.depends({ mode
: 'sta', eap_type
: 'tls', encryption
: 'wpa' });
1496 o
.depends({ mode
: 'sta-wds', eap_type
: 'tls', encryption
: 'wpa2' });
1497 o
.depends({ mode
: 'sta-wds', eap_type
: 'tls', encryption
: 'wpa' });
1499 o
= ss
.taboption('encryption', form
.Value
, 'anonymous_identity', _('Anonymous Identity'));
1500 o
.depends({ mode
: 'sta', eap_type
: 'fast', encryption
: 'wpa2' });
1501 o
.depends({ mode
: 'sta', eap_type
: 'fast', encryption
: 'wpa' });
1502 o
.depends({ mode
: 'sta', eap_type
: 'peap', encryption
: 'wpa2' });
1503 o
.depends({ mode
: 'sta', eap_type
: 'peap', encryption
: 'wpa' });
1504 o
.depends({ mode
: 'sta', eap_type
: 'ttls', encryption
: 'wpa2' });
1505 o
.depends({ mode
: 'sta', eap_type
: 'ttls', encryption
: 'wpa' });
1506 o
.depends({ mode
: 'sta-wds', eap_type
: 'fast', encryption
: 'wpa2' });
1507 o
.depends({ mode
: 'sta-wds', eap_type
: 'fast', encryption
: 'wpa' });
1508 o
.depends({ mode
: 'sta-wds', eap_type
: 'peap', encryption
: 'wpa2' });
1509 o
.depends({ mode
: 'sta-wds', eap_type
: 'peap', encryption
: 'wpa' });
1510 o
.depends({ mode
: 'sta-wds', eap_type
: 'ttls', encryption
: 'wpa2' });
1511 o
.depends({ mode
: 'sta-wds', eap_type
: 'ttls', encryption
: 'wpa' });
1512 o
.depends({ mode
: 'sta', eap_type
: 'tls', encryption
: 'wpa2' });
1513 o
.depends({ mode
: 'sta', eap_type
: 'tls', encryption
: 'wpa' });
1514 o
.depends({ mode
: 'sta-wds', eap_type
: 'tls', encryption
: 'wpa2' });
1515 o
.depends({ mode
: 'sta-wds', eap_type
: 'tls', encryption
: 'wpa' });
1517 o
= ss
.taboption('encryption', form
.Value
, 'password', _('Password'));
1518 o
.depends({ mode
: 'sta', eap_type
: 'fast', encryption
: 'wpa2' });
1519 o
.depends({ mode
: 'sta', eap_type
: 'fast', encryption
: 'wpa' });
1520 o
.depends({ mode
: 'sta', eap_type
: 'peap', encryption
: 'wpa2' });
1521 o
.depends({ mode
: 'sta', eap_type
: 'peap', encryption
: 'wpa' });
1522 o
.depends({ mode
: 'sta', eap_type
: 'ttls', encryption
: 'wpa2' });
1523 o
.depends({ mode
: 'sta', eap_type
: 'ttls', encryption
: 'wpa' });
1524 o
.depends({ mode
: 'sta-wds', eap_type
: 'fast', encryption
: 'wpa2' });
1525 o
.depends({ mode
: 'sta-wds', eap_type
: 'fast', encryption
: 'wpa' });
1526 o
.depends({ mode
: 'sta-wds', eap_type
: 'peap', encryption
: 'wpa2' });
1527 o
.depends({ mode
: 'sta-wds', eap_type
: 'peap', encryption
: 'wpa' });
1528 o
.depends({ mode
: 'sta-wds', eap_type
: 'ttls', encryption
: 'wpa2' });
1529 o
.depends({ mode
: 'sta-wds', eap_type
: 'ttls', encryption
: 'wpa' });
1533 if (hwtype
== 'mac80211') {
1534 // ieee802.11w options
1535 if (L
.hasSystemFeature('hostapd', '11w')) {
1536 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)"));
1537 o
.value('', _('Disabled'));
1538 o
.value('1', _('Optional'));
1539 o
.value('2', _('Required'));
1540 o
.depends({ mode
: 'ap', encryption
: 'wpa2' });
1541 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa2' });
1542 o
.depends({ mode
: 'ap', encryption
: 'psk2' });
1543 o
.depends({ mode
: 'ap', encryption
: 'psk-mixed' });
1544 o
.depends({ mode
: 'ap', encryption
: 'sae' });
1545 o
.depends({ mode
: 'ap', encryption
: 'sae-mixed' });
1546 o
.depends({ mode
: 'ap', encryption
: 'owe' });
1547 o
.depends({ mode
: 'ap-wds', encryption
: 'psk2' });
1548 o
.depends({ mode
: 'ap-wds', encryption
: 'psk-mixed' });
1549 o
.depends({ mode
: 'ap-wds', encryption
: 'sae' });
1550 o
.depends({ mode
: 'ap-wds', encryption
: 'sae-mixed' });
1551 o
.depends({ mode
: 'ap-wds', encryption
: 'owe' });
1552 o
.depends({ mode
: 'sta', encryption
: 'wpa2' });
1553 o
.depends({ mode
: 'sta-wds', encryption
: 'wpa2' });
1554 o
.depends({ mode
: 'sta', encryption
: 'psk2' });
1555 o
.depends({ mode
: 'sta', encryption
: 'psk-mixed' });
1556 o
.depends({ mode
: 'sta', encryption
: 'sae' });
1557 o
.depends({ mode
: 'sta', encryption
: 'sae-mixed' });
1558 o
.depends({ mode
: 'sta', encryption
: 'owe' });
1559 o
.depends({ mode
: 'sta-wds', encryption
: 'psk2' });
1560 o
.depends({ mode
: 'sta-wds', encryption
: 'psk-mixed' });
1561 o
.depends({ mode
: 'sta-wds', encryption
: 'sae' });
1562 o
.depends({ mode
: 'sta-wds', encryption
: 'sae-mixed' });
1563 o
.depends({ mode
: 'sta-wds', encryption
: 'owe' });
1565 '2': [{ encryption
: 'sae' }, { encryption
: 'owe' }],
1566 '1': [{ encryption
: 'sae-mixed'}],
1570 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1571 o
.depends('ieee80211w', '1');
1572 o
.depends('ieee80211w', '2');
1573 o
.datatype
= 'uinteger';
1574 o
.placeholder
= '1000';
1577 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1578 o
.depends('ieee80211w', '1');
1579 o
.depends('ieee80211w', '2');
1580 o
.datatype
= 'uinteger';
1581 o
.placeholder
= '201';
1585 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.'));
1586 o
.depends({ mode
: 'ap', encryption
: 'wpa2' });
1587 o
.depends({ mode
: 'ap', encryption
: 'psk2' });
1588 o
.depends({ mode
: 'ap', encryption
: 'psk-mixed' });
1589 o
.depends({ mode
: 'ap', encryption
: 'sae' });
1590 o
.depends({ mode
: 'ap', encryption
: 'sae-mixed' });
1591 o
.depends({ mode
: 'ap-wds', encryption
: 'wpa2' });
1592 o
.depends({ mode
: 'ap-wds', encryption
: 'psk2' });
1593 o
.depends({ mode
: 'ap-wds', encryption
: 'psk-mixed' });
1594 o
.depends({ mode
: 'ap-wds', encryption
: 'sae' });
1595 o
.depends({ mode
: 'ap-wds', encryption
: 'sae-mixed' });
1597 if (L
.hasSystemFeature('hostapd', 'cli') && L
.hasSystemFeature('wpasupplicant')) {
1598 o
= ss
.taboption('encryption', form
.Flag
, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1601 o
.default = o
.disabled
;
1602 o
.depends('encryption', 'psk');
1603 o
.depends('encryption', 'psk2');
1604 o
.depends('encryption', 'psk-mixed');
1605 o
.depends('encryption', 'sae');
1606 o
.depends('encryption', 'sae-mixed');
1613 s
.handleRemove = function(section_id
, ev
) {
1614 document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id
)).style
.opacity
= 0.5;
1615 return form
.TypedSection
.prototype.handleRemove
.apply(this, [section_id
, ev
]);
1618 s
.handleScan = function(radioDev
, ev
) {
1619 var table
= E('div', { 'class': 'table' }, [
1620 E('div', { 'class': 'tr table-titles' }, [
1621 E('div', { 'class': 'th col-2 middle center' }, _('Signal')),
1622 E('div', { 'class': 'th col-4 middle left' }, _('SSID')),
1623 E('div', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1624 E('div', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1625 E('div', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1626 E('div', { 'class': 'th col-3 middle left' }, _('Encryption')),
1627 E('div', { 'class': 'th cbi-section-actions right' }, ' '),
1631 cbi_update_table(table
, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1633 var md
= L
.ui
.showModal(_('Join Network: Wireless Scan'), [
1635 E('div', { 'class': 'right' },
1638 'click': L
.bind(this.handleScanAbort
, this)
1642 md
.style
.maxWidth
= '90%';
1643 md
.style
.maxHeight
= 'none';
1645 this.pollFn
= L
.bind(this.handleScanRefresh
, this, radioDev
, {}, table
);
1647 L
.Poll
.add(this.pollFn
);
1651 s
.handleScanRefresh = function(radioDev
, scanCache
, table
) {
1652 return radioDev
.getScanList().then(L
.bind(function(results
) {
1655 for (var i
= 0; i
< results
.length
; i
++)
1656 scanCache
[results
[i
].bssid
] = results
[i
];
1658 for (var k
in scanCache
)
1659 if (scanCache
[k
].stale
)
1660 results
.push(scanCache
[k
]);
1662 results
.sort(function(a
, b
) {
1663 var diff
= (b
.quality
- a
.quality
) || (a
.channel
- b
.channel
);
1668 if (a
.ssid
< b
.ssid
)
1670 else if (a
.ssid
> b
.ssid
)
1673 if (a
.bssid
< b
.bssid
)
1675 else if (a
.bssid
> b
.bssid
)
1679 for (var i
= 0; i
< results
.length
; i
++) {
1680 var res
= results
[i
],
1681 qv
= res
.quality
|| 0,
1682 qm
= res
.quality_max
|| 0,
1683 q
= (qv
> 0 && qm
> 0) ? Math
.floor((100 / qm
) * qv
) : 0,
1684 s
= res
.stale
? 'opacity:0.5' : '';
1687 E('span', { 'style': s
}, render_signal_badge(q
, res
.signal
, res
.noise
)),
1688 E('span', { 'style': s
}, '%h'.format(res
.ssid
)),
1689 E('span', { 'style': s
}, '%d'.format(res
.channel
)),
1690 E('span', { 'style': s
}, '%h'.format(res
.mode
)),
1691 E('span', { 'style': s
}, '%h'.format(res
.bssid
)),
1692 E('span', { 'style': s
}, '%h'.format(network
.formatWifiEncryption(res
.encryption
))),
1693 E('div', { 'class': 'right' }, E('button', {
1694 'class': 'cbi-button cbi-button-action important',
1695 'click': L
.bind(this.handleJoin
, this, radioDev
, res
)
1696 }, _('Join Network')))
1702 cbi_update_table(table
, rows
);
1706 s
.handleScanAbort = function(ev
) {
1707 var md
= L
.dom
.parent(ev
.target
, 'div[aria-modal="true"]');
1709 md
.style
.maxWidth
= '';
1710 md
.style
.maxHeight
= '';
1714 L
.Poll
.remove(this.pollFn
);
1719 s
.handleJoinConfirm = function(radioDev
, bss
, form
, ev
) {
1720 var nameopt
= L
.toArray(form
.lookupOption('name', '_new_'))[0],
1721 passopt
= L
.toArray(form
.lookupOption('password', '_new_'))[0],
1722 zoneopt
= L
.toArray(form
.lookupOption('zone', '_new_'))[0],
1723 replopt
= L
.toArray(form
.lookupOption('replace', '_new_'))[0],
1724 nameval
= (nameopt
&& nameopt
.isValid('_new_')) ? nameopt
.formvalue('_new_') : null,
1725 passval
= (passopt
&& passopt
.isValid('_new_')) ? passopt
.formvalue('_new_') : null,
1726 zoneval
= zoneopt
? zoneopt
.formvalue('_new_') : null,
1727 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
1728 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
1729 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' })),
1730 is_sae
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'sae' }));
1732 if (nameval
== null || (passopt
&& passval
== null))
1735 var section_id
= null;
1737 return this.map
.save(function() {
1738 var wifi_sections
= uci
.sections('wireless', 'wifi-iface');
1740 if (replopt
.formvalue('_new_') == '1') {
1741 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1742 if (wifi_sections
[i
].device
== radioDev
.getName())
1743 uci
.remove('wireless', wifi_sections
[i
]['.name']);
1746 if (uci
.get('wireless', radioDev
.getName(), 'disabled') == '1') {
1747 for (var i
= 0; i
< wifi_sections
.length
; i
++)
1748 if (wifi_sections
[i
].device
== radioDev
.getName())
1749 uci
.set('wireless', wifi_sections
[i
]['.name'], 'disabled', '1');
1751 uci
.unset('wireless', radioDev
.getName(), 'disabled');
1754 section_id
= next_free_sid(wifi_sections
.length
);
1756 uci
.add('wireless', 'wifi-iface', section_id
);
1757 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
1758 uci
.set('wireless', section_id
, 'mode', (bss
.mode
== 'Ad-Hoc') ? 'adhoc' : 'sta');
1759 uci
.set('wireless', section_id
, 'network', nameval
);
1761 if (bss
.ssid
!= null)
1762 uci
.set('wireless', section_id
, 'ssid', bss
.ssid
);
1763 else if (bss
.bssid
!= null)
1764 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
1767 uci
.set('wireless', section_id
, 'encryption', 'sae');
1768 uci
.set('wireless', section_id
, 'key', passval
);
1771 for (var i
= enc
.wpa
.length
- 1; i
>= 0; i
--) {
1772 if (enc
.wpa
[i
] == 2) {
1773 uci
.set('wireless', section_id
, 'encryption', 'psk2');
1776 else if (enc
.wpa
[i
] == 1) {
1777 uci
.set('wireless', section_id
, 'encryption', 'psk');
1782 uci
.set('wireless', section_id
, 'key', passval
);
1785 uci
.set('wireless', section_id
, 'encryption', 'wep-open');
1786 uci
.set('wireless', section_id
, 'key', '1');
1787 uci
.set('wireless', section_id
, 'key1', passval
);
1790 return network
.addNetwork(nameval
, { proto
: 'dhcp' }).then(function(net
) {
1791 firewall
.deleteNetwork(net
.getName());
1793 var zonePromise
= zoneval
1794 ? firewall
.getZone(zoneval
).then(function(zone
) { return zone
|| firewall
.addZone(zoneval
) })
1795 : Promise
.resolve();
1797 return zonePromise
.then(function(zone
) {
1799 zone
.addNetwork(net
.getName());
1802 }).then(L
.bind(function() {
1803 return this.renderMoreOptionsModal(section_id
);
1807 s
.handleJoin = function(radioDev
, bss
, ev
) {
1808 this.handleScanAbort(ev
);
1810 var m2
= new form
.Map('wireless'),
1811 s2
= m2
.section(form
.NamedSection
, '_new_'),
1812 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
1813 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
1814 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' || a
== 'sae' })),
1815 replace
, passphrase
, name
, zone
;
1817 s2
.render = function() {
1818 return Promise
.all([
1820 this.renderUCISection('_new_')
1821 ]).then(this.renderContents
.bind(this));
1824 replace
= s2
.option(form
.Flag
, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
1826 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>'));
1827 name
.datatype
= 'uciname';
1828 name
.default = 'wwan';
1829 name
.rmempty
= false;
1830 name
.validate = function(section_id
, value
) {
1831 if (uci
.get('network', value
))
1832 return _('The network name is already used');
1837 for (var i
= 2; uci
.get('network', name
.default); i
++)
1838 name
.default = 'wwan%d'.format(i
);
1840 if (is_wep
|| is_psk
) {
1841 passphrase
= s2
.option(form
.Value
, 'password', is_wep
? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
1842 passphrase
.datatype
= is_wep
? 'wepkey' : 'wpakey';
1843 passphrase
.password
= true;
1844 passphrase
.rmempty
= false;
1847 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.'));
1848 zone
.default = 'wan';
1850 return m2
.render().then(L
.bind(function(nodes
) {
1851 L
.ui
.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss
.ssid
)), [
1853 E('div', { 'class': 'right' }, [
1856 'click': L
.ui
.hideModal
1857 }, _('Cancel')), ' ',
1859 'class': 'cbi-button cbi-button-positive important',
1860 'click': L
.ui
.createHandlerFn(this, 'handleJoinConfirm', radioDev
, bss
, m2
)
1863 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase
|| name
).cbid('_new_'))).focus();
1867 s
.handleAdd = function(radioDev
, ev
) {
1868 var section_id
= next_free_sid(uci
.sections('wireless', 'wifi-iface').length
);
1870 uci
.unset('wireless', radioDev
.getName(), 'disabled');
1872 uci
.add('wireless', 'wifi-iface', section_id
);
1873 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
1874 uci
.set('wireless', section_id
, 'mode', 'ap');
1875 uci
.set('wireless', section_id
, 'ssid', 'OpenWrt');
1876 uci
.set('wireless', section_id
, 'encryption', 'none');
1878 this.addedSection
= section_id
;
1879 return this.renderMoreOptionsModal(section_id
);
1882 o
= s
.option(form
.DummyValue
, '_badge');
1883 o
.modalonly
= false;
1884 o
.textvalue = function(section_id
) {
1885 var inst
= this.section
.lookupRadioOrNetwork(section_id
),
1886 node
= E('div', { 'class': 'center' });
1888 if (inst
.getWifiNetworks
)
1889 node
.appendChild(render_radio_badge(inst
));
1891 node
.appendChild(render_network_badge(inst
));
1896 o
= s
.option(form
.DummyValue
, '_stat');
1897 o
.modalonly
= false;
1898 o
.textvalue = function(section_id
) {
1899 var inst
= this.section
.lookupRadioOrNetwork(section_id
);
1901 if (inst
.getWifiNetworks
)
1902 return render_radio_status(inst
, this.section
.wifis
.filter(function(e
) {
1903 return (e
.getWifiDeviceName() == inst
.getName());
1906 return render_network_status(inst
);
1909 return m
.render().then(L
.bind(function(m
, nodes
) {
1910 L
.Poll
.add(L
.bind(function() {
1911 var section_ids
= m
.children
[0].cfgsections(),
1912 tasks
= [ network
.getHostHints(), network
.getWifiDevices() ];
1914 for (var i
= 0; i
< section_ids
.length
; i
++) {
1915 var row
= nodes
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids
[i
])),
1916 dsc
= row
.querySelector('[data-name="_stat"] > div'),
1917 btns
= row
.querySelectorAll('.cbi-section-actions button');
1919 if (dsc
.getAttribute('restart') == '') {
1920 dsc
.setAttribute('restart', '1');
1921 tasks
.push(fs
.exec('/sbin/wifi', ['up', section_ids
[i
]]).catch(function(e
) {
1922 L
.ui
.addNotification(null, E('p', e
.message
));
1925 else if (dsc
.getAttribute('restart') == '1') {
1926 dsc
.removeAttribute('restart');
1927 btns
[0].classList
.remove('spinning');
1928 btns
[0].disabled
= false;
1932 return Promise
.all(tasks
)
1933 .then(L
.bind(function(hosts_radios
) {
1936 for (var i
= 0; i
< hosts_radios
[1].length
; i
++)
1937 tasks
.push(hosts_radios
[1][i
].getWifiNetworks());
1939 return Promise
.all(tasks
).then(function(data
) {
1940 hosts_radios
[2] = [];
1942 for (var i
= 0; i
< data
.length
; i
++)
1943 hosts_radios
[2].push
.apply(hosts_radios
[2], data
[i
]);
1945 return hosts_radios
;
1948 .then(L
.bind(function(hosts_radios_wifis
) {
1951 for (var i
= 0; i
< hosts_radios_wifis
[2].length
; i
++)
1952 tasks
.push(hosts_radios_wifis
[2][i
].getAssocList());
1954 return Promise
.all(tasks
).then(function(data
) {
1955 hosts_radios_wifis
[3] = [];
1957 for (var i
= 0; i
< data
.length
; i
++) {
1958 var wifiNetwork
= hosts_radios_wifis
[2][i
],
1959 radioDev
= hosts_radios_wifis
[1].filter(function(d
) { return d
.getName() == wifiNetwork
.getWifiDeviceName() })[0];
1961 for (var j
= 0; j
< data
[i
].length
; j
++)
1962 hosts_radios_wifis
[3].push(Object
.assign({ radio
: radioDev
, network
: wifiNetwork
}, data
[i
][j
]));
1965 return hosts_radios_wifis
;
1968 .then(L
.bind(this.poll_status
, this, nodes
));
1971 var table
= E('div', { 'class': 'table', 'id': 'wifi_assoclist_table' }, [
1972 E('div', { 'class': 'tr table-titles' }, [
1973 E('div', { 'class': 'th nowrap' }, _('Network')),
1974 E('div', { 'class': 'th hide-xs' }, _('MAC-Address')),
1975 E('div', { 'class': 'th nowrap' }, _('Host')),
1976 E('div', { 'class': 'th nowrap' }, _('Signal / Noise')),
1977 E('div', { 'class': 'th nowrap' }, _('RX Rate / TX Rate'))
1981 cbi_update_table(table
, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
1983 return E([ nodes
, E('h3', _('Associated Stations')), table
]);