summaryrefslogtreecommitdiffhomepage
path: root/modules/luci-mod-network/htdocs/luci-static
diff options
context:
space:
mode:
Diffstat (limited to 'modules/luci-mod-network/htdocs/luci-static')
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js172
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/diagnostics.js139
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/hosts.js3
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js83
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/routes.js5
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/switch.js19
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js614
7 files changed, 715 insertions, 320 deletions
diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js
index 540e9f8eb1..37cc29f32a 100644
--- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js
+++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js
@@ -1,9 +1,13 @@
'use strict';
+'require view';
+'require dom';
+'require poll';
'require rpc';
'require uci';
'require form';
+'require validation';
-var callHostHints, callDUIDHints, callDHCPLeases, CBILeaseStatus;
+var callHostHints, callDUIDHints, callDHCPLeases, CBILeaseStatus, CBILease6Status;
callHostHints = rpc.declare({
object: 'luci-rpc',
@@ -12,7 +16,7 @@ callHostHints = rpc.declare({
});
callDUIDHints = rpc.declare({
- object: 'luci',
+ object: 'luci-rpc',
method: 'getDUIDHints',
expect: { '': {} }
});
@@ -20,8 +24,7 @@ callDUIDHints = rpc.declare({
callDHCPLeases = rpc.declare({
object: 'luci-rpc',
method: 'getDHCPLeases',
- params: [ 'family' ],
- expect: { dhcp_leases: [] }
+ expect: { '': {} }
});
CBILeaseStatus = form.DummyValue.extend({
@@ -33,7 +36,26 @@ CBILeaseStatus = form.DummyValue.extend({
E('div', { 'class': 'th' }, _('Hostname')),
E('div', { 'class': 'th' }, _('IPv4-Address')),
E('div', { 'class': 'th' }, _('MAC-Address')),
- E('div', { 'class': 'th' }, _('Leasetime remaining'))
+ E('div', { 'class': 'th' }, _('Lease time remaining'))
+ ]),
+ E('div', { 'class': 'tr placeholder' }, [
+ E('div', { 'class': 'td' }, E('em', _('Collecting data...')))
+ ])
+ ])
+ ]);
+ }
+});
+
+CBILease6Status = form.DummyValue.extend({
+ renderWidget: function(section_id, option_id, cfgvalue) {
+ return E([
+ E('h4', _('Active DHCPv6 Leases')),
+ E('div', { 'id': 'lease6_status_table', 'class': 'table' }, [
+ E('div', { 'class': 'tr table-titles' }, [
+ E('div', { 'class': 'th' }, _('Host')),
+ E('div', { 'class': 'th' }, _('IPv6-Address')),
+ E('div', { 'class': 'th' }, _('DUID')),
+ E('div', { 'class': 'th' }, _('Lease time remaining'))
]),
E('div', { 'class': 'tr placeholder' }, [
E('div', { 'class': 'td' }, E('em', _('Collecting data...')))
@@ -43,7 +65,80 @@ CBILeaseStatus = form.DummyValue.extend({
}
});
-return L.view.extend({
+function validateHostname(sid, s) {
+ if (s == null || s == '')
+ return true;
+
+ if (s.length > 256)
+ return _('Expecting: %s').format(_('valid hostname'));
+
+ var labels = s.replace(/^\.+|\.$/g, '').split(/\./);
+
+ for (var i = 0; i < labels.length; i++)
+ if (!labels[i].match(/^[a-z0-9_](?:[a-z0-9-]{0,61}[a-z0-9])?$/i))
+ return _('Expecting: %s').format(_('valid hostname'));
+
+ return true;
+}
+
+function validateAddressList(sid, s) {
+ if (s == null || s == '')
+ return true;
+
+ var m = s.match(/^\/(.+)\/$/),
+ names = m ? m[1].split(/\//) : [ s ];
+
+ for (var i = 0; i < names.length; i++) {
+ var res = validateHostname(sid, names[i]);
+
+ if (res !== true)
+ return res;
+ }
+
+ return true;
+}
+
+function validateServerSpec(sid, s) {
+ if (s == null || s == '')
+ return true;
+
+ var m = s.match(/^(?:\/(.+)\/)?(.*)$/);
+ if (!m)
+ return _('Expecting: %s').format(_('valid hostname'));
+
+ var res = validateAddressList(sid, m[1]);
+ if (res !== true)
+ return res;
+
+ if (m[2] == '' || m[2] == '#')
+ return true;
+
+ // ipaddr%scopeid#srvport@source@interface#srcport
+
+ m = m[2].match(/^([0-9a-f:.]+)(?:%[^#@]+)?(?:#(\d+))?(?:@([0-9a-f:.]+)(?:@[^#]+)?(?:#(\d+))?)?$/);
+
+ if (!m)
+ return _('Expecting: %s').format(_('valid IP address'));
+
+ if (validation.parseIPv4(m[1])) {
+ if (m[3] != null && !validation.parseIPv4(m[3]))
+ return _('Expecting: %s').format(_('valid IPv4 address'));
+ }
+ else if (validation.parseIPv6(m[1])) {
+ if (m[3] != null && !validation.parseIPv6(m[3]))
+ return _('Expecting: %s').format(_('valid IPv6 address'));
+ }
+ else {
+ return _('Expecting: %s').format(_('valid IP address'));
+ }
+
+ if ((m[2] != null && +m[2] > 65535) || (m[4] != null && +m[4] > 65535))
+ return _('Expecting: %s').format(_('valid port value'));
+
+ return true;
+}
+
+return view.extend({
load: function() {
return Promise.all([
callHostHints(),
@@ -52,7 +147,8 @@ return L.view.extend({
},
render: function(hosts_duids) {
- var hosts = hosts_duids[0],
+ var has_dhcpv6 = L.hasSystemFeature('dnsmasq', 'dhcpv6') || L.hasSystemFeature('odhcpd'),
+ hosts = hosts_duids[0],
duids = hosts_duids[1],
m, s, o, ss, so;
@@ -92,7 +188,8 @@ return L.view.extend({
_('Resolve file'),
_('local <abbr title="Domain Name System">DNS</abbr> file'));
- o.depends('noresolv', '');
+ o.depends('noresolv', '0');
+ o.placeholder = '/tmp/resolv.conf.d/resolv.conf.auto';
o.optional = true;
@@ -126,10 +223,7 @@ return L.view.extend({
_('Localise queries'),
_('Localise hostname depending on the requesting subnet if multiple IPs are available'));
- //local have_dnssec_support = luci.util.checklib('/usr/sbin/dnsmasq', 'libhogweed.so');
- var have_dnssec_support = true;
-
- if (have_dnssec_support) {
+ if (L.hasSystemFeature('dnsmasq', 'dnssec')) {
o = s.taboption('advanced', form.Flag, 'dnssec',
_('DNSSEC'));
o.optional = true;
@@ -137,6 +231,7 @@ return L.view.extend({
o = s.taboption('advanced', form.Flag, 'dnsseccheckunsigned',
_('DNSSEC check unsigned'),
_('Requires upstream supports DNSSEC; verify unsigned domain responses really come from unsigned domains'));
+ o.default = o.enabled;
o.optional = true;
}
@@ -184,6 +279,7 @@ return L.view.extend({
o.optional = true;
o.placeholder = '/example.org/10.1.2.3';
+ o.validate = validateServerSpec;
o = s.taboption('general', form.DynamicList, 'address', _('Addresses'),
@@ -213,8 +309,8 @@ return L.view.extend({
o.optional = true;
o.depends('rebind_protection', '1');
- o.datatype = 'host(1)';
o.placeholder = 'ihost.netflix.com';
+ o.validate = validateAddressList;
o = s.taboption('advanced', form.Value, 'port',
@@ -297,6 +393,7 @@ return L.view.extend({
o = s.taboption('general', form.Flag, 'nonwildcard',
_('Non-wildcard'),
_('Bind dynamically to interfaces rather than wildcard address (recommended as linux default)'));
+ o.default = o.enabled;
o.optional = false;
o.rmempty = true;
@@ -320,7 +417,7 @@ return L.view.extend({
ss.anonymous = true;
so = ss.option(form.Value, 'name', _('Hostname'));
- so.datatype = 'hostname("strict")';
+ so.validate = validateHostname;
so.rmempty = true;
so.write = function(section, value) {
uci.set('dhcp', section, 'name', value);
@@ -365,7 +462,7 @@ return L.view.extend({
var node = ipopt.map.findElement('id', ipopt.cbid(section_id));
if (node)
- L.dom.callClassMethod(node, 'setValue', hosts[mac].ipv4);
+ dom.callClassMethod(node, 'setValue', hosts[mac].ipv4);
}, this, ipopt, section_id));
return node;
@@ -401,16 +498,22 @@ return L.view.extend({
so = ss.option(form.Value, 'duid', _('<abbr title="The DHCP Unique Identifier">DUID</abbr>'));
so.datatype = 'and(rangelength(20,36),hexstring)';
Object.keys(duids).forEach(function(duid) {
- so.value(duid, '%s (%s)'.format(duid, duids[duid].name || '?'));
+ so.value(duid, '%s (%s)'.format(duid, duids[duid].hostname || duids[duid].macaddr || duids[duid].ip6addr || '?'));
});
so = ss.option(form.Value, 'hostid', _('<abbr title="Internet Protocol Version 6">IPv6</abbr>-Suffix (hex)'));
o = s.taboption('leases', CBILeaseStatus, '__status__');
+ if (has_dhcpv6)
+ o = s.taboption('leases', CBILease6Status, '__status6__');
+
return m.render().then(function(mapEl) {
- L.Poll.add(function() {
- return callDHCPLeases(4).then(function(leases) {
+ poll.add(function() {
+ return callDHCPLeases().then(function(leaseinfo) {
+ var leases = Array.isArray(leaseinfo.dhcp_leases) ? leaseinfo.dhcp_leases : [],
+ leases6 = Array.isArray(leaseinfo.dhcp6_leases) ? leaseinfo.dhcp6_leases : [];
+
cbi_update_table(mapEl.querySelector('#lease_status_table'),
leases.map(function(lease) {
var exp;
@@ -430,6 +533,39 @@ return L.view.extend({
];
}),
E('em', _('There are no active leases')));
+
+ if (has_dhcpv6) {
+ cbi_update_table(mapEl.querySelector('#lease6_status_table'),
+ leases6.map(function(lease) {
+ var exp;
+
+ if (lease.expires === false)
+ exp = E('em', _('unlimited'));
+ else if (lease.expires <= 0)
+ exp = E('em', _('expired'));
+ else
+ exp = '%t'.format(lease.expires);
+
+ var hint = lease.macaddr ? hosts[lease.macaddr] : null,
+ name = hint ? (hint.name || hint.ipv4 || hint.ipv6) : null,
+ host = null;
+
+ if (name && lease.hostname && lease.hostname != name && lease.ip6addr != name)
+ host = '%s (%s)'.format(lease.hostname, name);
+ else if (lease.hostname)
+ host = lease.hostname;
+ else if (name)
+ host = name;
+
+ return [
+ host || '-',
+ lease.ip6addrs ? lease.ip6addrs.join(' ') : lease.ip6addr,
+ lease.duid,
+ exp
+ ];
+ }),
+ E('em', _('There are no active leases')));
+ }
});
});
diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/diagnostics.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/diagnostics.js
new file mode 100644
index 0000000000..1855ee6422
--- /dev/null
+++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/diagnostics.js
@@ -0,0 +1,139 @@
+'use strict';
+'require view';
+'require dom';
+'require fs';
+'require ui';
+'require uci';
+
+return view.extend({
+ handleCommand: function(exec, args) {
+ var buttons = document.querySelectorAll('.diag-action > .cbi-button');
+
+ for (var i = 0; i < buttons.length; i++)
+ buttons[i].setAttribute('disabled', 'true');
+
+ return fs.exec(exec, args).then(function(res) {
+ var out = document.querySelector('.command-output');
+ out.style.display = '';
+
+ dom.content(out, [ res.stdout || '', res.stderr || '' ]);
+ }).catch(function(err) {
+ ui.addNotification(null, E('p', [ err ]))
+ }).finally(function() {
+ for (var i = 0; i < buttons.length; i++)
+ buttons[i].removeAttribute('disabled');
+ });
+ },
+
+ handlePing: function(ev, cmd) {
+ var exec = cmd || 'ping',
+ addr = ev.currentTarget.parentNode.previousSibling.value,
+ args = (exec == 'ping') ? [ '-4', '-c', '5', '-W', '1', addr ] : [ '-6', '-c', '5', addr ];
+
+ return this.handleCommand(exec, args);
+ },
+
+ handleTraceroute: function(ev, cmd) {
+ var exec = cmd || 'traceroute',
+ addr = ev.currentTarget.parentNode.previousSibling.value,
+ args = (exec == 'traceroute') ? [ '-q', '1', '-w', '1', '-n', addr ] : [ '-q', '1', '-w', '2', '-n', addr ];
+
+ return this.handleCommand(exec, args);
+ },
+
+ handleNslookup: function(ev, cmd) {
+ var addr = ev.currentTarget.parentNode.previousSibling.value;
+
+ return this.handleCommand('nslookup', [ addr ]);
+ },
+
+ load: function() {
+ return Promise.all([
+ L.resolveDefault(fs.stat('/bin/ping6'), {}),
+ L.resolveDefault(fs.stat('/usr/bin/ping6'), {}),
+ L.resolveDefault(fs.stat('/bin/traceroute6'), {}),
+ L.resolveDefault(fs.stat('/usr/bin/traceroute6'), {}),
+ uci.load('luci')
+ ]);
+ },
+
+ render: function(res) {
+ var has_ping6 = res[0].path || res[1].path,
+ has_traceroute6 = res[2].path || res[3].path,
+ dns_host = uci.get('luci', 'diag', 'dns') || 'openwrt.org',
+ ping_host = uci.get('luci', 'diag', 'ping') || 'openwrt.org',
+ route_host = uci.get('luci', 'diag', 'route') || 'openwrt.org';
+
+ return E([], [
+ E('h2', {}, [ _('Network Utilities') ]),
+ E('div', { 'class': 'table' }, [
+ E('div', { 'class': 'tr' }, [
+ E('div', { 'class': 'td left' }, [
+ E('input', {
+ 'style': 'margin:5px 0',
+ 'type': 'text',
+ 'value': ping_host
+ }),
+ E('span', { 'class': 'diag-action' }, [
+ has_ping6 ? new ui.ComboButton('ping', {
+ 'ping': '%s %s'.format(_('IPv4'), _('Ping')),
+ 'ping6': '%s %s'.format(_('IPv6'), _('Ping')),
+ }, {
+ 'click': ui.createHandlerFn(this, 'handlePing'),
+ 'classes': {
+ 'ping': 'btn cbi-button cbi-button-action',
+ 'ping6': 'btn cbi-button cbi-button-action'
+ }
+ }).render() : E('button', {
+ 'class': 'cbi-button cbi-button-action',
+ 'click': ui.createHandlerFn(this, 'handlePing')
+ }, [ _('Ping') ])
+ ])
+ ]),
+
+ E('div', { 'class': 'td left' }, [
+ E('input', {
+ 'style': 'margin:5px 0',
+ 'type': 'text',
+ 'value': route_host
+ }),
+ E('span', { 'class': 'diag-action' }, [
+ has_traceroute6 ? new ui.ComboButton('traceroute', {
+ 'traceroute': '%s %s'.format(_('IPv4'), _('Traceroute')),
+ 'traceroute6': '%s %s'.format(_('IPv6'), _('Traceroute')),
+ }, {
+ 'click': ui.createHandlerFn(this, 'handleTraceroute'),
+ 'classes': {
+ 'traceroute': 'btn cbi-button cbi-button-action',
+ 'traceroute6': 'btn cbi-button cbi-button-action'
+ }
+ }).render() : E('button', {
+ 'class': 'cbi-button cbi-button-action',
+ 'click': ui.createHandlerFn(this, 'handleTraceroute')
+ }, [ _('Traceroute') ])
+ ])
+ ]),
+
+ E('div', { 'class': 'td left' }, [
+ E('input', {
+ 'style': 'margin:5px 0',
+ 'type': 'text',
+ 'value': dns_host
+ }),
+ E('span', { 'class': 'diag-action' }, [
+ E('button', {
+ 'class': 'cbi-button cbi-button-action',
+ 'click': ui.createHandlerFn(this, 'handleNslookup')
+ }, [ _('Nslookup') ])
+ ])
+ ])
+ ])
+ ]),
+ E('pre', { 'class': 'command-output', 'style': 'display:none' })
+ ]);
+ },
+
+ handleSaveApply: null,
+ handleSave: null,
+ handleReset: null
+});
diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/hosts.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/hosts.js
index 975ac2fec7..cd0dacbf67 100644
--- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/hosts.js
+++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/hosts.js
@@ -1,8 +1,9 @@
'use strict';
+'require view';
'require rpc';
'require form';
-return L.view.extend({
+return view.extend({
callHostHints: rpc.declare({
object: 'luci-rpc',
method: 'getHostHints',
diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js
index 9ca7773fe1..bee5753055 100644
--- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js
+++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js
@@ -1,4 +1,7 @@
'use strict';
+'require view';
+'require dom';
+'require poll';
'require fs';
'require ui';
'require uci';
@@ -7,6 +10,8 @@
'require firewall';
'require tools.widgets as widgets';
+var isReadonlyView = !L.hasViewPermission() || null;
+
function count_changes(section_id) {
var changes = ui.changes.changes, n = 0;
@@ -109,7 +114,7 @@ function render_status(node, ifc, with_device) {
function render_modal_status(node, ifc) {
var dev = ifc ? (ifc.getDevice() || ifc.getL3Device() || ifc.getL3Device()) : null;
- L.dom.content(node, [
+ dom.content(node, [
E('img', {
'src': L.resource('icons/%s%s.png').format(dev ? dev.getType() : 'ethernet', (dev && dev.isUp()) ? '' : '_disabled'),
'title': dev ? dev.getTypeI18n() : _('Not present')
@@ -140,7 +145,7 @@ function render_ifacebox_status(node, ifc) {
c.push(E('small', {}, ifc.isAlias() ? _('Alias of "%s"').format(ifc.isAlias())
: (dev ? dev.getName() : E('em', _('Not present')))));
- L.dom.content(node, c);
+ dom.content(node, c);
return firewall.getZoneByNetwork(ifc.getName()).then(L.bind(function(zone) {
this.style.backgroundColor = zone ? zone.getColor() : '#EEEEEE';
@@ -160,8 +165,8 @@ function iface_updown(up, id, ev, force) {
btns[1].disabled = true;
if (!up) {
- L.Request.get(L.url('admin/network/remote_addr')).then(function(res) {
- var info = res.json();
+ L.resolveDefault(fs.exec_direct('/usr/libexec/luci-peeraddr')).then(function(res) {
+ var info = null; try { info = JSON.parse(res); } catch(e) {}
if (L.isObject(info) &&
Array.isArray(info.inbound_interfaces) &&
@@ -185,7 +190,7 @@ function iface_updown(up, id, ev, force) {
'class': 'cbi-button cbi-button-negative important',
'click': function(ev) {
dsc.setAttribute('disconnect', '');
- L.dom.content(dsc, E('em', _('Interface is shutting down...')));
+ dom.content(dsc, E('em', _('Interface is shutting down...')));
ui.hideModal();
}
@@ -195,13 +200,13 @@ function iface_updown(up, id, ev, force) {
}
else {
dsc.setAttribute('disconnect', '');
- L.dom.content(dsc, E('em', _('Interface is shutting down...')));
+ dom.content(dsc, E('em', _('Interface is shutting down...')));
}
});
}
else {
dsc.setAttribute(up ? 'reconnect' : 'disconnect', force ? 'force' : '');
- L.dom.content(dsc, E('em', up ? _('Interface is reconnecting...') : _('Interface is shutting down...')));
+ dom.content(dsc, E('em', up ? _('Interface is reconnecting...') : _('Interface is shutting down...')));
}
}
@@ -224,7 +229,7 @@ function get_netmask(s, use_cfgvalue) {
return mask;
}
-return L.view.extend({
+return view.extend({
poll_status: function(map, networks) {
var resolveZone = null;
@@ -245,10 +250,10 @@ return L.view.extend({
dynamic = ifc ? ifc.isDynamic() : false;
if (dsc.hasAttribute('reconnect')) {
- L.dom.content(dsc, E('em', _('Interface is starting...')));
+ dom.content(dsc, E('em', _('Interface is starting...')));
}
else if (dsc.hasAttribute('disconnect')) {
- L.dom.content(dsc, E('em', _('Interface is stopping...')));
+ dom.content(dsc, E('em', _('Interface is stopping...')));
}
else if (ifc.getProtocol() || uci.get('network', ifc.getName()) == null) {
render_status(dsc, ifc, false);
@@ -258,18 +263,18 @@ return L.view.extend({
if (e) e.disabled = true;
var link = L.url('admin/system/opkg') + '?query=luci-proto';
- L.dom.content(dsc, [
+ dom.content(dsc, [
E('em', _('Unsupported protocol type.')), E('br'),
E('a', { href: link }, _('Install protocol extensions...'))
]);
}
else {
- L.dom.content(dsc, E('em', _('Interface not present or not connected yet.')));
+ dom.content(dsc, E('em', _('Interface not present or not connected yet.')));
}
if (stat) {
var dev = ifc.getDevice();
- L.dom.content(stat, [
+ dom.content(stat, [
E('img', {
'src': L.resource('icons/%s%s.png').format(dev ? dev.getType() : 'ethernet', (dev && dev.isUp()) ? '' : '_disabled'),
'title': dev ? dev.getTypeI18n() : _('Not present')
@@ -278,8 +283,8 @@ return L.view.extend({
]);
}
- btn1.disabled = btn1.classList.contains('spinning') || btn2.classList.contains('spinning') || dynamic;
- btn2.disabled = btn1.classList.contains('spinning') || btn2.classList.contains('spinning') || dynamic || disabled;
+ btn1.disabled = isReadonlyView || btn1.classList.contains('spinning') || btn2.classList.contains('spinning') || dynamic;
+ btn2.disabled = isReadonlyView || btn1.classList.contains('spinning') || btn2.classList.contains('spinning') || dynamic || disabled;
}
return Promise.all([ resolveZone, network.flushCache() ]);
@@ -336,7 +341,7 @@ return L.view.extend({
disabled = net ? !net.isUp() : true,
dynamic = net ? net.isDynamic() : false;
- L.dom.content(tdEl.lastChild, [
+ dom.content(tdEl.lastChild, [
E('button', {
'class': 'cbi-button cbi-button-neutral reconnect',
'click': iface_updown.bind(this, true, section_id),
@@ -408,7 +413,7 @@ return L.view.extend({
o.modalonly = true;
o.default = o.enabled;
- type = s.taboption('physical', form.Flag, 'type', _('Bridge interfaces'), _('creates a bridge over specified interface(s)'));
+ type = s.taboption('physical', form.Flag, 'type', _('Bridge interfaces'), _('Creates a bridge over specified interface(s)'));
type.modalonly = true;
type.disabled = '';
type.enabled = 'bridge';
@@ -483,7 +488,7 @@ return L.view.extend({
};
if (L.hasSystemFeature('firewall')) {
- o = s.taboption('firewall', widgets.ZoneSelect, '_zone', _('Create / Assign firewall-zone'), _('Choose the firewall zone you want to assign to this interface. Select <em>unspecified</em> to remove the interface from the associated zone or fill out the <em>create</em> field to define a new zone and attach the interface to it.'));
+ o = s.taboption('firewall', 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.'));
o.network = ifc.getName();
o.optional = true;
@@ -722,7 +727,7 @@ return L.view.extend({
proto = s2.option(form.ListValue, 'proto', _('Protocol'));
proto.validate = name.validate;
- bridge = s2.option(form.Flag, 'type', _('Bridge interfaces'), _('creates a bridge over specified interface(s)'));
+ bridge = s2.option(form.Flag, 'type', _('Bridge interfaces'), _('Creates a bridge over specified interface(s)'));
bridge.modalonly = true;
bridge.disabled = '';
bridge.enabled = 'bridge';
@@ -760,24 +765,35 @@ return L.view.extend({
'class': 'cbi-button cbi-button-positive important',
'click': ui.createHandlerFn(this, function(ev) {
var nameval = name.isValid('_new_') ? name.formvalue('_new_') : null,
- protoval = proto.isValid('_new_') ? proto.formvalue('_new_') : null;
+ protoval = proto.isValid('_new_') ? proto.formvalue('_new_') : null,
+ protoclass = protoval ? network.getProtocol(protoval) : null;
if (nameval == null || protoval == null || nameval == '' || protoval == '')
return;
- return m.save(function() {
- var section_id = uci.add('network', 'interface', nameval);
+ return protoclass.isCreateable(nameval).then(function(checkval) {
+ if (checkval != null) {
+ ui.addNotification(null,
+ E('p', _('New interface for "%s" can not be created: %s').format(protoclass.getI18n(), checkval)));
+ ui.hideModal();
+ return;
+ }
- uci.set('network', section_id, 'proto', protoval);
+ return m.save(function() {
+ var section_id = uci.add('network', 'interface', nameval);
- if (ifname_single.isActive('_new_')) {
- uci.set('network', section_id, 'ifname', ifname_single.formvalue('_new_'));
- }
- else if (ifname_multi.isActive('_new_')) {
- uci.set('network', section_id, 'type', 'bridge');
- uci.set('network', section_id, 'ifname', L.toArray(ifname_multi.formvalue('_new_')).join(' '));
- }
- }).then(L.bind(m.children[0].renderMoreOptionsModal, m.children[0], nameval));
+ uci.set('network', section_id, 'proto', protoval);
+
+ if (ifname_single.isActive('_new_')) {
+ uci.set('network', section_id, 'ifname', ifname_single.formvalue('_new_'));
+ }
+ else if (ifname_multi.isActive('_new_')) {
+ uci.set('network', section_id, 'type', 'bridge');
+ uci.set('network', section_id, 'ifname', L.toArray(ifname_multi.formvalue('_new_')).join(' '));
+ }
+ }).then(L.bind(m.children[0].renderMoreOptionsModal, m.children[0], nameval));
+
+ });
})
}, _('Create interface'))
])
@@ -863,6 +879,9 @@ return L.view.extend({
o = s.option(form.Value, 'ula_prefix', _('IPv6 ULA-Prefix'));
o.datatype = 'cidr6';
+ o = s.option(form.Flag, 'packet_steering', _('Packet Steering'), _('Enable packet steering across all CPUs. May help or hinder network speed.'));
+ o.optional = true;
+
if (dslModemType != null) {
s = m.section(form.TypedSection, 'dsl', _('DSL'));
@@ -965,7 +984,7 @@ return L.view.extend({
return m.render().then(L.bind(function(m, nodes) {
- L.Poll.add(L.bind(function() {
+ poll.add(L.bind(function() {
var section_ids = m.children[0].cfgsections(),
tasks = [];
diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/routes.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/routes.js
index 4a002c2520..b218daac34 100644
--- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/routes.js
+++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/routes.js
@@ -1,9 +1,10 @@
'use strict';
+'require view';
'require form';
'require network';
'require tools.widgets as widgets';
-return L.view.extend({
+return view.extend({
load: function() {
return network.getDevices();
},
@@ -76,7 +77,7 @@ return L.view.extend({
o.rmempty = true;
o.modalonly = true;
o.cfgvalue = function(section_id) {
- var cfgvalue = this.super('cfgvalue', [section_id]);
+ var cfgvalue = this.map.data.get('network', section_id, 'table');
return cfgvalue || 'main';
};
diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/switch.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/switch.js
index 80c8437fc4..3133d27250 100644
--- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/switch.js
+++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/switch.js
@@ -1,4 +1,7 @@
'use strict';
+'require view';
+'require dom';
+'require poll';
'require ui';
'require rpc';
'require uci';
@@ -66,14 +69,14 @@ function render_port_status(node, portstate) {
if (!node)
return null;
- if (!portstate.link)
- L.dom.content(node, [
+ if (!portstate || !portstate.link)
+ dom.content(node, [
E('img', { src: L.resource('icons/port_down.png') }),
E('br'),
_('no link')
]);
else
- L.dom.content(node, [
+ dom.content(node, [
E('img', { src: L.resource('icons/port_up.png') }),
E('br'),
'%d'.format(portstate.speed) + _('baseT'),
@@ -112,7 +115,7 @@ var callSwconfigPortState = rpc.declare({
expect: { result: [] }
});
-return L.view.extend({
+return view.extend({
load: function() {
return network.getSwitchTopologies().then(function(topologies) {
var tasks = [];
@@ -146,7 +149,7 @@ return L.view.extend({
if (!topology) {
ui.addNotification(null, _('Switch %q has an unknown topology - the VLAN settings might not be accurate.').replace(/%q/, switch_name));
- topology = {
+ topologies[switch_name] = topology = {
features: {},
netdevs: {
5: 'eth0'
@@ -334,6 +337,8 @@ return L.view.extend({
return (value || uci.get('network', section_id, 'vlan'));
};
+ s.option(form.Value, 'description', _('Description'));
+
for (var j = 0; Array.isArray(topology.ports) && j < topology.ports.length; j++) {
var portspec = topology.ports[j],
portstate = Array.isArray(topology.portstate) ? topology.portstate[portspec.num] : null;
@@ -359,11 +364,11 @@ return L.view.extend({
}
port_opts.sort(function(a, b) {
- return a.option < b.option;
+ return a.option > b.option;
});
}
- L.Poll.add(L.bind(update_port_status, m, topologies));
+ poll.add(L.bind(update_port_status, m, topologies));
return m.render();
}
diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js
index 1a98c4302c..dc75c9509f 100644
--- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js
+++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js
@@ -1,4 +1,7 @@
'use strict';
+'require view';
+'require dom';
+'require poll';
'require fs';
'require ui';
'require rpc';
@@ -8,6 +11,8 @@
'require firewall';
'require tools.widgets as widgets';
+var isReadonlyView = !L.hasViewPermission();
+
function count_changes(section_id) {
var changes = ui.changes.changes, n = 0;
@@ -29,8 +34,8 @@ function render_radio_badge(radioDev) {
]);
}
-function render_signal_badge(signalPercent, signalValue, noiseValue, wrap) {
- var icon, title;
+function render_signal_badge(signalPercent, signalValue, noiseValue, wrap, mode) {
+ var icon, title, value;
if (signalPercent < 0)
icon = L.resource('icons/signal-none.png');
@@ -46,21 +51,65 @@ function render_signal_badge(signalPercent, signalValue, noiseValue, wrap) {
icon = L.resource('icons/signal-75-100.png');
if (signalValue != null && signalValue != 0) {
- title = '%s %d %s'.format(_('Signal'), signalValue, _('dBm'));
+ if (noiseValue != null && noiseValue != 0) {
+ value = '%d/%d\xa0%s'.format(signalValue, noiseValue, _('dBm'));
+ title = '%s: %d %s / %s: %d %s / %s %d'.format(
+ _('Signal'), signalValue, _('dBm'),
+ _('Noise'), noiseValue, _('dBm'),
+ _('SNR'), signalValue - noiseValue);
+ }
+ else {
+ value = '%d\xa0%s'.format(signalValue, _('dBm'));
+ title = '%s: %d %s'.format(_('Signal'), signalValue, _('dBm'));
+ }
+ }
+ else if (signalPercent > -1) {
+ switch (mode) {
+ case 'ap':
+ title = _('No client associated');
+ break;
- if (noiseValue != null && noiseValue != 0)
- title += ' / %s: %d %s'.format(_('Noise'), noiseValue, _('dBm'));
+ case 'sta':
+ case 'adhoc':
+ case 'mesh':
+ title = _('Not associated');
+ break;
+
+ default:
+ title = _('No RX signal');
+ }
+
+ if (noiseValue != null && noiseValue != 0) {
+ value = '---/%d\x0a%s'.format(noiseValue, _('dBm'));
+ title = '%s / %s: %d %s'.format(title, _('Noise'), noiseValue, _('dBm'));
+ }
+ else {
+ value = '---\xa0%s'.format(_('dBm'));
+ }
}
else {
- title = _('No signal');
+ value = E('em', {}, E('small', {}, [ _('disabled') ]));
+ title = _('Interface is disabled');
}
- return E('div', { 'class': wrap ? 'center' : 'ifacebadge', 'title': title },
- [ E('img', { 'src': icon }), wrap ? E('br') : ' ', '%d%%'.format(Math.max(signalPercent, 0)) ]);
+ return E('div', {
+ 'class': wrap ? 'center' : 'ifacebadge',
+ 'title': title,
+ 'data-signal': signalValue,
+ 'data-noise': noiseValue
+ }, [
+ E('img', { 'src': icon }),
+ E('span', {}, [
+ wrap ? E('br') : ' ',
+ value
+ ])
+ ]);
}
function render_network_badge(radioNet) {
- return render_signal_badge(radioNet.isUp() ? radioNet.getSignalPercent() : -1, radioNet.getSignal(), radioNet.getNoise());
+ return render_signal_badge(
+ radioNet.isUp() ? radioNet.getSignalPercent() : -1,
+ radioNet.getSignal(), radioNet.getNoise(), false, radioNet.getMode());
}
function render_radio_status(radioDev, wifiNets) {
@@ -123,7 +172,9 @@ function render_modal_status(node, radioNet) {
if (node == null)
node = E('span', { 'class': 'ifacebadge large', 'data-network': radioNet.getName() }, [ E('small'), E('span') ]);
- L.dom.content(node.firstElementChild, render_signal_badge(disabled ? -1 : radioNet.getSignalPercent(), radioNet.getSignal(), noise, true));
+ dom.content(node.firstElementChild, render_signal_badge(
+ disabled ? -1 : radioNet.getSignalPercent(),
+ radioNet.getSignal(), noise, true, radioNet.getMode()));
L.itemlist(node.lastElementChild, [
_('Mode'), mode,
@@ -139,19 +190,22 @@ function render_modal_status(node, radioNet) {
], [ ' | ', E('br'), E('br'), E('br'), E('br'), E('br'), ' | ', E('br'), ' | ' ]);
if (!is_assoc)
- L.dom.append(node.lastElementChild, E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated')));
+ dom.append(node.lastElementChild, E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated')));
return node;
}
function format_wifirate(rate) {
- var s = '%.1f Mbit/s, %dMHz'.format(rate.rate / 1000, rate.mhz);
-
- if (rate.ht || rate.vht) {
- if (rate.vht) s += ', VHT-MCS %d'.format(rate.mcs);
- if (rate.nss) s += ', VHT-NSS %d'.format(rate.nss);
- if (rate.ht) s += ', MCS %s'.format(rate.mcs);
- if (rate.short_gi) s += ', Short GI';
+ var s = '%.1f\xa0%s, %d\xa0%s'.format(rate.rate / 1000, _('Mbit/s'), rate.mhz, _('MHz')),
+ ht = rate.ht, vht = rate.vht,
+ mhz = rate.mhz, nss = rate.nss,
+ mcs = rate.mcs, sgi = rate.short_gi;
+
+ if (ht || vht) {
+ if (vht) s += ', VHT-MCS\xa0%d'.format(mcs);
+ if (nss) s += ', VHT-NSS\xa0%d'.format(nss);
+ if (ht) s += ', MCS\xa0%s'.format(mcs);
+ if (sgi) s += ', ' + _('Short GI').replace(/ /g, '\xa0');
}
return s;
@@ -167,7 +221,7 @@ function radio_restart(id, ev) {
btn.disabled = true;
dsc.setAttribute('restart', '');
- L.dom.content(dsc, E('em', _('Device is restarting…')));
+ dom.content(dsc, E('em', _('Device is restarting…')));
}
function network_updown(id, map, ev) {
@@ -210,6 +264,31 @@ function next_free_sid(offset) {
return sid;
}
+function add_dependency_permutations(o, deps) {
+ var res = null;
+
+ for (var key in deps) {
+ if (!deps.hasOwnProperty(key) || !Array.isArray(deps[key]))
+ continue;
+
+ var list = deps[key],
+ tmp = [];
+
+ for (var j = 0; j < list.length; j++) {
+ for (var k = 0; k < (res ? res.length : 1); k++) {
+ var item = (res ? Object.assign({}, res[k]) : {});
+ item[key] = list[j];
+ tmp.push(item);
+ }
+ }
+
+ res = tmp;
+ }
+
+ for (var i = 0; i < (res ? res.length : 0); i++)
+ o.depends(res[i]);
+}
+
var CBIWifiFrequencyValue = form.Value.extend({
callFrequencyList: rpc.declare({
object: 'iwinfo',
@@ -358,13 +437,14 @@ var CBIWifiFrequencyValue = form.Value.extend({
renderWidget: function(section_id, option_index, cfgvalue) {
var elem = E('div');
- L.dom.content(elem, [
+ dom.content(elem, [
E('label', { 'style': 'float:left; margin-right:3px' }, [
_('Mode'), E('br'),
E('select', {
'class': 'mode',
'style': 'width:auto',
- 'change': L.bind(this.toggleWifiMode, this, elem)
+ 'change': L.bind(this.toggleWifiMode, this, elem),
+ 'disabled': (this.disabled != null) ? this.disabled : this.map.readonly
})
]),
E('label', { 'style': 'float:left; margin-right:3px' }, [
@@ -372,21 +452,24 @@ var CBIWifiFrequencyValue = form.Value.extend({
E('select', {
'class': 'band',
'style': 'width:auto',
- 'change': L.bind(this.toggleWifiBand, this, elem)
+ 'change': L.bind(this.toggleWifiBand, this, elem),
+ 'disabled': (this.disabled != null) ? this.disabled : this.map.readonly
})
]),
E('label', { 'style': 'float:left; margin-right:3px' }, [
_('Channel'), E('br'),
E('select', {
'class': 'channel',
- 'style': 'width:auto'
+ 'style': 'width:auto',
+ 'disabled': (this.disabled != null) ? this.disabled : this.map.readonly
})
]),
E('label', { 'style': 'float:left; margin-right:3px' }, [
_('Width'), E('br'),
E('select', {
'class': 'htmode',
- 'style': 'width:auto'
+ 'style': 'width:auto',
+ 'disabled': (this.disabled != null) ? this.disabled : this.map.readonly
})
]),
E('br', { 'style': 'clear:left' })
@@ -446,7 +529,7 @@ var CBIWifiTxPowerValue = form.ListValue.extend({
var widget = form.ListValue.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
widget.firstElementChild.style.width = 'auto';
- L.dom.append(widget, E('span', [
+ dom.append(widget, E('span', [
' - ', _('Current power'), ': ',
E('span', [ this.powerval != null ? '%d dBm'.format(this.powerval) : E('em', _('unknown')) ]),
this.poweroff ? ' + %d dB offset = %s dBm'.format(this.poweroff, this.powerval != null ? this.powerval + this.poweroff : '?') : ''
@@ -490,7 +573,7 @@ var CBIWifiCountryValue = form.Value.extend({
}
});
-return L.view.extend({
+return view.extend({
poll_status: function(map, data) {
var rows = map.querySelectorAll('.cbi-section-table-row[data-sid]');
@@ -504,23 +587,23 @@ return L.view.extend({
busy = btns[0].classList.contains('spinning') || btns[1].classList.contains('spinning') || btns[2].classList.contains('spinning');
if (radioDev) {
- L.dom.content(badge, render_radio_badge(radioDev));
- L.dom.content(stat, render_radio_status(radioDev, data[2].filter(function(n) { return n.getWifiDeviceName() == radioDev.getName() })));
+ dom.content(badge, render_radio_badge(radioDev));
+ dom.content(stat, render_radio_status(radioDev, data[2].filter(function(n) { return n.getWifiDeviceName() == radioDev.getName() })));
}
else {
- L.dom.content(badge, render_network_badge(radioNet));
- L.dom.content(stat, render_network_status(radioNet));
+ dom.content(badge, render_network_badge(radioNet));
+ dom.content(stat, render_network_status(radioNet));
}
if (stat.hasAttribute('restart'))
- L.dom.content(stat, E('em', _('Device is restarting…')));
+ dom.content(stat, E('em', _('Device is restarting…')));
- btns[0].disabled = busy;
- btns[1].disabled = busy;
- btns[2].disabled = busy;
+ btns[0].disabled = isReadonlyView || busy;
+ btns[1].disabled = (isReadonlyView && radioDev) || busy;
+ btns[2].disabled = isReadonlyView || busy;
}
- var table = document.querySelector('wifi_assoclist_table'),
+ var table = document.querySelector('#wifi_assoclist_table'),
hosts = data[0],
trows = [];
@@ -530,27 +613,65 @@ return L.view.extend({
ipv4 = hosts.getIPAddrByMACAddr(bss.mac),
ipv6 = hosts.getIP6AddrByMACAddr(bss.mac);
- trows.push([
- E('span', { 'class': 'ifacebadge' }, [
+ var hint;
+
+ if (name && ipv4 && ipv6)
+ hint = '%s <span class="hide-xs">(%s, %s)</span>'.format(name, ipv4, ipv6);
+ else if (name && (ipv4 || ipv6))
+ hint = '%s <span class="hide-xs">(%s)</span>'.format(name, ipv4 || ipv6);
+ else
+ hint = name || ipv4 || ipv6 || '?';
+
+ var row = [
+ E('span', {
+ 'class': 'ifacebadge',
+ 'data-ifname': bss.network.getIfname(),
+ 'data-ssid': bss.network.getSSID()
+ }, [
E('img', {
'src': L.resource('icons/wifi%s.png').format(bss.network.isUp() ? '' : '_disabled'),
'title': bss.radio.getI18n()
}),
- ' %s '.format(bss.network.getShortName()),
- E('small', '(%s)'.format(bss.network.getIfname()))
+ E('span', [
+ ' %s '.format(bss.network.getShortName()),
+ 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 cbi-section-actions'}));
+
+ row.push(E('button', {
+ 'class': 'cbi-button cbi-button-remove',
+ 'click': L.bind(function(net, mac, ev) {
+ 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),
+ 'disabled': isReadonlyView || null
+ }, [ _('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');
@@ -772,7 +893,7 @@ return L.view.extend({
o.default = o.enabled;
o = ss.taboption('advanced', form.Value, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.'));
- o.datatype = 'range(0,114750)';
+ o.datatype = 'or(range(0,114750),"auto")';
o.placeholder = 'auto';
o = ss.taboption('advanced', form.Value, 'frag', _('Fragmentation Threshold'));
@@ -835,7 +956,7 @@ return L.view.extend({
o = ss.taboption('general', form.Value, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>'));
o.datatype = 'macaddr';
- o = ss.taboption('general', widgets.NetworkSelect, 'network', _('Network'), _('Choose the network(s) you want to attach to this wireless interface or fill out the <em>create</em> field to define a new network.'));
+ 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.'));
o.rmempty = true;
o.multiple = true;
o.novirtual = true;
@@ -1015,7 +1136,7 @@ return L.view.extend({
var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id),
co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id);
- if (value == 'wpa' || value == 'wpa2')
+ if (value == 'wpa' || value == 'wpa2' || value == 'wpa3' || value == 'wpa3-mixed')
uci.unset('wireless', section_id, 'key');
if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp'))
@@ -1027,6 +1148,8 @@ return L.view.extend({
o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher'));
o.depends('encryption', 'wpa');
o.depends('encryption', 'wpa2');
+ o.depends('encryption', 'wpa3');
+ o.depends('encryption', 'wpa3-mixed');
o.depends('encryption', 'psk');
o.depends('encryption', 'psk2');
o.depends('encryption', 'wpa-mixed');
@@ -1068,9 +1191,16 @@ return L.view.extend({
var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'),
has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe');
+ // Probe Suite-B support
+ var has_ap_eap192 = L.hasSystemFeature('hostapd', 'suiteb192'),
+ has_sta_eap192 = L.hasSystemFeature('wpasupplicant', 'suiteb192');
+
+ // Probe WEP support
+ var has_ap_wep = L.hasSystemFeature('hostapd', 'wep'),
+ has_sta_wep = L.hasSystemFeature('wpasupplicant', 'wep');
if (has_hostapd || has_supplicant) {
- crypto_modes.push(['psk2', 'WPA2-PSK', 33]);
+ crypto_modes.push(['psk2', 'WPA2-PSK', 35]);
crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]);
crypto_modes.push(['psk', 'WPA-PSK', 21]);
}
@@ -1083,8 +1213,18 @@ return L.view.extend({
crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]);
}
+ if (has_ap_wep || has_sta_wep) {
+ crypto_modes.push(['wep-open', _('WEP Open System'), 11]);
+ crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]);
+ }
+
if (has_ap_eap || has_sta_eap) {
- crypto_modes.push(['wpa2', 'WPA2-EAP', 32]);
+ if (has_ap_eap192 || has_sta_eap192) {
+ crypto_modes.push(['wpa3', 'WPA3-EAP', 33]);
+ crypto_modes.push(['wpa3-mixed', 'WPA2-EAP/WPA3-EAP Mixed Mode', 32]);
+ }
+
+ crypto_modes.push(['wpa2', 'WPA2-EAP', 34]);
crypto_modes.push(['wpa', 'WPA-EAP', 20]);
}
@@ -1094,8 +1234,8 @@ return L.view.extend({
encr.crypto_support = {
'ap': {
- 'wep-open': true,
- 'wep-shared': true,
+ 'wep-open': has_ap_wep || _('Requires hostapd with WEP support'),
+ 'wep-shared': has_ap_wep || _('Requires hostapd with WEP support'),
'psk': has_hostapd || _('Requires hostapd'),
'psk2': has_hostapd || _('Requires hostapd'),
'psk-mixed': has_hostapd || _('Requires hostapd'),
@@ -1103,11 +1243,13 @@ return L.view.extend({
'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'),
'wpa': has_ap_eap || _('Requires hostapd with EAP support'),
'wpa2': has_ap_eap || _('Requires hostapd with EAP support'),
+ 'wpa3': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
+ 'wpa3-mixed': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'),
'owe': has_ap_owe || _('Requires hostapd with OWE support')
},
'sta': {
- 'wep-open': true,
- 'wep-shared': true,
+ 'wep-open': has_sta_wep || _('Requires wpa-supplicant with WEP support'),
+ 'wep-shared': has_sta_wep || _('Requires wpa-supplicant with WEP support'),
'psk': has_supplicant || _('Requires wpa-supplicant'),
'psk2': has_supplicant || _('Requires wpa-supplicant'),
'psk-mixed': has_supplicant || _('Requires wpa-supplicant'),
@@ -1115,6 +1257,8 @@ return L.view.extend({
'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'),
'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'),
+ 'wpa3': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
+ 'wpa3-mixed': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'),
'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support')
},
'adhoc': {
@@ -1159,10 +1303,10 @@ return L.view.extend({
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(['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] });
@@ -1177,74 +1321,47 @@ return L.view.extend({
o = ss.taboption('encryption', form.Value, 'auth_server', _('Radius-Authentication-Server'));
- o.depends({ mode: 'ap', encryption: 'wpa' });
- o.depends({ mode: 'ap', encryption: 'wpa2' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.datatype = 'host(0)';
o = ss.taboption('encryption', form.Value, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812));
- o.depends({ mode: 'ap', encryption: 'wpa' });
- o.depends({ mode: 'ap', encryption: 'wpa2' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.datatype = 'port';
o = ss.taboption('encryption', form.Value, 'auth_secret', _('Radius-Authentication-Secret'));
- o.depends({ mode: 'ap', encryption: 'wpa' });
- o.depends({ mode: 'ap', encryption: 'wpa2' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.password = true;
o = ss.taboption('encryption', form.Value, 'acct_server', _('Radius-Accounting-Server'));
- o.depends({ mode: 'ap', encryption: 'wpa' });
- o.depends({ mode: 'ap', encryption: 'wpa2' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.datatype = 'host(0)';
o = ss.taboption('encryption', form.Value, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813));
- o.depends({ mode: 'ap', encryption: 'wpa' });
- o.depends({ mode: 'ap', encryption: 'wpa2' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.datatype = 'port';
o = ss.taboption('encryption', form.Value, 'acct_secret', _('Radius-Accounting-Secret'));
- o.depends({ mode: 'ap', encryption: 'wpa' });
- o.depends({ mode: 'ap', encryption: 'wpa2' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.password = true;
o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client'));
- o.depends({ mode: 'ap', encryption: 'wpa' });
- o.depends({ mode: 'ap', encryption: 'wpa2' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.datatype = 'host(0)';
o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Default %d').format(3799));
- o.depends({ mode: 'ap', encryption: 'wpa' });
- o.depends({ mode: 'ap', encryption: 'wpa2' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.datatype = 'port';
o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret'));
- o.depends({ mode: 'ap', encryption: 'wpa' });
- o.depends({ mode: 'ap', encryption: 'wpa2' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.rmempty = true;
o.password = true;
@@ -1312,29 +1429,13 @@ return L.view.extend({
var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap');
o = ss.taboption('encryption', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain'));
- o.depends({ mode: 'ap', encryption: 'wpa' });
- o.depends({ mode: 'ap', encryption: 'wpa2' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
- if (has_80211r) {
- o.depends({ mode: 'ap', encryption: 'psk' });
- o.depends({ mode: 'ap', encryption: 'psk2' });
- o.depends({ mode: 'ap', encryption: 'psk-mixed' });
- o.depends({ mode: 'ap', encryption: 'sae' });
- o.depends({ mode: 'ap', encryption: 'sae-mixed' });
- o.depends({ mode: 'ap-wds', encryption: 'psk' });
- o.depends({ mode: 'ap-wds', encryption: 'psk2' });
- o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
- o.depends({ mode: 'ap-wds', encryption: 'sae' });
- o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
- }
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+ if (has_80211r)
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] });
o.rmempty = true;
o = ss.taboption('encryption', form.Value, 'nasid', _('NAS ID'), _('Used for two different purposes: RADIUS NAS ID and 802.11r R0KH-ID. Not needed with normal WPA(2)-PSK.'));
- o.depends({ mode: 'ap', encryption: 'wpa' });
- o.depends({ mode: 'ap', encryption: 'wpa2' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o.depends({ ieee80211r: '1' });
o.rmempty = true;
@@ -1392,34 +1493,43 @@ return L.view.extend({
o.value('ttls', 'TTLS');
o.value('peap', 'PEAP');
o.value('fast', 'FAST');
- o.depends({ mode: 'sta', encryption: 'wpa' });
- o.depends({ mode: 'sta', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+
+ 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"));
+ o.enabled = '1';
+ o.disabled = '0';
+ o.default = o.disabled;
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+ o.validate = function(section_id, value) {
+ if (value == '1' && !L.hasSystemFeature('cabundle')) {
+ return _("This option cannot be used because the ca-bundle package is not installed.");
+ }
+ return true;
+ };
o = ss.taboption('encryption', form.FileUpload, 'ca_cert', _('Path to CA-Certificate'));
- o.depends({ mode: 'sta', encryption: 'wpa' });
- o.depends({ mode: 'sta', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], ca_cert_usesystem: ['0'] });
+
+ 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"));
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+
+ 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"));
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+
+ 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)"));
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+
+ 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)"));
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] });
o = ss.taboption('encryption', form.FileUpload, 'client_cert', _('Path to Client-Certificate'));
- o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
- o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['tls'] });
o = ss.taboption('encryption', form.FileUpload, 'priv_key', _('Path to Private Key'));
- o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
- o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['tls'] });
o = ss.taboption('encryption', form.Value, 'priv_key_pwd', _('Password of Private Key'));
- o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
- o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['tls'] });
o.password = true;
o = ss.taboption('encryption', form.ListValue, 'auth', _('Authentication'));
@@ -1427,22 +1537,11 @@ return L.view.extend({
o.value('CHAP', 'CHAP');
o.value('MSCHAP', 'MSCHAP');
o.value('MSCHAPV2', 'MSCHAPv2');
- o.value('EAP-GTC');
- o.value('EAP-MD5');
- o.value('EAP-MSCHAPV2');
- o.value('EAP-TLS');
- o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
- o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
- o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
- o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
- o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
- o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
+ o.value('EAP-GTC', 'EAP-GTC');
+ o.value('EAP-MD5', 'EAP-MD5');
+ o.value('EAP-MSCHAPV2', 'EAP-MSCHAPv2');
+ o.value('EAP-TLS', 'EAP-TLS');
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'ttls'] });
o.validate = function(section_id, value) {
var eo = this.section.children.filter(function(o) { return o.option == 'eap_type' })[0],
@@ -1454,80 +1553,51 @@ return L.view.extend({
return true;
};
+ 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"));
+ o.enabled = '1';
+ o.disabled = '0';
+ o.default = o.disabled;
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
+ o.validate = function(section_id, value) {
+ if (value == '1' && !L.hasSystemFeature('cabundle')) {
+ return _("This option cannot be used because the ca-bundle package is not installed.");
+ }
+ return true;
+ };
+
o = ss.taboption('encryption', form.FileUpload, 'ca_cert2', _('Path to inner CA-Certificate'));
- o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
- o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'], ca_cert2_usesystem: ['0'] });
+
+ 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"));
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
+
+ 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"));
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
+
+ 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)"));
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
+
+ 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)"));
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
o = ss.taboption('encryption', form.FileUpload, 'client_cert2', _('Path to inner Client-Certificate'));
- o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
- o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
o = ss.taboption('encryption', form.FileUpload, 'priv_key2', _('Path to inner Private Key'));
- o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
- o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
o = ss.taboption('encryption', form.Value, 'priv_key2_pwd', _('Password of inner Private Key'));
- o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' });
- o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] });
o.password = true;
o = ss.taboption('encryption', form.Value, 'identity', _('Identity'));
- o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
- o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
- o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
- o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
- o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
- o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
- o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
- o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'tls', 'ttls'] });
o = ss.taboption('encryption', form.Value, 'anonymous_identity', _('Anonymous Identity'));
- o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
- o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
- o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
- o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
- o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
- o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
- o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' });
- o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'tls', 'ttls'] });
o = ss.taboption('encryption', form.Value, 'password', _('Password'));
- o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' });
- o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' });
- o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' });
- o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' });
- o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' });
- o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' });
- o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' });
+ add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'ttls'] });
o.password = true;
@@ -1538,32 +1608,10 @@ return L.view.extend({
o.value('', _('Disabled'));
o.value('1', _('Optional'));
o.value('2', _('Required'));
- o.depends({ mode: 'ap', encryption: 'wpa2' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
- o.depends({ mode: 'ap', encryption: 'psk2' });
- o.depends({ mode: 'ap', encryption: 'psk-mixed' });
- o.depends({ mode: 'ap', encryption: 'sae' });
- o.depends({ mode: 'ap', encryption: 'sae-mixed' });
- o.depends({ mode: 'ap', encryption: 'owe' });
- o.depends({ mode: 'ap-wds', encryption: 'psk2' });
- o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
- o.depends({ mode: 'ap-wds', encryption: 'sae' });
- o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
- o.depends({ mode: 'ap-wds', encryption: 'owe' });
- o.depends({ mode: 'sta', encryption: 'wpa2' });
- o.depends({ mode: 'sta-wds', encryption: 'wpa2' });
- o.depends({ mode: 'sta', encryption: 'psk2' });
- o.depends({ mode: 'sta', encryption: 'psk-mixed' });
- o.depends({ mode: 'sta', encryption: 'sae' });
- o.depends({ mode: 'sta', encryption: 'sae-mixed' });
- o.depends({ mode: 'sta', encryption: 'owe' });
- o.depends({ mode: 'sta-wds', encryption: 'psk2' });
- o.depends({ mode: 'sta-wds', encryption: 'psk-mixed' });
- o.depends({ mode: 'sta-wds', encryption: 'sae' });
- o.depends({ mode: 'sta-wds', encryption: 'sae-mixed' });
- o.depends({ mode: 'sta-wds', encryption: 'owe' });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds', 'sta', 'sta-wds'], encryption: ['owe', 'psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
+
o.defaults = {
- '2': [{ encryption: 'sae' }, { encryption: 'owe' }],
+ '2': [{ encryption: 'sae' }, { encryption: 'owe' }, { encryption: 'wpa3' }, { encryption: 'wpa3-mixed' }],
'1': [{ encryption: 'sae-mixed'}],
'': []
};
@@ -1584,16 +1632,7 @@ return L.view.extend({
};
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.'));
- o.depends({ mode: 'ap', encryption: 'wpa2' });
- o.depends({ mode: 'ap', encryption: 'psk2' });
- o.depends({ mode: 'ap', encryption: 'psk-mixed' });
- o.depends({ mode: 'ap', encryption: 'sae' });
- o.depends({ mode: 'ap', encryption: 'sae-mixed' });
- o.depends({ mode: 'ap-wds', encryption: 'wpa2' });
- o.depends({ mode: 'ap-wds', encryption: 'psk2' });
- o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' });
- o.depends({ mode: 'ap-wds', encryption: 'sae' });
- o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' });
+ add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] });
if (L.hasSystemFeature('hostapd', 'cli') && L.hasSystemFeature('wpasupplicant')) {
o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE'))
@@ -1629,27 +1668,37 @@ return L.view.extend({
])
]);
+ var stop = E('button', {
+ 'class': 'btn',
+ 'click': L.bind(this.handleScanStartStop, this),
+ 'style': 'display:none',
+ 'data-state': 'stop'
+ }, _('Stop refresh'));
+
cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...')));
var md = ui.showModal(_('Join Network: Wireless Scan'), [
table,
- E('div', { 'class': 'right' },
+ E('div', { 'class': 'right' }, [
+ stop,
+ ' ',
E('button', {
'class': 'btn',
'click': L.bind(this.handleScanAbort, this)
- }, _('Dismiss')))
+ }, _('Dismiss'))
+ ])
]);
md.style.maxWidth = '90%';
md.style.maxHeight = 'none';
- this.pollFn = L.bind(this.handleScanRefresh, this, radioDev, {}, table);
+ this.pollFn = L.bind(this.handleScanRefresh, this, radioDev, {}, table, stop);
- L.Poll.add(this.pollFn);
- L.Poll.start();
+ poll.add(this.pollFn);
+ poll.start();
};
- s.handleScanRefresh = function(radioDev, scanCache, table) {
+ s.handleScanRefresh = function(radioDev, scanCache, table, stop) {
return radioDev.getScanList().then(L.bind(function(results) {
var rows = [];
@@ -1686,7 +1735,7 @@ return L.view.extend({
rows.push([
E('span', { 'style': s }, render_signal_badge(q, res.signal, res.noise)),
- E('span', { 'style': s }, '%h'.format(res.ssid)),
+ E('span', { 'style': s }, (res.ssid != null) ? '%h'.format(res.ssid) : E('em', _('hidden'))),
E('span', { 'style': s }, '%d'.format(res.channel)),
E('span', { 'style': s }, '%h'.format(res.mode)),
E('span', { 'style': s }, '%h'.format(res.bssid)),
@@ -1701,18 +1750,39 @@ return L.view.extend({
}
cbi_update_table(table, rows);
+
+ stop.disabled = false;
+ stop.style.display = '';
+ stop.classList.remove('spinning');
}, this));
};
+ s.handleScanStartStop = function(ev) {
+ var btn = ev.currentTarget;
+
+ if (btn.getAttribute('data-state') == 'stop') {
+ poll.remove(this.pollFn);
+ btn.firstChild.data = _('Start refresh');
+ btn.setAttribute('data-state', 'start');
+ }
+ else {
+ poll.add(this.pollFn);
+ btn.firstChild.data = _('Stop refresh');
+ btn.setAttribute('data-state', 'stop');
+ btn.classList.add('spinning');
+ btn.disabled = true;
+ }
+ };
+
s.handleScanAbort = function(ev) {
- var md = L.dom.parent(ev.target, 'div[aria-modal="true"]');
+ var md = dom.parent(ev.target, 'div[aria-modal="true"]');
if (md) {
md.style.maxWidth = '';
md.style.maxHeight = '';
}
ui.hideModal();
- L.Poll.remove(this.pollFn);
+ poll.remove(this.pollFn);
this.pollFn = null;
};
@@ -1720,10 +1790,12 @@ return L.view.extend({
s.handleJoinConfirm = function(radioDev, bss, form, ev) {
var nameopt = L.toArray(form.lookupOption('name', '_new_'))[0],
passopt = L.toArray(form.lookupOption('password', '_new_'))[0],
+ bssidopt = L.toArray(form.lookupOption('bssid', '_new_'))[0],
zoneopt = L.toArray(form.lookupOption('zone', '_new_'))[0],
replopt = L.toArray(form.lookupOption('replace', '_new_'))[0],
nameval = (nameopt && nameopt.isValid('_new_')) ? nameopt.formvalue('_new_') : null,
passval = (passopt && passopt.isValid('_new_')) ? passopt.formvalue('_new_') : null,
+ bssidval = (bssidopt && bssidopt.isValid('_new_')) ? bssidopt.formvalue('_new_') : null,
zoneval = zoneopt ? zoneopt.formvalue('_new_') : null,
enc = L.isObject(bss.encryption) ? bss.encryption : null,
is_wep = (enc && Array.isArray(enc.wep)),
@@ -1759,10 +1831,15 @@ return L.view.extend({
uci.set('wireless', section_id, 'mode', (bss.mode == 'Ad-Hoc') ? 'adhoc' : 'sta');
uci.set('wireless', section_id, 'network', nameval);
- if (bss.ssid != null)
+ if (bss.ssid != null) {
uci.set('wireless', section_id, 'ssid', bss.ssid);
- else if (bss.bssid != null)
+
+ if (bssidval == '1')
+ uci.set('wireless', section_id, 'bssid', bss.bssid);
+ }
+ else if (bss.bssid != null) {
uci.set('wireless', section_id, 'bssid', bss.bssid);
+ }
if (is_sae) {
uci.set('wireless', section_id, 'encryption', 'sae');
@@ -1787,6 +1864,9 @@ return L.view.extend({
uci.set('wireless', section_id, 'key', '1');
uci.set('wireless', section_id, 'key1', passval);
}
+ else {
+ uci.set('wireless', section_id, 'encryption', 'none');
+ }
return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) {
firewall.deleteNetwork(net.getName());
@@ -1813,7 +1893,16 @@ return L.view.extend({
enc = L.isObject(bss.encryption) ? bss.encryption : null,
is_wep = (enc && Array.isArray(enc.wep)),
is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' || a == 'sae' })),
- replace, passphrase, name, zone;
+ replace, passphrase, name, bssid, zone;
+
+ var nameUsed = function(name) {
+ var s = uci.get('network', name);
+ if (s != null && s['.type'] != 'interface')
+ return true;
+
+ var net = (s != null) ? network.instantiateNetwork(name) : null;
+ return (net != null && !net.isEmpty());
+ };
s2.render = function() {
return Promise.all([
@@ -1829,13 +1918,13 @@ return L.view.extend({
name.default = 'wwan';
name.rmempty = false;
name.validate = function(section_id, value) {
- if (uci.get('network', value))
+ if (nameUsed(value))
return _('The network name is already used');
return true;
};
- for (var i = 2; uci.get('network', name.default); i++)
+ for (var i = 2; nameUsed(name.default); i++)
name.default = 'wwan%d'.format(i);
if (is_wep || is_psk) {
@@ -1845,7 +1934,12 @@ return L.view.extend({
passphrase.rmempty = false;
}
- zone = s2.option(widgets.ZoneSelect, 'zone', _('Create / Assign firewall-zone'), _('Choose the firewall zone you want to assign to this interface. Select <em>unspecified</em> to remove the interface from the associated zone or fill out the <em>create</em> field to define a new zone and attach the interface to it.'));
+ if (bss.ssid != null) {
+ 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));
+ bssid.default = '0';
+ }
+
+ 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.'));
zone.default = 'wan';
return m2.render().then(L.bind(function(nodes) {
@@ -1908,7 +2002,7 @@ return L.view.extend({
};
return m.render().then(L.bind(function(m, nodes) {
- L.Poll.add(L.bind(function() {
+ poll.add(L.bind(function() {
var section_ids = m.children[0].cfgsections(),
tasks = [ network.getHostHints(), network.getWifiDevices() ];
@@ -1969,13 +2063,13 @@ return L.view.extend({
.then(L.bind(this.poll_status, this, nodes));
}, this), 5);
- var table = E('div', { 'class': 'table', 'id': 'wifi_assoclist_table' }, [
+ var table = E('div', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [
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 nowrap' }, _('Signal / Noise')),
- E('div', { 'class': 'th nowrap' }, _('RX Rate / TX Rate'))
+ E('div', { 'class': 'th' }, _('Host')),
+ E('div', { 'class': 'th' }, _('Signal / Noise')),
+ E('div', { 'class': 'th' }, _('RX Rate / TX Rate'))
])
]);