diff mbox series

[luci] luci-mod-network: drop support for *editing* legacy bridges

Message ID 20210517090251.27217-1-zajec5@gmail.com
State Accepted
Delegated to: Rafał Miłecki
Headers show
Series [luci] luci-mod-network: drop support for *editing* legacy bridges | expand

Commit Message

Rafał Miłecki May 17, 2021, 9:02 a.m. UTC
From: Rafał Miłecki <rafal@milecki.pl>

The old way of defining bridge (L2) as part of interface (L3) is
deprecated. All such configs should be migrated to define bridge as L3
UCI section type "device".

Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
---
 .../luci-static/resources/tools/network.js    | 388 +++++++-----------
 .../resources/view/network/interfaces.js      |   7 +-
 2 files changed, 147 insertions(+), 248 deletions(-)
diff mbox series

Patch

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..b4d941c60d 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
@@ -385,107 +385,41 @@  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());
+		var o, ss;
 
-				if (!value)
-					new_ifnames.length = Math.max(new_ifnames.length, 1);
-
-				old_ifnames.sort();
-				new_ifnames.sort();
-
-				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;
-					}
-				}
+		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'));
 
-				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);
+		};
+		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, 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) {
@@ -504,29 +438,12 @@  return baseclass.extend({
 
 			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 +463,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.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;
+			}
 
-				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 +530,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 +601,24 @@  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 = 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', '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 +637,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 +672,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 +803,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 +850,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 +914,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, 'ifname')).forEach(function(ifname) {
+			seen_ports[ifname] = 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/interfaces.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js
index a707fcf954..6eebb09e9d 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
@@ -420,6 +420,12 @@  return view.extend({
 						.then(L.bind(this.renderMoreOptionsModal, this, s.section));
 				}, this);
 
+				o = s.taboption('general', widgets.DeviceSelect, 'ifname', _('Interface'));
+				o.nobridges = false;
+				o.noaliases = 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;
@@ -677,7 +683,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'));