diff options
Diffstat (limited to 'modules/luci-base/htdocs/luci-static')
4 files changed, 254 insertions, 37 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')+': "{{value}}"" 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 9e43c2d125..43afc698f6 100644 --- a/modules/luci-base/htdocs/luci-static/resources/ui.js +++ b/modules/luci-base/htdocs/luci-static/resources/ui.js @@ -213,13 +213,13 @@ var UISelect = UIElement.extend({ if (!Array.isArray(value)) value = (value != null && value != '') ? [ value ] : []; - if (!options.multi && value.length > 1) + if (!options.multiple && value.length > 1) value.length = 1; this.values = value; this.choices = choices; this.options = Object.assign({ - multi: false, + multiple: false, widget: 'select', orientation: 'horizontal' }, options); @@ -240,7 +240,7 @@ var UISelect = UIElement.extend({ 'name': this.options.name, 'size': this.options.size, 'class': 'cbi-input-select', - 'multiple': this.options.multi ? '' : null + 'multiple': this.options.multiple ? '' : null })); if (this.options.optional) @@ -267,8 +267,8 @@ var UISelect = UIElement.extend({ E('input', { 'id': this.options.id ? 'widget.' + this.options.id : null, 'name': this.options.id || this.options.name, - 'type': this.options.multi ? 'checkbox' : 'radio', - 'class': this.options.multi ? 'cbi-input-checkbox' : 'cbi-input-radio', + 'type': this.options.multiple ? 'checkbox' : 'radio', + 'class': this.options.multiple ? 'cbi-input-checkbox' : 'cbi-input-radio', 'value': keys[i], 'checked': (this.values.indexOf(keys[i]) > -1) ? '' : null }), @@ -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"]'); @@ -345,7 +345,7 @@ var UIDropdown = UIElement.extend({ this.choices = choices; this.options = Object.assign({ sort: true, - multi: Array.isArray(value), + multiple: Array.isArray(value), optional: true, select_placeholder: _('-- Please choose --'), custom_placeholder: _('-- custom --'), @@ -361,7 +361,7 @@ var UIDropdown = UIElement.extend({ var sb = E('div', { 'id': this.options.id, 'class': 'cbi-dropdown', - 'multiple': this.options.multi ? '' : null, + 'multiple': this.options.multiple ? '' : null, 'optional': this.options.optional ? '' : null, }, E('ul')); @@ -409,7 +409,7 @@ var UIDropdown = UIElement.extend({ bind: function(sb) { var o = this.options; - o.multi = sb.hasAttribute('multiple'); + o.multiple = sb.hasAttribute('multiple'); o.optional = sb.hasAttribute('optional'); o.placeholder = sb.getAttribute('placeholder') || o.placeholder; o.display_items = parseInt(sb.getAttribute('display-items') || o.display_items); @@ -425,7 +425,7 @@ var UIDropdown = UIElement.extend({ ndisplay = this.options.display_items, n = 0; - if (this.options.multi) { + if (this.options.multiple) { var items = ul.querySelectorAll('li'); for (var i = 0; i < items.length; i++) { @@ -657,7 +657,7 @@ var UIDropdown = UIElement.extend({ if (li.hasAttribute('unselectable')) return; - if (this.options.multi) { + if (this.options.multiple) { var cbox = li.querySelector('input[type="checkbox"]'), items = li.parentNode.querySelectorAll('li'), label = sb.querySelector('ul.preview'), @@ -780,7 +780,7 @@ var UIDropdown = UIElement.extend({ element: sb }; - if (this.options.multi) + if (this.options.multiple) detail.values = values; else detail.value = values.length ? values[0] : null; @@ -800,12 +800,12 @@ var UIDropdown = UIElement.extend({ for (var value in values) { this.createItems(sb, value); - if (!this.options.multi) + if (!this.options.multiple) break; } } - if (this.options.multi) { + if (this.options.multiple) { var lis = ul.querySelectorAll('li[data-value]'); for (var i = 0; i < lis.length; i++) { var value = lis[i].getAttribute('data-value'); @@ -857,7 +857,7 @@ var UIDropdown = UIElement.extend({ val = (value || '').trim(), ul = sb.querySelector('ul'); - if (!sbox.options.multi) + if (!sbox.options.multiple) val = val.length ? [ val ] : []; else val = val.length ? val.split(/\s+/) : []; @@ -879,9 +879,9 @@ 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.multi) { + if (sbox.options.multiple) { sbox.transformItem(sb, new_item); } else { @@ -1071,7 +1071,7 @@ var UIDropdown = UIElement.extend({ }, setValue: function(values) { - if (this.options.multi) { + if (this.options.multiple) { if (!Array.isArray(values)) values = (values != null && values != '') ? [ values ] : []; @@ -1104,7 +1104,7 @@ var UIDropdown = UIElement.extend({ for (var i = 0; i < h.length; i++) v.push(h[i].value); - return this.options.multi ? v : v[0]; + return this.options.multiple ? v : v[0]; } }); @@ -1116,7 +1116,7 @@ var UICombobox = UIDropdown.extend({ dropdown_items: -1, sort: true }, options, { - multi: false, + multiple: false, create: true, optional: true }) ]); @@ -1134,7 +1134,7 @@ var UIDynamicList = UIElement.extend({ this.values = values; this.choices = choices; this.options = Object.assign({}, options, { - multi: false, + multiple: false, optional: true }); }, @@ -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); }; |