summaryrefslogtreecommitdiffhomepage
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/firewall.js8
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/form.js1
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/tools/widgets.js234
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/ui.js10
-rw-r--r--modules/luci-base/po/ru/base.po4
-rw-r--r--modules/luci-base/po/uk/base.po4
-rwxr-xr-xmodules/luci-base/root/usr/libexec/rpcd/luci35
-rw-r--r--modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json14
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_join.js2
-rw-r--r--modules/luci-mod-system/htdocs/luci-static/resources/view/system/leds.js78
10 files changed, 330 insertions, 60 deletions
diff --git a/modules/luci-base/htdocs/luci-static/resources/firewall.js b/modules/luci-base/htdocs/luci-static/resources/firewall.js
index d034d6e010..9ae14e16d9 100644
--- a/modules/luci-base/htdocs/luci-static/resources/firewall.js
+++ b/modules/luci-base/htdocs/luci-static/resources/firewall.js
@@ -375,6 +375,14 @@ Zone = AbstractFirewallItem.extend({
this.set('network', ' ');
},
+ getDevices: function() {
+ return L.toArray(this.get('device'));
+ },
+
+ getSubnets: function() {
+ return L.toArray(this.get('subnet'));
+ },
+
getForwardingsBy: function(what) {
var sections = uci.sections('firewall', 'forwarding'),
forwards = [];
diff --git a/modules/luci-base/htdocs/luci-static/resources/form.js b/modules/luci-base/htdocs/luci-static/resources/form.js
index 508e2c4857..58d8f7100c 100644
--- a/modules/luci-base/htdocs/luci-static/resources/form.js
+++ b/modules/luci-base/htdocs/luci-static/resources/form.js
@@ -1461,6 +1461,7 @@ var CBIListValue = CBIValue.extend({
size: this.size,
sort: this.keylist,
optional: this.rmempty || this.optional,
+ placeholder: this.placeholder,
validate: L.bind(this.validate, this, section_id)
});
diff --git a/modules/luci-base/htdocs/luci-static/resources/tools/widgets.js b/modules/luci-base/htdocs/luci-static/resources/tools/widgets.js
index 7f2997f173..39e5aa1655 100644
--- a/modules/luci-base/htdocs/luci-static/resources/tools/widgets.js
+++ b/modules/luci-base/htdocs/luci-static/resources/tools/widgets.js
@@ -30,8 +30,22 @@ var CBIZoneSelect = form.ListValue.extend({
renderWidget: function(section_id, option_index, cfgvalue) {
var values = L.toArray((cfgvalue != null) ? cfgvalue : this.default),
+ isOutputOnly = false,
choices = {};
+ if (this.option == 'dest') {
+ for (var i = 0; i < this.section.children.length; i++) {
+ var opt = this.section.children[i];
+ if (opt.option == 'src') {
+ var val = opt.cfgvalue(section_id) || opt.default;
+ isOutputOnly = (val == null || val == '');
+ break;
+ }
+ }
+
+ this.title = isOutputOnly ? _('Output zone') : _('Destination zone');
+ }
+
if (this.allowlocal) {
choices[''] = E('span', {
'class': 'zonebadge',
@@ -39,7 +53,7 @@ var CBIZoneSelect = form.ListValue.extend({
}, [
E('strong', _('Device')),
(this.allowany || this.allowlocal)
- ? ' (%s)'.format(this.alias != 'dest' ? _('output') : _('input')) : ''
+ ? ' (%s)'.format(this.option != 'dest' ? _('output') : _('input')) : ''
]);
}
else if (!this.multiple && (this.rmempty || this.optional)) {
@@ -55,7 +69,7 @@ var CBIZoneSelect = form.ListValue.extend({
'style': 'background-color:' + firewall.getColorForName(null)
}, [
E('strong', _('Any zone')),
- (this.allowany && this.allowlocal) ? ' (%s)'.format(_('forward')) : ''
+ (this.allowany && this.allowlocal && !isOutputOnly) ? ' (%s)'.format(_('forward')) : ''
]);
}
@@ -120,7 +134,64 @@ var CBIZoneSelect = form.ListValue.extend({
'</li>'
});
- return widget.render();
+ var elem = widget.render();
+
+ if (this.option == 'src') {
+ elem.addEventListener('cbi-dropdown-change', L.bind(function(ev) {
+ var opt = this.map.lookupOption('dest', section_id),
+ val = ev.detail.instance.getValue();
+
+ if (opt == null)
+ return;
+
+ var cbid = opt[0].cbid(section_id),
+ label = document.querySelector('label[for="widget.%s"]'.format(cbid)),
+ node = document.getElementById(cbid);
+
+ L.dom.content(label, val == '' ? _('Output zone') : _('Destination zone'));
+
+ if (val == '') {
+ if (L.dom.callClassMethod(node, 'getValue') == '')
+ L.dom.callClassMethod(node, 'setValue', '*');
+
+ var emptyval = node.querySelector('[data-value=""]'),
+ anyval = node.querySelector('[data-value="*"]');
+
+ L.dom.content(anyval.querySelector('span'), E('strong', _('Any zone')));
+
+ if (emptyval != null)
+ emptyval.parentNode.removeChild(emptyval);
+ }
+ else {
+ var anyval = node.querySelector('[data-value="*"]'),
+ emptyval = node.querySelector('[data-value=""]');
+
+ if (emptyval == null) {
+ emptyval = anyval.cloneNode(true);
+ emptyval.removeAttribute('display');
+ emptyval.removeAttribute('selected');
+ emptyval.setAttribute('data-value', '');
+ }
+
+ L.dom.content(emptyval.querySelector('span'), [
+ E('strong', _('Device')), ' (%s)'.format(_('input'))
+ ]);
+
+ L.dom.content(anyval.querySelector('span'), [
+ E('strong', _('Any zone')), ' (%s)'.format(_('forward'))
+ ]);
+
+ anyval.parentNode.insertBefore(emptyval, anyval);
+ }
+
+ }, this));
+ }
+ else if (isOutputOnly) {
+ var emptyval = elem.querySelector('[data-value=""]');
+ emptyval.parentNode.removeChild(emptyval);
+ }
+
+ return elem;
},
});
@@ -128,10 +199,16 @@ var CBIZoneForwards = form.DummyValue.extend({
__name__: 'CBI.ZoneForwards',
load: function(section_id) {
- return Promise.all([ firewall.getDefaults(), firewall.getZones(), network.getNetworks() ]).then(L.bind(function(dzn) {
- this.defaults = dzn[0];
- this.zones = dzn[1];
- this.networks = dzn[2];
+ return Promise.all([
+ firewall.getDefaults(),
+ firewall.getZones(),
+ network.getNetworks(),
+ network.getDevices()
+ ]).then(L.bind(function(dznd) {
+ this.defaults = dznd[0];
+ this.zones = dznd[1];
+ this.networks = dznd[2];
+ this.devices = dznd[3];
return this.super('load', section_id);
}, this));
@@ -140,6 +217,8 @@ var CBIZoneForwards = form.DummyValue.extend({
renderZone: function(zone) {
var name = zone.getName(),
networks = zone.getNetworks(),
+ devices = zone.getDevices(),
+ subnets = zone.getSubnets(),
ifaces = [];
for (var j = 0; j < networks.length; j++) {
@@ -152,21 +231,39 @@ var CBIZoneForwards = form.DummyValue.extend({
'class': 'ifacebadge' + (network.getName() == this.network ? ' ifacebadge-active' : '')
}, network.getName() + ': ');
- var devices = network.isBridge() ? network.getDevices() : L.toArray(network.getDevice());
+ var subdevs = network.isBridge() ? network.getDevices() : L.toArray(network.getDevice());
- for (var k = 0; k < devices.length && devices[k]; k++) {
+ for (var k = 0; k < subdevs.length && subdevs[k]; k++) {
span.appendChild(E('img', {
- 'title': devices[k].getI18n(),
- 'src': L.resource('icons/%s%s.png'.format(devices[k].getType(), devices[k].isUp() ? '' : '_disabled'))
+ 'title': subdevs[k].getI18n(),
+ 'src': L.resource('icons/%s%s.png'.format(subdevs[k].getType(), subdevs[k].isUp() ? '' : '_disabled'))
}));
}
- if (!devices.length)
+ if (!subdevs.length)
span.appendChild(E('em', _('(empty)')));
ifaces.push(span);
}
+ for (var i = 0; i < devices.length; i++) {
+ var device = this.devices.filter(function(dev) { return dev.getName() == devices[i] })[0],
+ title = device ? device.getI18n() : _('Absent Interface'),
+ type = device ? device.getType() : 'ethernet',
+ up = device ? device.isUp() : false;
+
+ ifaces.push(E('span', { 'class': 'ifacebadge' }, [
+ E('img', {
+ 'title': title,
+ 'src': L.resource('icons/%s%s.png'.format(type, up ? '' : '_disabled'))
+ }),
+ device ? device.getName() : devices[i]
+ ]));
+ }
+
+ if (subnets.length > 0)
+ ifaces.push(E('span', { 'class': 'ifacebadge' }, [ '{ %s }'.format(subnets.join('; ')) ]));
+
if (!ifaces.length)
ifaces.push(E('span', { 'class': 'ifacebadge' }, E('em', _('(empty)'))));
@@ -319,9 +416,120 @@ var CBINetworkSelect = form.ListValue.extend({
},
});
+var CBIDeviceSelect = form.ListValue.extend({
+ __name__: 'CBI.DeviceSelect',
+
+ load: function(section_id) {
+ return network.getDevices().then(L.bind(function(devices) {
+ this.devices = devices;
+
+ return this.super('load', section_id);
+ }, this));
+ },
+
+ filter: function(section_id, value) {
+ return true;
+ },
+
+ renderWidget: function(section_id, option_index, cfgvalue) {
+ var values = L.toArray((cfgvalue != null) ? cfgvalue : this.default),
+ choices = {},
+ checked = {},
+ order = [];
+
+ for (var i = 0; i < values.length; i++)
+ checked[values[i]] = true;
+
+ values = [];
+
+ if (!this.multiple && (this.rmempty || this.optional))
+ choices[''] = E('em', _('unspecified'));
+
+ for (var i = 0; i < this.devices.length; i++) {
+ var device = this.devices[i],
+ name = device.getName(),
+ type = device.getType();
+
+ if (name == 'lo' || name == this.exclude || !this.filter(section_id, name))
+ continue;
+
+ if (this.noaliases && type == 'alias')
+ continue;
+
+ if (this.nobridges && type == 'bridge')
+ continue;
+
+ if (this.noinactive && device.isUp() == false)
+ continue;
+
+ var item = E([
+ E('img', {
+ 'title': device.getI18n(),
+ 'src': L.resource('icons/%s%s.png'.format(type, device.isUp() ? '' : '_disabled'))
+ }),
+ E('span', { 'class': 'hide-open' }, [ name ]),
+ E('span', { 'class': 'hide-close'}, [ device.getI18n() ])
+ ]);
+
+ var networks = device.getNetworks();
+
+ if (networks.length > 0)
+ L.dom.append(item.lastChild, [ ' (', networks.join(', '), ')' ]);
+
+ if (checked[name])
+ values.push(name);
+
+ choices[name] = item;
+ order.push(name);
+ }
+
+ if (!this.nocreate) {
+ var keys = Object.keys(checked).sort();
+
+ for (var i = 0; i < keys.length; i++) {
+ if (choices.hasOwnProperty(keys[i]))
+ continue;
+
+ choices[keys[i]] = E([
+ E('img', {
+ 'title': _('Absent Interface'),
+ 'src': L.resource('icons/ethernet_disabled.png')
+ }),
+ E('span', { 'class': 'hide-open' }, [ keys[i] ]),
+ E('span', { 'class': 'hide-close'}, [ '%s: "%h"'.format(_('Absent Interface'), keys[i]) ])
+ ]);
+
+ values.push(keys[i]);
+ order.push(keys[i]);
+ }
+ }
+
+ var widget = new ui.Dropdown(this.multiple ? values : values[0], choices, {
+ id: this.cbid(section_id),
+ sort: order,
+ multiple: this.multiple,
+ optional: this.optional || this.rmempty,
+ select_placeholder: E('em', _('unspecified')),
+ display_items: this.display_size || this.size || 3,
+ dropdown_items: this.dropdown_size || this.size || 5,
+ validate: L.bind(this.validate, this, section_id),
+ create: !this.nocreate,
+ create_markup: '' +
+ '<li data-value="{{value}}">' +
+ '<img title="'+_('Custom Interface')+': &quot;{{value}}&quot;" src="'+L.resource('icons/ethernet_disabled.png')+'" />' +
+ '<span class="hide-open">{{value}}</span>' +
+ '<span class="hide-close">'+_('Custom Interface')+': "{{value}}"</span>' +
+ '</li>'
+ });
+
+ return widget.render();
+ },
+});
+
return L.Class.extend({
ZoneSelect: CBIZoneSelect,
ZoneForwards: CBIZoneForwards,
- NetworkSelect: CBINetworkSelect
+ NetworkSelect: CBINetworkSelect,
+ DeviceSelect: CBIDeviceSelect,
});
diff --git a/modules/luci-base/htdocs/luci-static/resources/ui.js b/modules/luci-base/htdocs/luci-static/resources/ui.js
index 9c16a9a0dd..43afc698f6 100644
--- a/modules/luci-base/htdocs/luci-static/resources/ui.js
+++ b/modules/luci-base/htdocs/luci-static/resources/ui.js
@@ -287,8 +287,8 @@ var UISelect = UIElement.extend({
this.node = frameEl;
if (this.options.widget == 'select') {
- this.setUpdateEvents(frameEl, 'change', 'click', 'blur');
- this.setChangeEvents(frameEl, 'change');
+ this.setUpdateEvents(frameEl.firstChild, 'change', 'click', 'blur');
+ this.setChangeEvents(frameEl.firstChild, 'change');
}
else {
var radioEls = frameEl.querySelectorAll('input[type="radio"]');
@@ -879,7 +879,7 @@ var UIDropdown = UIElement.extend({
else
markup = '<li data-value="{{value}}">{{value}}</li>';
- new_item = E(markup.replace(/{{value}}/g, item));
+ new_item = E(markup.replace(/{{value}}/g, '%h'.format(item)));
if (sbox.options.multiple) {
sbox.transformItem(sb, new_item);
@@ -1797,7 +1797,7 @@ return L.Class.extend({
return chg[1];
case 4:
- return "'" + chg[3].replace(/'/g, "'\"'\"'") + "'";
+ return "'%h'".format(chg[3].replace(/'/g, "'\"'\"'"));
default:
return chg[m1-1];
@@ -1929,7 +1929,7 @@ return L.Class.extend({
method: 'post',
timeout: L.env.apply_timeout * 1000,
query: L.ui.changes.confirm_auth
- }).then(call);
+ }).then(call, call);
}, delay);
};
diff --git a/modules/luci-base/po/ru/base.po b/modules/luci-base/po/ru/base.po
index f47419134f..d4b5a82211 100644
--- a/modules/luci-base/po/ru/base.po
+++ b/modules/luci-base/po/ru/base.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: LuCI: base\n"
"POT-Creation-Date: 2010-05-09 01:01+0300\n"
-"PO-Revision-Date: 2019-07-15 15:36+0300\n"
+"PO-Revision-Date: 2019-07-20 15:31+0300\n"
"Last-Translator: Anton Kikin <a.kikin@tano-systems.com>\n"
"Language-Team: http://cyber-place.ru\n"
"Language: ru\n"
@@ -3489,7 +3489,7 @@ msgstr "Не задано имя сети"
#: modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_join.js:147
msgid "No networks in range"
-msgstr ""
+msgstr "Нет сетей в радиусе действия"
#: themes/luci-theme-bootstrap/luasrc/view/themes/bootstrap/header.htm:173
#: themes/luci-theme-material/luasrc/view/themes/material/header.htm:211
diff --git a/modules/luci-base/po/uk/base.po b/modules/luci-base/po/uk/base.po
index 60f42b07ee..724ab4ccc5 100644
--- a/modules/luci-base/po/uk/base.po
+++ b/modules/luci-base/po/uk/base.po
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
-"PO-Revision-Date: 2019-07-16 20:35+0300\n"
+"PO-Revision-Date: 2019-07-21 13:22+0300\n"
"Last-Translator: Yurii <yuripet@gmail.com>\n"
"Language-Team: none\n"
"Language: uk\n"
@@ -3516,7 +3516,7 @@ msgstr "Ім'я мережі не визначено"
#: modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_join.js:147
msgid "No networks in range"
-msgstr ""
+msgstr "Немає мереж у межах досяжності"
#: themes/luci-theme-bootstrap/luasrc/view/themes/bootstrap/header.htm:173
#: themes/luci-theme-material/luasrc/view/themes/material/header.htm:211
diff --git a/modules/luci-base/root/usr/libexec/rpcd/luci b/modules/luci-base/root/usr/libexec/rpcd/luci
index 55233d6d0a..7644745efd 100755
--- a/modules/luci-base/root/usr/libexec/rpcd/luci
+++ b/modules/luci-base/root/usr/libexec/rpcd/luci
@@ -285,6 +285,41 @@ local methods = {
local fs = require "nixio.fs"
return { offload_support = not not fs.access("/sys/module/xt_FLOWOFFLOAD/refcnt") }
end
+ },
+
+ conntrack_helpers = {
+ call = function()
+ local fd = io.open("/usr/share/fw3/helpers.conf", "r")
+ local rv = {}
+
+ local line, entry
+ while true do
+ line = fd:read("*l")
+ if not line then
+ break
+ end
+
+ if line:match("^%s*config%s") then
+ if entry then
+ rv[#rv+1] = entry
+ end
+ entry = {}
+ else
+ local opt, val = line:match("^%s*option%s+(%S+)%s+(%S.*)$")
+ if opt and val then
+ opt = opt:gsub("^'(.+)'$", "%1"):gsub('^"(.+)"$', "%1")
+ val = val:gsub("^'(.+)'$", "%1"):gsub('^"(.+)"$', "%1")
+ entry[opt] = val
+ end
+ end
+ end
+
+ if entry then
+ rv[#rv+1] = entry
+ end
+
+ return { helpers = rv }
+ end
}
}
diff --git a/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json b/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json
index a9baef8f9c..de145ce784 100644
--- a/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json
+++ b/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json
@@ -13,7 +13,7 @@
"read": {
"ubus": {
"iwinfo": [ "info" ],
- "luci": [ "boardjson", "duid_hints", "host_hints", "ifaddrs", "initList", "getLocaltime", "leases", "leds", "netdevs", "offload_support", "usb" ],
+ "luci": [ "boardjson", "duid_hints", "host_hints", "ifaddrs", "initList", "getLocaltime", "leases", "leds", "netdevs", "usb" ],
"network.device": [ "status" ],
"network.interface": [ "dump" ],
"network.wireless": [ "status" ],
@@ -28,5 +28,17 @@
},
"uci": [ "*" ]
}
+ },
+ "luci-app-firewall": {
+ "description": "Grant access to firewall procedures",
+ "read": {
+ "ubus": {
+ "luci": [ "conntrack_helpers", "offload_support" ]
+ },
+ "uci": [ "firewall" ]
+ },
+ "write": {
+ "uci": [ "firewall" ]
+ }
}
}
diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_join.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_join.js
index af926ab4a2..f30e47ec7c 100644
--- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_join.js
+++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_join.js
@@ -144,7 +144,7 @@ function scan() {
]);
});
- cbi_update_table(tbl, bss, E('em' {}, _('No networks in range')));
+ cbi_update_table(tbl, bss, E('em', {}, _('No networks in range')));
}
else {
cbi_update_table(tbl, [], E('em', { class: 'spinning' }, _('No scan results available yet...')));
diff --git a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/leds.js b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/leds.js
index adcf4a4f8b..c1109b5d64 100644
--- a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/leds.js
+++ b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/leds.js
@@ -72,8 +72,10 @@ return L.view.extend({
trigger = s.option(form.ListValue, 'trigger', _('Trigger'));
Object.keys(triggers).sort().forEach(function(t) { trigger.value(t, t.replace(/-/g, '')) });
- trigger.value('usbdev');
- trigger.value('usbport');
+ if (usb.devices && usb.devices.length)
+ trigger.value('usbdev');
+ if (usb.ports && usb.ports.length)
+ trigger.value('usbport');
o = s.option(form.Value, 'delayon', _('On-State Delay'));
o.depends('trigger', 'timer');
@@ -100,42 +102,46 @@ return L.view.extend({
o.value('tx', _('Transmit'));
o.value('rx', _('Receive'));
- o = s.option(form.ListValue, '_usb_dev', _('USB Device'));
- o.depends('trigger', 'usbdev');
- o.rmempty = true;
- o.ucioption = 'dev';
- o.remove = function(section_id) {
- var t = trigger.formvalue(section_id);
- if (t != 'netdev' && t != 'usbdev')
- uci.unset('system', section_id, 'dev');
+ if (usb.devices && usb.devices.length) {
+ o = s.option(form.ListValue, '_usb_dev', _('USB Device'));
+ o.depends('trigger', 'usbdev');
+ o.rmempty = true;
+ o.ucioption = 'dev';
+ o.remove = function(section_id) {
+ var t = trigger.formvalue(section_id);
+ if (t != 'netdev' && t != 'usbdev')
+ uci.unset('system', section_id, 'dev');
+ }
+ o.value('');
+ usb.devices.forEach(function(usbdev) {
+ o.value(usbdev.id, '%s (%s - %s)'.format(usbdev.id, usbdev.vendor || '?', usbdev.product || '?'));
+ });
}
- o.value('');
- (usb.devices || []).forEach(function(usbdev) {
- o.value(usbdev.id, '%s (%s - %s)'.format(usbdev.id, usbdev.vendor || '?', usbdev.product || '?'));
- });
- o = s.option(form.MultiValue, 'port', _('USB Ports'));
- o.depends('trigger', 'usbport');
- o.rmempty = true;
- o.cfgvalue = function(section_id) {
- var ports = [],
- value = uci.get('system', section_id, 'port');
-
- if (!Array.isArray(value))
- value = String(value || '').split(/\s+/);
-
- for (var i = 0; i < value.length; i++)
- if (value[i].match(/^usb(\d+)-port(\d+)$/))
- ports.push(value[i]);
- else if (value[i].match(/^(\d+)-(\d+)$/))
- ports.push('usb%d-port%d'.format(Regexp.$1, Regexp.$2));
-
- return ports;
- };
- (usb.ports || []).forEach(function(usbport) {
- o.value('usb%d-port%d'.format(usbport.hub, usbport.port),
- 'Hub %d, Port %d'.format(usbport.hub, usbport.port));
- });
+ if (usb.ports && usb.ports.length) {
+ o = s.option(form.MultiValue, 'port', _('USB Ports'));
+ o.depends('trigger', 'usbport');
+ o.rmempty = true;
+ o.cfgvalue = function(section_id) {
+ var ports = [],
+ value = uci.get('system', section_id, 'port');
+
+ if (!Array.isArray(value))
+ value = String(value || '').split(/\s+/);
+
+ for (var i = 0; i < value.length; i++)
+ if (value[i].match(/^usb(\d+)-port(\d+)$/))
+ ports.push(value[i]);
+ else if (value[i].match(/^(\d+)-(\d+)$/))
+ ports.push('usb%d-port%d'.format(Regexp.$1, Regexp.$2));
+
+ return ports;
+ };
+ usb.ports.forEach(function(usbport) {
+ o.value('usb%d-port%d'.format(usbport.hub, usbport.port),
+ 'Hub %d, Port %d'.format(usbport.hub, usbport.port));
+ });
+ }
o = s.option(form.Value, 'port_mask', _('Switch Port Mask'));
o.depends('trigger', 'switch0');