mirror of
				https://github.com/Ysurac/openmptcprouter-feeds.git
				synced 2025-03-09 15:40:03 +00:00 
			
		
		
		
	Update luci-base
This commit is contained in:
		
							parent
							
								
									bf0a1269dd
								
							
						
					
					
						commit
						86437d902f
					
				
					 5 changed files with 275 additions and 19 deletions
				
			
		| 
						 | 
				
			
			@ -1461,6 +1461,7 @@ var CBIListValue = CBIValue.extend({
 | 
			
		|||
			size: this.size,
 | 
			
		||||
			sort: this.keylist,
 | 
			
		||||
			optional: this.rmempty || this.optional,
 | 
			
		||||
			placeholder: this.placeholder,
 | 
			
		||||
			validate: L.bind(this.validate, this, section_id)
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,8 +30,22 @@ var CBIZoneSelect = form.ListValue.extend({
 | 
			
		|||
 | 
			
		||||
	renderWidget: function(section_id, option_index, cfgvalue) {
 | 
			
		||||
		var values = L.toArray((cfgvalue != null) ? cfgvalue : this.default),
 | 
			
		||||
		    isOutputOnly = false,
 | 
			
		||||
		    choices = {};
 | 
			
		||||
 | 
			
		||||
		if (this.option == 'dest') {
 | 
			
		||||
			for (var i = 0; i < this.section.children.length; i++) {
 | 
			
		||||
				var opt = this.section.children[i];
 | 
			
		||||
				if (opt.option == 'src') {
 | 
			
		||||
					var val = opt.cfgvalue(section_id) || opt.default;
 | 
			
		||||
					isOutputOnly = (val == null || val == '');
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			this.title = isOutputOnly ? _('Output zone') :  _('Destination zone');
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (this.allowlocal) {
 | 
			
		||||
			choices[''] = E('span', {
 | 
			
		||||
				'class': 'zonebadge',
 | 
			
		||||
| 
						 | 
				
			
			@ -39,7 +53,7 @@ var CBIZoneSelect = form.ListValue.extend({
 | 
			
		|||
			}, [
 | 
			
		||||
				E('strong', _('Device')),
 | 
			
		||||
				(this.allowany || this.allowlocal)
 | 
			
		||||
					? ' (%s)'.format(this.alias != 'dest' ? _('output') : _('input')) : ''
 | 
			
		||||
					? ' (%s)'.format(this.option != 'dest' ? _('output') : _('input')) : ''
 | 
			
		||||
			]);
 | 
			
		||||
		}
 | 
			
		||||
		else if (!this.multiple && (this.rmempty || this.optional)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -55,7 +69,7 @@ var CBIZoneSelect = form.ListValue.extend({
 | 
			
		|||
				'style': 'background-color:' + firewall.getColorForName(null)
 | 
			
		||||
			}, [
 | 
			
		||||
				E('strong', _('Any zone')),
 | 
			
		||||
				(this.allowany && this.allowlocal) ? ' (%s)'.format(_('forward')) : ''
 | 
			
		||||
				(this.allowany && this.allowlocal && !isOutputOnly) ? ' (%s)'.format(_('forward')) : ''
 | 
			
		||||
			]);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -120,7 +134,64 @@ var CBIZoneSelect = form.ListValue.extend({
 | 
			
		|||
				'</li>'
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		return widget.render();
 | 
			
		||||
		var elem = widget.render();
 | 
			
		||||
 | 
			
		||||
		if (this.option == 'src') {
 | 
			
		||||
			elem.addEventListener('cbi-dropdown-change', L.bind(function(ev) {
 | 
			
		||||
				var opt = this.map.lookupOption('dest', section_id),
 | 
			
		||||
				    val = ev.detail.instance.getValue();
 | 
			
		||||
 | 
			
		||||
				if (opt == null)
 | 
			
		||||
					return;
 | 
			
		||||
 | 
			
		||||
				var cbid = opt[0].cbid(section_id),
 | 
			
		||||
				    label = document.querySelector('label[for="widget.%s"]'.format(cbid)),
 | 
			
		||||
				    node = document.getElementById(cbid);
 | 
			
		||||
 | 
			
		||||
				L.dom.content(label, val == '' ? _('Output zone') : _('Destination zone'));
 | 
			
		||||
 | 
			
		||||
				if (val == '') {
 | 
			
		||||
					if (L.dom.callClassMethod(node, 'getValue') == '')
 | 
			
		||||
						L.dom.callClassMethod(node, 'setValue', '*');
 | 
			
		||||
 | 
			
		||||
					var emptyval = node.querySelector('[data-value=""]'),
 | 
			
		||||
					    anyval = node.querySelector('[data-value="*"]');
 | 
			
		||||
 | 
			
		||||
					L.dom.content(anyval.querySelector('span'), E('strong', _('Any zone')));
 | 
			
		||||
 | 
			
		||||
					if (emptyval != null)
 | 
			
		||||
						emptyval.parentNode.removeChild(emptyval);
 | 
			
		||||
				}
 | 
			
		||||
				else {
 | 
			
		||||
					var anyval = node.querySelector('[data-value="*"]'),
 | 
			
		||||
					    emptyval = node.querySelector('[data-value=""]');
 | 
			
		||||
 | 
			
		||||
					if (emptyval == null) {
 | 
			
		||||
						emptyval = anyval.cloneNode(true);
 | 
			
		||||
						emptyval.removeAttribute('display');
 | 
			
		||||
						emptyval.removeAttribute('selected');
 | 
			
		||||
						emptyval.setAttribute('data-value', '');
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					L.dom.content(emptyval.querySelector('span'), [
 | 
			
		||||
						E('strong', _('Device')), ' (%s)'.format(_('input'))
 | 
			
		||||
					]);
 | 
			
		||||
 | 
			
		||||
					L.dom.content(anyval.querySelector('span'), [
 | 
			
		||||
						E('strong', _('Any zone')), ' (%s)'.format(_('forward'))
 | 
			
		||||
					]);
 | 
			
		||||
 | 
			
		||||
					anyval.parentNode.insertBefore(emptyval, anyval);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			}, this));
 | 
			
		||||
		}
 | 
			
		||||
		else if (isOutputOnly) {
 | 
			
		||||
			var emptyval = elem.querySelector('[data-value=""]');
 | 
			
		||||
			emptyval.parentNode.removeChild(emptyval);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return elem;
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -128,10 +199,16 @@ var CBIZoneForwards = form.DummyValue.extend({
 | 
			
		|||
	__name__: 'CBI.ZoneForwards',
 | 
			
		||||
 | 
			
		||||
	load: function(section_id) {
 | 
			
		||||
		return Promise.all([ firewall.getDefaults(), firewall.getZones(), network.getNetworks() ]).then(L.bind(function(dzn) {
 | 
			
		||||
			this.defaults = dzn[0];
 | 
			
		||||
			this.zones = dzn[1];
 | 
			
		||||
			this.networks = dzn[2];
 | 
			
		||||
		return Promise.all([
 | 
			
		||||
			firewall.getDefaults(),
 | 
			
		||||
			firewall.getZones(),
 | 
			
		||||
			network.getNetworks(),
 | 
			
		||||
			network.getDevices()
 | 
			
		||||
		]).then(L.bind(function(dznd) {
 | 
			
		||||
			this.defaults = dznd[0];
 | 
			
		||||
			this.zones = dznd[1];
 | 
			
		||||
			this.networks = dznd[2];
 | 
			
		||||
			this.devices = dznd[3];
 | 
			
		||||
 | 
			
		||||
			return this.super('load', section_id);
 | 
			
		||||
		}, this));
 | 
			
		||||
| 
						 | 
				
			
			@ -140,6 +217,8 @@ var CBIZoneForwards = form.DummyValue.extend({
 | 
			
		|||
	renderZone: function(zone) {
 | 
			
		||||
		var name = zone.getName(),
 | 
			
		||||
		    networks = zone.getNetworks(),
 | 
			
		||||
		    devices = zone.getDevices(),
 | 
			
		||||
		    subnets = zone.getSubnets(),
 | 
			
		||||
		    ifaces = [];
 | 
			
		||||
 | 
			
		||||
		for (var j = 0; j < networks.length; j++) {
 | 
			
		||||
| 
						 | 
				
			
			@ -152,21 +231,39 @@ var CBIZoneForwards = form.DummyValue.extend({
 | 
			
		|||
				'class': 'ifacebadge' + (network.getName() == this.network ? ' ifacebadge-active' : '')
 | 
			
		||||
			}, network.getName() + ': ');
 | 
			
		||||
 | 
			
		||||
			var devices = network.isBridge() ? network.getDevices() : L.toArray(network.getDevice());
 | 
			
		||||
			var subdevs = network.isBridge() ? network.getDevices() : L.toArray(network.getDevice());
 | 
			
		||||
 | 
			
		||||
			for (var k = 0; k < devices.length && devices[k]; k++) {
 | 
			
		||||
			for (var k = 0; k < subdevs.length && subdevs[k]; k++) {
 | 
			
		||||
				span.appendChild(E('img', {
 | 
			
		||||
					'title': devices[k].getI18n(),
 | 
			
		||||
					'src': L.resource('icons/%s%s.png'.format(devices[k].getType(), devices[k].isUp() ? '' : '_disabled'))
 | 
			
		||||
					'title': subdevs[k].getI18n(),
 | 
			
		||||
					'src': L.resource('icons/%s%s.png'.format(subdevs[k].getType(), subdevs[k].isUp() ? '' : '_disabled'))
 | 
			
		||||
				}));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (!devices.length)
 | 
			
		||||
			if (!subdevs.length)
 | 
			
		||||
				span.appendChild(E('em', _('(empty)')));
 | 
			
		||||
 | 
			
		||||
			ifaces.push(span);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for (var i = 0; i < devices.length; i++) {
 | 
			
		||||
			var device = this.devices.filter(function(dev) { return dev.getName() == devices[i] })[0],
 | 
			
		||||
			    title = device ? device.getI18n() : _('Absent Interface'),
 | 
			
		||||
			    type = device ? device.getType() : 'ethernet',
 | 
			
		||||
			    up = device ? device.isUp() : false;
 | 
			
		||||
 | 
			
		||||
			ifaces.push(E('span', { 'class': 'ifacebadge' }, [
 | 
			
		||||
				E('img', {
 | 
			
		||||
					'title': title,
 | 
			
		||||
					'src': L.resource('icons/%s%s.png'.format(type, up ? '' : '_disabled'))
 | 
			
		||||
				}),
 | 
			
		||||
				device ? device.getName() : devices[i]
 | 
			
		||||
			]));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (subnets.length > 0)
 | 
			
		||||
			ifaces.push(E('span', { 'class': 'ifacebadge' }, [ '{ %s }'.format(subnets.join('; ')) ]));
 | 
			
		||||
 | 
			
		||||
		if (!ifaces.length)
 | 
			
		||||
			ifaces.push(E('span', { 'class': 'ifacebadge' }, E('em', _('(empty)'))));
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -319,9 +416,120 @@ var CBINetworkSelect = form.ListValue.extend({
 | 
			
		|||
	},
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
var CBIDeviceSelect = form.ListValue.extend({
 | 
			
		||||
	__name__: 'CBI.DeviceSelect',
 | 
			
		||||
 | 
			
		||||
	load: function(section_id) {
 | 
			
		||||
		return network.getDevices().then(L.bind(function(devices) {
 | 
			
		||||
			this.devices = devices;
 | 
			
		||||
 | 
			
		||||
			return this.super('load', section_id);
 | 
			
		||||
		}, this));
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	filter: function(section_id, value) {
 | 
			
		||||
		return true;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	renderWidget: function(section_id, option_index, cfgvalue) {
 | 
			
		||||
		var values = L.toArray((cfgvalue != null) ? cfgvalue : this.default),
 | 
			
		||||
		    choices = {},
 | 
			
		||||
		    checked = {},
 | 
			
		||||
		    order = [];
 | 
			
		||||
 | 
			
		||||
		for (var i = 0; i < values.length; i++)
 | 
			
		||||
			checked[values[i]] = true;
 | 
			
		||||
 | 
			
		||||
		values = [];
 | 
			
		||||
 | 
			
		||||
		if (!this.multiple && (this.rmempty || this.optional))
 | 
			
		||||
			choices[''] = E('em', _('unspecified'));
 | 
			
		||||
 | 
			
		||||
		for (var i = 0; i < this.devices.length; i++) {
 | 
			
		||||
			var device = this.devices[i],
 | 
			
		||||
			    name = device.getName(),
 | 
			
		||||
			    type = device.getType();
 | 
			
		||||
 | 
			
		||||
			if (name == 'lo' || name == this.exclude || !this.filter(section_id, name))
 | 
			
		||||
				continue;
 | 
			
		||||
 | 
			
		||||
			if (this.noaliases && type == 'alias')
 | 
			
		||||
				continue;
 | 
			
		||||
 | 
			
		||||
			if (this.nobridges && type == 'bridge')
 | 
			
		||||
				continue;
 | 
			
		||||
 | 
			
		||||
			if (this.noinactive && device.isUp() == false)
 | 
			
		||||
				continue;
 | 
			
		||||
 | 
			
		||||
			var item = E([
 | 
			
		||||
				E('img', {
 | 
			
		||||
					'title': device.getI18n(),
 | 
			
		||||
					'src': L.resource('icons/%s%s.png'.format(type, device.isUp() ? '' : '_disabled'))
 | 
			
		||||
				}),
 | 
			
		||||
				E('span', { 'class': 'hide-open' }, [ name ]),
 | 
			
		||||
				E('span', { 'class': 'hide-close'}, [ device.getI18n() ])
 | 
			
		||||
			]);
 | 
			
		||||
 | 
			
		||||
			var networks = device.getNetworks();
 | 
			
		||||
 | 
			
		||||
			if (networks.length > 0)
 | 
			
		||||
				L.dom.append(item.lastChild, [ ' (', networks.join(', '), ')' ]);
 | 
			
		||||
 | 
			
		||||
			if (checked[name])
 | 
			
		||||
				values.push(name);
 | 
			
		||||
 | 
			
		||||
			choices[name] = item;
 | 
			
		||||
			order.push(name);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (!this.nocreate) {
 | 
			
		||||
			var keys = Object.keys(checked).sort();
 | 
			
		||||
 | 
			
		||||
			for (var i = 0; i < keys.length; i++) {
 | 
			
		||||
				if (choices.hasOwnProperty(keys[i]))
 | 
			
		||||
					continue;
 | 
			
		||||
 | 
			
		||||
				choices[keys[i]] = E([
 | 
			
		||||
					E('img', {
 | 
			
		||||
						'title': _('Absent Interface'),
 | 
			
		||||
						'src': L.resource('icons/ethernet_disabled.png')
 | 
			
		||||
					}),
 | 
			
		||||
					E('span', { 'class': 'hide-open' }, [ keys[i] ]),
 | 
			
		||||
					E('span', { 'class': 'hide-close'}, [ '%s: "%h"'.format(_('Absent Interface'), keys[i]) ])
 | 
			
		||||
				]);
 | 
			
		||||
 | 
			
		||||
				values.push(keys[i]);
 | 
			
		||||
				order.push(keys[i]);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var widget = new ui.Dropdown(this.multiple ? values : values[0], choices, {
 | 
			
		||||
			id: this.cbid(section_id),
 | 
			
		||||
			sort: order,
 | 
			
		||||
			multiple: this.multiple,
 | 
			
		||||
			optional: this.optional || this.rmempty,
 | 
			
		||||
			select_placeholder: E('em', _('unspecified')),
 | 
			
		||||
			display_items: this.display_size || this.size || 3,
 | 
			
		||||
			dropdown_items: this.dropdown_size || this.size || 5,
 | 
			
		||||
			validate: L.bind(this.validate, this, section_id),
 | 
			
		||||
			create: !this.nocreate,
 | 
			
		||||
			create_markup: '' +
 | 
			
		||||
				'<li data-value="{{value}}">' +
 | 
			
		||||
					'<img title="'+_('Custom Interface')+': "{{value}}"" src="'+L.resource('icons/ethernet_disabled.png')+'" />' +
 | 
			
		||||
					'<span class="hide-open">{{value}}</span>' +
 | 
			
		||||
					'<span class="hide-close">'+_('Custom Interface')+': "{{value}}"</span>' +
 | 
			
		||||
				'</li>'
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		return widget.render();
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
return L.Class.extend({
 | 
			
		||||
	ZoneSelect: CBIZoneSelect,
 | 
			
		||||
	ZoneForwards: CBIZoneForwards,
 | 
			
		||||
	NetworkSelect: CBINetworkSelect
 | 
			
		||||
	NetworkSelect: CBINetworkSelect,
 | 
			
		||||
	DeviceSelect: CBIDeviceSelect,
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -287,8 +287,8 @@ var UISelect = UIElement.extend({
 | 
			
		|||
		this.node = frameEl;
 | 
			
		||||
 | 
			
		||||
		if (this.options.widget == 'select') {
 | 
			
		||||
			this.setUpdateEvents(frameEl, 'change', 'click', 'blur');
 | 
			
		||||
			this.setChangeEvents(frameEl, 'change');
 | 
			
		||||
			this.setUpdateEvents(frameEl.firstChild, 'change', 'click', 'blur');
 | 
			
		||||
			this.setChangeEvents(frameEl.firstChild, 'change');
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			var radioEls = frameEl.querySelectorAll('input[type="radio"]');
 | 
			
		||||
| 
						 | 
				
			
			@ -879,7 +879,7 @@ var UIDropdown = UIElement.extend({
 | 
			
		|||
				else
 | 
			
		||||
					markup = '<li data-value="{{value}}">{{value}}</li>';
 | 
			
		||||
 | 
			
		||||
				new_item = E(markup.replace(/{{value}}/g, item));
 | 
			
		||||
				new_item = E(markup.replace(/{{value}}/g, '%h'.format(item)));
 | 
			
		||||
 | 
			
		||||
				if (sbox.options.multi) {
 | 
			
		||||
					sbox.transformItem(sb, new_item);
 | 
			
		||||
| 
						 | 
				
			
			@ -1797,7 +1797,7 @@ return L.Class.extend({
 | 
			
		|||
								return chg[1];
 | 
			
		||||
 | 
			
		||||
						case 4:
 | 
			
		||||
							return "'" + chg[3].replace(/'/g, "'\"'\"'") + "'";
 | 
			
		||||
							return "'%ḧ'".format(chg[3].replace(/'/g, "'\"'\"'"));
 | 
			
		||||
 | 
			
		||||
						default:
 | 
			
		||||
							return chg[m1-1];
 | 
			
		||||
| 
						 | 
				
			
			@ -1929,7 +1929,7 @@ return L.Class.extend({
 | 
			
		|||
						method: 'post',
 | 
			
		||||
						timeout: L.env.apply_timeout * 1000,
 | 
			
		||||
						query: L.ui.changes.confirm_auth
 | 
			
		||||
					}).then(call);
 | 
			
		||||
					}).then(call, call);
 | 
			
		||||
				}, delay);
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -285,6 +285,41 @@ local methods = {
 | 
			
		|||
			local fs = require "nixio.fs"
 | 
			
		||||
			return { offload_support = not not fs.access("/sys/module/xt_FLOWOFFLOAD/refcnt") }
 | 
			
		||||
		end
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	conntrack_helpers = {
 | 
			
		||||
		call = function()
 | 
			
		||||
			local fd = io.open("/usr/share/fw3/helpers.conf", "r")
 | 
			
		||||
			local rv = {}
 | 
			
		||||
 | 
			
		||||
			local line, entry
 | 
			
		||||
			while true do
 | 
			
		||||
				line = fd:read("*l")
 | 
			
		||||
				if not line then
 | 
			
		||||
					break
 | 
			
		||||
				end
 | 
			
		||||
 | 
			
		||||
				if line:match("^%s*config%s") then
 | 
			
		||||
					if entry then
 | 
			
		||||
						rv[#rv+1] = entry
 | 
			
		||||
					end
 | 
			
		||||
					entry = {}
 | 
			
		||||
				else
 | 
			
		||||
					local opt, val = line:match("^%s*option%s+(%S+)%s+(%S.*)$")
 | 
			
		||||
					if opt and val then
 | 
			
		||||
						opt = opt:gsub("^'(.+)'$", "%1"):gsub('^"(.+)"$', "%1")
 | 
			
		||||
						val = val:gsub("^'(.+)'$", "%1"):gsub('^"(.+)"$', "%1")
 | 
			
		||||
						entry[opt] = val
 | 
			
		||||
					end
 | 
			
		||||
				end
 | 
			
		||||
			end
 | 
			
		||||
			if entry then
 | 
			
		||||
				rv[#rv+1] = entry
 | 
			
		||||
			end
 | 
			
		||||
 | 
			
		||||
			return { helpers = rv }
 | 
			
		||||
		end
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,7 +13,7 @@
 | 
			
		|||
		"read": {
 | 
			
		||||
			"ubus": {
 | 
			
		||||
				"iwinfo": [ "info" ],
 | 
			
		||||
				"luci": [ "boardjson", "duid_hints", "host_hints", "ifaddrs", "initList", "getLocaltime", "leases", "leds", "netdevs", "offload_support", "usb" ],
 | 
			
		||||
				"luci": [ "boardjson", "duid_hints", "host_hints", "ifaddrs", "initList", "getLocaltime", "leases", "leds", "netdevs", "usb" ],
 | 
			
		||||
				"network.device": [ "status" ],
 | 
			
		||||
				"network.interface": [ "dump" ],
 | 
			
		||||
				"network.wireless": [ "status" ],
 | 
			
		||||
| 
						 | 
				
			
			@ -28,5 +28,17 @@
 | 
			
		|||
			},
 | 
			
		||||
			"uci": [ "*" ]
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	"luci-app-firewall": {
 | 
			
		||||
		"description": "Grant access to firewall procedures",
 | 
			
		||||
		"read": {
 | 
			
		||||
			"ubus": {
 | 
			
		||||
				"luci": [ "conntrack_helpers", "offload_support" ]
 | 
			
		||||
			},
 | 
			
		||||
			"uci": [ "firewall" ]
 | 
			
		||||
		},
 | 
			
		||||
		"write": {
 | 
			
		||||
			"uci": [ "firewall" ]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue