diff options
Diffstat (limited to 'modules/luci-mod-network/htdocs/luci-static')
4 files changed, 330 insertions, 320 deletions
diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/tools/network.js b/modules/luci-mod-network/htdocs/luci-static/resources/tools/network.js index 5ed55e24fc..d008feb56a 100644 --- a/modules/luci-mod-network/htdocs/luci-static/resources/tools/network.js +++ b/modules/luci-mod-network/htdocs/luci-static/resources/tools/network.js @@ -46,11 +46,15 @@ function validateQoSMap(section_id, value) { return true; } -function deviceSectionExists(section_id, devname, devtype) { +function deviceSectionExists(section_id, devname, ignore_type_match) { var exists = false; uci.sections('network', 'device', function(ss) { - exists = exists || (ss['.name'] != section_id && ss.name == devname && (!devtype || devtype == ss.type)); + exists = exists || ( + ss['.name'] != section_id && + ss.name == devname && + (!ignore_type_match || !ignore_type_match.test(ss.type || '')) + ); }); return exists; @@ -385,148 +389,68 @@ return baseclass.extend({ }, addDeviceOptions: function(s, dev, isNew) { - var isIface = (s.sectiontype == 'interface'), - ifc = isIface ? network.instantiateNetwork(s.section) : null, - gensection = ifc ? 'physical' : 'devgeneral', - advsection = ifc ? 'physical' : 'devadvanced', - simpledep = ifc ? { type: '', ifname_single: /^[^@]/ } : { type: '' }, - disableLegacyBridging = isIface && deviceSectionExists(null, 'br-%s'.format(ifc.getName()), 'bridge'), - o, ss; - - if (isIface) { - if (!s.hasOwnProperty('parse')) - s.parse = sectionParse; - - var type; - - /* If an externally configured br-xxx interface already exists, - * then disable legacy bridge configuration */ - if (disableLegacyBridging) { - type = this.addOption(s, gensection, form.HiddenValue, 'type'); - type.cfgvalue = function() { return '' }; - } - else { - type = this.addOption(s, gensection, form.Flag, 'type', _('Bridge interfaces'), _('Creates a bridge over specified interface(s)')); - } - - type.modalonly = true; - type.disabled = ''; - type.enabled = 'bridge'; - type.write = type.remove = function(section_id, value) { - var protoname = this.section.formvalue(section_id, 'proto'), - protocol = network.getProtocol(protoname), - new_ifnames = this.isActive(section_id) ? L.toArray(this.section.formvalue(section_id, value ? 'ifname_multi' : 'ifname_single')) : []; - - if (!protocol.isVirtual() && !this.isActive(section_id)) - return; - - var old_ifnames = [], - devs = ifc.getDevices() || L.toArray(ifc.getDevice()); - - for (var i = 0; i < devs.length; i++) - old_ifnames.push(devs[i].getName()); - - if (!value) - new_ifnames.length = Math.max(new_ifnames.length, 1); + var o, ss; - old_ifnames.sort(); - new_ifnames.sort(); + s.tab('devgeneral', _('General device options')); + s.tab('devadvanced', _('Advanced device options')); + s.tab('brport', _('Bridge port specific options')); + s.tab('bridgevlan', _('Bridge VLAN filtering')); - for (var i = 0; i < Math.max(old_ifnames.length, new_ifnames.length); i++) { - if (old_ifnames[i] != new_ifnames[i]) { - // backup_ifnames() - for (var j = 0; j < old_ifnames.length; j++) - ifc.deleteDevice(old_ifnames[j]); - - for (var j = 0; j < new_ifnames.length; j++) - ifc.addDevice(new_ifnames[j]); - - break; - } - } - - if (value) - uci.set('network', section_id, 'type', 'bridge'); - else - uci.unset('network', section_id, 'type'); - }; - } - else { - s.tab('devgeneral', _('General device options')); - s.tab('devadvanced', _('Advanced device options')); - s.tab('brport', _('Bridge port specific options')); - s.tab('bridgevlan', _('Bridge VLAN filtering')); - - o = this.addOption(s, gensection, form.ListValue, 'type', _('Device type')); - o.readonly = !isNew; - o.value('', _('Network device')); - o.value('bridge', _('Bridge device')); - o.value('8021q', _('VLAN (802.1q)')); - o.value('8021ad', _('VLAN (802.1ad)')); - o.value('macvlan', _('MAC VLAN')); - o.value('veth', _('Virtual Ethernet')); - - o = this.addOption(s, gensection, widgets.DeviceSelect, 'name_simple', _('Existing device')); - o.readonly = !isNew; - o.rmempty = false; - o.noaliases = true; - o.default = (dev ? dev.getName() : ''); - o.ucioption = 'name'; - o.write = o.remove = setIfActive; - o.filter = function(section_id, value) { - return !deviceSectionExists(section_id, value); - }; - o.validate = function(section_id, value) { - return deviceSectionExists(section_id, value) ? _('A configuration for the device "%s" already exists').format(value) : true; - }; - o.depends('type', ''); - } + o = this.addOption(s, 'devgeneral', form.ListValue, 'type', _('Device type')); + o.readonly = !isNew; + o.value('', _('Network device')); + o.value('bridge', _('Bridge device')); + o.value('8021q', _('VLAN (802.1q)')); + o.value('8021ad', _('VLAN (802.1ad)')); + o.value('macvlan', _('MAC VLAN')); + o.value('veth', _('Virtual Ethernet')); + + o = this.addOption(s, 'devgeneral', widgets.DeviceSelect, 'name_simple', _('Existing device')); + o.readonly = !isNew; + o.rmempty = false; + o.noaliases = true; + o.default = (dev ? dev.getName() : ''); + o.ucioption = 'name'; + o.write = o.remove = setIfActive; + o.filter = function(section_id, value) { + return !deviceSectionExists(section_id, value, /^(?:bridge|8021q|8021ad|macvlan|veth)$/); + }; + o.validate = function(section_id, value) { + return deviceSectionExists(section_id, value, /^(?:bridge|8021q|8021ad|macvlan|veth)$/) + ? _('A configuration for the device "%s" already exists').format(value) : true; + }; + o.depends('type', ''); - o = this.addOption(s, gensection, widgets.DeviceSelect, 'ifname_single', isIface ? _('Interface') : _('Base device')); + o = this.addOption(s, 'devgeneral', widgets.DeviceSelect, 'ifname_single', _('Base device')); o.readonly = !isNew; o.rmempty = false; - o.noaliases = !isIface; + o.noaliases = true; o.default = (dev ? dev.getName() : '').match(/^.+\.\d+$/) ? dev.getName().replace(/\.\d+$/, '') : ''; o.ucioption = 'ifname'; o.validate = function(section_id, value) { - var type = this.section.formvalue(section_id, 'type'), - name = this.section.getUIElement(section_id, 'name_complex'); + if (isNew) { + var type = this.section.formvalue(section_id, 'type'), + name = this.section.getUIElement(section_id, 'name_complex'); - if (type == 'macvlan' && value && name && !name.isChanged()) { - var i = 0; + if (type == 'macvlan' && value && name && !name.isChanged()) { + var i = 0; - while (deviceSectionExists(section_id, '%smac%d'.format(value, i))) - i++; + while (deviceSectionExists(section_id, '%smac%d'.format(value, i))) + i++; - name.setValue('%smac%d'.format(value, i)); - name.triggerValidation(); + name.setValue('%smac%d'.format(value, i)); + name.triggerValidation(); + } } return true; }; - if (isIface) { - o.write = o.remove = function() {}; - o.cfgvalue = function(section_id) { - return (ifc.getDevices() || L.toArray(ifc.getDevice())).map(function(dev) { - return dev.getName(); - }); - }; - o.onchange = function(ev, section_id, values) { - for (var i = 0, co; (co = this.section.children[i]) != null; i++) - if (co !== this && co.refresh) - co.refresh(section_id); - - }; - o.depends('type', ''); - } - else { - o.write = o.remove = setIfActive; - o.depends('type', '8021q'); - o.depends('type', '8021ad'); - o.depends('type', 'macvlan'); - } + o.write = o.remove = setIfActive; + o.depends('type', '8021q'); + o.depends('type', '8021ad'); + o.depends('type', 'macvlan'); - o = this.addOption(s, gensection, form.Value, 'vid', _('VLAN ID')); + o = this.addOption(s, 'devgeneral', form.Value, 'vid', _('VLAN ID')); o.readonly = !isNew; o.datatype = 'range(1, 4094)'; o.rmempty = false; @@ -546,74 +470,64 @@ return baseclass.extend({ o.depends('type', '8021q'); o.depends('type', '8021ad'); - o = this.addOption(s, gensection, form.ListValue, 'mode', _('Mode')); + o = this.addOption(s, 'devgeneral', form.ListValue, 'mode', _('Mode')); o.value('vepa', _('VEPA (Virtual Ethernet Port Aggregator)', 'MACVLAN mode')); o.value('private', _('Private (Prevent communication between MAC VLANs)', 'MACVLAN mode')); o.value('bridge', _('Bridge (Support direct communication between MAC VLANs)', 'MACVLAN mode')); o.value('passthru', _('Pass-through (Mirror physical device to single MAC VLAN)', 'MACVLAN mode')); o.depends('type', 'macvlan'); - if (!isIface) { - o = this.addOption(s, gensection, form.Value, 'name_complex', _('Device name')); - o.rmempty = false; - o.datatype = 'maxlength(15)'; - o.readonly = !isNew; - o.ucioption = 'name'; - o.write = o.remove = setIfActive; - o.validate = function(section_id, value) { - return deviceSectionExists(section_id, value) ? _('The device name "%s" is already taken').format(value) : true; - }; - o.depends({ type: '', '!reverse': true }); - } + o = this.addOption(s, 'devgeneral', form.Value, 'name_complex', _('Device name')); + o.rmempty = false; + o.datatype = 'maxlength(15)'; + o.readonly = !isNew; + o.ucioption = 'name'; + o.write = o.remove = setIfActive; + o.validate = function(section_id, value) { + return deviceSectionExists(section_id, value, /^$/) ? _('The device name "%s" is already taken').format(value) : true; + }; + o.depends({ type: '', '!reverse': true }); - o = this.addOption(s, advsection, form.DynamicList, 'ingress_qos_mapping', _('Ingress QoS mapping'), _('Defines a mapping of VLAN header priority to the Linux internal packet priority on incoming frames')); + o = this.addOption(s, 'devadvanced', form.DynamicList, 'ingress_qos_mapping', _('Ingress QoS mapping'), _('Defines a mapping of VLAN header priority to the Linux internal packet priority on incoming frames')); o.rmempty = true; o.validate = validateQoSMap; o.depends('type', '8021q'); o.depends('type', '8021ad'); - o = this.addOption(s, advsection, form.DynamicList, 'egress_qos_mapping', _('Egress QoS mapping'), _('Defines a mapping of Linux internal packet priority to VLAN header priority but for outgoing frames')); + o = this.addOption(s, 'devadvanced', form.DynamicList, 'egress_qos_mapping', _('Egress QoS mapping'), _('Defines a mapping of Linux internal packet priority to VLAN header priority but for outgoing frames')); o.rmempty = true; o.validate = validateQoSMap; o.depends('type', '8021q'); o.depends('type', '8021ad'); - o = this.addOption(s, gensection, widgets.DeviceSelect, 'ifname_multi', _('Bridge ports')); + o = this.addOption(s, 'devgeneral', widgets.DeviceSelect, 'ifname_multi', _('Bridge ports')); o.size = 10; o.rmempty = true; o.multiple = true; o.noaliases = true; o.nobridges = true; - o.ucioption = 'ifname'; - if (isIface) { - o.write = o.remove = function() {}; - o.cfgvalue = function(section_id) { - return (ifc.getDevices() || L.toArray(ifc.getDevice())).map(function(dev) { return dev.getName() }); - }; - } - else { - o.write = o.remove = setIfActive; - o.default = L.toArray(dev ? dev.getPorts() : null).filter(function(p) { return p.getType() != 'wifi' }).map(function(p) { return p.getName() }); - o.filter = function(section_id, device_name) { - var bridge_name = uci.get('network', section_id, 'name'), - choice_dev = network.instantiateDevice(device_name), - parent_dev = choice_dev.getParent(); - - /* only show wifi networks which are already present in "option ifname" */ - if (choice_dev.getType() == 'wifi') { - var ifnames = L.toArray(uci.get('network', section_id, 'ifname')); - - for (var i = 0; i < ifnames.length; i++) - if (ifnames[i] == device_name) - return true; - - return false; - } + o.ucioption = 'ports'; + o.write = o.remove = setIfActive; + o.default = L.toArray(dev ? dev.getPorts() : null).filter(function(p) { return p.getType() != 'wifi' }).map(function(p) { return p.getName() }); + o.filter = function(section_id, device_name) { + var bridge_name = uci.get('network', section_id, 'name'), + choice_dev = network.instantiateDevice(device_name), + parent_dev = choice_dev.getParent(); + + /* only show wifi networks which are already present in "option ifname" */ + if (choice_dev.getType() == 'wifi') { + var ifnames = L.toArray(uci.get('network', section_id, 'ports')); + + for (var i = 0; i < ifnames.length; i++) + if (ifnames[i] == device_name) + return true; + + return false; + } - return (!parent_dev || parent_dev.getName() != bridge_name); - }; - o.description = _('Specifies the wired ports to attach to this bridge. In order to attach wireless networks, choose the associated interface as network in the wireless settings.') - } + return (!parent_dev || parent_dev.getName() != bridge_name); + }; + o.description = _('Specifies the wired ports to attach to this bridge. In order to attach wireless networks, choose the associated interface as network in the wireless settings.') o.onchange = function(ev, section_id, values) { ss.updatePorts(values); @@ -623,64 +537,64 @@ return baseclass.extend({ }; o.depends('type', 'bridge'); - o = this.replaceOption(s, gensection, form.Flag, 'bridge_empty', _('Bring up empty bridge'), _('Bring up the bridge interface even if no ports are attached')); + o = this.replaceOption(s, 'devgeneral', form.Flag, 'bridge_empty', _('Bring up empty bridge'), _('Bring up the bridge interface even if no ports are attached')); o.default = o.disabled; o.depends('type', 'bridge'); - o = this.replaceOption(s, advsection, form.Value, 'priority', _('Priority')); + o = this.replaceOption(s, 'devadvanced', form.Value, 'priority', _('Priority')); o.placeholder = '32767'; o.datatype = 'range(0, 65535)'; o.depends('type', 'bridge'); - o = this.replaceOption(s, advsection, form.Value, 'ageing_time', _('Ageing time'), _('Timeout in seconds for learned MAC addresses in the forwarding database')); + o = this.replaceOption(s, 'devadvanced', form.Value, 'ageing_time', _('Ageing time'), _('Timeout in seconds for learned MAC addresses in the forwarding database')); o.placeholder = '30'; o.datatype = 'uinteger'; o.depends('type', 'bridge'); - o = this.replaceOption(s, advsection, form.Flag, 'stp', _('Enable <abbr title="Spanning Tree Protocol">STP</abbr>'), _('Enables the Spanning Tree Protocol on this bridge')); + o = this.replaceOption(s, 'devadvanced', form.Flag, 'stp', _('Enable <abbr title="Spanning Tree Protocol">STP</abbr>'), _('Enables the Spanning Tree Protocol on this bridge')); o.default = o.disabled; o.depends('type', 'bridge'); - o = this.replaceOption(s, advsection, form.Value, 'hello_time', _('Hello interval'), _('Interval in seconds for STP hello packets')); + o = this.replaceOption(s, 'devadvanced', form.Value, 'hello_time', _('Hello interval'), _('Interval in seconds for STP hello packets')); o.placeholder = '2'; o.datatype = 'range(1, 10)'; o.depends({ type: 'bridge', stp: '1' }); - o = this.replaceOption(s, advsection, form.Value, 'forward_delay', _('Forward delay'), _('Time in seconds to spend in listening and learning states')); + o = this.replaceOption(s, 'devadvanced', form.Value, 'forward_delay', _('Forward delay'), _('Time in seconds to spend in listening and learning states')); o.placeholder = '15'; o.datatype = 'range(2, 30)'; o.depends({ type: 'bridge', stp: '1' }); - o = this.replaceOption(s, advsection, form.Value, 'max_age', _('Maximum age'), _('Timeout in seconds until topology updates on link loss')); + o = this.replaceOption(s, 'devadvanced', form.Value, 'max_age', _('Maximum age'), _('Timeout in seconds until topology updates on link loss')); o.placeholder = '20'; o.datatype = 'range(6, 40)'; o.depends({ type: 'bridge', stp: '1' }); - o = this.replaceOption(s, advsection, form.Flag, 'igmp_snooping', _('Enable <abbr title="Internet Group Management Protocol">IGMP</abbr> snooping'), _('Enables IGMP snooping on this bridge')); + o = this.replaceOption(s, 'devadvanced', form.Flag, 'igmp_snooping', _('Enable <abbr title="Internet Group Management Protocol">IGMP</abbr> snooping'), _('Enables IGMP snooping on this bridge')); o.default = o.disabled; o.depends('type', 'bridge'); - o = this.replaceOption(s, advsection, form.Value, 'hash_max', _('Maximum snooping table size')); + o = this.replaceOption(s, 'devadvanced', form.Value, 'hash_max', _('Maximum snooping table size')); o.placeholder = '512'; o.datatype = 'uinteger'; o.depends({ type: 'bridge', igmp_snooping: '1' }); - o = this.replaceOption(s, advsection, form.Flag, 'multicast_querier', _('Enable multicast querier')); + o = this.replaceOption(s, 'devadvanced', form.Flag, 'multicast_querier', _('Enable multicast querier')); o.defaults = { '1': [{'igmp_snooping': '1'}], '0': [{'igmp_snooping': '0'}] }; o.depends('type', 'bridge'); - o = this.replaceOption(s, advsection, form.Value, 'robustness', _('Robustness'), _('The robustness value allows tuning for the expected packet loss on the network. If a network is expected to be lossy, the robustness value may be increased. IGMP is robust to (Robustness-1) packet losses')); + o = this.replaceOption(s, 'devadvanced', form.Value, 'robustness', _('Robustness'), _('The robustness value allows tuning for the expected packet loss on the network. If a network is expected to be lossy, the robustness value may be increased. IGMP is robust to (Robustness-1) packet losses')); o.placeholder = '2'; o.datatype = 'min(1)'; o.depends({ type: 'bridge', multicast_querier: '1' }); - o = this.replaceOption(s, advsection, form.Value, 'query_interval', _('Query interval'), _('Interval in centiseconds between multicast general queries. By varying the value, an administrator may tune the number of IGMP messages on the subnet; larger values cause IGMP Queries to be sent less often')); + o = this.replaceOption(s, 'devadvanced', form.Value, 'query_interval', _('Query interval'), _('Interval in centiseconds between multicast general queries. By varying the value, an administrator may tune the number of IGMP messages on the subnet; larger values cause IGMP Queries to be sent less often')); o.placeholder = '12500'; o.datatype = 'uinteger'; o.depends({ type: 'bridge', multicast_querier: '1' }); - o = this.replaceOption(s, advsection, form.Value, 'query_response_interval', _('Query response interval'), _('The max response time in centiseconds inserted into the periodic general queries. By varying the value, an administrator may tune the burstiness of IGMP messages on the subnet; larger values make the traffic less bursty, as host responses are spread out over a larger interval')); + o = this.replaceOption(s, 'devadvanced', form.Value, 'query_response_interval', _('Query response interval'), _('The max response time in centiseconds inserted into the periodic general queries. By varying the value, an administrator may tune the burstiness of IGMP messages on the subnet; larger values make the traffic less bursty, as host responses are spread out over a larger interval')); o.placeholder = '1000'; o.datatype = 'uinteger'; o.validate = function(section_id, value) { @@ -694,24 +608,26 @@ return baseclass.extend({ }; o.depends({ type: 'bridge', multicast_querier: '1' }); - o = this.replaceOption(s, advsection, form.Value, 'last_member_interval', _('Last member interval'), _('The max response time in centiseconds inserted into group-specific queries sent in response to leave group messages. It is also the amount of time between group-specific query messages. This value may be tuned to modify the "leave latency" of the network. A reduced value results in reduced time to detect the loss of the last member of a group')); + o = this.replaceOption(s, 'devadvanced', form.Value, 'last_member_interval', _('Last member interval'), _('The max response time in centiseconds inserted into group-specific queries sent in response to leave group messages. It is also the amount of time between group-specific query messages. This value may be tuned to modify the "leave latency" of the network. A reduced value results in reduced time to detect the loss of the last member of a group')); o.placeholder = '100'; o.datatype = 'uinteger'; o.depends({ type: 'bridge', multicast_querier: '1' }); - o = this.addOption(s, gensection, form.Value, 'mtu', _('MTU')); - o.placeholder = getDeviceValue(ifc || dev, 'getMTU'); + o = this.addOption(s, 'devgeneral', form.Value, 'mtu', _('MTU')); + o.placeholder = getDeviceValue(dev, 'getMTU'); o.datatype = 'max(9200)'; - o.depends(simpledep); + o.depends('type', ''); + o.depends('type', 'bridge'); - o = this.addOption(s, gensection, form.Value, 'macaddr', _('MAC address')); - o.placeholder = getDeviceValue(ifc || dev, 'getMAC'); + o = this.addOption(s, 'devgeneral', form.Value, 'macaddr', _('MAC address')); + o.placeholder = getDeviceValue(dev, 'getMAC'); o.datatype = 'macaddr'; - o.depends(simpledep); + o.depends('type', ''); + o.depends('type', 'bridge'); o.depends('type', 'macvlan'); o.depends('type', 'veth'); - o = this.addOption(s, gensection, form.Value, 'peer_name', _('Peer device name')); + o = this.addOption(s, 'devgeneral', form.Value, 'peer_name', _('Peer device name')); o.rmempty = true; o.datatype = 'maxlength(15)'; o.depends('type', 'veth'); @@ -730,21 +646,21 @@ return baseclass.extend({ return form.Value.prototype.load.apply(this, arguments); }; - o = this.addOption(s, gensection, form.Value, 'peer_macaddr', _('Peer MAC address')); + o = this.addOption(s, 'devgeneral', form.Value, 'peer_macaddr', _('Peer MAC address')); o.rmempty = true; o.datatype = 'macaddr'; o.depends('type', 'veth'); - o = this.addOption(s, gensection, form.Value, 'txqueuelen', _('TX queue length')); + o = this.addOption(s, 'devgeneral', form.Value, 'txqueuelen', _('TX queue length')); o.placeholder = dev ? dev._devstate('qlen') : ''; o.datatype = 'uinteger'; - o.depends(simpledep); + o.depends('type', ''); - o = this.addOption(s, advsection, form.Flag, 'promisc', _('Enable promiscious mode')); + o = this.addOption(s, 'devadvanced', form.Flag, 'promisc', _('Enable promiscious mode')); o.default = o.disabled; - o.depends(simpledep); + o.depends('type', ''); - o = this.addOption(s, advsection, form.ListValue, 'rpfilter', _('Reverse path filter')); + o = this.addOption(s, 'devadvanced', form.ListValue, 'rpfilter', _('Reverse path filter')); o.default = ''; o.value('', _('disabled')); o.value('loose', _('Loose filtering')); @@ -765,96 +681,96 @@ return baseclass.extend({ return ''; } }; - o.depends(simpledep); + o.depends('type', ''); - o = this.addOption(s, advsection, form.Flag, 'acceptlocal', _('Accept local'), _('Accept packets with local source addresses')); + o = this.addOption(s, 'devadvanced', form.Flag, 'acceptlocal', _('Accept local'), _('Accept packets with local source addresses')); o.default = o.disabled; - o.depends(simpledep); + o.depends('type', ''); - o = this.addOption(s, advsection, form.Flag, 'sendredirects', _('Send ICMP redirects')); + o = this.addOption(s, 'devadvanced', form.Flag, 'sendredirects', _('Send ICMP redirects')); o.default = o.enabled; - o.depends(simpledep); + o.depends('type', ''); - o = this.addOption(s, advsection, form.Value, 'neighreachabletime', _('Neighbour cache validity'), _('Time in milliseconds')); + o = this.addOption(s, 'devadvanced', form.Value, 'neighreachabletime', _('Neighbour cache validity'), _('Time in milliseconds')); o.placeholder = '30000'; o.datatype = 'uinteger'; - o.depends(simpledep); + o.depends('type', ''); - o = this.addOption(s, advsection, form.Value, 'neighgcstaletime', _('Stale neighbour cache timeout'), _('Timeout in seconds')); + o = this.addOption(s, 'devadvanced', form.Value, 'neighgcstaletime', _('Stale neighbour cache timeout'), _('Timeout in seconds')); o.placeholder = '60'; o.datatype = 'uinteger'; - o.depends(simpledep); + o.depends('type', ''); - o = this.addOption(s, advsection, form.Value, 'neighlocktime', _('Minimum ARP validity time'), _('Minimum required time in seconds before an ARP entry may be replaced. Prevents ARP cache thrashing.')); + o = this.addOption(s, 'devadvanced', form.Value, 'neighlocktime', _('Minimum ARP validity time'), _('Minimum required time in seconds before an ARP entry may be replaced. Prevents ARP cache thrashing.')); o.placeholder = '0'; o.datatype = 'uinteger'; - o.depends(simpledep); + o.depends('type', ''); - o = this.addOption(s, gensection, form.Flag, 'ipv6', _('Enable IPv6')); + o = this.addOption(s, 'devgeneral', form.Flag, 'ipv6', _('Enable IPv6')); o.migrate = false; o.default = o.enabled; - o.depends(simpledep); + o.depends('type', ''); - o = this.addOption(s, gensection, form.Value, 'mtu6', _('IPv6 MTU')); - o.placeholder = getDeviceValue(ifc || dev, 'getMTU'); + o = this.addOption(s, 'devgeneral', form.Value, 'mtu6', _('IPv6 MTU')); + o.placeholder = getDeviceValue(dev, 'getMTU'); o.datatype = 'max(9200)'; - o.depends(Object.assign({ ipv6: '1' }, simpledep)); + o.depends(Object.assign({ ipv6: '1' }, 'type', '')); - o = this.addOption(s, gensection, form.Value, 'dadtransmits', _('DAD transmits'), _('Amount of Duplicate Address Detection probes to send')); + o = this.addOption(s, 'devgeneral', form.Value, 'dadtransmits', _('DAD transmits'), _('Amount of Duplicate Address Detection probes to send')); o.placeholder = '1'; o.datatype = 'uinteger'; - o.depends(Object.assign({ ipv6: '1' }, simpledep)); + o.depends(Object.assign({ ipv6: '1' }, 'type', '')); - o = this.addOption(s, advsection, form.Flag, 'multicast', _('Enable multicast support')); + o = this.addOption(s, 'devadvanced', form.Flag, 'multicast', _('Enable multicast support')); o.default = o.enabled; - o.depends(simpledep); + o.depends('type', ''); - o = this.addOption(s, advsection, form.ListValue, 'igmpversion', _('Force IGMP version')); + o = this.addOption(s, 'devadvanced', form.ListValue, 'igmpversion', _('Force IGMP version')); o.value('', _('No enforcement')); o.value('1', _('Enforce IGMPv1')); o.value('2', _('Enforce IGMPv2')); o.value('3', _('Enforce IGMPv3')); - o.depends(Object.assign({ multicast: '1' }, simpledep)); + o.depends(Object.assign({ multicast: '1' }, 'type', '')); - o = this.addOption(s, advsection, form.ListValue, 'mldversion', _('Force MLD version')); + o = this.addOption(s, 'devadvanced', form.ListValue, 'mldversion', _('Force MLD version')); o.value('', _('No enforcement')); o.value('1', _('Enforce MLD version 1')); o.value('2', _('Enforce MLD version 2')); - o.depends(Object.assign({ multicast: '1' }, simpledep)); + o.depends(Object.assign({ multicast: '1' }, 'type', '')); if (isBridgePort(dev)) { o = this.addOption(s, 'brport', form.Flag, 'learning', _('Enable MAC address learning')); o.default = o.enabled; - o.depends(simpledep); + o.depends('type', ''); o = this.addOption(s, 'brport', form.Flag, 'unicast_flood', _('Enable unicast flooding')); o.default = o.enabled; - o.depends(simpledep); + o.depends('type', ''); o = this.addOption(s, 'brport', form.Flag, 'isolated', _('Port isolation'), _('Only allow communication with non-isolated bridge ports when enabled')); o.default = o.disabled; - o.depends(simpledep); + o.depends('type', ''); o = this.addOption(s, 'brport', form.ListValue, 'multicast_router', _('Multicast routing')); o.value('', _('Never')); o.value('1', _('Learn')); o.value('2', _('Always')); - o.depends(Object.assign({ multicast: '1' }, simpledep)); + o.depends(Object.assign({ multicast: '1' }, 'type', '')); o = this.addOption(s, 'brport', form.Flag, 'multicast_to_unicast', _('Multicast to unicast'), _('Forward multicast packets as unicast packets on this device.')); o.default = o.disabled; - o.depends(Object.assign({ multicast: '1' }, simpledep)); + o.depends(Object.assign({ multicast: '1' }, 'type', '')); o = this.addOption(s, 'brport', form.Flag, 'multicast_fast_leave', _('Enable multicast fast leave')); o.default = o.disabled; - o.depends(Object.assign({ multicast: '1' }, simpledep)); + o.depends(Object.assign({ multicast: '1' }, 'type', '')); } o = this.addOption(s, 'bridgevlan', form.Flag, 'vlan_filtering', _('Enable VLAN filterering')); o.depends('type', 'bridge'); o.updateDefaultValue = function(section_id) { - var device = isIface ? 'br-%s'.format(s.section) : uci.get('network', s.section, 'name'), + var device = uci.get('network', s.section, 'name'), uielem = this.getUIElement(section_id), has_vlans = false; @@ -896,7 +812,7 @@ return baseclass.extend({ }; ss.filter = function(section_id) { - var devname = isIface ? 'br-%s'.format(s.section) : uci.get('network', s.section, 'name'); + var devname = uci.get('network', s.section, 'name'); return (uci.get('network', section_id, 'device') == devname); }; @@ -943,7 +859,7 @@ return baseclass.extend({ ss.handleAdd = function(ev) { return s.parse().then(L.bind(function() { - var device = isIface ? 'br-%s'.format(s.section) : uci.get('network', s.section, 'name'), + var device = uci.get('network', s.section, 'name'), section_ids = this.cfgsections(), section_id = null, max_vlan_id = 0; @@ -1007,38 +923,25 @@ return baseclass.extend({ o = ss.option(form.Flag, 'local', _('Local')); o.default = o.enabled; - /* Do not touch bridge port state from interface config if legacy - * bridge config is disabled due to explicitely declared br-xxx - * device section... */ - if (disableLegacyBridging) - return; - var ports = []; - if (isIface) { - Array.prototype.push.apply(ports, L.toArray(ifc.getDevices() || ifc.getDevice()).map(function(dev) { - return dev.getName(); - })); - } - else { - var seen_ports = {}; + var seen_ports = {}; - L.toArray(uci.get('network', s.section, 'ifname')).forEach(function(ifname) { - seen_ports[ifname] = true; - }); + L.toArray(uci.get('network', s.section, 'ports')).forEach(function(port) { + seen_ports[port] = true; + }); - uci.sections('network', 'bridge-vlan', function(bvs) { - L.toArray(bvs.ports).forEach(function(portspec) { - var m = portspec.match(/^([^:]+)(?::[ut*]+)?$/); + uci.sections('network', 'bridge-vlan', function(bvs) { + L.toArray(bvs.ports).forEach(function(portspec) { + var m = portspec.match(/^([^:]+)(?::[ut*]+)?$/); - if (m) - seen_ports[m[1]] = true; - }); + if (m) + seen_ports[m[1]] = true; }); + }); - for (var port_name in seen_ports) - ports.push(port_name); - } + for (var port_name in seen_ports) + ports.push(port_name); ports.sort(); 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 253b37b846..0d1420772e 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 @@ -450,7 +450,11 @@ return view.extend({ node.addEventListener('cbi-dropdown-change', L.bind(function(ipopt, section_id, ev) { var mac = ev.detail.value.value; - if (mac == null || mac == '' || !hosts[mac] || !hosts[mac].ipv4) + if (mac == null || mac == '' || !hosts[mac]) + return; + + var iphint = L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4)[0]; + if (iphint == null) return; var ip = ipopt.formvalue(section_id); @@ -459,13 +463,13 @@ return view.extend({ var node = ipopt.map.findElement('id', ipopt.cbid(section_id)); if (node) - dom.callClassMethod(node, 'setValue', hosts[mac].ipv4); + dom.callClassMethod(node, 'setValue', iphint); }, this, ipopt, section_id)); return node; }; Object.keys(hosts).forEach(function(mac) { - var hint = hosts[mac].name || hosts[mac].ipv4; + var hint = hosts[mac].name || L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4)[0]; so.value(mac, hint ? '%s (%s)'.format(mac, hint) : mac); }); @@ -501,11 +505,18 @@ return view.extend({ return true; }; + + var ipaddrs = {}; + Object.keys(hosts).forEach(function(mac) { - if (hosts[mac].ipv4) { - var hint = hosts[mac].name; - so.value(hosts[mac].ipv4, hint ? '%s (%s)'.format(hosts[mac].ipv4, hint) : hosts[mac].ipv4); - } + var addrs = L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4); + + for (var i = 0; i < addrs.length; i++) + ipaddrs[addrs[i]] = hosts[mac].name; + }); + + L.sortedKeys(ipaddrs, null, 'addr').forEach(function(ipv4) { + so.value(ipv4, ipaddrs[ipv4] ? '%s (%s)'.format(ipv4, ipaddrs[ipv4]) : ipv4); }); so = ss.option(form.Value, 'leasetime', _('Lease time')); @@ -563,7 +574,7 @@ return view.extend({ exp = '%t'.format(lease.expires); var hint = lease.macaddr ? hosts[lease.macaddr] : null, - name = hint ? (hint.name || hint.ipv4 || hint.ipv6) : null, + name = hint ? (hint.name || L.toArray(hint.ipaddrs || hint.ipv4)[0] || L.toArray(hint.ip6addrs || hint.ipv6)[0]) : null, host = null; if (name && lease.hostname && lease.hostname != name && lease.ip6addr != name) 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 cd0dacbf67..93ebf5ba68 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 @@ -31,11 +31,18 @@ return view.extend({ o = s.option(form.Value, 'ip', _('IP address')); o.datatype = 'ipaddr'; o.rmempty = true; - L.sortedKeys(hosts, 'ipv4', 'addr').forEach(function(mac) { - o.value(hosts[mac].ipv4, '%s (%s)'.format( - hosts[mac].ipv4, - hosts[mac].name || mac - )); + + var ipaddrs = {}; + + Object.keys(hosts).forEach(function(mac) { + var addrs = L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4); + + for (var i = 0; i < addrs.length; i++) + ipaddrs[addrs[i]] = hosts[mac].name || mac; + }); + + L.sortedKeys(ipaddrs, null, 'addr').forEach(function(ipv4) { + o.value(ipv4, '%s (%s)'.format(ipv4, ipaddrs[ipv4])); }); return m.render(); 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 4a2e94faf6..544cad1aa1 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 @@ -294,11 +294,111 @@ return view.extend({ network.getDSLModemType(), network.getDevices(), fs.lines('/etc/iproute2/rt_tables'), + L.resolveDefault(fs.read('/usr/lib/opkg/info/netifd.control')), uci.changes() ]); }, + interfaceBridgeWithIfnameSections: function() { + return uci.sections('network', 'interface').filter(function(ns) { + return ns.type == 'bridge' && !ns.ports && ns.ifname; + }); + }, + + deviceWithIfnameSections: function() { + return uci.sections('network', 'device').filter(function(ns) { + return ns.type == 'bridge' && !ns.ports && ns.ifname; + }); + }, + + interfaceWithIfnameSections: function() { + return uci.sections('network', 'interface').filter(function(ns) { + return !ns.device && ns.ifname; + }); + }, + + handleBridgeMigration: function(ev) { + var tasks = []; + + this.interfaceBridgeWithIfnameSections().forEach(function(ns) { + var device_name = 'br-' + ns['.name']; + + tasks.push(uci.callAdd('network', 'device', null, { + 'name': device_name, + 'type': 'bridge', + 'ports': L.toArray(ns.ifname), + 'macaddr': ns.macaddr + })); + + tasks.push(uci.callSet('network', ns['.name'], { + 'type': '', + 'ifname': '', + 'macaddr': '', + 'device': device_name + })); + }); + + return Promise.all(tasks) + .then(L.bind(ui.changes.init, ui.changes)) + .then(L.bind(ui.changes.apply, ui.changes)); + }, + + renderBridgeMigration: function() { + ui.showModal(_('Network bridge configuration migration'), [ + E('p', _('The existing network configuration needs to be changed for LuCI to function properly.')), + E('p', _('Upon pressing "Continue", bridges configuration will be updated and the network will be restarted to apply the updated configuration.')), + E('div', { 'class': 'right' }, + E('button', { + 'class': 'btn cbi-button-action important', + 'click': ui.createHandlerFn(this, 'handleBridgeMigration') + }, _('Continue'))) + ]); + }, + + handleIfnameMigration: function(ev) { + var tasks = []; + + this.deviceWithIfnameSections().forEach(function(ds) { + tasks.push(uci.add('network', ds['.name'], { + 'ifname': '', + 'ports': L.toArray(ds.ifname) + })); + }); + + this.interfaceWithIfnameSections().forEach(function(ns) { + tasks.push(uci.callSet('network', ns['.name'], { + 'ifname': '', + 'device': ns.ifname + })); + }); + + return Promise.all(tasks) + .then(L.bind(ui.changes.init, ui.changes)) + .then(L.bind(ui.changes.apply, ui.changes)); + }, + + renderIfnameMigration: function() { + ui.showModal(_('Network ifname configuration migration'), [ + E('p', _('The existing network configuration needs to be changed for LuCI to function properly.')), + E('p', _('Upon pressing "Continue", ifname options will get renamed and the network will be restarted to apply the updated configuration.')), + E('div', { 'class': 'right' }, + E('button', { + 'class': 'btn cbi-button-action important', + 'click': ui.createHandlerFn(this, 'handleIfnameMigration') + }, _('Continue'))) + ]); + }, + render: function(data) { + var netifdVersion = (data[3] || '').match(/Version: ([^\n]+)/); + + if (netifdVersion && netifdVersion[1] >= "2021-05-26") { + if (this.interfaceBridgeWithIfnameSections().length) + return this.renderBridgeMigration(); + else if (this.deviceWithIfnameSections().length || this.interfaceWithIfnameSections().length) + return this.renderIfnameMigration(); + } + var dslModemType = data[0], netDevs = data[1], m, s, o; @@ -381,7 +481,7 @@ return view.extend({ s.addModalOptions = function(s) { var protoval = uci.get('network', s.section, 'proto'), protoclass = protoval ? network.getProtocol(protoval) : null, - o, ifname_single, ifname_multi, proto_select, proto_switch, type, stp, igmp, ss, so; + o, proto_select, proto_switch, type, stp, igmp, ss, so; if (!protoval) return; @@ -405,6 +505,7 @@ return view.extend({ }, this); o.write = function() {}; + proto_select = s.taboption('general', form.ListValue, 'proto', _('Protocol')); proto_select.modalonly = true; @@ -420,6 +521,12 @@ return view.extend({ .then(L.bind(this.renderMoreOptionsModal, this, s.section)); }, this); + o = s.taboption('general', widgets.DeviceSelect, '_net_device', _('Device')); + o.ucioption = 'device'; + o.nobridges = false; + o.optional = false; + o.network = ifc.getName(); + o = s.taboption('general', form.Flag, 'auto', _('Bring up on boot')); o.modalonly = true; o.default = o.enabled; @@ -568,7 +675,7 @@ return view.extend({ so = ss.taboption('ipv6', form.Value, 'ra_maxinterval', _('Max <abbr title="Router Advertisement">RA</abbr> interval'), _('Maximum time allowed \ between sending unsolicited <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr>. Default is 600 seconds (<code>600</code>).')); so.optional = true; - so.default = '600'; + so.placeholder = '600'; so.depends('ra', 'server'); so.depends('ra', 'hybrid'); so.depends('ra', 'relay'); @@ -577,7 +684,7 @@ return view.extend({ so = ss.taboption('ipv6', form.Value, 'ra_mininterval', _('Min <abbr title="Router Advertisement">RA</abbr> interval'), _('Minimum time allowed \ between sending unsolicited <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr>. Default is 200 seconds (<code>200</code>).')); so.optional = true; - so.default = '200'; + so.placeholder = '200'; so.depends('ra', 'server'); so.depends('ra', 'hybrid'); so.depends('ra', 'relay'); @@ -586,7 +693,6 @@ return view.extend({ in <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages. Default is 1800 seconds (<code>1800</code>). \ Max 9000 seconds.')); so.optional = true; - so.default = '1800'; so.depends('ra', 'server'); so.depends('ra', 'hybrid'); so.depends('ra', 'relay'); @@ -595,7 +701,6 @@ return view.extend({ to be published in <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages. Default is 0 (<code>0</code>).\ Min 1280.')); so.optional = true; - so.default = '0'; so.depends('ra', 'server'); so.depends('ra', 'hybrid'); so.depends('ra', 'relay'); @@ -604,7 +709,6 @@ return view.extend({ to be published in <abbr title="Router Advertisement">RA</abbr> messages.<br />Default is 0 (<code>0</code>), meaning unspecified.\ Max 255.')); so.optional = true; - so.default = '0'; so.depends('ra', 'server'); so.depends('ra', 'hybrid'); so.depends('ra', 'relay'); @@ -657,6 +761,7 @@ return view.extend({ so = ss.taboption('ipv6', form.Flag, 'ndproxy_routing', _('Learn routes from NDP'), _('Default is on.')); so.default = '1'; + so.optional = true; so = ss.taboption('ipv6', form.Flag, 'ndproxy_slave', _('NDP-Proxy slave'), _('Set interface as NDP-Proxy external slave. Default is off.')); @@ -666,10 +771,6 @@ return view.extend({ so.depends('dhcpv6', 'relay'); so.depends('dhcpv6', 'hybrid'); - so = ss.taboption('ipv6', form.Flag , 'master', _('Master'), _('Set this interface as master for the dhcpv6 relay.')); - so.depends('dhcpv6', 'relay'); - so.depends('dhcpv6', 'hybrid'); - so = ss.taboption('ipv6', form.Flag, 'ra_default', _('Announce as default router'), _('Always, even if no public prefix is available.')); so.depends('ra', 'server'); so.depends('ra', 'hybrid'); @@ -679,7 +780,6 @@ return view.extend({ } ifc.renderFormOptions(s); - nettools.addDeviceOptions(s, null, true); // Common interface options o = nettools.replaceOption(s, 'advanced', form.Flag, 'defaultroute', _('Use default gateway'), _('If unchecked, no default route is configured')); @@ -786,11 +886,10 @@ return view.extend({ case '_ifacestat_modal': continue; - case 'ifname_multi': - case 'ifname_single': case 'igmp_snooping': case 'stp': case 'type': + case '_net_device': var deps = []; for (var j = 0; j < protocols.length; j++) { if (!protocols[j].isVirtual()) { @@ -819,10 +918,10 @@ return view.extend({ s.handleModalCancel = function(/* ... */) { var type = uci.get('network', this.activeSection || this.addedSection, 'type'), - ifname = (type == 'bridge') ? 'br-%s'.format(this.activeSection || this.addedSection) : null; + device = (type == 'bridge') ? 'br-%s'.format(this.activeSection || this.addedSection) : null; uci.sections('network', 'bridge-vlan', function(bvs) { - if (ifname != null && bvs.device == ifname) + if (device != null && bvs.device == device) uci.remove('network', bvs['.name']); }); @@ -833,7 +932,7 @@ return view.extend({ var m2 = new form.Map('network'), s2 = m2.section(form.NamedSection, '_new_'), protocols = network.getProtocols(), - proto, name, bridge, ifname_single, ifname_multi; + proto, name, device; protocols.sort(function(a, b) { return a.getProtocol() > b.getProtocol(); @@ -866,30 +965,15 @@ return 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.modalonly = true; - bridge.disabled = ''; - bridge.enabled = 'bridge'; - - ifname_single = s2.option(widgets.DeviceSelect, 'ifname_single', _('Interface')); - ifname_single.noaliases = false; - ifname_single.optional = false; - - ifname_multi = s2.option(widgets.DeviceSelect, 'ifname_multi', _('Interface')); - ifname_multi.nobridges = true; - ifname_multi.noaliases = true; - ifname_multi.multiple = true; - ifname_multi.optional = true; - ifname_multi.display_size = 6; + device = s2.option(widgets.DeviceSelect, 'device', _('Device')); + device.noaliases = false; + device.optional = false; for (var i = 0; i < protocols.length; i++) { proto.value(protocols[i].getProtocol(), protocols[i].getI18n()); - if (!protocols[i].isVirtual()) { - bridge.depends({ proto: protocols[i].getProtocol() }); - ifname_single.depends({ type: '', proto: protocols[i].getProtocol() }); - ifname_multi.depends({ type: 'bridge', proto: protocols[i].getProtocol() }); - } + if (!protocols[i].isVirtual()) + device.depends('proto', protocols[i].getProtocol()); } m2.render().then(L.bind(function(nodes) { @@ -922,16 +1006,7 @@ return view.extend({ var section_id = uci.add('network', 'interface', nameval); protoclass.set('proto', protoval); - - if (ifname_single.isActive('_new_')) { - protoclass.addDevice(ifname_single.formvalue('_new_')); - } - else if (ifname_multi.isActive('_new_')) { - protoclass.set('type', 'bridge'); - L.toArray(ifname_multi.formvalue('_new_')).map(function(dev) { - protoclass.addDevice(dev); - }); - } + protoclass.addDevice(device.formvalue('_new_')); m.children[0].addedSection = section_id; }).then(L.bind(m.children[0].renderMoreOptionsModal, m.children[0], nameval)); @@ -1049,6 +1124,18 @@ return view.extend({ if (m) { var devtype = getDevType(section_id); + /* Treat not explicitly configured, preexisting VLAN interfaces + as simple network devices when adding configuration for them, + since it is more likely that people want to set general device + properties such as MAC address instead of reconfiguring ingress/ + egress QoS mapping, which is the only editable property of + preexisting VLAN device config dialogs. + + Ref: https://github.com/openwrt/luci/issues/5102 + */ + if (devtype == '8021q') + devtype = 'ethernet'; + section_id = uci.add('network', 'device'); uci.set('network', section_id, 'name', m[1]); @@ -1065,6 +1152,7 @@ return view.extend({ deleteBtn = trEl.querySelector('button:last-child'); deleteBtn.firstChild.data = _('Reset'); + deleteBtn.setAttribute('title', _('Remove related device settings from the configuration')); deleteBtn.disabled = section_id.match(/^dev:/) ? true : null; return trEl; @@ -1103,10 +1191,11 @@ return view.extend({ } function getDevType(section_id) { - var cfgtype = uci.get('network', section_id, 'type'), - dev = getDevice(section_id); + var dev = getDevice(section_id), + cfg = uci.get('network', section_id), + type = cfg ? (uci.get('network', section_id, 'type') || 'ethernet') : (dev ? dev.getType() : ''); - switch (cfgtype || (dev ? dev.getType() : '')) { + switch (type) { case '': return null; |