'use strict';
+'require fs';
+'require ui';
'require rpc';
'require uci';
'require form';
'require tools.widgets as widgets';
function count_changes(section_id) {
- var changes = L.ui.changes.changes, n = 0;
+ var changes = ui.changes.changes, n = 0;
if (!L.isObject(changes))
return n;
channel = radioNet.getChannel(),
disabled = (radioNet.get('disabled') == '1' || uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == '1'),
is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled),
+ is_mesh = (radioNet.getMode() == 'mesh'),
changecount = count_changes(radioNet.getName()),
status_text = null;
if (changecount)
status_text = E('a', {
href: '#',
- click: L.bind(L.ui.changes.displayChanges, L.ui.changes)
+ click: L.bind(ui.changes.displayChanges, ui.changes)
}, _('Interface has %d pending changes').format(changecount));
else if (!is_assoc)
status_text = E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated'));
return L.itemlist(E('div'), [
- _('SSID'), radioNet.getSSID() || '?',
+ is_mesh ? _('Mesh ID') : _('SSID'), (is_mesh ? radioNet.getMeshID() : radioNet.getSSID()) || '?',
_('Mode'), mode,
_('BSSID'), (!changecount && is_assoc) ? bssid : null,
_('Encryption'), (!changecount && is_assoc) ? radioNet.getActiveEncryption() || _('None') : null,
}
return map.save().then(function() {
- L.ui.changes.apply()
+ ui.changes.apply()
});
}
'11a': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : []
};
- for (var i = 0; Array.isArray(data[1]) && i < data[1].length; i++)
+ for (var i = 0; i < data[1].length; i++)
this.channels[(data[1][i].mhz > 2484) ? '11a' : '11g'].push(
data[1][i].channel,
'%d (%d Mhz)'.format(data[1][i].channel, data[1][i].mhz),
if (vals[i+2])
sel.add(E('option', { value: vals[i+0] }, [ vals[i+1] ]));
- if (!isNaN(vals.selected))
+ if (vals && !isNaN(vals.selected))
sel.selectedIndex = vals.selected;
sel.parentNode.style.display = (sel.options.length <= 1) ? 'none' : '';
},
renderWidget: function(section_id, option_index, cfgvalue) {
- var typeClass = this.keylist.length ? form.ListValue : form.Value;
+ var typeClass = (this.keylist && this.keylist.length) ? form.ListValue : form.Value;
return typeClass.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
}
});
btns[2].disabled = busy;
}
- var table = document.querySelector('wifi_assoclist_table'),
+ var table = document.querySelector('#wifi_assoclist_table'),
hosts = data[0],
trows = [];
ipv4 = hosts.getIPAddrByMACAddr(bss.mac),
ipv6 = hosts.getIP6AddrByMACAddr(bss.mac);
- trows.push([
+ var hint;
+
+ if (name && ipv4 && ipv6)
+ hint = '%s (%s, %s)'.format(name, ipv4, ipv6);
+ else if (name && (ipv4 || ipv6))
+ hint = '%s (%s)'.format(name, ipv4 || ipv6);
+ else
+ hint = name || ipv4 || ipv6 || '?';
+
+ var row = [
E('span', { 'class': 'ifacebadge' }, [
E('img', {
'src': L.resource('icons/wifi%s.png').format(bss.network.isUp() ? '' : '_disabled'),
E('small', '(%s)'.format(bss.network.getIfname()))
]),
bss.mac,
- name ? '%s (%s)'.format(name, ipv4 || ipv6 || '?') : ipv4 || ipv6 || '?',
+ hint,
render_signal_badge(Math.min((bss.signal + 110) / 70 * 100, 100), bss.signal, bss.noise),
E('span', {}, [
E('span', format_wifirate(bss.rx)),
E('br'),
E('span', format_wifirate(bss.tx))
])
- ]);
+ ];
+
+ if (bss.network.isClientDisconnectSupported()) {
+ if (table.firstElementChild.childNodes.length < 6)
+ table.firstElementChild.appendChild(E('div', { 'class': 'th nowrap right'}, [ _('Disconnect') ]));
+
+ row.push(E('button', {
+ 'class': 'cbi-button cbi-button-remove',
+ 'click': L.bind(function(net, mac, ev) {
+ L.dom.parent(ev.currentTarget, '.tr').style.opacity = 0.5;
+ ev.currentTarget.classList.add('spinning');
+ ev.currentTarget.disabled = true;
+ ev.currentTarget.blur();
+
+ net.disconnectClient(mac, true, 5, 60000);
+ }, this, bss.network, bss.mac)
+ }, [ _('Disconnect') ]));
+ }
+ else {
+ row.push('-');
+ }
+
+ trows.push(row);
}
- cbi_update_table('#wifi_assoclist_table', trows, E('em', _('No information available')));
+ cbi_update_table(table, trows, E('em', _('No information available')));
var stat = document.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large');
}
return Promise.all(tasks)
- .then(L.bind(L.ui.changes.init, L.ui.changes))
- .then(L.bind(L.ui.changes.apply, L.ui.changes));
+ .then(L.bind(ui.changes.init, ui.changes))
+ .then(L.bind(ui.changes.apply, ui.changes));
},
renderMigration: function() {
- L.ui.showModal(_('Wireless configuration migration'), [
+ ui.showModal(_('Wireless configuration migration'), [
E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')),
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.')),
E('div', { 'class': 'right' },
E('button', {
'class': 'btn cbi-button-action important',
- 'click': L.ui.createHandlerFn(this, 'handleMigration')
+ 'click': ui.createHandlerFn(this, 'handleMigration')
}, _('Continue')))
]);
},
E('button', {
'class': 'cbi-button cbi-button-neutral',
'title': _('Restart radio interface'),
- 'click': L.ui.createHandlerFn(this, radio_restart, section_id)
+ 'click': ui.createHandlerFn(this, radio_restart, section_id)
}, _('Restart')),
E('button', {
'class': 'cbi-button cbi-button-action important',
'title': _('Find and join network'),
- 'click': L.ui.createHandlerFn(this, 'handleScan', inst)
+ 'click': ui.createHandlerFn(this, 'handleScan', inst)
}, _('Scan')),
E('button', {
'class': 'cbi-button cbi-button-add',
'title': _('Provide new network'),
- 'click': L.ui.createHandlerFn(this, 'handleAdd', inst)
+ 'click': ui.createHandlerFn(this, 'handleAdd', inst)
}, _('Add'))
];
}
E('button', {
'class': 'cbi-button cbi-button-neutral enable-disable',
'title': isDisabled ? _('Enable this network') : _('Disable this network'),
- 'click': L.ui.createHandlerFn(this, network_updown, section_id, this.map)
+ 'click': ui.createHandlerFn(this, network_updown, section_id, this.map)
}, isDisabled ? _('Enable') : _('Disable')),
E('button', {
'class': 'cbi-button cbi-button-action important',
'title': _('Edit this network'),
- 'click': L.ui.createHandlerFn(this, 'renderMoreOptionsModal', section_id)
+ 'click': ui.createHandlerFn(this, 'renderMoreOptionsModal', section_id)
}, _('Edit')),
E('button', {
'class': 'cbi-button cbi-button-negative remove',
'title': _('Delete this network'),
- 'click': L.ui.createHandlerFn(this, 'handleRemove', section_id)
+ 'click': ui.createHandlerFn(this, 'handleRemove', section_id)
}, _('Remove'))
];
}
ss.tab('general', _('General Setup'));
ss.tab('advanced', _('Advanced Settings'));
- var isDisabled = (radioNet.get('disabled') == '1');
+ var isDisabled = (radioNet.get('disabled') == '1' ||
+ uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == 1);
o = ss.taboption('general', form.DummyValue, '_wifistat_modal', _('Status'));
o.cfgvalue = L.bind(function(radioNet) {
o = ss.taboption('general', form.Button, '_toggle', isDisabled ? _('Wireless network is disabled') : _('Wireless network is enabled'));
o.inputstyle = isDisabled ? 'apply' : 'reset';
o.inputtitle = isDisabled ? _('Enable') : _('Disable');
- o.onclick = L.ui.createHandlerFn(s, network_updown, s.section, s.map);
+ o.onclick = ui.createHandlerFn(s, network_updown, s.section, s.map);
o = ss.taboption('general', CBIWifiFrequencyValue, '_freq', '<br />' + _('Operating frequency'));
o.ucisection = s.section;
};
- encr.value('none', _('No Encryption'));
- encr.value('wep-open', _('WEP Open System'));
- encr.value('wep-shared', _('WEP Shared Key'));
+ var crypto_modes = [];
if (hwtype == 'mac80211') {
var has_supplicant = L.hasSystemFeature('wpasupplicant'),
if (has_hostapd || has_supplicant) {
- encr.value('psk', 'WPA-PSK');
- encr.value('psk2', 'WPA2-PSK');
- encr.value('psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode');
+ crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
+ crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
+ crypto_modes.push(['psk', 'WPA-PSK', 21]);
}
else {
encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.');
}
if (has_ap_sae || has_sta_sae) {
- encr.value('sae', 'WPA3-SAE');
- encr.value('sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode');
+ crypto_modes.push(['sae', 'WPA3-SAE', 31]);
+ crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
}
if (has_ap_eap || has_sta_eap) {
- encr.value('wpa', 'WPA-EAP');
- encr.value('wpa2', 'WPA2-EAP');
+ crypto_modes.push(['wpa2', 'WPA2-EAP', 32]);
+ crypto_modes.push(['wpa', 'WPA-EAP', 20]);
}
if (has_ap_owe || has_sta_owe) {
- encr.value('owe', 'OWE');
+ crypto_modes.push(['owe', 'OWE', 1]);
}
encr.crypto_support = {
};
}
else if (hwtype == 'broadcom') {
- encr.value('psk', 'WPA-PSK');
- encr.value('psk2', 'WPA2-PSK');
- encr.value('psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode');
+ crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
+ crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
+ crypto_modes.push(['psk', 'WPA-PSK', 21]);
+ }
+
+ crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
+ crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
+ crypto_modes.push(['none', _('No Encryption'), 0]);
+
+ crypto_modes.sort(function(a, b) { return b[2] - a[2] });
+
+ for (var i = 0; i < crypto_modes.length; i++) {
+ var security_level = (crypto_modes[i][2] >= 30) ? _('strong security')
+ : (crypto_modes[i][2] >= 20) ? _('medium security')
+ : (crypto_modes[i][2] >= 10) ? _('weak security') : _('open network');
+
+ encr.value(crypto_modes[i][0], '%s (%s)'.format(crypto_modes[i][1], security_level));
}
o.write = function(section_id, value) {
uci.set('wireless', section_id, 'key', value);
uci.unset('wireless', section_id, 'key1');
+ uci.unset('wireless', section_id, 'key2');
+ uci.unset('wireless', section_id, 'key3');
+ uci.unset('wireless', section_id, 'key4');
};
o.cfgvalue = function(section_id) {
var slot = +uci.get('wireless', section_id, 'key');
- return String((slot >= 1 && slot <= 4) ? slot : 1);
+ return (slot >= 1 && slot <= 4) ? String(slot) : '';
};
o.write = function(section_id, value) {
o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
if (L.hasSystemFeature('hostapd', 'cli') && L.hasSystemFeature('wpasupplicant')) {
- o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK'))
+ o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
o.enabled = '1';
o.disabled = '0';
o.default = o.disabled;
o.depends('encryption', 'psk');
o.depends('encryption', 'psk2');
o.depends('encryption', 'psk-mixed');
+ o.depends('encryption', 'sae');
+ o.depends('encryption', 'sae-mixed');
}
}
}
cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
- var md = L.ui.showModal(_('Join Network: Wireless Scan'), [
+ var md = ui.showModal(_('Join Network: Wireless Scan'), [
table,
E('div', { 'class': 'right' },
E('button', {
md.style.maxHeight = '';
}
- L.ui.hideModal();
+ ui.hideModal();
L.Poll.remove(this.pollFn);
this.pollFn = null;
zoneval = zoneopt ? zoneopt.formvalue('_new_') : null,
enc = L.isObject(bss.encryption) ? bss.encryption : null,
is_wep = (enc && Array.isArray(enc.wep)),
- is_psk = (enc && Array.isArray(enc.wpa) && Array.isArray(enc.authentication) && enc.authentication[0] == 'psk');
+ is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' })),
+ is_sae = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'sae' }));
if (nameval == null || (passopt && passval == null))
return;
var section_id = null;
return this.map.save(function() {
+ var wifi_sections = uci.sections('wireless', 'wifi-iface');
+
if (replopt.formvalue('_new_') == '1') {
- var sections = uci.sections('wireless', 'wifi-iface');
+ for (var i = 0; i < wifi_sections.length; i++)
+ if (wifi_sections[i].device == radioDev.getName())
+ uci.remove('wireless', wifi_sections[i]['.name']);
+ }
+
+ if (uci.get('wireless', radioDev.getName(), 'disabled') == '1') {
+ for (var i = 0; i < wifi_sections.length; i++)
+ if (wifi_sections[i].device == radioDev.getName())
+ uci.set('wireless', wifi_sections[i]['.name'], 'disabled', '1');
- for (var i = 0; i < sections.length; i++)
- if (sections[i].device == radioDev.getName())
- uci.remove('wireless', sections[i]['.name']);
+ uci.unset('wireless', radioDev.getName(), 'disabled');
}
- section_id = next_free_sid(uci.sections('wifi-iface').length);
+ section_id = next_free_sid(wifi_sections.length);
uci.add('wireless', 'wifi-iface', section_id);
uci.set('wireless', section_id, 'device', radioDev.getName());
else if (bss.bssid != null)
uci.set('wireless', section_id, 'bssid', bss.bssid);
- if (is_psk) {
+ if (is_sae) {
+ uci.set('wireless', section_id, 'encryption', 'sae');
+ uci.set('wireless', section_id, 'key', passval);
+ }
+ else if (is_psk) {
for (var i = enc.wpa.length - 1; i >= 0; i--) {
if (enc.wpa[i] == 2) {
uci.set('wireless', section_id, 'encryption', 'psk2');
uci.set('wireless', section_id, 'key1', passval);
}
- var zonePromise = zoneval
- ? firewall.getZone(zoneval).then(function(zone) { return zone || firewall.addZone(zoneval) })
- : Promise.resolve();
+ return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) {
+ firewall.deleteNetwork(net.getName());
- return zonePromise.then(function(zone) {
- return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) {
- firewall.deleteNetwork(net.getName());
+ var zonePromise = zoneval
+ ? firewall.getZone(zoneval).then(function(zone) { return zone || firewall.addZone(zoneval) })
+ : Promise.resolve();
+ return zonePromise.then(function(zone) {
if (zone)
zone.addNetwork(net.getName());
});
s2 = m2.section(form.NamedSection, '_new_'),
enc = L.isObject(bss.encryption) ? bss.encryption : null,
is_wep = (enc && Array.isArray(enc.wep)),
- is_psk = (enc && Array.isArray(enc.wpa) && Array.isArray(enc.authentication) && enc.authentication[0] == 'psk'),
+ is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' || a == 'sae' })),
replace, passphrase, name, zone;
s2.render = function() {
zone.default = 'wan';
return m2.render().then(L.bind(function(nodes) {
- L.ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [
+ ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [
nodes,
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn',
- 'click': L.ui.hideModal
+ 'click': ui.hideModal
}, _('Cancel')), ' ',
E('button', {
'class': 'cbi-button cbi-button-positive important',
- 'click': L.ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2)
+ 'click': ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2)
}, _('Submit'))
])
], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase || name).cbid('_new_'))).focus();
if (dsc.getAttribute('restart') == '') {
dsc.setAttribute('restart', '1');
- tasks.push(L.Request.post(
- L.url('admin/network/wireless_reconnect', section_ids[i]),
- 'token=' + L.env.token,
- { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
- ).catch(function() {}));
+ tasks.push(fs.exec('/sbin/wifi', ['up', section_ids[i]]).catch(function(e) {
+ ui.addNotification(null, E('p', e.message));
+ }));
}
else if (dsc.getAttribute('restart') == '1') {
dsc.removeAttribute('restart');
E('div', { 'class': 'tr table-titles' }, [
E('div', { 'class': 'th nowrap' }, _('Network')),
E('div', { 'class': 'th hide-xs' }, _('MAC-Address')),
- E('div', { 'class': 'th nowrap' }, _('Host')),
+ E('div', { 'class': 'th' }, _('Host')),
E('div', { 'class': 'th nowrap' }, _('Signal / Noise')),
E('div', { 'class': 'th nowrap' }, _('RX Rate / TX Rate'))
])