12 'require tools.widgets as widgets';
14 var isReadonlyView
= !L
.hasViewPermission();
16 function count_changes(section_id
) {
17 var changes
= ui
.changes
.changes
, n
= 0;
19 if (!L
.isObject(changes
))
22 if (Array
.isArray(changes
.wireless
))
23 for (var i
= 0; i
< changes
.wireless
.length
; i
++)
24 n
+= (changes
.wireless
[i
][1] == section_id
);
29 function render_radio_badge(radioDev
) {
30 return E('span', { 'class': 'ifacebadge' }, [
31 E('img', { 'src': L
.resource('icons/wifi%s.png').format(radioDev
.isUp() ? '' : '_disabled') }),
37 function render_signal_badge(signalPercent
, signalValue
, noiseValue
, wrap
, mode
) {
38 var icon
, title
, value
;
40 if (signalPercent
< 0)
41 icon
= L
.resource('icons/signal-none.png');
42 else if (signalPercent
== 0)
43 icon
= L
.resource('icons/signal-0.png');
44 else if (signalPercent
< 25)
45 icon
= L
.resource('icons/signal-0-25.png');
46 else if (signalPercent
< 50)
47 icon
= L
.resource('icons/signal-25-50.png');
48 else if (signalPercent
< 75)
49 icon
= L
.resource('icons/signal-50-75.png');
51 icon
= L
.resource('icons/signal-75-100.png');
53 if (signalValue
!= null && signalValue
!= 0) {
54 if (noiseValue
!= null && noiseValue
!= 0) {
55 value
= '%d/%d\xa0%s'.format(signalValue
, noiseValue
, _('dBm'));
56 title
= '%s: %d %s / %s: %d %s / %s %d'.format(
57 _('Signal'), signalValue
, _('dBm'),
58 _('Noise'), noiseValue
, _('dBm'),
59 _('SNR'), signalValue
- noiseValue
);
62 value
= '%d\xa0%s'.format(signalValue
, _('dBm'));
63 title
= '%s: %d %s'.format(_('Signal'), signalValue
, _('dBm'));
66 else if (signalPercent
> -1) {
69 title
= _('No client associated');
75 title
= _('Not associated');
79 title
= _('No RX signal');
82 if (noiseValue
!= null && noiseValue
!= 0) {
83 value
= '---/%d\xa0%s'.format(noiseValue
, _('dBm'));
84 title
= '%s / %s: %d %s'.format(title
, _('Noise'), noiseValue
, _('dBm'));
87 value
= '---\xa0%s'.format(_('dBm'));
91 value
= E('em', {}, E('small', {}, [ _('disabled') ]));
92 title
= _('Interface is disabled');
96 'class': wrap
? 'center' : 'ifacebadge',
98 'data-signal': signalValue
,
99 'data-noise': noiseValue
101 E('img', { 'src': icon
}),
103 wrap
? E('br') : ' ',
109 function render_network_badge(radioNet
) {
110 return render_signal_badge(
111 radioNet
.isUp() ? radioNet
.getSignalPercent() : -1,
112 radioNet
.getSignal(), radioNet
.getNoise(), false, radioNet
.getMode());
115 function render_radio_status(radioDev
, wifiNets
) {
116 var name
= radioDev
.getI18n().replace(/ Wireless Controller
.+$/, ''),
117 node
= E('div', [ E('big', {}, E('strong', {}, name
)), E('div') ]),
118 channel
, frequency
, bitrate
;
120 for (var i
= 0; i
< wifiNets
.length
; i
++) {
121 channel
= channel
|| wifiNets
[i
].getChannel();
122 frequency
= frequency
|| wifiNets
[i
].getFrequency();
123 bitrate
= bitrate
|| wifiNets
[i
].getBitRate();
127 L
.itemlist(node
.lastElementChild
, [
128 _('Channel'), '%s (%s %s)'.format(channel
|| '?', frequency
|| '?', _('GHz')),
129 _('Bitrate'), '%s %s'.format(bitrate
|| '?', _('Mbit/s'))
132 node
.lastElementChild
.appendChild(E('em', _('Device is not active')));
137 function render_network_status(radioNet
) {
138 var mode
= radioNet
.getActiveMode(),
139 bssid
= radioNet
.getActiveBSSID(),
140 channel
= radioNet
.getChannel(),
141 disabled
= (radioNet
.get('disabled') == '1' || uci
.get('wireless', radioNet
.getWifiDeviceName(), 'disabled') == '1'),
142 is_assoc
= (bssid
&& bssid
!= '00:00:00:00:00:00' && channel
&& mode
!= 'Unknown' && !disabled
),
143 is_mesh
= (radioNet
.getMode() == 'mesh'),
144 changecount
= count_changes(radioNet
.getName()),
148 status_text
= E('a', {
150 click
: L
.bind(ui
.changes
.displayChanges
, ui
.changes
)
151 }, _('Interface has %d pending changes').format(changecount
));
153 status_text
= E('em', disabled
? _('Wireless is disabled') : _('Wireless is not associated'));
155 return L
.itemlist(E('div'), [
156 is_mesh
? _('Mesh ID') : _('SSID'), (is_mesh
? radioNet
.getMeshID() : radioNet
.getSSID()) || '?',
158 _('BSSID'), (!changecount
&& is_assoc
) ? bssid
: null,
159 _('Encryption'), (!changecount
&& is_assoc
) ? radioNet
.getActiveEncryption() || _('None') : null,
161 ], [ ' | ', E('br') ]);
164 function render_modal_status(node
, radioNet
) {
165 var mode
= radioNet
.getActiveMode(),
166 noise
= radioNet
.getNoise(),
167 bssid
= radioNet
.getActiveBSSID(),
168 channel
= radioNet
.getChannel(),
169 disabled
= (radioNet
.get('disabled') == '1'),
170 is_assoc
= (bssid
&& bssid
!= '00:00:00:00:00:00' && channel
&& mode
!= 'Unknown' && !disabled
);
173 node
= E('span', { 'class': 'ifacebadge large', 'data-network': radioNet
.getName() }, [ E('small'), E('span') ]);
175 dom
.content(node
.firstElementChild
, render_signal_badge(
176 disabled
? -1 : radioNet
.getSignalPercent(),
177 radioNet
.getSignal(), noise
, true, radioNet
.getMode()));
179 L
.itemlist(node
.lastElementChild
, [
181 _('SSID'), radioNet
.getSSID() || '?',
182 _('BSSID'), is_assoc
? bssid
: null,
183 _('Encryption'), is_assoc
? radioNet
.getActiveEncryption() || _('None') : null,
184 _('Channel'), is_assoc
? '%d (%.3f %s)'.format(radioNet
.getChannel(), radioNet
.getFrequency() || 0, _('GHz')) : null,
185 _('Tx-Power'), is_assoc
? '%d %s'.format(radioNet
.getTXPower(), _('dBm')) : null,
186 _('Signal'), is_assoc
? '%d %s'.format(radioNet
.getSignal(), _('dBm')) : null,
187 _('Noise'), (is_assoc
&& noise
!= null) ? '%d %s'.format(noise
, _('dBm')) : null,
188 _('Bitrate'), is_assoc
? '%.1f %s'.format(radioNet
.getBitRate() || 0, _('Mbit/s')) : null,
189 _('Country'), is_assoc
? radioNet
.getCountryCode() : null
190 ], [ ' | ', E('br'), E('br'), E('br'), E('br'), E('br'), ' | ', E('br'), ' | ' ]);
193 dom
.append(node
.lastElementChild
, E('em', disabled
? _('Wireless is disabled') : _('Wireless is not associated')));
198 function format_wifirate(rate
) {
199 var s
= '%.1f\xa0%s, %d\xa0%s'.format(rate
.rate
/ 1000, _('Mbit/s'), rate
.mhz
, _('MHz')),
200 ht
= rate
.ht
, vht
= rate
.vht
,
201 mhz
= rate
.mhz
, nss
= rate
.nss
,
202 mcs
= rate
.mcs
, sgi
= rate
.short_gi
,
203 he
= rate
.he
, he_gi
= rate
.he_gi
,
204 he_dcm
= rate
.he_dcm
;
207 if (vht
) s
+= ', VHT-MCS\xa0%d'.format(mcs
);
208 if (nss
) s
+= ', VHT-NSS\xa0%d'.format(nss
);
209 if (ht
) s
+= ', MCS\xa0%s'.format(mcs
);
210 if (sgi
) s
+= ', ' + _('Short GI').replace(/ /g
, '\xa0');
214 s
+= ', HE-MCS\xa0%d'.format(mcs
);
215 if (nss
) s
+= ', HE-NSS\xa0%d'.format(nss
);
216 if (he_gi
) s
+= ', HE-GI\xa0%d'.format(he_gi
);
217 if (he_dcm
) s
+= ', HE-DCM\xa0%d'.format(he_dcm
);
223 function radio_restart(id
, ev
) {
224 var row
= document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id
)),
225 dsc
= row
.querySelector('[data-name="_stat"] > div'),
226 btn
= row
.querySelector('.cbi-section-actions button');
229 btn
.classList
.add('spinning');
232 dsc
.setAttribute('restart', '');
233 dom
.content(dsc
, E('em', _('Device is restarting…')));
236 function network_updown(id
, map
, ev
) {
237 var radio
= uci
.get('wireless', id
, 'device'),
238 disabled
= (uci
.get('wireless', id
, 'disabled') == '1') ||
239 (uci
.get('wireless', radio
, 'disabled') == '1');
242 uci
.unset('wireless', id
, 'disabled');
243 uci
.unset('wireless', radio
, 'disabled');
246 uci
.set('wireless', id
, 'disabled', '1');
248 var all_networks_disabled
= true,
249 wifi_ifaces
= uci
.sections('wireless', 'wifi-iface');
251 for (var i
= 0; i
< wifi_ifaces
.length
; i
++) {
252 if (wifi_ifaces
[i
].device
== radio
&& wifi_ifaces
[i
].disabled
!= '1') {
253 all_networks_disabled
= false;
258 if (all_networks_disabled
)
259 uci
.set('wireless', radio
, 'disabled', '1');
262 return map
.save().then(function() {
267 function next_free_sid(offset
) {
268 var sid
= 'wifinet' + offset
;
270 while (uci
.get('wireless', sid
))
271 sid
= 'wifinet' + (++offset
);
276 function add_dependency_permutations(o
, deps
) {
279 for (var key
in deps
) {
280 if (!deps
.hasOwnProperty(key
) || !Array
.isArray(deps
[key
]))
283 var list
= deps
[key
],
286 for (var j
= 0; j
< list
.length
; j
++) {
287 for (var k
= 0; k
< (res
? res
.length
: 1); k
++) {
288 var item
= (res
? Object
.assign({}, res
[k
]) : {});
297 for (var i
= 0; i
< (res
? res
.length
: 0); i
++)
301 var CBIWifiFrequencyValue
= form
.Value
.extend({
302 callFrequencyList
: rpc
.declare({
305 params
: [ 'device' ],
306 expect
: { results
: [] }
309 load: function(section_id
) {
311 network
.getWifiDevice(section_id
),
312 this.callFrequencyList(section_id
)
313 ]).then(L
.bind(function(data
) {
315 '2g': L
.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
316 '5g': L
.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
317 '6g': L
.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [],
321 for (var i
= 0; i
< data
[1].length
; i
++) {
322 if (!data
[1][i
].band
)
325 var band
= '%dg'.format(data
[1][i
].band
);
327 this.channels
[band
].push(
329 '%d (%d Mhz)'.format(data
[1][i
].channel
, data
[1][i
].mhz
),
330 !data
[1][i
].restricted
334 var hwmodelist
= L
.toArray(data
[0] ? data
[0].getHWModes() : null)
335 .reduce(function(o
, v
) { o
[v
] = true; return o
}, {});
338 '', 'Legacy', hwmodelist
.a
|| hwmodelist
.b
|| hwmodelist
.g
,
339 'n', 'N', hwmodelist
.n
,
340 'ac', 'AC', L
.hasSystemFeature('hostapd', '11ac') && hwmodelist
.ac
,
341 'ax', 'AX', L
.hasSystemFeature('hostapd', '11ax') && hwmodelist
.ax
344 var htmodelist
= L
.toArray(data
[0] ? data
[0].getHTModes() : null)
345 .reduce(function(o
, v
) { o
[v
] = true; return o
}, {});
348 '': [ '', '-', true ],
350 'HT20', '20 MHz', htmodelist
.HT20
,
351 'HT40', '40 MHz', htmodelist
.HT40
354 'VHT20', '20 MHz', htmodelist
.VHT20
,
355 'VHT40', '40 MHz', htmodelist
.VHT40
,
356 'VHT80', '80 MHz', htmodelist
.VHT80
,
357 'VHT160', '160 MHz', htmodelist
.VHT160
360 'HE20', '20 MHz', htmodelist
.HE20
,
361 'HE40', '40 MHz', htmodelist
.HE40
,
362 'HE80', '80 MHz', htmodelist
.HE80
,
363 'HE160', '160 MHz', htmodelist
.HE160
369 '2g', '2.4 GHz', this.channels
['2g'].length
> 3,
370 '5g', '5 GHz', this.channels
['5g'].length
> 3,
371 '60g', '60 GHz', this.channels
['60g'].length
> 0
374 '2g', '2.4 GHz', this.channels
['2g'].length
> 3,
375 '5g', '5 GHz', this.channels
['5g'].length
> 3
381 '2g', '2.4 GHz', this.channels
['2g'].length
> 3,
382 '5g', '5 GHz', this.channels
['5g'].length
> 3,
383 '6g', '6 GHz', this.channels
['6g'].length
> 3
389 setValues: function(sel
, vals
) {
391 sel
.vals
.selected
= sel
.selectedIndex
;
393 while (sel
.options
[0])
396 for (var i
= 0; vals
&& i
< vals
.length
; i
+= 3)
398 sel
.add(E('option', { value
: vals
[i
+0] }, [ vals
[i
+1] ]));
400 if (vals
&& !isNaN(vals
.selected
))
401 sel
.selectedIndex
= vals
.selected
;
403 sel
.parentNode
.style
.display
= (sel
.options
.length
<= 1) ? 'none' : '';
407 toggleWifiMode: function(elem
) {
408 this.toggleWifiHTMode(elem
);
409 this.toggleWifiBand(elem
);
412 toggleWifiHTMode: function(elem
) {
413 var mode
= elem
.querySelector('.mode');
414 var bwdt
= elem
.querySelector('.htmode');
416 this.setValues(bwdt
, this.htmodes
[mode
.value
]);
419 toggleWifiBand: function(elem
) {
420 var mode
= elem
.querySelector('.mode');
421 var band
= elem
.querySelector('.band');
423 this.setValues(band
, this.bands
[mode
.value
]);
424 this.toggleWifiChannel(elem
);
426 this.map
.checkDepends();
429 toggleWifiChannel: function(elem
) {
430 var band
= elem
.querySelector('.band');
431 var chan
= elem
.querySelector('.channel');
433 this.setValues(chan
, this.channels
[band
.value
]);
436 setInitialValues: function(section_id
, elem
) {
437 var mode
= elem
.querySelector('.mode'),
438 band
= elem
.querySelector('.band'),
439 chan
= elem
.querySelector('.channel'),
440 bwdt
= elem
.querySelector('.htmode'),
441 htval
= uci
.get('wireless', section_id
, 'htmode'),
442 hwval
= uci
.get('wireless', section_id
, 'hwmode'),
443 chval
= uci
.get('wireless', section_id
, 'channel'),
444 bandval
= uci
.get('wireless', section_id
, 'band');
446 this.setValues(mode
, this.modes
);
448 if (/HE20|HE40|HE80|HE160/.test(htval
))
450 else if (/VHT20|VHT40|VHT80|VHT160/.test(htval
))
452 else if (/HT20|HT40/.test(htval
))
457 this.toggleWifiMode(elem
);
460 this.useBandOption
= false;
468 this.useBandOption
= true;
470 band
.value
= bandval
;
473 this.toggleWifiBand(elem
);
476 chan
.value
= chval
|| (chan
.options
[0] ? chan
.options
[0].value
: 'auto');
481 renderWidget: function(section_id
, option_index
, cfgvalue
) {
485 E('label', { 'style': 'float:left; margin-right:3px' }, [
489 'style': 'width:auto',
490 'change': L
.bind(this.toggleWifiMode
, this, elem
),
491 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
494 E('label', { 'style': 'float:left; margin-right:3px' }, [
498 'style': 'width:auto',
499 'change': L
.bind(this.toggleWifiBand
, this, elem
),
500 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
503 E('label', { 'style': 'float:left; margin-right:3px' }, [
504 _('Channel'), E('br'),
507 'style': 'width:auto',
508 'change': L
.bind(this.map
.checkDepends
, this.map
),
509 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
512 E('label', { 'style': 'float:left; margin-right:3px' }, [
516 'style': 'width:auto',
517 'change': L
.bind(this.map
.checkDepends
, this.map
),
518 'disabled': (this.disabled
!= null) ? this.disabled
: this.map
.readonly
521 E('br', { 'style': 'clear:left' })
524 return this.setInitialValues(section_id
, elem
);
527 cfgvalue: function(section_id
) {
529 uci
.get('wireless', section_id
, 'htmode'),
530 uci
.get('wireless', section_id
, 'hwmode') || uci
.get('wireless', section_id
, 'band'),
531 uci
.get('wireless', section_id
, 'channel')
535 formvalue: function(section_id
) {
536 var node
= this.map
.findElement('data-field', this.cbid(section_id
));
539 node
.querySelector('.htmode').value
,
540 node
.querySelector('.band').value
,
541 node
.querySelector('.channel').value
545 write: function(section_id
, value
) {
546 uci
.set('wireless', section_id
, 'htmode', value
[0] || null);
548 if (this.useBandOption
)
549 uci
.set('wireless', section_id
, 'band', value
[1]);
551 uci
.set('wireless', section_id
, 'hwmode', (value
[1] == '2g') ? '11g' : '11a');
553 uci
.set('wireless', section_id
, 'channel', value
[2]);
557 var CBIWifiTxPowerValue
= form
.ListValue
.extend({
558 callTxPowerList
: rpc
.declare({
560 method
: 'txpowerlist',
561 params
: [ 'device' ],
562 expect
: { results
: [] }
565 load: function(section_id
) {
566 return this.callTxPowerList(section_id
).then(L
.bind(function(pwrlist
) {
567 this.powerval
= this.wifiNetwork
? this.wifiNetwork
.getTXPower() : null;
568 this.poweroff
= this.wifiNetwork
? this.wifiNetwork
.getTXPowerOffset() : null;
570 this.value('', _('driver default'));
572 for (var i
= 0; i
< pwrlist
.length
; i
++)
573 this.value(pwrlist
[i
].dbm
, '%d dBm (%d mW)'.format(pwrlist
[i
].dbm
, pwrlist
[i
].mw
));
575 return form
.ListValue
.prototype.load
.apply(this, [section_id
]);
579 renderWidget: function(section_id
, option_index
, cfgvalue
) {
580 var widget
= form
.ListValue
.prototype.renderWidget
.apply(this, [section_id
, option_index
, cfgvalue
]);
581 widget
.firstElementChild
.style
.width
= 'auto';
583 dom
.append(widget
, E('span', [
584 ' - ', _('Current power'), ': ',
585 E('span', [ this.powerval
!= null ? '%d dBm'.format(this.powerval
) : E('em', _('unknown')) ]),
586 this.poweroff
? ' + %d dB offset = %s dBm'.format(this.poweroff
, this.powerval
!= null ? this.powerval
+ this.poweroff
: '?') : ''
593 var CBIWifiCountryValue
= form
.Value
.extend({
594 callCountryList
: rpc
.declare({
596 method
: 'countrylist',
597 params
: [ 'device' ],
598 expect
: { results
: [] }
601 load: function(section_id
) {
602 return this.callCountryList(section_id
).then(L
.bind(function(countrylist
) {
603 if (Array
.isArray(countrylist
) && countrylist
.length
> 0) {
604 this.value('', _('driver default'));
606 for (var i
= 0; i
< countrylist
.length
; i
++)
607 this.value(countrylist
[i
].iso3166
, '%s - %s'.format(countrylist
[i
].iso3166
, countrylist
[i
].country
));
610 return form
.Value
.prototype.load
.apply(this, [section_id
]);
614 validate: function(section_id
, formvalue
) {
615 if (formvalue
!= null && formvalue
!= '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue
))
616 return _('Use ISO/IEC 3166 alpha2 country codes.');
621 renderWidget: function(section_id
, option_index
, cfgvalue
) {
622 var typeClass
= (this.keylist
&& this.keylist
.length
) ? form
.ListValue
: form
.Value
;
623 return typeClass
.prototype.renderWidget
.apply(this, [section_id
, option_index
, cfgvalue
]);
628 poll_status: function(map
, data
) {
629 var rows
= map
.querySelectorAll('.cbi-section-table-row[data-sid]');
631 for (var i
= 0; i
< rows
.length
; i
++) {
632 var section_id
= rows
[i
].getAttribute('data-sid'),
633 radioDev
= data
[1].filter(function(d
) { return d
.getName() == section_id
})[0],
634 radioNet
= data
[2].filter(function(n
) { return n
.getName() == section_id
})[0],
635 badge
= rows
[i
].querySelector('[data-name="_badge"] > div'),
636 stat
= rows
[i
].querySelector('[data-name="_stat"]'),
637 btns
= rows
[i
].querySelectorAll('.cbi-section-actions button'),
638 busy
= btns
[0].classList
.contains('spinning') || btns
[1].classList
.contains('spinning') || btns
[2].classList
.contains('spinning');
641 dom
.content(badge
, render_radio_badge(radioDev
));
642 dom
.content(stat
, render_radio_status(radioDev
, data
[2].filter(function(n
) { return n
.getWifiDeviceName() == radioDev
.getName() })));
645 dom
.content(badge
, render_network_badge(radioNet
));
646 dom
.content(stat
, render_network_status(radioNet
));
649 if (stat
.hasAttribute('restart'))
650 dom
.content(stat
, E('em', _('Device is restarting…')));
652 btns
[0].disabled
= isReadonlyView
|| busy
;
653 btns
[1].disabled
= (isReadonlyView
&& radioDev
) || busy
;
654 btns
[2].disabled
= isReadonlyView
|| busy
;
657 var table
= document
.querySelector('#wifi_assoclist_table'),
661 for (var i
= 0; i
< data
[3].length
; i
++) {
662 var bss
= data
[3][i
],
663 name
= hosts
.getHostnameByMACAddr(bss
.mac
),
664 ipv4
= hosts
.getIPAddrByMACAddr(bss
.mac
),
665 ipv6
= hosts
.getIP6AddrByMACAddr(bss
.mac
);
669 if (name
&& ipv4
&& ipv6
)
670 hint
= '%s <span class="hide-xs">(%s, %s)</span>'.format(name
, ipv4
, ipv6
);
671 else if (name
&& (ipv4
|| ipv6
))
672 hint
= '%s <span class="hide-xs">(%s)</span>'.format(name
, ipv4
|| ipv6
);
674 hint
= name
|| ipv4
|| ipv6
|| '?';
678 'class': 'ifacebadge',
679 'data-ifname': bss
.network
.getIfname(),
680 'data-ssid': bss
.network
.getSSID()
683 'src': L
.resource('icons/wifi%s.png').format(bss
.network
.isUp() ? '' : '_disabled'),
684 'title': bss
.radio
.getI18n()
687 ' %s '.format(bss
.network
.getShortName()),
688 E('small', '(%s)'.format(bss
.network
.getIfname()))
693 render_signal_badge(Math
.min((bss
.signal
+ 110) / 70 * 100, 100), bss
.signal
, bss
.noise
),
695 E('span', format_wifirate(bss
.rx
)),
697 E('span', format_wifirate(bss
.tx
))
701 if (bss
.network
.isClientDisconnectSupported()) {
702 if (table
.firstElementChild
.childNodes
.length
< 6)
703 table
.firstElementChild
.appendChild(E('th', { 'class': 'th cbi-section-actions'}));
705 row
.push(E('button', {
706 'class': 'cbi-button cbi-button-remove',
707 'click': L
.bind(function(net
, mac
, ev
) {
708 dom
.parent(ev
.currentTarget
, '.tr').style
.opacity
= 0.5;
709 ev
.currentTarget
.classList
.add('spinning');
710 ev
.currentTarget
.disabled
= true;
711 ev
.currentTarget
.blur();
713 net
.disconnectClient(mac
, true, 5, 60000);
714 }, this, bss
.network
, bss
.mac
),
715 'disabled': isReadonlyView
|| null
716 }, [ _('Disconnect') ]));
725 cbi_update_table(table
, trows
, E('em', _('No information available')));
727 var stat
= document
.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large');
730 render_modal_status(stat
, data
[2].filter(function(n
) { return n
.getName() == stat
.getAttribute('data-network') })[0]);
732 return network
.flushCache();
738 uci
.load('wireless'),
743 checkAnonymousSections: function() {
744 var wifiIfaces
= uci
.sections('wireless', 'wifi-iface');
746 for (var i
= 0; i
< wifiIfaces
.length
; i
++)
747 if (wifiIfaces
[i
]['.anonymous'])
753 callUciRename
: rpc
.declare({
756 params
: [ 'config', 'section', 'name' ]
760 if (this.checkAnonymousSections())
761 return this.renderMigration();
763 return this.renderOverview();
766 handleMigration: function(ev
) {
767 var wifiIfaces
= uci
.sections('wireless', 'wifi-iface'),
771 for (var i
= 0; i
< wifiIfaces
.length
; i
++) {
772 if (!wifiIfaces
[i
]['.anonymous'])
775 var new_name
= next_free_sid(id_offset
);
777 tasks
.push(this.callUciRename('wireless', wifiIfaces
[i
]['.name'], new_name
));
778 id_offset
= +new_name
.substring(7) + 1;
781 return Promise
.all(tasks
)
782 .then(L
.bind(ui
.changes
.init
, ui
.changes
))
783 .then(L
.bind(ui
.changes
.apply
, ui
.changes
));
786 renderMigration: function() {
787 ui
.showModal(_('Wireless configuration migration'), [
788 E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')),
789 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.')),
790 E('div', { 'class': 'right' },
792 'class': 'btn cbi-button-action important',
793 'click': ui
.createHandlerFn(this, 'handleMigration')
798 renderOverview: function() {
801 m
= new form
.Map('wireless');
805 s
= m
.section(form
.GridSection
, 'wifi-device', _('Wireless Overview'));
809 s
.load = function() {
810 return network
.getWifiDevices().then(L
.bind(function(radios
) {
811 this.radios
= radios
.sort(function(a
, b
) {
812 return a
.getName() > b
.getName();
817 for (var i
= 0; i
< radios
.length
; i
++)
818 tasks
.push(radios
[i
].getWifiNetworks());
820 return Promise
.all(tasks
);
821 }, this)).then(L
.bind(function(data
) {
824 for (var i
= 0; i
< data
.length
; i
++)
825 this.wifis
.push
.apply(this.wifis
, data
[i
]);
829 s
.cfgsections = function() {
832 for (var i
= 0; i
< this.radios
.length
; i
++) {
833 rv
.push(this.radios
[i
].getName());
835 for (var j
= 0; j
< this.wifis
.length
; j
++)
836 if (this.wifis
[j
].getWifiDeviceName() == this.radios
[i
].getName())
837 rv
.push(this.wifis
[j
].getName());
843 s
.modaltitle = function(section_id
) {
844 var radioNet
= this.wifis
.filter(function(w
) { return w
.getName() == section_id
})[0];
845 return radioNet
? radioNet
.getI18n() : _('Edit wireless network');
848 s
.lookupRadioOrNetwork = function(section_id
) {
849 var radioDev
= this.radios
.filter(function(r
) { return r
.getName() == section_id
})[0];
853 var radioNet
= this.wifis
.filter(function(w
) { return w
.getName() == section_id
})[0];
860 s
.renderRowActions = function(section_id
) {
861 var inst
= this.lookupRadioOrNetwork(section_id
), btns
;
863 if (inst
.getWifiNetworks
) {
866 'class': 'cbi-button cbi-button-neutral',
867 'title': _('Restart radio interface'),
868 'click': ui
.createHandlerFn(this, radio_restart
, section_id
)
871 'class': 'cbi-button cbi-button-action important',
872 'title': _('Find and join network'),
873 'click': ui
.createHandlerFn(this, 'handleScan', inst
)
876 'class': 'cbi-button cbi-button-add',
877 'title': _('Provide new network'),
878 'click': ui
.createHandlerFn(this, 'handleAdd', inst
)
883 var isDisabled
= (inst
.get('disabled') == '1' ||
884 uci
.get('wireless', inst
.getWifiDeviceName(), 'disabled') == '1');
888 'class': 'cbi-button cbi-button-neutral enable-disable',
889 'title': isDisabled
? _('Enable this network') : _('Disable this network'),
890 'click': ui
.createHandlerFn(this, network_updown
, section_id
, this.map
)
891 }, isDisabled
? _('Enable') : _('Disable')),
893 'class': 'cbi-button cbi-button-action important',
894 'title': _('Edit this network'),
895 'click': ui
.createHandlerFn(this, 'renderMoreOptionsModal', section_id
)
898 'class': 'cbi-button cbi-button-negative remove',
899 'title': _('Delete this network'),
900 'click': ui
.createHandlerFn(this, 'handleRemove', section_id
)
905 return E('td', { 'class': 'td middle cbi-section-actions' }, E('div', btns
));
908 s
.addModalOptions = function(s
) {
909 return network
.getWifiNetwork(s
.section
).then(function(radioNet
) {
910 var hwtype
= uci
.get('wireless', radioNet
.getWifiDeviceName(), 'type');
913 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getWifiDeviceName(), 'wifi-device', _('Device Configuration'));
917 ss
.tab('general', _('General Setup'));
918 ss
.tab('advanced', _('Advanced Settings'));
920 var isDisabled
= (radioNet
.get('disabled') == '1' ||
921 uci
.get('wireless', radioNet
.getWifiDeviceName(), 'disabled') == 1);
923 o
= ss
.taboption('general', form
.DummyValue
, '_wifistat_modal', _('Status'));
924 o
.cfgvalue
= L
.bind(function(radioNet
) {
925 return render_modal_status(null, radioNet
);
927 o
.write = function() {};
929 o
= ss
.taboption('general', form
.Button
, '_toggle', isDisabled
? _('Wireless network is disabled') : _('Wireless network is enabled'));
930 o
.inputstyle
= isDisabled
? 'apply' : 'reset';
931 o
.inputtitle
= isDisabled
? _('Enable') : _('Disable');
932 o
.onclick
= ui
.createHandlerFn(s
, network_updown
, s
.section
, s
.map
);
934 o
= ss
.taboption('general', CBIWifiFrequencyValue
, '_freq', '<br />' + _('Operating frequency'));
935 o
.ucisection
= s
.section
;
937 if (hwtype
== 'mac80211') {
938 o
= ss
.taboption('general', form
.Flag
, 'legacy_rates', _('Allow legacy 802.11b rates'), _('Legacy or badly behaving devices may require legacy 802.11b rates to interoperate. Airtime efficiency may be significantly reduced where these are used. It is recommended to not allow 802.11b rates where possible.'));
939 o
.depends({'_freq': '2g', '!contains': true});
941 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.'));
942 o
.wifiNetwork
= radioNet
;
944 o
= ss
.taboption('advanced', CBIWifiCountryValue
, 'country', _('Country Code'));
945 o
.wifiNetwork
= radioNet
;
947 o
= ss
.taboption('advanced', form
.ListValue
, 'cell_density', _('Coverage cell density'), _('Configures data rates based on the coverage cell density. Normal configures basic rates to 6, 12, 24 Mbps if legacy 802.11b rates are not used else to 5.5, 11 Mbps. High configures basic rates to 12, 24 Mbps if legacy 802.11b rates are not used else to the 11 Mbps rate. Very High configures 24 Mbps as the basic rate. Supported rates lower than the minimum basic rate are not offered.'));
948 o
.value('0', _('Disabled'));
949 o
.value('1', _('Normal'));
950 o
.value('2', _('High'));
951 o
.value('3', _('Very High'));
953 o
= ss
.taboption('advanced', form
.Value
, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters. Set only for distances above one kilometer; otherwise it is harmful.'));
954 o
.datatype
= 'or(range(0,114750),"auto")';
955 o
.placeholder
= 'auto';
957 o
= ss
.taboption('advanced', form
.Value
, 'frag', _('Fragmentation Threshold'));
958 o
.datatype
= 'min(256)';
959 o
.placeholder
= _('off');
961 o
= ss
.taboption('advanced', form
.Value
, 'rts', _('RTS/CTS Threshold'));
962 o
.datatype
= 'uinteger';
963 o
.placeholder
= _('off');
965 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!'));
968 o
= ss
.taboption('advanced', form
.Value
, 'beacon_int', _('Beacon Interval'));
969 o
.datatype
= 'range(15,65535)';
975 o
= s
.option(form
.SectionValue
, '_device', form
.NamedSection
, radioNet
.getName(), 'wifi-iface', _('Interface Configuration'));
979 ss
.tab('general', _('General Setup'));
980 ss
.tab('encryption', _('Wireless Security'));
981 ss
.tab('macfilter', _('MAC-Filter'));
982 ss
.tab('advanced', _('Advanced Settings'));
983 ss
.tab('roaming', _('WLAN roaming'), _('Settings for assisting wireless clients in roaming between multiple APs: 802.11r, 802.11k and 802.11v'));
985 o
= ss
.taboption('general', form
.ListValue
, 'mode', _('Mode'));
986 o
.value('ap', _('Access Point'));
987 o
.value('sta', _('Client'));
988 o
.value('adhoc', _('Ad-Hoc'));
990 o
= ss
.taboption('general', form
.Value
, 'mesh_id', _('Mesh Id'));
991 o
.depends('mode', 'mesh');
993 o
= ss
.taboption('advanced', form
.Flag
, 'mesh_fwding', _('Forward mesh peer traffic'));
996 o
.depends('mode', 'mesh');
998 o
= ss
.taboption('advanced', form
.Value
, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default'));
1001 o
.datatype
= 'range(-255,1)';
1002 o
.depends('mode', 'mesh');
1004 o
= ss
.taboption('general', form
.Value
, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>'));
1005 o
.datatype
= 'maxlength(32)';
1006 o
.depends('mode', 'ap');
1007 o
.depends('mode', 'sta');
1008 o
.depends('mode', 'adhoc');
1009 o
.depends('mode', 'ahdemo');
1010 o
.depends('mode', 'monitor');
1011 o
.depends('mode', 'ap-wds');
1012 o
.depends('mode', 'sta-wds');
1013 o
.depends('mode', 'wds');
1015 o
= ss
.taboption('general', form
.Value
, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
1016 o
.datatype
= 'macaddr';
1018 o
= ss
.taboption('general', widgets
.NetworkSelect
, 'network', _('Network'), _('Choose the network(s) you want to attach to this wireless interface or fill out the <em>custom</em> field to define a new network.'));
1022 o
.write = function(section_id
, value
) {
1023 return network
.getDevice(section_id
).then(L
.bind(function(dev
) {
1024 var old_networks
= dev
.getNetworks().reduce(function(o
, v
) { o
[v
.getName()] = v
; return o
}, {}),
1026 values
= L
.toArray(value
),
1029 for (var i
= 0; i
< values
.length
; i
++) {
1030 new_networks
[values
[i
]] = true;
1032 if (old_networks
[values
[i
]])
1035 tasks
.push(network
.getNetwork(values
[i
]).then(L
.bind(function(name
, net
) {
1036 return net
|| network
.addNetwork(name
, { proto
: 'none' });
1037 }, this, values
[i
])).then(L
.bind(function(dev
, net
) {
1039 if (!net
.isEmpty()) {
1040 var target_dev
= net
.getDevice();
1042 /* Resolve parent interface of vlan */
1043 while (target_dev
&& target_dev
.getType() == 'vlan')
1044 target_dev
= target_dev
.getParent();
1046 if (!target_dev
|| target_dev
.getType() != 'bridge')
1047 net
.set('type', 'bridge');
1055 for (var name
in old_networks
)
1056 if (!new_networks
[name
])
1057 tasks
.push(network
.getNetwork(name
).then(L
.bind(function(dev
, net
) {
1059 net
.deleteDevice(dev
);
1062 return Promise
.all(tasks
);
1066 if (hwtype
== 'mac80211') {
1067 var mode
= ss
.children
[0],
1068 bssid
= ss
.children
[5],
1071 mode
.value('mesh', '802.11s');
1072 mode
.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)'));
1073 mode
.value('monitor', _('Monitor'));
1075 bssid
.depends('mode', 'adhoc');
1076 bssid
.depends('mode', 'sta');
1077 bssid
.depends('mode', 'sta-wds');
1079 o
= ss
.taboption('macfilter', form
.ListValue
, 'macfilter', _('MAC Address Filter'));
1080 o
.depends('mode', 'ap');
1081 o
.depends('mode', 'ap-wds');
1082 o
.value('', _('disable'));
1083 o
.value('allow', _('Allow listed only'));
1084 o
.value('deny', _('Allow all except listed'));
1086 o
= ss
.taboption('macfilter', form
.DynamicList
, 'maclist', _('MAC-List'));
1087 o
.datatype
= 'macaddr';
1089 o
.depends('macfilter', 'allow');
1090 o
.depends('macfilter', 'deny');
1091 o
.load = function(section_id
) {
1092 return network
.getHostHints().then(L
.bind(function(hints
) {
1093 hints
.getMACHints().map(L
.bind(function(hint
) {
1094 this.value(hint
[0], hint
[1] ? '%s (%s)'.format(hint
[0], hint
[1]) : hint
[0]);
1097 return form
.DynamicList
.prototype.load
.apply(this, [section_id
]);
1101 mode
.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS')));
1102 mode
.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS')));
1104 mode
.write = function(section_id
, value
) {
1107 uci
.set('wireless', section_id
, 'mode', 'ap');
1108 uci
.set('wireless', section_id
, 'wds', '1');
1112 uci
.set('wireless', section_id
, 'mode', 'sta');
1113 uci
.set('wireless', section_id
, 'wds', '1');
1117 uci
.set('wireless', section_id
, 'mode', value
);
1118 uci
.unset('wireless', section_id
, 'wds');
1123 mode
.cfgvalue = function(section_id
) {
1124 var mode
= uci
.get('wireless', section_id
, 'mode'),
1125 wds
= uci
.get('wireless', section_id
, 'wds');
1127 if (mode
== 'ap' && wds
)
1129 else if (mode
== 'sta' && wds
)
1135 o
= ss
.taboption('general', form
.Flag
, 'hidden', _('Hide <abbr title="Extended Service Set Identifier">ESSID</abbr>'), _('Where the ESSID is hidden, clients may fail to roam and airtime efficiency may be significantly reduced.'));
1136 o
.depends('mode', 'ap');
1137 o
.depends('mode', 'ap-wds');
1139 o
= ss
.taboption('general', form
.Flag
, 'wmm', _('WMM Mode'), _('Where Wi-Fi Multimedia (WMM) Mode QoS is disabled, clients may be limited to 802.11a/802.11g rates.'));
1140 o
.depends('mode', 'ap');
1141 o
.depends('mode', 'ap-wds');
1142 o
.default = o
.enabled
;
1144 /* https://w1.fi/cgit/hostap/commit/?id=34f7c699a6bcb5c45f82ceb6743354ad79296078 */
1145 /* multicast_to_unicast https://github.com/openwrt/openwrt/commit/7babb978ad9d7fc29acb1ff86afb1eb343af303a */
1146 o = ss.taboption('advanced', form.Flag, 'multicast_to_unicast_all', _('Multi To Unicast'), _('ARP, IPv4 and IPv6 (even 802.1Q) with multicast destination MACs are unicast to the STA MAC address. Note: This is not Directed Multicast Service (DMS) in 802.11v. Note: might break receiver STA multicast expectations.'));
1149 o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication'));
1150 o.depends('mode', 'ap');
1151 o.depends('mode', 'ap-wds');
1153 o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name'));
1155 o.datatype = 'netdevname';
1156 o.placeholder = radioNet.getIfname();
1157 if (/^radio\d+\.network/.test(o.placeholder))
1160 var macaddr = uci.get('wireless', radioNet.getName(), 'macaddr');
1161 o = ss.taboption('advanced', form.Value, 'macaddr', _('MAC address'), _('Override default MAC address - the range of usable addresses might be limited by the driver'));
1162 o.value('', _('driver default (%s)').format(!macaddr ? radioNet.getActiveBSSID() : _('no override')));
1163 o.value('random', _('randomly generated'));
1164 o.datatype = "or('random',macaddr)";
1166 o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble'));
1167 o.default = o.enabled;
1169 o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval'));
1172 o.datatype = 'range(1,255)';
1174 o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec'));
1176 o.placeholder = 600;
1177 o.datatype = 'uinteger';
1179 o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling'));
1181 o.datatype = 'uinteger';
1183 o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('802.11v: BSS Max Idle. Units: seconds.'));
1185 o.placeholder = 300;
1186 o.datatype = 'uinteger';
1188 o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval'));
1190 o.placeholder = 65535;
1191 o.datatype = 'uinteger';
1193 o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition'));
1194 o.default = o.enabled;
1198 encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption'));
1199 o.depends('mode', 'ap');
1200 o.depends('mode', 'sta');
1201 o.depends('mode', 'adhoc');
1202 o.depends('mode', 'ahdemo');
1203 o.depends('mode', 'ap-wds');
1204 o.depends('mode', 'sta-wds');
1205 o.depends('mode', 'mesh');
1207 o.cfgvalue = function(section_id) {
1208 var v = String(uci.get('wireless', section_id, 'encryption'));
1211 else if (v.match(/\+/))
1212 return v.replace(/\+.+$/, '');
1216 o.write = function(section_id, value) {
1217 var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id),
1218 co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id);
1220 if (value == 'wpa' || value == 'wpa2' || value == 'wpa3' || value == 'wpa3-mixed' || value == 'wpa3-192')
1221 uci.unset('wireless', section_id, 'key');
1223 if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'ccmp256' || c == 'gcmp' || c == 'gcmp256' || c == 'tkip+ccmp'))
1226 uci.set('wireless', section_id, 'encryption', e);
1229 o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher'));
1230 o.depends('encryption', 'wpa');
1231 o.depends('encryption', 'wpa2');
1232 o.depends('encryption', 'wpa3');
1233 o.depends('encryption', 'wpa3-mixed');
1234 o.depends('encryption', 'wpa3-192');
1235 o.depends('encryption', 'psk');
1236 o.depends('encryption', 'psk2');
1237 o.depends('encryption', 'wpa-mixed');
1238 o.depends('encryption', 'psk-mixed');
1239 o.value('auto', _('auto'));
1240 o.value('ccmp', _('Force CCMP (AES)'));
1241 o.value('ccmp256', _('Force CCMP-256 (AES)'));
1242 o.value('gcmp', _('Force GCMP (AES)'));
1243 o.value('gcmp256', _('Force GCMP-256 (AES)'));
1244 o.value('tkip', _('Force TKIP'));
1245 o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)'));
1246 o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write;
1248 o.cfgvalue = function(section_id) {
1249 var v = String(uci.get('wireless', section_id, 'encryption'));
1250 if (v.match(/\+/)) {
1251 v = v.replace(/^[^+]+\+/, '');
1254 else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip')
1261 var crypto_modes = [];
1263 if (hwtype == 'mac80211') {
1264 var has_supplicant = L.hasSystemFeature('wpasupplicant'),
1265 has_hostapd = L.hasSystemFeature('hostapd');
1267 // Probe EAP support
1268 var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'),
1269 has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap');
1271 // Probe SAE support
1272 var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'),
1273 has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae');
1275 // Probe OWE support
1276 var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
1277 has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
1279 // Probe Suite-B support
1280 var has_ap_eap192 = L.hasSystemFeature('hostapd', 'suiteb192'),
1281 has_sta_eap192 = L.hasSystemFeature('wpasupplicant', 'suiteb192');
1283 // Probe WEP support
1284 var has_ap_wep = L.hasSystemFeature('hostapd', 'wep'),
1285 has_sta_wep = L.hasSystemFeature('wpasupplicant', 'wep');
1287 if (has_hostapd || has_supplicant) {
1288 crypto_modes.push(['psk2', 'WPA2-PSK', 35]);
1289 crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1290 crypto_modes.push(['psk', 'WPA-PSK', 12]);
1293 encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
1296 if (has_ap_sae || has_sta_sae) {
1297 crypto_modes.push(['sae', 'WPA3-SAE', 31]);
1298 crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
1301 if (has_ap_wep || has_sta_wep) {
1302 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1303 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1306 if (has_ap_eap || has_sta_eap) {
1307 if (has_ap_eap192 || has_sta_eap192) {
1308 crypto_modes.push(['wpa3', 'WPA3-EAP', 33]);
1309 crypto_modes.push(['wpa3-mixed', 'WPA2-EAP/WPA3-EAP Mixed Mode', 32]);
1310 crypto_modes.push(['wpa3-192', 'WPA3-EAP 192-bit Mode', 36]);
1313 crypto_modes.push(['wpa2', 'WPA2-EAP', 34]);
1314 crypto_modes.push(['wpa', 'WPA-EAP', 20]);
1317 if (has_ap_owe || has_sta_owe) {
1318 crypto_modes.push(['owe', 'OWE', 1]);
1321 encr.crypto_support = {
1323 'wep-open': has_ap_wep || _('Requires hostapd with WEP support'),
1324 'wep-shared': has_ap_wep || _('Requires hostapd with WEP support'),
1325 'psk': has_hostapd || _('Requires hostapd'),
1326 'psk2': has_hostapd || _('Requires hostapd'),
1327 'psk-mixed': has_hostapd || _('Requires hostapd'),
1328 'sae': has_ap_sae || _('Requires hostapd with SAE support'),
1329 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
1330 'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
1331 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
1332 'wpa3': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
1333 'wpa3-mixed': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
1334 'wpa3-192': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
1335 'owe': has_ap_owe || _('Requires hostapd with OWE support')
1338 'wep-open': has_sta_wep || _('Requires wpa-supplicant with WEP support'),
1339 'wep-shared': has_sta_wep || _('Requires wpa-supplicant with WEP support'),
1340 'psk': has_supplicant || _('Requires wpa-supplicant'),
1341 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1342 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1343 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1344 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
1345 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1346 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
1347 'wpa3': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
1348 'wpa3-mixed': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
1349 'wpa3-192': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
1350 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
1355 'psk': has_supplicant || _('Requires wpa-supplicant'),
1356 'psk2': has_supplicant || _('Requires wpa-supplicant'),
1357 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
1360 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support')
1372 encr.crypto_support['ap-wds'] = encr.crypto_support['ap'];
1373 encr.crypto_support['sta-wds'] = encr.crypto_support['sta'];
1375 encr.validate = function(section_id, value) {
1376 var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0],
1377 modeval = modeopt.formvalue(section_id),
1378 modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)],
1379 enctitle = this.vallist[this.keylist.indexOf(value)];
1381 if (value == 'none')
1384 if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value))
1385 return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle);
1387 return this.crypto_support[modeval][value];
1390 else if (hwtype == 'broadcom') {
1391 crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
1392 crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
1393 crypto_modes.push(['psk', 'WPA-PSK', 12]);
1394 crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
1395 crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
1398 crypto_modes.push(['none', _('No Encryption'), 0]);
1400 crypto_modes.sort(function(a, b) { return b[2] - a[2] });
1402 for (var i = 0; i < crypto_modes.length; i++) {
1403 var security_level = (crypto_modes[i][2] >= 30) ? _('strong security')
1404 : (crypto_modes[i][2] >= 20) ? _('medium security')
1405 : (crypto_modes[i][2] >= 10) ? _('weak security') : _('open network');
1407 encr.value(crypto_modes[i][0], '%s (%s)'.format(crypto_modes[i][1], security_level));
1411 o = ss.taboption('encryption', form.Value, 'auth_server', _('RADIUS Authentication Server'));
1412 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1414 o.datatype = 'host(0)';
1416 o = ss.taboption('encryption', form.Value, 'auth_port', _('RADIUS Authentication Port'));
1417 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1419 o.datatype = 'port';
1420 o.placeholder = '1812';
1422 o = ss.taboption('encryption', form.Value, 'auth_secret', _('RADIUS Authentication Secret'));
1423 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1427 o = ss.taboption('encryption', form.Value, 'acct_server', _('RADIUS Accounting Server'));
1428 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1430 o.datatype = 'host(0)';
1432 o = ss.taboption('encryption', form.Value, 'acct_port', _('RADIUS Accounting Port'));
1433 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1435 o.datatype = 'port';
1436 o.placeholder = '1813';
1438 o = ss.taboption('encryption', form.Value, 'acct_secret', _('RADIUS Accounting Secret'));
1439 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1443 /* extra RADIUS settings start */
1444 var attr_validate = function(section_id
, value
) {
1448 if (!/^[0-9]+(:s:.+|:d:[0-9]+|:x:([0-9a-zA-Z]{2})+)?$/.test(value
) )
1449 return _('Must be in %s format.').format('<attr_id>[:<syntax:value>]');
1454 var req_attr_syntax
= _('Format:') + '<code><attr_id>[:<syntax:value>]</code>' + '<br />' +
1455 '<code>syntax: s = %s; '.format(_('string (UTF-8)')) + 'd = %s; '.format(_('integer')) + 'x = %s</code>'.format(_('octet string'))
1457 /* https://w1.fi/cgit/hostap/commit/?id=af35e7af7f8bb1ca9f0905b4074fb56a264aa12b */
1458 o = ss.taboption('encryption', form.DynamicList, 'radius_auth_req_attr', _('RADIUS Access-Request attributes'),
1459 _('Attributes to add/replace in each request.') + '<br />' + req_attr_syntax );
1460 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1462 o.validate = attr_validate;
1463 o.placeholder = '126:s:Operator';
1465 o = ss.taboption('encryption', form.DynamicList, 'radius_acct_req_attr', _('RADIUS Accounting-Request attributes'),
1466 _('Attributes to add/replace in each request.') + '<br />' + req_attr_syntax );
1467 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1469 o.validate = attr_validate;
1470 o.placeholder = '77:x:74657374696e67';
1472 o = ss.taboption('encryption', form.ListValue, 'dynamic_vlan', _('RADIUS Dynamic VLAN Assignment'), _('Required: Rejects auth if RADIUS server does not provide appropriate VLAN attributes.'));
1473 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1474 o.value('0', _('Disabled'));
1475 o.value('1', _('Optional'));
1476 o.value('2', _('Required'));
1477 o.write = function (section_id, value) {
1478 return this.super('write', [section_id, (value == 0) ? null: value]);
1481 o = ss.taboption('encryption', form.Flag, 'per_sta_vif', _('RADIUS Per STA VLAN'), _('Each STA is assigned its own AP_VLAN interface.'));
1482 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1484 //hostapd internally defaults to vlan_naming=1 even with dynamic VLAN off
1485 o = ss.taboption('encryption', form.Flag, 'vlan_naming', _('RADIUS VLAN Naming'), _('Off: <code>vlanXXX</code>, e.g., <code>vlan1</code>. On: <code>vlan_tagged_interface.XXX</code>, e.g. <code>eth0.1</code>.'));
1486 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1488 o = ss.taboption('encryption', widgets.DeviceSelect, 'vlan_tagged_interface', _('RADIUS VLAN Tagged Interface'), _('E.g. eth0, eth1'));
1489 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1495 o.noinactive = true;
1497 o = ss.taboption('encryption', form.Value, 'vlan_bridge', _('RADIUS VLAN Bridge Naming Scheme'), _('E.g. <code>br-vlan</code> or <code>brvlan</code>.'));
1498 add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1501 /* extra RADIUS settings end */
1503 o
= ss
.taboption('encryption', form
.Value
, 'dae_client', _('DAE-Client'), _('Dynamic Authorization Extension client.'));
1504 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1506 o
.datatype
= 'host(0)';
1508 o
= ss
.taboption('encryption', form
.Value
, 'dae_port', _('DAE-Port'), _('Dynamic Authorization Extension port.'));
1509 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1511 o
.datatype
= 'port';
1512 o
.placeholder
= '3799';
1514 o
= ss
.taboption('encryption', form
.Value
, 'dae_secret', _('DAE-Secret'), _('Dynamic Authorization Extension secret.'));
1515 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1519 //WPA(1) has only WPA IE. Only >= WPA2 has RSN IE Preauth frames.
1520 o
= ss
.taboption('encryption', form
.Flag
, 'rsn_preauth', _('RSN Preauth'), _('Robust Security Network (RSN): Allow roaming preauth for WPA2-EAP networks (and advertise it in WLAN beacons). Only works if the specified network interface is a bridge. Shortens the time-critical reassociation process.'));
1521 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa2', 'wpa3', 'wpa3-mixed'] });
1524 o
= ss
.taboption('encryption', form
.Value
, '_wpa_key', _('Key'));
1525 o
.depends('encryption', 'psk');
1526 o
.depends('encryption', 'psk2');
1527 o
.depends('encryption', 'psk+psk2');
1528 o
.depends('encryption', 'psk-mixed');
1529 o
.depends('encryption', 'sae');
1530 o
.depends('encryption', 'sae-mixed');
1531 o
.datatype
= 'wpakey';
1535 o
.cfgvalue = function(section_id
) {
1536 var key
= uci
.get('wireless', section_id
, 'key');
1537 return /^[1234]$/.test(key
) ? null : key
;
1540 o
.write = function(section_id
, value
) {
1541 uci
.set('wireless', section_id
, 'key', value
);
1542 uci
.unset('wireless', section_id
, 'key1');
1543 uci
.unset('wireless', section_id
, 'key2');
1544 uci
.unset('wireless', section_id
, 'key3');
1545 uci
.unset('wireless', section_id
, 'key4');
1549 o
= ss
.taboption('encryption', form
.ListValue
, '_wep_key', _('Used Key Slot'));
1550 o
.depends('encryption', 'wep-open');
1551 o
.depends('encryption', 'wep-shared');
1552 o
.value('1', _('Key #%d').format(1));
1553 o
.value('2', _('Key #%d').format(2));
1554 o
.value('3', _('Key #%d').format(3));
1555 o
.value('4', _('Key #%d').format(4));
1557 o
.cfgvalue = function(section_id
) {
1558 var slot
= +uci
.get('wireless', section_id
, 'key');
1559 return (slot
>= 1 && slot
<= 4) ? String(slot
) : '';
1562 o
.write = function(section_id
, value
) {
1563 uci
.set('wireless', section_id
, 'key', value
);
1566 for (var slot
= 1; slot
<= 4; slot
++) {
1567 o
= ss
.taboption('encryption', form
.Value
, 'key%d'.format(slot
), _('Key #%d').format(slot
));
1568 o
.depends('encryption', 'wep-open');
1569 o
.depends('encryption', 'wep-shared');
1570 o
.datatype
= 'wepkey';
1574 o
.write = function(section_id
, value
) {
1575 if (value
!= null && (value
.length
== 5 || value
.length
== 13))
1576 value
= 's:%s'.format(value
);
1577 uci
.set('wireless', section_id
, this.option
, value
);
1582 if (hwtype
== 'mac80211') {
1583 // Probe 802.11r support (and EAP support as a proxy for Openwrt)
1584 var has_80211r
= L
.hasSystemFeature('hostapd', '11r') || L
.hasSystemFeature('hostapd', 'eap');
1586 o
= ss
.taboption('roaming', form
.Flag
, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
1587 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa2', 'wpa3', 'wpa3-mixed', , 'wpa3-192'] });
1589 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1592 o
= ss
.taboption('roaming', 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.'));
1593 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1594 o
.depends({ ieee80211r
: '1' });
1597 o
= ss
.taboption('roaming', form
.Value
, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID'));
1598 o
.depends({ ieee80211r
: '1' });
1599 o
.placeholder
= '4f57';
1600 o
.datatype
= 'and(hexstring,length(4))';
1603 o
= ss
.taboption('roaming', form
.Value
, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]'));
1604 o
.depends({ ieee80211r
: '1' });
1605 o
.placeholder
= '1000';
1606 o
.datatype
= 'range(1000,65535)';
1609 o
= ss
.taboption('roaming', form
.ListValue
, 'ft_over_ds', _('FT protocol'));
1610 o
.depends({ ieee80211r
: '1' });
1611 o
.value('0', _('FT over the Air'));
1612 o
.value('1', _('FT over DS'));
1615 o
= ss
.taboption('roaming', 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.'));
1616 add_dependency_permutations(o
, { ieee80211r
: ['1'], mode
: ['ap', 'ap-wds'], encryption
: ['psk2', 'psk-mixed'] });
1617 o
.default = o
.enabled
;
1620 o
= ss
.taboption('roaming', form
.Value
, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes'));
1621 o
.depends({ ieee80211r
: '1' });
1622 o
.placeholder
= '10000';
1623 o
.datatype
= 'uinteger';
1626 o
= ss
.taboption('roaming', form
.Value
, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons'));
1627 o
.depends({ ieee80211r
: '1' });
1628 o
.placeholder
= '00004f577274';
1629 o
.datatype
= 'and(hexstring,length(12))';
1632 o
= ss
.taboption('roaming', form
.Flag
, 'pmk_r1_push', _('PMK R1 Push'));
1633 o
.depends({ ieee80211r
: '1' });
1634 o
.placeholder
= '0';
1637 o
= ss
.taboption('roaming', form
.DynamicList
, 'r0kh', _('External R0 Key Holder List'), _('List of R0KHs in the same Mobility Domain. <br />Format: MAC-address,NAS-Identifier,256-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.'));
1638 o
.depends({ ieee80211r
: '1' });
1641 o
= ss
.taboption('roaming', 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,256-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.'));
1642 o
.depends({ ieee80211r
: '1' });
1644 // End of 802.11r options
1646 // Probe 802.11k and 802.11v support via EAP support (full hostapd has EAP)
1647 if (L
.hasSystemFeature('hostapd', 'eap')) {
1648 /* 802.11k settings start */
1649 o
= ss
.taboption('roaming', form
.Flag
, 'ieee80211k', _('802.11k RRM'), _('Radio Resource Measurement - Sends beacons to assist roaming. Not all clients support this.'));
1650 // add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
1651 o
.depends('mode', 'ap');
1652 o
.depends('mode', 'ap-wds');
1654 o
= ss
.taboption('roaming', form
.Flag
, 'rrm_neighbor_report', _('Neighbour Report'), _('802.11k: Enable neighbor report via radio measurements.'));
1655 o
.depends({ ieee80211k
: '1' });
1656 o
.default = o
.enabled
;
1658 o
= ss
.taboption('roaming', form
.Flag
, 'rrm_beacon_report', _('Beacon Report'), _('802.11k: Enable beacon report via radio measurements.'));
1659 o
.depends({ ieee80211k
: '1' });
1660 o
.default = o
.enabled
;
1661 /* 802.11k settings end */
1663 /* 802.11v settings start */
1664 o
= ss
.taboption('roaming', form
.ListValue
, 'time_advertisement', _('Time advertisement'), _('802.11v: Time Advertisement in management frames.'));
1665 o
.value('0', _('Disabled'));
1666 o
.value('2', _('Enabled'));
1667 o
.write = function (section_id
, value
) {
1668 return this.super('write', [section_id
, (value
== 2) ? value
: null]);
1671 //Pull current System TZ setting
1672 var tz
= uci
.get('system', '@system[0]', 'timezone');
1673 o
= ss
.taboption('roaming', form
.Value
, 'time_zone', _('Time zone'), _('802.11v: Local Time Zone Advertisement in management frames.'));
1677 o
= ss
.taboption('roaming', form
.Flag
, 'wnm_sleep_mode', _('WNM Sleep Mode'), _('802.11v: Wireless Network Management (WNM) Sleep Mode (extended sleep mode for stations).'));
1680 /* wnm_sleep_mode_no_keys: https://git.openwrt.org/?p=openwrt/openwrt.git;a=commitdiff;h=bf98faaac8ed24cf7d3d93dd4fcd7304d109363b */
1681 o = ss.taboption('roaming', form.Flag, 'wnm_sleep_mode_no_keys', _('WNM Sleep Mode Fixes'), _('802.11v: Wireless Network Management (WNM) Sleep Mode Fixes: Prevents reinstallation attacks.'));
1684 o = ss.taboption('roaming', form.Flag, 'bss_transition', _('BSS Transition'), _('802.11v: Basic Service Set (BSS) transition management.'));
1687 /* in master, but not 21.02.1: proxy_arp */
1688 o
= ss
.taboption('roaming', form
.Flag
, 'proxy_arp', _('ProxyARP'), _('802.11v: Proxy ARP enables non-AP STA to remain in power-save for longer.'));
1691 /* TODO: na_mcast_to_ucast is missing: needs adding to hostapd.sh - nice to have */
1693 /* 802.11v settings end */
1696 if (hwtype
== 'mac80211') {
1697 o
= ss
.taboption('encryption', form
.ListValue
, 'eap_type', _('EAP-Method'));
1698 o
.value('tls', 'TLS');
1699 o
.value('ttls', 'TTLS');
1700 o
.value('peap', 'PEAP');
1701 o
.value('fast', 'FAST');
1702 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1704 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"));
1707 o
.default = o
.disabled
;
1708 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1709 o
.validate = function(section_id
, value
) {
1710 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1711 return _("This option cannot be used because the ca-bundle package is not installed.");
1716 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert', _('Path to CA-Certificate'));
1717 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], ca_cert_usesystem
: ['0'] });
1719 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"));
1720 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1722 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"));
1723 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1725 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)"));
1726 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1728 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)"));
1729 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'] });
1731 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert', _('Path to Client-Certificate'));
1732 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], eap_type
: ['tls'] });
1734 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key', _('Path to Private Key'));
1735 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], eap_type
: ['tls'] });
1737 o
= ss
.taboption('encryption', form
.Value
, 'priv_key_pwd', _('Password of Private Key'));
1738 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], eap_type
: ['tls'] });
1741 o
= ss
.taboption('encryption', form
.ListValue
, 'auth', _('Authentication'));
1742 o
.value('PAP', 'PAP');
1743 o
.value('CHAP', 'CHAP');
1744 o
.value('MSCHAP', 'MSCHAP');
1745 o
.value('MSCHAPV2', 'MSCHAPv2');
1746 o
.value('EAP-GTC', 'EAP-GTC');
1747 o
.value('EAP-MD5', 'EAP-MD5');
1748 o
.value('EAP-MSCHAPV2', 'EAP-MSCHAPv2');
1749 o
.value('EAP-TLS', 'EAP-TLS');
1750 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], eap_type
: ['fast', 'peap', 'ttls'] });
1752 o
.validate = function(section_id
, value
) {
1753 var eo
= this.section
.children
.filter(function(o
) { return o
.option
== 'eap_type' })[0],
1754 ev
= eo
.formvalue(section_id
);
1756 if (ev
!= 'ttls' && (value
== 'PAP' || value
== 'CHAP' || value
== 'MSCHAP' || value
== 'MSCHAPV2'))
1757 return _('This authentication type is not applicable to the selected EAP method.');
1762 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"));
1765 o
.default = o
.disabled
;
1766 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth
: ['EAP-TLS'] });
1767 o
.validate = function(section_id
, value
) {
1768 if (value
== '1' && !L
.hasSystemFeature('cabundle')) {
1769 return _("This option cannot be used because the ca-bundle package is not installed.");
1774 o
= ss
.taboption('encryption', form
.FileUpload
, 'ca_cert2', _('Path to inner CA-Certificate'));
1775 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth
: ['EAP-TLS'], ca_cert2_usesystem
: ['0'] });
1777 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"));
1778 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth
: ['EAP-TLS'] });
1780 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"));
1781 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth
: ['EAP-TLS'] });
1783 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)"));
1784 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth
: ['EAP-TLS'] });
1786 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)"));
1787 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth
: ['EAP-TLS'] });
1789 o
= ss
.taboption('encryption', form
.FileUpload
, 'client_cert2', _('Path to inner Client-Certificate'));
1790 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth
: ['EAP-TLS'] });
1792 o
= ss
.taboption('encryption', form
.FileUpload
, 'priv_key2', _('Path to inner Private Key'));
1793 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth
: ['EAP-TLS'] });
1795 o
= ss
.taboption('encryption', form
.Value
, 'priv_key2_pwd', _('Password of inner Private Key'));
1796 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], auth
: ['EAP-TLS'] });
1799 o
= ss
.taboption('encryption', form
.Value
, 'identity', _('Identity'));
1800 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1802 o
= ss
.taboption('encryption', form
.Value
, 'anonymous_identity', _('Anonymous Identity'));
1803 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], eap_type
: ['fast', 'peap', 'tls', 'ttls'] });
1805 o
= ss
.taboption('encryption', form
.Value
, 'password', _('Password'));
1806 add_dependency_permutations(o
, { mode
: ['sta', 'sta-wds'], encryption
: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed', 'wpa3-192'], eap_type
: ['fast', 'peap', 'ttls'] });
1810 if (hwtype
== 'mac80211') {
1811 // ieee802.11w options
1812 o
= ss
.taboption('encryption', form
.ListValue
, 'ieee80211w', _('802.11w Management Frame Protection'), _("Note: Some wireless drivers do not fully support 802.11w. E.g. mwlwifi may have problems"));
1813 o
.value('0', _('Disabled'));
1814 o
.value('1', _('Optional'));
1815 o
.value('2', _('Required'));
1816 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds', 'sta', 'sta-wds'], encryption
: ['owe', 'psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1819 '2': [{ encryption
: 'sae' }, { encryption
: 'owe' }, { encryption
: 'wpa3' }, { encryption
: 'wpa3-mixed' }],
1820 '1': [{ encryption
: 'sae-mixed'}],
1824 o
.write = function(section_id
, value
) {
1825 if (value
!= this.default)
1826 return form
.ListValue
.prototype.write
.call(this, section_id
, value
);
1828 return form
.ListValue
.prototype.remove
.call(this, section_id
);
1831 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout'));
1832 o
.depends('ieee80211w', '1');
1833 o
.depends('ieee80211w', '2');
1834 o
.datatype
= 'uinteger';
1835 o
.placeholder
= '1000';
1838 o
= ss
.taboption('encryption', form
.Value
, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout'));
1839 o
.depends('ieee80211w', '1');
1840 o
.depends('ieee80211w', '2');
1841 o
.datatype
= 'uinteger';
1842 o
.placeholder
= '201';
1845 if (L
.hasSystemFeature('hostapd', 'ocv') || L
.hasSystemFeature('wpasupplicant', 'ocv')) {
1846 o
= ss
.taboption('encryption', form
.ListValue
, 'ocv', _('Operating Channel Validation'), _("Note: Workaround mode allows a STA that claims OCV capability to connect even if the STA doesn't send OCI or negotiate PMF."));
1847 o
.value('0', _('Disabled'));
1848 o
.value('1', _('Enabled'));
1849 o
.value('2', _('Enabled (workaround mode)'));
1851 o
.depends('ieee80211w', '1');
1852 o
.depends('ieee80211w', '2');
1854 o
.validate = function(section_id
, value
) {
1855 var modeopt
= this.section
.children
.filter(function(o
) { return o
.option
== 'mode' })[0],
1856 modeval
= modeopt
.formvalue(section_id
);
1858 if ((value
== '2') && ((modeval
== 'sta') || (modeval
== 'sta-wds'))) {
1859 return _('Workaround mode can only be used when acting as an access point.');
1866 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.'));
1867 add_dependency_permutations(o
, { mode
: ['ap', 'ap-wds'], encryption
: ['psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
1869 if (L
.hasSystemFeature('hostapd', 'wps') && L
.hasSystemFeature('wpasupplicant')) {
1870 o
= ss
.taboption('encryption', form
.Flag
, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
1873 o
.default = o
.disabled
;
1874 o
.depends('encryption', 'psk');
1875 o
.depends('encryption', 'psk2');
1876 o
.depends('encryption', 'psk-mixed');
1877 o
.depends('encryption', 'sae');
1878 o
.depends('encryption', 'sae-mixed');
1885 s
.handleRemove = function(section_id
, ev
) {
1886 document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id
)).style
.opacity
= 0.5;
1887 return form
.TypedSection
.prototype.handleRemove
.apply(this, [section_id
, ev
]);
1890 s
.handleScan = function(radioDev
, ev
) {
1891 var table
= E('table', { 'class': 'table' }, [
1892 E('tr', { 'class': 'tr table-titles' }, [
1893 E('th', { 'class': 'th col-2 middle center' }, _('Signal')),
1894 E('th', { 'class': 'th col-4 middle left' }, _('SSID')),
1895 E('th', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')),
1896 E('th', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')),
1897 E('th', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')),
1898 E('th', { 'class': 'th col-3 middle left' }, _('Encryption')),
1899 E('th', { 'class': 'th cbi-section-actions right' }, ' '),
1903 var stop
= E('button', {
1905 'click': L
.bind(this.handleScanStartStop
, this),
1906 'style': 'display:none',
1907 'data-state': 'stop'
1908 }, _('Stop refresh'));
1910 cbi_update_table(table
, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
1912 var md
= ui
.showModal(_('Join Network: Wireless Scan'), [
1914 E('div', { 'class': 'right' }, [
1919 'click': L
.bind(this.handleScanAbort
, this)
1924 md
.style
.maxWidth
= '90%';
1925 md
.style
.maxHeight
= 'none';
1927 this.pollFn
= L
.bind(this.handleScanRefresh
, this, radioDev
, {}, table
, stop
);
1929 poll
.add(this.pollFn
);
1933 s
.handleScanRefresh = function(radioDev
, scanCache
, table
, stop
) {
1934 return radioDev
.getScanList().then(L
.bind(function(results
) {
1937 for (var i
= 0; i
< results
.length
; i
++)
1938 scanCache
[results
[i
].bssid
] = results
[i
];
1940 for (var k
in scanCache
)
1941 if (scanCache
[k
].stale
)
1942 results
.push(scanCache
[k
]);
1944 results
.sort(function(a
, b
) {
1945 var diff
= (b
.quality
- a
.quality
) || (a
.channel
- b
.channel
);
1950 if (a
.ssid
< b
.ssid
)
1952 else if (a
.ssid
> b
.ssid
)
1955 if (a
.bssid
< b
.bssid
)
1957 else if (a
.bssid
> b
.bssid
)
1961 for (var i
= 0; i
< results
.length
; i
++) {
1962 var res
= results
[i
],
1963 qv
= res
.quality
|| 0,
1964 qm
= res
.quality_max
|| 0,
1965 q
= (qv
> 0 && qm
> 0) ? Math
.floor((100 / qm
) * qv
) : 0,
1966 s
= res
.stale
? 'opacity:0.5' : '';
1969 E('span', { 'style': s
}, render_signal_badge(q
, res
.signal
, res
.noise
)),
1970 E('span', { 'style': s
}, (res
.ssid
!= null) ? '%h'.format(res
.ssid
) : E('em', _('hidden'))),
1971 E('span', { 'style': s
}, '%d'.format(res
.channel
)),
1972 E('span', { 'style': s
}, '%h'.format(res
.mode
)),
1973 E('span', { 'style': s
}, '%h'.format(res
.bssid
)),
1974 E('span', { 'style': s
}, '%h'.format(network
.formatWifiEncryption(res
.encryption
))),
1975 E('div', { 'class': 'right' }, E('button', {
1976 'class': 'cbi-button cbi-button-action important',
1977 'click': ui
.createHandlerFn(this, 'handleJoin', radioDev
, res
)
1978 }, _('Join Network')))
1984 cbi_update_table(table
, rows
);
1986 stop
.disabled
= false;
1987 stop
.style
.display
= '';
1988 stop
.classList
.remove('spinning');
1992 s
.handleScanStartStop = function(ev
) {
1993 var btn
= ev
.currentTarget
;
1995 if (btn
.getAttribute('data-state') == 'stop') {
1996 poll
.remove(this.pollFn
);
1997 btn
.firstChild
.data
= _('Start refresh');
1998 btn
.setAttribute('data-state', 'start');
2001 poll
.add(this.pollFn
);
2002 btn
.firstChild
.data
= _('Stop refresh');
2003 btn
.setAttribute('data-state', 'stop');
2004 btn
.classList
.add('spinning');
2005 btn
.disabled
= true;
2009 s
.handleScanAbort = function(ev
) {
2010 var md
= dom
.parent(ev
.target
, 'div[aria-modal="true"]');
2012 md
.style
.maxWidth
= '';
2013 md
.style
.maxHeight
= '';
2017 poll
.remove(this.pollFn
);
2022 s
.handleJoinConfirm = function(radioDev
, bss
, form
, ev
) {
2023 var nameopt
= L
.toArray(form
.lookupOption('name', '_new_'))[0],
2024 passopt
= L
.toArray(form
.lookupOption('password', '_new_'))[0],
2025 ssidopt
= L
.toArray(form
.lookupOption('ssid', '_new_'))[0],
2026 bssidopt
= L
.toArray(form
.lookupOption('bssid', '_new_'))[0],
2027 zoneopt
= L
.toArray(form
.lookupOption('zone', '_new_'))[0],
2028 replopt
= L
.toArray(form
.lookupOption('replace', '_new_'))[0],
2029 nameval
= (nameopt
&& nameopt
.isValid('_new_')) ? nameopt
.formvalue('_new_') : null,
2030 passval
= (passopt
&& passopt
.isValid('_new_')) ? passopt
.formvalue('_new_') : null,
2031 ssidval
= (ssidopt
&& ssidopt
.isValid('_new_')) ? ssidopt
.formvalue('_new_') : null,
2032 bssidval
= (bssidopt
&& bssidopt
.isValid('_new_')) ? bssidopt
.formvalue('_new_') : null,
2033 zoneval
= zoneopt
? zoneopt
.formvalue('_new_') : null,
2034 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
2035 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
2036 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' }).length
> 0),
2037 is_sae
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'sae' }).length
> 0);
2039 if (nameval
== null || (passopt
&& passval
== null))
2042 var section_id
= null;
2044 return this.map
.save(function() {
2045 var wifi_sections
= uci
.sections('wireless', 'wifi-iface');
2047 if (replopt
.formvalue('_new_') == '1') {
2048 for (var i
= 0; i
< wifi_sections
.length
; i
++)
2049 if (wifi_sections
[i
].device
== radioDev
.getName())
2050 uci
.remove('wireless', wifi_sections
[i
]['.name']);
2053 if (uci
.get('wireless', radioDev
.getName(), 'disabled') == '1') {
2054 for (var i
= 0; i
< wifi_sections
.length
; i
++)
2055 if (wifi_sections
[i
].device
== radioDev
.getName())
2056 uci
.set('wireless', wifi_sections
[i
]['.name'], 'disabled', '1');
2058 uci
.unset('wireless', radioDev
.getName(), 'disabled');
2061 var htmodes
= radioDev
.getHTModes();
2063 if (bss
.vht_operation
&& htmodes
&& htmodes
.indexOf('VHT20') !== -1) {
2064 for (var w
= bss
.vht_operation
.channel_width
; w
>= 20; w
/= 2) {
2065 if (htmodes
.indexOf('VHT'+w
) !== -1) {
2066 uci
.set('wireless', radioDev
.getName(), 'htmode', 'VHT'+w
);
2071 else if (bss
.ht_operation
&& htmodes
&& htmodes
.indexOf('HT20') !== -1) {
2072 var w
= (bss
.ht_operation
.secondary_channel_offset
== 'no secondary') ? 20 : 40;
2073 uci
.set('wireless', radioDev
.getName(), 'htmode', 'HT'+w
);
2076 uci
.remove('wireless', radioDev
.getName(), 'htmode');
2079 uci
.set('wireless', radioDev
.getName(), 'channel', bss
.channel
);
2081 section_id
= next_free_sid(wifi_sections
.length
);
2083 uci
.add('wireless', 'wifi-iface', section_id
);
2084 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
2085 uci
.set('wireless', section_id
, 'mode', (bss
.mode
== 'Ad-Hoc') ? 'adhoc' : 'sta');
2086 uci
.set('wireless', section_id
, 'network', nameval
);
2088 if (bss
.ssid
!= null) {
2089 uci
.set('wireless', section_id
, 'ssid', bss
.ssid
);
2091 if (bssidval
== '1')
2092 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
2094 else if (bss
.bssid
!= null) {
2095 uci
.set('wireless', section_id
, 'bssid', bss
.bssid
);
2098 if (ssidval
!= null)
2099 uci
.set('wireless', section_id
, 'ssid', ssidval
);
2102 uci
.set('wireless', section_id
, 'encryption', 'sae');
2103 uci
.set('wireless', section_id
, 'key', passval
);
2106 for (var i
= enc
.wpa
.length
- 1; i
>= 0; i
--) {
2107 if (enc
.wpa
[i
] == 2) {
2108 uci
.set('wireless', section_id
, 'encryption', 'psk2');
2111 else if (enc
.wpa
[i
] == 1) {
2112 uci
.set('wireless', section_id
, 'encryption', 'psk');
2117 uci
.set('wireless', section_id
, 'key', passval
);
2120 uci
.set('wireless', section_id
, 'encryption', 'wep-open');
2121 uci
.set('wireless', section_id
, 'key', '1');
2122 uci
.set('wireless', section_id
, 'key1', passval
);
2125 uci
.set('wireless', section_id
, 'encryption', 'none');
2128 return network
.addNetwork(nameval
, { proto
: 'dhcp' }).then(function(net
) {
2129 firewall
.deleteNetwork(net
.getName());
2131 var zonePromise
= zoneval
2132 ? firewall
.getZone(zoneval
).then(function(zone
) { return zone
|| firewall
.addZone(zoneval
) })
2133 : Promise
.resolve();
2135 return zonePromise
.then(function(zone
) {
2137 zone
.addNetwork(net
.getName());
2140 }).then(L
.bind(function() {
2141 ui
.showModal(null, E('p', { 'class': 'spinning' }, [ _('Loading data…') ]));
2143 return this.renderMoreOptionsModal(section_id
);
2147 s
.handleJoin = function(radioDev
, bss
, ev
) {
2148 poll
.remove(this.pollFn
);
2150 var m2
= new form
.Map('wireless'),
2151 s2
= m2
.section(form
.NamedSection
, '_new_'),
2152 enc
= L
.isObject(bss
.encryption
) ? bss
.encryption
: null,
2153 is_wep
= (enc
&& Array
.isArray(enc
.wep
)),
2154 is_psk
= (enc
&& Array
.isArray(enc
.wpa
) && L
.toArray(enc
.authentication
).filter(function(a
) { return a
== 'psk' || a
== 'sae' })),
2155 replace
, passphrase
, name
, bssid
, zone
;
2157 var nameUsed = function(name
) {
2158 var s
= uci
.get('network', name
);
2159 if (s
!= null && s
['.type'] != 'interface')
2162 var net
= (s
!= null) ? network
.instantiateNetwork(name
) : null;
2163 return (net
!= null && !net
.isEmpty());
2166 s2
.render = function() {
2167 return Promise
.all([
2169 this.renderUCISection('_new_')
2170 ]).then(this.renderContents
.bind(this));
2173 if (bss
.ssid
== null) {
2174 name
= s2
.option(form
.Value
, 'ssid', _('Network SSID'), _('The correct SSID must be manually specified when joining a hidden wireless network'));
2175 name
.rmempty
= false;
2178 replace
= s2
.option(form
.Flag
, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.'));
2180 name
= s2
.option(form
.Value
, 'name', _('Name of the new network'),
2181 _('Name for OpenWrt network configuration. (No relation to wireless network name/SSID)') + '<br />' +
2182 _('The allowed characters are: <code>A-Z</code>, <code>a-z</code>, <code>0-9</code> and <code>_</code>'));
2183 name
.datatype
= 'uciname';
2184 name
.default = 'wwan';
2185 name
.rmempty
= false;
2186 name
.validate = function(section_id
, value
) {
2187 if (nameUsed(value
))
2188 return _('The network name is already used');
2193 for (var i
= 2; nameUsed(name
.default); i
++)
2194 name
.default = 'wwan%d'.format(i
);
2196 if (is_wep
|| is_psk
) {
2197 passphrase
= s2
.option(form
.Value
, 'password', is_wep
? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.'));
2198 passphrase
.datatype
= is_wep
? 'wepkey' : 'wpakey';
2199 passphrase
.password
= true;
2200 passphrase
.rmempty
= false;
2203 if (bss
.ssid
!= null) {
2204 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
));
2205 bssid
.default = '0';
2208 zone
= s2
.option(widgets
.ZoneSelect
, 'zone', _('Create / Assign firewall-zone'), _('Choose the firewall zone you want to assign to this interface. Select <em>unspecified</em> to remove the interface from the associated zone or fill out the <em>custom</em> field to define a new zone and attach the interface to it.'));
2209 zone
.default = 'wan';
2211 return m2
.render().then(L
.bind(function(nodes
) {
2212 ui
.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss
.ssid
)), [
2214 E('div', { 'class': 'right' }, [
2217 'click': ui
.hideModal
2218 }, _('Cancel')), ' ',
2220 'class': 'cbi-button cbi-button-positive important',
2221 'click': ui
.createHandlerFn(this, 'handleJoinConfirm', radioDev
, bss
, m2
)
2224 ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase
|| name
).cbid('_new_'))).focus();
2228 s
.handleAdd = function(radioDev
, ev
) {
2229 var section_id
= next_free_sid(uci
.sections('wireless', 'wifi-iface').length
);
2231 uci
.unset('wireless', radioDev
.getName(), 'disabled');
2233 uci
.add('wireless', 'wifi-iface', section_id
);
2234 uci
.set('wireless', section_id
, 'device', radioDev
.getName());
2235 uci
.set('wireless', section_id
, 'mode', 'ap');
2236 uci
.set('wireless', section_id
, 'ssid', 'OpenWrt');
2237 uci
.set('wireless', section_id
, 'encryption', 'none');
2239 this.addedSection
= section_id
;
2240 return this.renderMoreOptionsModal(section_id
);
2243 o
= s
.option(form
.DummyValue
, '_badge');
2244 o
.modalonly
= false;
2245 o
.textvalue = function(section_id
) {
2246 var inst
= this.section
.lookupRadioOrNetwork(section_id
),
2247 node
= E('div', { 'class': 'center' });
2249 if (inst
.getWifiNetworks
)
2250 node
.appendChild(render_radio_badge(inst
));
2252 node
.appendChild(render_network_badge(inst
));
2257 o
= s
.option(form
.DummyValue
, '_stat');
2258 o
.modalonly
= false;
2259 o
.textvalue = function(section_id
) {
2260 var inst
= this.section
.lookupRadioOrNetwork(section_id
);
2262 if (inst
.getWifiNetworks
)
2263 return render_radio_status(inst
, this.section
.wifis
.filter(function(e
) {
2264 return (e
.getWifiDeviceName() == inst
.getName());
2267 return render_network_status(inst
);
2270 return m
.render().then(L
.bind(function(m
, nodes
) {
2271 poll
.add(L
.bind(function() {
2272 var section_ids
= m
.children
[0].cfgsections(),
2273 tasks
= [ network
.getHostHints(), network
.getWifiDevices() ];
2275 for (var i
= 0; i
< section_ids
.length
; i
++) {
2276 var row
= nodes
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids
[i
])),
2277 dsc
= row
.querySelector('[data-name="_stat"] > div'),
2278 btns
= row
.querySelectorAll('.cbi-section-actions button');
2280 if (dsc
.getAttribute('restart') == '') {
2281 dsc
.setAttribute('restart', '1');
2282 tasks
.push(fs
.exec('/sbin/wifi', ['up', section_ids
[i
]]).catch(function(e
) {
2283 ui
.addNotification(null, E('p', e
.message
));
2286 else if (dsc
.getAttribute('restart') == '1') {
2287 dsc
.removeAttribute('restart');
2288 btns
[0].classList
.remove('spinning');
2289 btns
[0].disabled
= false;
2293 return Promise
.all(tasks
)
2294 .then(L
.bind(function(hosts_radios
) {
2297 for (var i
= 0; i
< hosts_radios
[1].length
; i
++)
2298 tasks
.push(hosts_radios
[1][i
].getWifiNetworks());
2300 return Promise
.all(tasks
).then(function(data
) {
2301 hosts_radios
[2] = [];
2303 for (var i
= 0; i
< data
.length
; i
++)
2304 hosts_radios
[2].push
.apply(hosts_radios
[2], data
[i
]);
2306 return hosts_radios
;
2309 .then(L
.bind(function(hosts_radios_wifis
) {
2312 for (var i
= 0; i
< hosts_radios_wifis
[2].length
; i
++)
2313 tasks
.push(hosts_radios_wifis
[2][i
].getAssocList());
2315 return Promise
.all(tasks
).then(function(data
) {
2316 hosts_radios_wifis
[3] = [];
2318 for (var i
= 0; i
< data
.length
; i
++) {
2319 var wifiNetwork
= hosts_radios_wifis
[2][i
],
2320 radioDev
= hosts_radios_wifis
[1].filter(function(d
) { return d
.getName() == wifiNetwork
.getWifiDeviceName() })[0];
2322 for (var j
= 0; j
< data
[i
].length
; j
++)
2323 hosts_radios_wifis
[3].push(Object
.assign({ radio
: radioDev
, network
: wifiNetwork
}, data
[i
][j
]));
2326 return hosts_radios_wifis
;
2329 .then(L
.bind(this.poll_status
, this, nodes
));
2332 var table
= E('table', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [
2333 E('tr', { 'class': 'tr table-titles' }, [
2334 E('th', { 'class': 'th nowrap' }, _('Network')),
2335 E('th', { 'class': 'th hide-xs' }, _('MAC address')),
2336 E('th', { 'class': 'th' }, _('Host')),
2337 E('th', { 'class': 'th' }, _('Signal / Noise')),
2338 E('th', { 'class': 'th' }, _('RX Rate / TX Rate'))
2342 cbi_update_table(table
, [], E('em', { 'class': 'spinning' }, _('Collecting data...')))
2344 return E([ nodes
, E('h3', _('Associated Stations')), table
]);