'use strict';
'require view';
'require dom';
'require poll';
'require fs';
'require ui';
'require uci';
'require form';
'require network';
'require firewall';
'require tools.widgets as widgets';
var isReadonlyView = !L.hasViewPermission() || null;
function count_changes(section_id) {
	var changes = ui.changes.changes, n = 0;
	if (!L.isObject(changes))
		return n;
	if (Array.isArray(changes.network))
		for (var i = 0; i < changes.network.length; i++)
			n += (changes.network[i][1] == section_id);
	if (Array.isArray(changes.dhcp))
		for (var i = 0; i < changes.dhcp.length; i++)
			n += (changes.dhcp[i][1] == section_id);
	return n;
}
function render_iface(dev, alias) {
	var type = dev ? dev.getType() : 'ethernet',
	    up   = dev ? dev.isUp() : false;
	return E('span', { class: 'cbi-tooltip-container' }, [
		E('img', { 'class' : 'middle', 'src': L.resource('icons/%s%s.png').format(
			alias ? 'alias' : type,
			up ? '' : '_disabled') }),
		E('span', { 'class': 'cbi-tooltip ifacebadge large' }, [
			E('img', { 'src': L.resource('icons/%s%s.png').format(
				type, up ? '' : '_disabled') }),
			L.itemlist(E('span', { 'class': 'left' }), [
				_('Type'),      dev ? dev.getTypeI18n() : null,
				_('Device'),    dev ? dev.getName() : _('Not present'),
				_('Connected'), up ? _('yes') : _('no'),
				_('MAC'),       dev ? dev.getMAC() : null,
				_('RX'),        dev ? '%.2mB (%d %s)'.format(dev.getRXBytes(), dev.getRXPackets(), _('Pkts.')) : null,
				_('TX'),        dev ? '%.2mB (%d %s)'.format(dev.getTXBytes(), dev.getTXPackets(), _('Pkts.')) : null
			])
		])
	]);
}
function render_status(node, ifc, with_device) {
	var desc = null, c = [];
	if (ifc.isDynamic())
		desc = _('Virtual dynamic interface');
	else if (ifc.isAlias())
		desc = _('Alias Interface');
	else if (!uci.get('network', ifc.getName()))
		return L.itemlist(node, [
			null, E('em', _('Interface is marked for deletion'))
		]);
	var i18n = ifc.getI18n();
	if (i18n)
		desc = desc ? '%s (%s)'.format(desc, i18n) : i18n;
	var changecount = with_device ? 0 : count_changes(ifc.getName()),
	    ipaddrs = changecount ? [] : ifc.getIPAddrs(),
	    ip6addrs = changecount ? [] : ifc.getIP6Addrs(),
	    errors = ifc.getErrors(),
	    maindev = ifc.getL3Device() || ifc.getDevice(),
	    macaddr = maindev ? maindev.getMAC() : null;
	return L.itemlist(node, [
		_('Label'), ifc.get('label'),
		_('Protocol'), with_device ? null : (desc || '?'),
		_('Device'),   with_device ? (maindev ? maindev.getShortName() : E('em', _('Not present'))) : null,
		_('Uptime'),   (!changecount && ifc.isUp()) ? '%t'.format(ifc.getUptime()) : null,
		_('MAC'),      (!changecount && !ifc.isDynamic() && !ifc.isAlias() && macaddr) ? macaddr : null,
		_('RX'),       (!changecount && !ifc.isDynamic() && !ifc.isAlias() && maindev) ? '%.2mB (%d %s)'.format(maindev.getRXBytes(), maindev.getRXPackets(), _('Pkts.')) : null,
		_('TX'),       (!changecount && !ifc.isDynamic() && !ifc.isAlias() && maindev) ? '%.2mB (%d %s)'.format(maindev.getTXBytes(), maindev.getTXPackets(), _('Pkts.')) : null,
		_('IPv4'),     ipaddrs[0],
		_('IPv4'),     ipaddrs[1],
		_('IPv4'),     ipaddrs[2],
		_('IPv4'),     ipaddrs[3],
		_('IPv4'),     ipaddrs[4],
		_('IPv6'),     ip6addrs[0],
		_('IPv6'),     ip6addrs[1],
		_('IPv6'),     ip6addrs[2],
		_('IPv6'),     ip6addrs[3],
		_('IPv6'),     ip6addrs[4],
		_('IPv6'),     ip6addrs[5],
		_('IPv6'),     ip6addrs[6],
		_('IPv6'),     ip6addrs[7],
		_('IPv6'),     ip6addrs[8],
		_('IPv6'),     ip6addrs[9],
		_('IPv6-PD'),  changecount ? null : ifc.getIP6Prefix(),
		_('Information'), with_device ? null : (ifc.get('auto') != '0' ? null : _('Not started on boot')),
		_('Error'),    errors ? errors[0] : null,
		_('Error'),    errors ? errors[1] : null,
		_('Error'),    errors ? errors[2] : null,
		_('Error'),    errors ? errors[3] : null,
		_('Error'),    errors ? errors[4] : null,
		null, changecount ? E('a', {
			href: '#',
			click: L.bind(ui.changes.displayChanges, ui.changes)
		}, _('Interface has %d pending changes').format(changecount)) : null
	]);
}
function render_modal_status(node, ifc) {
	var dev = ifc ? (ifc.getDevice() || ifc.getL3Device() || ifc.getL3Device()) : null;
	dom.content(node, [
		E('img', {
			'src': L.resource('icons/%s%s.png').format(dev ? dev.getType() : 'ethernet', (dev && dev.isUp()) ? '' : '_disabled'),
			'title': dev ? dev.getTypeI18n() : _('Not present')
		}),
		ifc ? render_status(E('span'), ifc, true) : E('em', _('Interface not present or not connected yet.'))
	]);
	return node;
}
function render_ifacebox_status(node, ifc) {
	var dev = ifc.getL3Device() || ifc.getDevice(),
	    subdevs = ifc.getDevices(),
	    c = [ render_iface(dev, ifc.isAlias()) ];
	if (subdevs && subdevs.length) {
		var sifs = [ ' (' ];
		for (var j = 0; j < subdevs.length; j++)
			sifs.push(render_iface(subdevs[j]));
		sifs.push(')');
		c.push(E('span', {}, sifs));
	}
	c.push(E('br'));
	c.push(E('small', {}, ifc.isAlias() ? _('Alias of "%s"').format(ifc.isAlias())
	                                    : (dev ? dev.getName() : E('em', _('Not present')))));
	dom.content(node, c);
	return firewall.getZoneByNetwork(ifc.getName()).then(L.bind(function(zone) {
		this.style.backgroundColor = zone ? zone.getColor() : '#EEEEEE';
		this.title = zone ? _('Part of zone %q').format(zone.getName()) : _('No zone assigned');
	}, node.previousElementSibling));
}
function iface_updown(up, id, ev, force) {
	var row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id)),
	    dsc = row.querySelector('[data-name="_ifacestat"] > div'),
	    btns = row.querySelectorAll('.cbi-section-actions .reconnect, .cbi-section-actions .down');
	btns[+!up].blur();
	btns[+!up].classList.add('spinning');
	btns[0].disabled = true;
	btns[1].disabled = true;
	if (!up) {
		L.resolveDefault(fs.exec_direct('/usr/libexec/luci-peeraddr')).then(function(res) {
			var info = null; try { info = JSON.parse(res); } catch(e) {}
			if (L.isObject(info) &&
			    Array.isArray(info.inbound_interfaces) &&
			    info.inbound_interfaces.filter(function(i) { return i == id })[0]) {
				ui.showModal(_('Confirm disconnect'), [
					E('p', _('You appear to be currently connected to the device via the "%h" interface. Do you really want to shut down the interface?').format(id)),
					E('div', { 'class': 'right' }, [
						E('button', {
							'class': 'cbi-button cbi-button-neutral',
							'click': function(ev) {
								btns[1].classList.remove('spinning');
								btns[1].disabled = false;
								btns[0].disabled = false;
								ui.hideModal();
							}
						}, _('Cancel')),
						' ',
						E('button', {
							'class': 'cbi-button cbi-button-negative important',
							'click': function(ev) {
								dsc.setAttribute('disconnect', '');
								dom.content(dsc, E('em', _('Interface is shutting down...')));
								ui.hideModal();
							}
						}, _('Disconnect'))
					])
				]);
			}
			else {
				dsc.setAttribute('disconnect', '');
				dom.content(dsc, E('em', _('Interface is shutting down...')));
			}
		});
	}
	else {
		dsc.setAttribute(up ? 'reconnect' : 'disconnect', force ? 'force' : '');
		dom.content(dsc, E('em', up ? _('Interface is reconnecting...') : _('Interface is shutting down...')));
	}
}
function get_netmask(s, use_cfgvalue) {
	var readfn = use_cfgvalue ? 'cfgvalue' : 'formvalue',
	    addropt = s.children.filter(function(o) { return o.option == 'ipaddr'})[0],
	    addrvals = addropt ? L.toArray(addropt[readfn](s.section)) : [],
	    maskopt = s.children.filter(function(o) { return o.option == 'netmask'})[0],
	    maskval = maskopt ? maskopt[readfn](s.section) : null,
	    firstsubnet = maskval ? addrvals[0] + '/' + maskval : addrvals.filter(function(a) { return a.indexOf('/') > 0 })[0];
	if (firstsubnet == null)
		return null;
	var mask = firstsubnet.split('/')[1];
	if (!isNaN(mask))
		mask = network.prefixToMask(+mask);
	return mask;
}
return view.extend({
	poll_status: function(map, networks) {
		var resolveZone = null;
		for (var i = 0; i < networks.length; i++) {
			var ifc = networks[i],
			    row = map.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(ifc.getName()));
			if (row == null)
				continue;
			var dsc = row.querySelector('[data-name="_ifacestat"] > div'),
			    box = row.querySelector('[data-name="_ifacebox"] .ifacebox-body'),
			    btn1 = row.querySelector('.cbi-section-actions .reconnect'),
			    btn2 = row.querySelector('.cbi-section-actions .down'),
			    stat = document.querySelector('[id="%s-ifc-status"]'.format(ifc.getName())),
			    resolveZone = render_ifacebox_status(box, ifc),
			    disabled = ifc ? !ifc.isUp() : true,
			    dynamic = ifc ? ifc.isDynamic() : false;
			if (dsc.hasAttribute('reconnect')) {
				dom.content(dsc, E('em', _('Interface is starting...')));
			}
			else if (dsc.hasAttribute('disconnect')) {
				dom.content(dsc, E('em', _('Interface is stopping...')));
			}
			else if (ifc.getProtocol() || uci.get('network', ifc.getName()) == null) {
				render_status(dsc, ifc, false);
			}
			else if (!ifc.getProtocol()) {
				var e = map.querySelector('[id="cbi-network-%s"] .cbi-button-edit'.format(ifc.getName()));
				if (e) e.disabled = true;
				var link = L.url('admin/system/opkg') + '?query=luci-proto';
				dom.content(dsc, [
					E('em', _('Unsupported protocol type.')), E('br'),
					E('a', { href: link }, _('Install protocol extensions...'))
				]);
			}
			else {
				dom.content(dsc, E('em', _('Interface not present or not connected yet.')));
			}
			if (stat) {
				var dev = ifc.getDevice();
				dom.content(stat, [
					E('img', {
						'src': L.resource('icons/%s%s.png').format(dev ? dev.getType() : 'ethernet', (dev && dev.isUp()) ? '' : '_disabled'),
						'title': dev ? dev.getTypeI18n() : _('Not present')
					}),
					render_status(E('span'), ifc, true)
				]);
			}
			btn1.disabled = isReadonlyView || btn1.classList.contains('spinning') || btn2.classList.contains('spinning') || dynamic;
			btn2.disabled = isReadonlyView || btn1.classList.contains('spinning') || btn2.classList.contains('spinning') || dynamic || disabled;
		}
		return Promise.all([ resolveZone, network.flushCache() ]);
	},
	load: function() {
		return Promise.all([
			network.getDSLModemType(),
			uci.changes()
		]);
	},
	render: function(data) {
		var dslModemType = data[0],
		    m, s, o;
		m = new form.Map('network');
		m.tabbed = true;
		m.chain('dhcp');
		s = m.section(form.GridSection, 'interface', _('Interfaces'));
		s.anonymous = true;
		s.addremove = true;
		s.addbtntitle = _('Add new interface...');
		s.load = function() {
			return Promise.all([
				network.getNetworks(),
				firewall.getZones()
			]).then(L.bind(function(data) {
				this.networks = data[0];
				this.zones = data[1];
			}, this));
		};
		s.tab('general', _('General Settings'));
		s.tab('advanced', _('Advanced Settings'));
		s.tab('physical', _('Physical Settings'));
		s.tab('firewall', _('Firewall Settings'));
		s.tab('dhcp', _('DHCP Server'));
		s.cfgsections = function() {
			return this.networks.map(function(n) { return n.getName() })
				.filter(function(n) { return n != 'loopback' });
		};
		s.modaltitle = function(section_id) {
			return _('Interfaces') + ' » ' + section_id.toUpperCase();
		};
		s.renderRowActions = function(section_id) {
			var tdEl = this.super('renderRowActions', [ section_id, _('Edit') ]),
			    net = this.networks.filter(function(n) { return n.getName() == section_id })[0],
			    disabled = net ? !net.isUp() : true,
			    dynamic = net ? net.isDynamic() : false;
			dom.content(tdEl.lastChild, [
				E('button', {
					'class': 'cbi-button cbi-button-neutral reconnect',
					'click': iface_updown.bind(this, true, section_id),
					'title': _('Reconnect this interface'),
					'disabled': dynamic ? 'disabled' : null
				}, _('Restart')),
				E('button', {
					'class': 'cbi-button cbi-button-neutral down',
					'click': iface_updown.bind(this, false, section_id),
					'title': _('Shutdown this interface'),
					'disabled': (dynamic || disabled) ? 'disabled' : null
				}, _('Stop')),
				tdEl.lastChild.firstChild,
				tdEl.lastChild.lastChild
			]);
			if (!dynamic && net && !uci.get('network', net.getName())) {
				tdEl.lastChild.childNodes[0].disabled = true;
				tdEl.lastChild.childNodes[2].disabled = true;
				tdEl.lastChild.childNodes[3].disabled = true;
			}
			return tdEl;
		};
		s.addModalOptions = function(s) {
			var protoval = uci.get('network', s.section, 'proto'),
			    protoclass = protoval ? network.getProtocol(protoval) : null,
			    o, ifname_single, ifname_multi, ifname_master, proto_select, proto_switch, type, stp, igmp, ss, so;
			if (!protoval)
				return;
			return network.getNetwork(s.section).then(L.bind(function(ifc) {
				var protocols = network.getProtocols();
				protocols.sort(function(a, b) {
					return a.getProtocol() > b.getProtocol();
				});
				o = s.taboption('general', form.DummyValue, '_ifacestat_modal', _('Status'));
				o.modalonly = true;
				o.cfgvalue = L.bind(function(section_id) {
					var net = this.networks.filter(function(n) { return n.getName() == section_id })[0];
					return render_modal_status(E('div', {
						'id': '%s-ifc-status'.format(section_id),
						'class': 'ifacebadge large'
					}), net);
				}, this);
				o.write = function() {};
				o = s.taboption('general', form.Value, 'label', _('Label'));
				o.modalonly = true;
				o.optional = true;
				proto_select = s.taboption('general', form.ListValue, 'proto', _('Protocol'));
				proto_select.modalonly = true;
				proto_switch = s.taboption('general', form.Button, '_switch_proto');
				proto_switch.modalonly  = true;
				proto_switch.title      = _('Really switch protocol?');
				proto_switch.inputtitle = _('Switch protocol');
				proto_switch.inputstyle = 'apply';
				proto_switch.onclick = L.bind(function(ev) {
					s.map.save()
						.then(L.bind(m.load, m))
						.then(L.bind(m.render, m))
						.then(L.bind(this.renderMoreOptionsModal, this, s.section));
				}, this);
				o = s.taboption('general', form.Flag, 'auto', _('Bring up on boot'));
				o.modalonly = true;
				o.default = o.enabled;
				type = s.taboption('physical', form.ListValue, 'type', _('Type'));
				type.value('',_('Normal'));
				type.value('bridge',_('Bridge'));
				type.value('macvlan',_('MacVLAN'));
				type.write = type.remove = function(section_id, value) {
					var protocol = network.getProtocol(proto_select.formvalue(section_id)),
					    ifnameopt = this.section.children.filter(function(o) { 
						    if (value == 'bridge')
							return o.option == 'ifname_multi';
						    else if (value == 'macvlan')
							return o.option == 'ifname_master';
						    else
							return o.option == 'ifname_single';
					    })[0];
					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 new_ifnames = L.toArray(ifnameopt.formvalue(section_id));
					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;
						}
					}
					if (value)
						uci.set('network', section_id, 'type', value);
					else
						uci.unset('network', section_id, 'type');
				};
				stp = s.taboption('physical', form.Flag, 'stp', _('Enable STP'), _('Enables the Spanning Tree Protocol on this bridge'));
				igmp = s.taboption('physical', form.Flag, 'igmp_snooping', _('Enable IGMP snooping'), _('Enables IGMP snooping on this bridge'));
				ifname_master = s.taboption('physical', widgets.DeviceSelect, 'ifname_master', _('Base interface'));
				ifname_master.nobridges = true;
				ifname_master.noaliases = true;
				ifname_master.optional = false;
				ifname_master.modalonly = true;
				ifname_master.network = ifc.getName();
				//ifname_master.write = ifname_master.remove = function() {};
				ifname_master.ucioption = 'masterintf';
				//ifname_master.cfgvalue = function(section_id) {
				//	return uci.get('network', section_id, 'masterintf');
				//};
				//ifname_master.write = function(section_id, value) {
				//	uci.set('network', section_id, 'masterintf', value);
				//};
				ifname_single = s.taboption('physical', widgets.DeviceSelect, 'ifname_single', _('Interface'));
				ifname_single.nobridges = ifc.isBridge();
				ifname_single.noaliases = false;
				ifname_single.optional = false;
				ifname_single.network = ifc.getName();
				ifname_single.write = ifname_single.remove = function() {};
				ifname_multi = s.taboption('physical', widgets.DeviceSelect, 'ifname_multi', _('Interface'));
				ifname_multi.nobridges = ifc.isBridge();
				ifname_multi.noaliases = true;
				ifname_multi.multiple = true;
				ifname_multi.optional = true;
				ifname_multi.network = ifc.getName();
				ifname_multi.display_size = 6;
				ifname_multi.write = ifname_multi.remove = function() {};
				ifname_single.cfgvalue = ifname_multi.cfgvalue = function(section_id) {
					var devs = ifc.getDevices() || L.toArray(ifc.getDevice()),
					    ifnames = [];
					for (var i = 0; i < devs.length; i++)
						ifnames.push(devs[i].getName());
					return ifnames;
				};
				if (L.hasSystemFeature('firewall')) {
					o = s.taboption('firewall', widgets.ZoneSelect, '_zone', _('Create / Assign firewall-zone'), _('Choose the firewall zone you want to assign to this interface. Select unspecified to remove the interface from the associated zone or fill out the custom field to define a new zone and attach the interface to it.'));
					o.network = ifc.getName();
					o.optional = true;
					o.cfgvalue = function(section_id) {
						return firewall.getZoneByNetwork(ifc.getName()).then(function(zone) {
							return (zone != null ? zone.getName() : null);
						});
					};
					o.write = o.remove = function(section_id, value) {
						return Promise.all([
							firewall.getZoneByNetwork(ifc.getName()),
							(value != null) ? firewall.getZone(value) : null
						]).then(function(data) {
							var old_zone = data[0],
							    new_zone = data[1];
							if (old_zone == null && new_zone == null && (value == null || value == ''))
								return;
							if (old_zone != null && new_zone != null && old_zone.getName() == new_zone.getName())
								return;
							if (old_zone != null)
								old_zone.deleteNetwork(ifc.getName());
							if (new_zone != null)
								new_zone.addNetwork(ifc.getName());
							else if (value != null)
								return firewall.addZone(value).then(function(new_zone) {
									new_zone.addNetwork(ifc.getName());
								});
						});
					};
				}
				for (var i = 0; i < protocols.length; i++) {
					proto_select.value(protocols[i].getProtocol(), protocols[i].getI18n());
					if (protocols[i].getProtocol() != uci.get('network', s.section, 'proto'))
						proto_switch.depends('proto', protocols[i].getProtocol());
					if (!protocols[i].isVirtual()) {
						type.depends('proto', protocols[i].getProtocol());
						stp.depends({ type: 'bridge', proto: protocols[i].getProtocol() });
						igmp.depends({ type: 'bridge', proto: protocols[i].getProtocol() });
						ifname_single.depends({ type: '', proto: protocols[i].getProtocol() });
						ifname_master.depends({ type: 'macvlan', proto: protocols[i].getProtocol() });
						ifname_multi.depends({ type: 'bridge', proto: protocols[i].getProtocol() });
					}
				}
				o = s.taboption('advanced', form.ListValue, 'multipath', _('Multipath setting'), _('Only one interface must be set as Master.'));
				o.value('on',_('Enabled'));
				o.value('off',_('Disabled'));
				o.value('master',_('Master'));
				o.value('backup',_('Backup'));
				o.default = 'off';
				o = s.taboption('advanced', form.Value, 'addlatency', _('Additional latency'));
				o.datatype = 'uinteger';
				o.default = '0';
				if (L.hasSystemFeature('dnsmasq') || L.hasSystemFeature('odhcpd')) {
					o = s.taboption('dhcp', form.SectionValue, '_dhcp', form.TypedSection, 'dhcp');
					o.depends('proto', 'static');
					ss = o.subsection;
					ss.uciconfig = 'dhcp';
					ss.addremove = false;
					ss.anonymous = true;
					ss.tab('general',  _('General Setup'));
					ss.tab('advanced', _('Advanced Settings'));
					ss.tab('ipv6', _('IPv6 Settings'));
					ss.filter = function(section_id) {
						return (uci.get('dhcp', section_id, 'interface') == ifc.getName());
					};
					ss.renderSectionPlaceholder = function() {
						return E('div', { 'class': 'cbi-section-create' }, [
							E('p', _('No DHCP Server configured for this interface') + '   '),
							E('button', {
								'class': 'cbi-button cbi-button-add',
								'title': _('Setup DHCP Server'),
								'click': ui.createHandlerFn(this, function(section_id, ev) {
									this.map.save(function() {
										uci.add('dhcp', 'dhcp', section_id);
										uci.set('dhcp', section_id, 'interface', section_id);
										uci.set('dhcp', section_id, 'start', 100);
										uci.set('dhcp', section_id, 'limit', 150);
										uci.set('dhcp', section_id, 'leasetime', '12h');
									});
								}, ifc.getName())
							}, _('Setup DHCP Server'))
						]);
					};
					ss.taboption('general', form.Flag, 'ignore', _('Ignore interface'), _('Disable DHCP for this interface.'));
					so = ss.taboption('general', form.Value, 'start', _('Start'), _('Lowest leased address as offset from the network address.'));
					so.optional = true;
					so.datatype = 'or(uinteger,ip4addr("nomask"))';
					so.default = '100';
					so = ss.taboption('general', form.Value, 'limit', _('Limit'), _('Maximum number of leased addresses.'));
					so.optional = true;
					so.datatype = 'uinteger';
					so.default = '150';
					so = ss.taboption('general', form.Value, 'leasetime', _('Lease time'), _('Expiry time of leased addresses, minimum is 2 minutes (2m).'));
					so.optional = true;
					so.default = '12h';
					so = ss.taboption('advanced', form.Flag, 'dynamicdhcp', _('Dynamic DHCP'), _('Dynamically allocate DHCP addresses for clients. If disabled, only clients having static leases will be served.'));
					so.default = so.enabled;
					ss.taboption('advanced', form.Flag, 'force', _('Force'), _('Force DHCP on this network even if another server is detected.'));
					// XXX: is this actually useful?
					//ss.taboption('advanced', form.Value, 'name', _('Name'), _('Define a name for this network.'));
					so = ss.taboption('advanced', form.Value, 'netmask', _('IPv4-Netmask'), _('Override the netmask sent to clients. Normally it is calculated from the subnet that is served.'));
					so.optional = true;
					so.datatype = 'ip4addr';
					so.render = function(option_index, section_id, in_table) {
						this.placeholder = get_netmask(s, true);
						return form.Value.prototype.render.apply(this, [ option_index, section_id, in_table ]);
					};
					so.validate = function(section_id, value) {
						var node = this.map.findElement('id', this.cbid(section_id));
						if (node)
							node.querySelector('input').setAttribute('placeholder', get_netmask(s, false));
						return form.Value.prototype.validate.apply(this, [ section_id, value ]);
					};
					ss.taboption('advanced', form.DynamicList, 'dhcp_option', _('DHCP-Options'), _('Define additional DHCP options, for example "6,192.168.2.1,192.168.2.2" which advertises different DNS servers to clients.'));
					for (var i = 0; i < ss.children.length; i++)
						if (ss.children[i].option != 'ignore')
							ss.children[i].depends('ignore', '0');
					so = ss.taboption('ipv6', form.ListValue, 'ra', _('Router Advertisement-Service'));
					so.value('', _('disabled'));
					so.value('server', _('server mode'));
					so.value('relay', _('relay mode'));
					so.value('hybrid', _('hybrid mode'));
					so = ss.taboption('ipv6', form.ListValue, 'dhcpv6', _('DHCPv6-Service'));
					so.value('', _('disabled'));
					so.value('server', _('server mode'));
					so.value('relay', _('relay mode'));
					so.value('hybrid', _('hybrid mode'));
					so = ss.taboption('ipv6', form.ListValue, 'ndp', _('NDP-Proxy'));
					so.value('', _('disabled'));
					so.value('relay', _('relay mode'));
					so.value('hybrid', _('hybrid mode'));
					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.ListValue, 'ra_management', _('DHCPv6-Mode'), _('Default is stateless + stateful'));
					so.value('0', _('stateless'));
					so.value('1', _('stateless + stateful'));
					so.value('2', _('stateful-only'));
					so.depends('dhcpv6', 'server');
					so.depends('dhcpv6', 'hybrid');
					so.default = '1';
					so = ss.taboption('ipv6', form.Flag, 'ra_default', _('Always announce default router'), _('Announce as default router even if no public prefix is available.'));
					so.depends('ra', 'server');
					so.depends('ra', 'hybrid');
					ss.taboption('ipv6', form.DynamicList, 'dns', _('Announced DNS servers'));
					ss.taboption('ipv6', form.DynamicList, 'domain', _('Announced DNS domains'));
				}
				ifc.renderFormOptions(s);
				for (var i = 0; i < s.children.length; i++) {
					o = s.children[i];
					switch (o.option) {
					case 'proto':
					case 'delegate':
					case 'auto':
					case 'type':
					case 'stp':
					case 'igmp_snooping':
					case 'ifname_single':
					case 'ifname_multi':
					case 'ifname_master':
					case '_dhcp':
					case '_zone':
					case '_switch_proto':
					case '_ifacestat_modal':
						continue;
					default:
						if (o.deps.length)
							for (var j = 0; j < o.deps.length; j++)
								o.deps[j].proto = protoval;
						else
							o.depends('proto', protoval);
					}
				}
			}, this));
		};
		s.handleAdd = function(ev) {
			var m2 = new form.Map('network'),
			    s2 = m2.section(form.NamedSection, '_new_'),
			    protocols = network.getProtocols(),
			    proto, name, type, ifname_single, ifname_multi, ifname_master;
			protocols.sort(function(a, b) {
				return a.getProtocol() > b.getProtocol();
			});
			s2.render = function() {
				return Promise.all([
					{},
					this.renderUCISection('_new_')
				]).then(this.renderContents.bind(this));
			};
			name = s2.option(form.Value, 'name', _('Name'));
			name.rmempty = false;
			name.datatype = 'uciname';
			name.placeholder = _('New interface name…');
			name.validate = function(section_id, value) {
				if (uci.get('network', value) != null)
					return _('The interface name is already used');
				var pr = network.getProtocol(proto.formvalue(section_id), value),
				    ifname = pr.isVirtual() ? '%s-%s'.format(pr.getProtocol(), value) : 'br-%s'.format(value);
				if (value.length > 15)
					return _('The interface name is too long');
				return true;
			};
			proto = s2.option(form.ListValue, 'proto', _('Protocol'));
			proto.validate = name.validate;
			type = s2.option(form.ListValue, 'type', _('Interface type'));
			type.value('',_('Normal'));
			type.value('bridge',_('Bridge'));
			type.value('macvlan',_('MacVLAN'));
			ifname_single = s2.option(widgets.DeviceSelect, 'ifname_single', _('Interface'));
			ifname_single.noaliases = false;
			ifname_single.optional = false;
			ifname_master = s2.option(widgets.DeviceSelect, 'ifname_master', _('Base interface'));
			ifname_master.noaliases = false;
			ifname_master.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;
			for (var i = 0; i < protocols.length; i++) {
				proto.value(protocols[i].getProtocol(), protocols[i].getI18n());
				if (!protocols[i].isVirtual()) {
					type.depends({ proto: protocols[i].getProtocol() });
					ifname_single.depends({ type: '', proto: protocols[i].getProtocol() });
					ifname_multi.depends({ type: 'bridge', proto: protocols[i].getProtocol() });
					ifname_master.depends({ type: 'macvlan', proto: protocols[i].getProtocol() });
				}
			}
			m2.render().then(L.bind(function(nodes) {
				ui.showModal(_('Add new interface...'), [
					nodes,
					E('div', { 'class': 'right' }, [
						E('button', {
							'class': 'btn',
							'click': ui.hideModal
						}, _('Cancel')), ' ',
						E('button', {
							'class': 'cbi-button cbi-button-positive important',
							'click': ui.createHandlerFn(this, function(ev) {
								var nameval = name.isValid('_new_') ? name.formvalue('_new_') : null,
								    protoval = proto.isValid('_new_') ? proto.formvalue('_new_') : null,
								    protoclass = protoval ? network.getProtocol(protoval) : null;
								if (nameval == null || protoval == null || nameval == '' || protoval == '')
									return;
								return protoclass.isCreateable(nameval).then(function(checkval) {
									if (checkval != null) {
										ui.addNotification(null,
												E('p', _('New interface for "%s" can not be created: %s').format(protoclass.getI18n(), checkval)));
										ui.hideModal();
										return;
									}
									return m.save(function() {
										var section_id = uci.add('network', 'interface', nameval);
										uci.set('network', section_id, 'proto', protoval);
										if (ifname_single.isActive('_new_')) {
											uci.set('network', section_id, 'ifname', ifname_single.formvalue('_new_'));
										}
										else if (ifname_multi.isActive('_new_')) {
											uci.set('network', section_id, 'type', 'bridge');
											uci.set('network', section_id, 'ifname', L.toArray(ifname_multi.formvalue('_new_')).join(' '));
										}
										else if (ifname_master.isActive('_new_')) {
											uci.set('network', section_id, 'type', 'macvlan');
											uci.set('network', section_id, 'ifname', section_id);
											uci.set('network', section_id, 'masterintf', L.toArray(ifname_multi.formvalue('_new_')).join(' '));
										}
									}).then(L.bind(m.children[0].renderMoreOptionsModal, m.children[0], nameval));
								});
							})
						}, _('Create interface'))
					])
				], 'cbi-modal');
				nodes.querySelector('[id="%s"] input[type="text"]'.format(name.cbid('_new_'))).focus();
			}, this));
		};
		s.handleRemove = function(section_id, ev) {
			return network.deleteNetwork(section_id).then(L.bind(function(section_id, ev) {
				return form.GridSection.prototype.handleRemove.apply(this, [section_id, ev]);
			}, this, section_id, ev));
		};
		o = s.option(form.DummyValue, '_ifacebox');
		o.modalonly = false;
		o.textvalue = function(section_id) {
			var net = this.section.networks.filter(function(n) { return n.getName() == section_id })[0],
			    zone = net ? this.section.zones.filter(function(z) { return !!z.getNetworks().filter(function(n) { return n == section_id })[0] })[0] : null;
			if (!net)
				return;
			var node = E('div', { 'class': 'ifacebox' }, [
				E('div', {
					'class': 'ifacebox-head',
					'style': 'background-color:%s'.format(zone ? zone.getColor() : '#EEEEEE'),
					'title': zone ? _('Part of zone %q').format(zone.getName()) : _('No zone assigned')
				}, E('strong', net.getName().toUpperCase())),
				E('div', {
					'class': 'ifacebox-body',
					'id': '%s-ifc-devices'.format(section_id),
					'data-network': section_id
				}, [
					E('img', {
						'src': L.resource('icons/ethernet_disabled.png'),
						'style': 'width:16px; height:16px'
					}),
					E('br'), E('small', '?')
				])
			]);
			render_ifacebox_status(node.childNodes[1], net);
			return node;
		};
		o = s.option(form.DummyValue, '_ifacestat');
		o.modalonly = false;
		o.textvalue = function(section_id) {
			var net = this.section.networks.filter(function(n) { return n.getName() == section_id })[0];
			if (!net)
				return;
			var node = E('div', { 'id': '%s-ifc-description'.format(section_id) });
			render_status(node, net, false);
			return node;
		};
		o = s.taboption('advanced', form.Flag, 'delegate', _('Use builtin IPv6-management'));
		o.modalonly = true;
		o.default = o.enabled;
		o = s.taboption('advanced', form.Flag, 'force_link', _('Force link'), _('Set interface properties regardless of the link carrier (If set, carrier sense events do not invoke hotplug handlers).'));
		o.modalonly = true;
		o.render = function(option_index, section_id, in_table) {
			var protoopt = this.section.children.filter(function(o) { return o.option == 'proto' })[0],
			    protoval = protoopt ? protoopt.cfgvalue(section_id) : null;
			this.default = (protoval == 'static') ? this.enabled : this.disabled;
			return this.super('render', [ option_index, section_id, in_table ]);
		};
		s = m.section(form.TypedSection, 'globals', _('Global network options'));
		s.addremove = false;
		s.anonymous = true;
		o = s.option(form.Value, 'ula_prefix', _('IPv6 ULA-Prefix'));
		o.datatype = 'cidr6';
		o = s.option(form.Flag, 'packet_steering', _('Packet Steering'), _('Enable packet steering across all CPUs. May help or hinder network speed.'));
		o.optional = true;
		if (dslModemType != null) {
			s = m.section(form.TypedSection, 'dsl', _('DSL'));
			s.anonymous = true;
			o = s.option(form.ListValue, 'annex', _('Annex'));
			o.value('a', _('Annex A + L + M (all)'));
			o.value('b', _('Annex B (all)'));
			o.value('j', _('Annex J (all)'));
			o.value('m', _('Annex M (all)'));
			o.value('bdmt', _('Annex B G.992.1'));
			o.value('b2', _('Annex B G.992.3'));
			o.value('b2p', _('Annex B G.992.5'));
			o.value('at1', _('ANSI T1.413'));
			o.value('admt', _('Annex A G.992.1'));
			o.value('alite', _('Annex A G.992.2'));
			o.value('a2', _('Annex A G.992.3'));
			o.value('a2p', _('Annex A G.992.5'));
			o.value('l', _('Annex L G.992.3 POTS 1'));
			o.value('m2', _('Annex M G.992.3'));
			o.value('m2p', _('Annex M G.992.5'));
			o = s.option(form.ListValue, 'tone', _('Tone'));
			o.value('', _('auto'));
			o.value('a', _('A43C + J43 + A43'));
			o.value('av', _('A43C + J43 + A43 + V43'));
			o.value('b', _('B43 + B43C'));
			o.value('bv', _('B43 + B43C + V43'));
			if (dslModemType == 'vdsl') {
				o = s.option(form.ListValue, 'xfer_mode', _('Encapsulation mode'));
				o.value('', _('auto'));
				o.value('atm', _('ATM (Asynchronous Transfer Mode)'));
				o.value('ptm', _('PTM/EFM (Packet Transfer Mode)'));
				o = s.option(form.ListValue, 'line_mode', _('DSL line mode'));
				o.value('', _('auto'));
				o.value('adsl', _('ADSL'));
				o.value('vdsl', _('VDSL'));
				o = s.option(form.ListValue, 'ds_snr_offset', _('Downstream SNR offset'));
				o.default = '0';
				for (var i = -100; i <= 100; i += 5)
					o.value(i, _('%.1f dB').format(i / 10));
			}
			s.option(form.Value, 'firmware', _('Firmware File'));
		}
		// Show ATM bridge section if we have the capabilities
		if (L.hasSystemFeature('br2684ctl')) {
			s = m.section(form.TypedSection, 'atm-bridge', _('ATM Bridges'), _('ATM bridges expose encapsulated ethernet in AAL5 connections as virtual Linux network interfaces which can be used in conjunction with DHCP or PPP to dial into the provider network.'));
			s.addremove = true;
			s.anonymous = true;
			s.addbtntitle = _('Add ATM Bridge');
			s.handleAdd = function(ev) {
				var sections = uci.sections('network', 'atm-bridge'),
				    max_unit = -1;
				for (var i = 0; i < sections.length; i++) {
					var unit = +sections[i].unit;
					if (!isNaN(unit) && unit > max_unit)
						max_unit = unit;
				}
				return this.map.save(function() {
					var sid = uci.add('network', 'atm-bridge');
					uci.set('network', sid, 'unit', max_unit + 1);
					uci.set('network', sid, 'atmdev', 0);
					uci.set('network', sid, 'encaps', 'llc');
					uci.set('network', sid, 'payload', 'bridged');
					uci.set('network', sid, 'vci', 35);
					uci.set('network', sid, 'vpi', 8);
				});
			};
			s.tab('general', _('General Setup'));
			s.tab('advanced', _('Advanced Settings'));
			o = s.taboption('general', form.Value, 'vci', _('ATM Virtual Channel Identifier (VCI)'));
			s.taboption('general', form.Value, 'vpi', _('ATM Virtual Path Identifier (VPI)'));
			o = s.taboption('general', form.ListValue, 'encaps', _('Encapsulation mode'));
			o.value('llc', _('LLC'));
			o.value('vc', _('VC-Mux'));
			s.taboption('advanced', form.Value, 'atmdev', _('ATM device number'));
			s.taboption('advanced', form.Value, 'unit', _('Bridge unit number'));
			o = s.taboption('advanced', form.ListValue, 'payload', _('Forwarding mode'));
			o.value('bridged', _('bridged'));
			o.value('routed', _('routed'));
		}
		return m.render().then(L.bind(function(m, nodes) {
			poll.add(L.bind(function() {
				var section_ids = m.children[0].cfgsections(),
				    tasks = [];
				for (var i = 0; i < section_ids.length; i++) {
					var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])),
					    dsc = row.querySelector('[data-name="_ifacestat"] > div'),
					    btn1 = row.querySelector('.cbi-section-actions .reconnect'),
					    btn2 = row.querySelector('.cbi-section-actions .down');
					if (dsc.getAttribute('reconnect') == '') {
						dsc.setAttribute('reconnect', '1');
						tasks.push(fs.exec('/sbin/ifup', [section_ids[i]]).catch(function(e) {
							ui.addNotification(null, E('p', e.message));
						}));
					}
					else if (dsc.getAttribute('disconnect') == '') {
						dsc.setAttribute('disconnect', '1');
						tasks.push(fs.exec('/sbin/ifdown', [section_ids[i]]).catch(function(e) {
							ui.addNotification(null, E('p', e.message));
						}));
					}
					else if (dsc.getAttribute('reconnect') == '1') {
						dsc.removeAttribute('reconnect');
						btn1.classList.remove('spinning');
						btn1.disabled = false;
					}
					else if (dsc.getAttribute('disconnect') == '1') {
						dsc.removeAttribute('disconnect');
						btn2.classList.remove('spinning');
						btn2.disabled = false;
					}
				}
				return Promise.all(tasks)
					.then(L.bind(network.getNetworks, network))
					.then(L.bind(this.poll_status, this, nodes));
			}, this), 5);
			return nodes;
		}, this, m));
	}
});