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
							
								
									c3dc257a80
								
							
						
					
					
						commit
						e7ae8eed52
					
				
					 8 changed files with 761 additions and 290 deletions
				
			
		| 
						 | 
				
			
			@ -99,7 +99,7 @@ Firewall = L.Class.extend({
 | 
			
		|||
			if (name == null || !/^[a-zA-Z0-9_]+$/.test(name))
 | 
			
		||||
				return null;
 | 
			
		||||
 | 
			
		||||
			if (this.getZone(name) != null)
 | 
			
		||||
			if (lookupZone(name) != null)
 | 
			
		||||
				return null;
 | 
			
		||||
 | 
			
		||||
			var d = new Defaults(),
 | 
			
		||||
| 
						 | 
				
			
			@ -375,6 +375,14 @@ Zone = AbstractFirewallItem.extend({
 | 
			
		|||
		this.set('network', ' ');
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	getDevices: function() {
 | 
			
		||||
		return L.toArray(this.get('device'));
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	getSubnets: function() {
 | 
			
		||||
		return L.toArray(this.get('subnet'));
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	getForwardingsBy: function(what) {
 | 
			
		||||
		var sections = uci.sections('firewall', 'forwarding'),
 | 
			
		||||
		    forwards = [];
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,6 +57,23 @@ var CBINode = Class.extend({
 | 
			
		|||
 | 
			
		||||
		var x = E('div', {}, s);
 | 
			
		||||
		return x.textContent || x.innerText || '';
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	titleFn: function(attr /*, ... */) {
 | 
			
		||||
		var s = null;
 | 
			
		||||
 | 
			
		||||
		if (typeof(this[attr]) == 'function')
 | 
			
		||||
			s = this[attr].apply(this, this.varargs(arguments, 1));
 | 
			
		||||
		else if (typeof(this[attr]) == 'string')
 | 
			
		||||
			s = (arguments.length > 1) ? ''.format.apply(this[attr], this.varargs(arguments, 1)) : this[attr];
 | 
			
		||||
 | 
			
		||||
		if (s != null)
 | 
			
		||||
			s = this.stripTags(String(s)).trim();
 | 
			
		||||
 | 
			
		||||
		if (s == null || s == '')
 | 
			
		||||
			return null;
 | 
			
		||||
 | 
			
		||||
		return s;
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -115,10 +132,11 @@ var CBIMap = CBINode.extend({
 | 
			
		|||
		return Promise.all(tasks);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	save: function() {
 | 
			
		||||
	save: function(cb) {
 | 
			
		||||
		this.checkDepends();
 | 
			
		||||
 | 
			
		||||
		return this.parse()
 | 
			
		||||
			.then(cb)
 | 
			
		||||
			.then(uci.save.bind(uci))
 | 
			
		||||
			.then(this.load.bind(this))
 | 
			
		||||
			.then(this.renderContents.bind(this))
 | 
			
		||||
| 
						 | 
				
			
			@ -156,7 +174,10 @@ var CBIMap = CBINode.extend({
 | 
			
		|||
			if (this.description != null && this.description != '')
 | 
			
		||||
				mapEl.appendChild(E('div', { 'class': 'cbi-map-descr' }, this.description));
 | 
			
		||||
 | 
			
		||||
			L.dom.append(mapEl, nodes);
 | 
			
		||||
			if (this.tabbed)
 | 
			
		||||
				L.dom.append(mapEl, E('div', { 'class': 'cbi-map-tabbed' }, nodes));
 | 
			
		||||
			else
 | 
			
		||||
				L.dom.append(mapEl, nodes);
 | 
			
		||||
 | 
			
		||||
			if (!initialRender) {
 | 
			
		||||
				mapEl.classList.remove('flash');
 | 
			
		||||
| 
						 | 
				
			
			@ -168,17 +189,22 @@ var CBIMap = CBINode.extend({
 | 
			
		|||
 | 
			
		||||
			this.checkDepends();
 | 
			
		||||
 | 
			
		||||
			var tabGroups = mapEl.querySelectorAll('.cbi-map-tabbed, .cbi-section-node-tabbed');
 | 
			
		||||
 | 
			
		||||
			for (var i = 0; i < tabGroups.length; i++)
 | 
			
		||||
				ui.tabs.initTabGroup(tabGroups[i].childNodes);
 | 
			
		||||
 | 
			
		||||
			return mapEl;
 | 
			
		||||
		}, this));
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	lookupOption: function(name, section_id) {
 | 
			
		||||
	lookupOption: function(name, section_id, config_name) {
 | 
			
		||||
		var id, elem, sid, inst;
 | 
			
		||||
 | 
			
		||||
		if (name.indexOf('.') > -1)
 | 
			
		||||
			id = 'cbid.%s'.format(name);
 | 
			
		||||
		else
 | 
			
		||||
			id = 'cbid.%s.%s.%s'.format(this.config, section_id, name);
 | 
			
		||||
			id = 'cbid.%s.%s.%s'.format(config_name || this.config, section_id, name);
 | 
			
		||||
 | 
			
		||||
		elem = this.findElement('data-field', id);
 | 
			
		||||
		sid  = elem ? id.split(/\./)[2] : null;
 | 
			
		||||
| 
						 | 
				
			
			@ -437,7 +463,11 @@ var CBIAbstractValue = CBINode.extend({
 | 
			
		|||
						else if (k.indexOf('.') !== -1)
 | 
			
		||||
							dep['cbid.%s'.format(k)] = list[i][k];
 | 
			
		||||
						else
 | 
			
		||||
							dep['cbid.%s.%s.%s'.format(this.config, this.ucisection || section_id, k)] = list[i][k];
 | 
			
		||||
							dep['cbid.%s.%s.%s'.format(
 | 
			
		||||
								this.uciconfig || this.section.uciconfig || this.map.config,
 | 
			
		||||
								this.ucisection || section_id,
 | 
			
		||||
								k
 | 
			
		||||
							)] = list[i][k];
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -484,7 +514,8 @@ var CBIAbstractValue = CBINode.extend({
 | 
			
		|||
					istat = false;
 | 
			
		||||
				}
 | 
			
		||||
				else {
 | 
			
		||||
					var res = this.map.lookupOption(dep, section_id),
 | 
			
		||||
					var conf = this.uciconfig || this.section.uciconfig || this.map.config,
 | 
			
		||||
					    res = this.map.lookupOption(dep, section_id, conf),
 | 
			
		||||
					    val = res ? res[0].formvalue(res[1]) : null;
 | 
			
		||||
 | 
			
		||||
					istat = (istat && isEqual(val, this.deps[i][dep]));
 | 
			
		||||
| 
						 | 
				
			
			@ -502,7 +533,9 @@ var CBIAbstractValue = CBINode.extend({
 | 
			
		|||
		if (section_id == null)
 | 
			
		||||
			L.error('TypeError', 'Section ID required');
 | 
			
		||||
 | 
			
		||||
		return 'cbid.%s.%s.%s'.format(this.map.config, section_id, this.option);
 | 
			
		||||
		return 'cbid.%s.%s.%s'.format(
 | 
			
		||||
			this.uciconfig || this.section.uciconfig || this.map.config,
 | 
			
		||||
			section_id, this.option);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	load: function(section_id) {
 | 
			
		||||
| 
						 | 
				
			
			@ -510,7 +543,7 @@ var CBIAbstractValue = CBINode.extend({
 | 
			
		|||
			L.error('TypeError', 'Section ID required');
 | 
			
		||||
 | 
			
		||||
		return uci.get(
 | 
			
		||||
			this.uciconfig || this.map.config,
 | 
			
		||||
			this.uciconfig || this.section.uciconfig || this.map.config,
 | 
			
		||||
			this.ucisection || section_id,
 | 
			
		||||
			this.ucioption || this.option);
 | 
			
		||||
	},
 | 
			
		||||
| 
						 | 
				
			
			@ -598,7 +631,7 @@ var CBIAbstractValue = CBINode.extend({
 | 
			
		|||
 | 
			
		||||
	write: function(section_id, formvalue) {
 | 
			
		||||
		return uci.set(
 | 
			
		||||
			this.uciconfig || this.map.config,
 | 
			
		||||
			this.uciconfig || this.section.uciconfig || this.map.config,
 | 
			
		||||
			this.ucisection || section_id,
 | 
			
		||||
			this.ucioption || this.option,
 | 
			
		||||
			formvalue);
 | 
			
		||||
| 
						 | 
				
			
			@ -606,7 +639,7 @@ var CBIAbstractValue = CBINode.extend({
 | 
			
		|||
 | 
			
		||||
	remove: function(section_id) {
 | 
			
		||||
		return uci.unset(
 | 
			
		||||
			this.uciconfig || this.map.config,
 | 
			
		||||
			this.uciconfig || this.section.uciconfig || this.map.config,
 | 
			
		||||
			this.ucisection || section_id,
 | 
			
		||||
			this.ucioption || this.option);
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -640,7 +673,8 @@ var CBITypedSection = CBIAbstractSection.extend({
 | 
			
		|||
			return E([]);
 | 
			
		||||
 | 
			
		||||
		var createEl = E('div', { 'class': 'cbi-section-create' }),
 | 
			
		||||
		    config_name = this.uciconfig || this.map.config;
 | 
			
		||||
		    config_name = this.uciconfig || this.map.config,
 | 
			
		||||
		    btn_title = this.titleFn('addbtntitle');
 | 
			
		||||
 | 
			
		||||
		if (extra_class != null)
 | 
			
		||||
			createEl.classList.add(extra_class);
 | 
			
		||||
| 
						 | 
				
			
			@ -649,8 +683,8 @@ var CBITypedSection = CBIAbstractSection.extend({
 | 
			
		|||
			createEl.appendChild(E('input', {
 | 
			
		||||
				'type': 'submit',
 | 
			
		||||
				'class': 'cbi-button cbi-button-add',
 | 
			
		||||
				'value': _('Add'),
 | 
			
		||||
				'title': _('Add'),
 | 
			
		||||
				'value': btn_title || _('Add'),
 | 
			
		||||
				'title': btn_title || _('Add'),
 | 
			
		||||
				'click': L.bind(this.handleAdd, this)
 | 
			
		||||
			}));
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -665,8 +699,8 @@ var CBITypedSection = CBIAbstractSection.extend({
 | 
			
		|||
				E('input', {
 | 
			
		||||
					'class': 'cbi-button cbi-button-add',
 | 
			
		||||
					'type': 'submit',
 | 
			
		||||
					'value': _('Add'),
 | 
			
		||||
					'title': _('Add'),
 | 
			
		||||
					'value': btn_title || _('Add'),
 | 
			
		||||
					'title': btn_title || _('Add'),
 | 
			
		||||
					'click': L.bind(function(ev) {
 | 
			
		||||
						if (nameEl.classList.contains('cbi-input-invalid'))
 | 
			
		||||
							return;
 | 
			
		||||
| 
						 | 
				
			
			@ -682,12 +716,21 @@ var CBITypedSection = CBIAbstractSection.extend({
 | 
			
		|||
		return createEl;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	renderSectionPlaceholder: function() {
 | 
			
		||||
		return E([
 | 
			
		||||
			E('em', _('This section contains no values yet')),
 | 
			
		||||
			E('br'), E('br')
 | 
			
		||||
		]);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	renderContents: function(cfgsections, nodes) {
 | 
			
		||||
		var section_id = null,
 | 
			
		||||
		    config_name = this.uciconfig || this.map.config,
 | 
			
		||||
		    sectionEl = E('div', {
 | 
			
		||||
				'id': 'cbi-%s-%s'.format(config_name, this.sectiontype),
 | 
			
		||||
				'class': 'cbi-section'
 | 
			
		||||
				'class': 'cbi-section',
 | 
			
		||||
				'data-tab': this.map.tabbed ? this.sectiontype : null,
 | 
			
		||||
				'data-tab-title': this.map.tabbed ? this.title || this.sectiontype : null
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
		if (this.title != null && this.title != '')
 | 
			
		||||
| 
						 | 
				
			
			@ -716,18 +759,13 @@ var CBITypedSection = CBIAbstractSection.extend({
 | 
			
		|||
			sectionEl.appendChild(E('div', {
 | 
			
		||||
				'id': 'cbi-%s-%s'.format(config_name, cfgsections[i]),
 | 
			
		||||
				'class': this.tabs
 | 
			
		||||
					? 'cbi-section-node cbi-section-node-tabbed' : 'cbi-section-node'
 | 
			
		||||
					? 'cbi-section-node cbi-section-node-tabbed' : 'cbi-section-node',
 | 
			
		||||
				'data-section-id': cfgsections[i]
 | 
			
		||||
			}, nodes[i]));
 | 
			
		||||
 | 
			
		||||
			if (this.tabs)
 | 
			
		||||
				ui.tabs.initTabGroup(sectionEl.lastChild.childNodes);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (nodes.length == 0)
 | 
			
		||||
			L.dom.append(sectionEl, [
 | 
			
		||||
				E('em', _('This section contains no values yet')),
 | 
			
		||||
				E('br'), E('br')
 | 
			
		||||
			]);
 | 
			
		||||
			sectionEl.appendChild(this.renderSectionPlaceholder());
 | 
			
		||||
 | 
			
		||||
		sectionEl.appendChild(this.renderSectionAdd());
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -761,7 +799,9 @@ var CBITableSection = CBITypedSection.extend({
 | 
			
		|||
		    has_more = max_cols < this.children.length,
 | 
			
		||||
		    sectionEl = E('div', {
 | 
			
		||||
				'id': 'cbi-%s-%s'.format(config_name, this.sectiontype),
 | 
			
		||||
				'class': 'cbi-section cbi-tblsection'
 | 
			
		||||
				'class': 'cbi-section cbi-tblsection',
 | 
			
		||||
				'data-tab': this.map.tabbed ? this.sectiontype : null,
 | 
			
		||||
				'data-tab-title': this.map.tabbed ? this.title || this.sectiontype : null
 | 
			
		||||
			}),
 | 
			
		||||
			tableEl = E('div', {
 | 
			
		||||
				'class': 'table cbi-section-table'
 | 
			
		||||
| 
						 | 
				
			
			@ -776,8 +816,7 @@ var CBITableSection = CBITypedSection.extend({
 | 
			
		|||
		tableEl.appendChild(this.renderHeaderRows(max_cols));
 | 
			
		||||
 | 
			
		||||
		for (var i = 0; i < nodes.length; i++) {
 | 
			
		||||
			var sectionname = this.stripTags((typeof(this.sectiontitle) == 'function')
 | 
			
		||||
				? String(this.sectiontitle(cfgsections[i]) || '') : cfgsections[i]).trim();
 | 
			
		||||
			var sectionname = this.titleFn('sectiontitle', cfgsections[i]);
 | 
			
		||||
 | 
			
		||||
			var trEl = E('div', {
 | 
			
		||||
				'id': 'cbi-%s-%s'.format(config_name, cfgsections[i]),
 | 
			
		||||
| 
						 | 
				
			
			@ -791,7 +830,8 @@ var CBITableSection = CBITypedSection.extend({
 | 
			
		|||
				'dragleave': this.sortable ? L.bind(this.handleDragLeave, this) : null,
 | 
			
		||||
				'dragend': this.sortable ? L.bind(this.handleDragEnd, this) : null,
 | 
			
		||||
				'drop': this.sortable ? L.bind(this.handleDrop, this) : null,
 | 
			
		||||
				'data-title': (sectionname && (!this.anonymous || this.sectiontitle)) ? sectionname : null
 | 
			
		||||
				'data-title': (sectionname && (!this.anonymous || this.sectiontitle)) ? sectionname : null,
 | 
			
		||||
				'data-section-id': cfgsections[i]
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			if (this.extedit || this.rowcolors)
 | 
			
		||||
| 
						 | 
				
			
			@ -954,11 +994,13 @@ var CBITableSection = CBITypedSection.extend({
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		if (this.addremove) {
 | 
			
		||||
			var btn_title = this.titleFn('removebtntitle', section_id);
 | 
			
		||||
 | 
			
		||||
			L.dom.append(tdEl.lastElementChild,
 | 
			
		||||
				E('input', {
 | 
			
		||||
					'type': 'submit',
 | 
			
		||||
					'value': _('Delete'),
 | 
			
		||||
					'title': _('Delete'),
 | 
			
		||||
					'value': btn_title || _('Delete'),
 | 
			
		||||
					'title': btn_title || _('Delete'),
 | 
			
		||||
					'class': 'cbi-button cbi-button-remove',
 | 
			
		||||
					'click': L.bind(function(sid, ev) {
 | 
			
		||||
						uci.remove(config_name, sid);
 | 
			
		||||
| 
						 | 
				
			
			@ -1077,16 +1119,16 @@ var CBITableSection = CBITypedSection.extend({
 | 
			
		|||
		    name = null,
 | 
			
		||||
		    m = new CBIMap(this.map.config, null, null),
 | 
			
		||||
		    s = m.section(CBINamedSection, section_id, this.sectiontype);
 | 
			
		||||
		    s.tabs = this.tabs;
 | 
			
		||||
		    s.tab_names = this.tab_names;
 | 
			
		||||
 | 
			
		||||
		if (typeof(this.sectiontitle) == 'function')
 | 
			
		||||
			name = this.stripTags(String(this.sectiontitle(section_id) || ''));
 | 
			
		||||
		s.tabs = this.tabs;
 | 
			
		||||
		s.tab_names = this.tab_names;
 | 
			
		||||
 | 
			
		||||
		if ((name = this.titleFn('modaltitle', section_id)) != null)
 | 
			
		||||
			title = name;
 | 
			
		||||
		else if ((name = this.titleFn('sectiontitle', section_id)) != null)
 | 
			
		||||
			title = '%s - %s'.format(parent.title, name);
 | 
			
		||||
		else if (!this.anonymous)
 | 
			
		||||
			name = section_id;
 | 
			
		||||
 | 
			
		||||
		if (name)
 | 
			
		||||
			title += ' - ' + name;
 | 
			
		||||
			title = '%s - %s'.format(parent.title, section_id);
 | 
			
		||||
 | 
			
		||||
		for (var i = 0; i < this.children.length; i++) {
 | 
			
		||||
			var o1 = this.children[i];
 | 
			
		||||
| 
						 | 
				
			
			@ -1258,7 +1300,9 @@ var CBINamedSection = CBIAbstractSection.extend({
 | 
			
		|||
		    config_name = this.uciconfig || this.map.config,
 | 
			
		||||
		    sectionEl = E('div', {
 | 
			
		||||
				'id': ucidata ? null : 'cbi-%s-%s'.format(config_name, section_id),
 | 
			
		||||
				'class': 'cbi-section'
 | 
			
		||||
				'class': 'cbi-section',
 | 
			
		||||
				'data-tab': this.map.tabbed ? this.sectiontype : null,
 | 
			
		||||
				'data-tab-title': this.map.tabbed ? this.title || this.sectiontype : null
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
		if (typeof(this.title) === 'string' && this.title !== '')
 | 
			
		||||
| 
						 | 
				
			
			@ -1282,11 +1326,9 @@ var CBINamedSection = CBIAbstractSection.extend({
 | 
			
		|||
			sectionEl.appendChild(E('div', {
 | 
			
		||||
				'id': 'cbi-%s-%s'.format(config_name, section_id),
 | 
			
		||||
				'class': this.tabs
 | 
			
		||||
					? 'cbi-section-node cbi-section-node-tabbed' : 'cbi-section-node'
 | 
			
		||||
					? 'cbi-section-node cbi-section-node-tabbed' : 'cbi-section-node',
 | 
			
		||||
				'data-section-id': section_id
 | 
			
		||||
			}, nodes));
 | 
			
		||||
 | 
			
		||||
			if (this.tabs)
 | 
			
		||||
				ui.tabs.initTabGroup(sectionEl.lastChild.childNodes);
 | 
			
		||||
		}
 | 
			
		||||
		else if (this.addremove) {
 | 
			
		||||
			sectionEl.appendChild(
 | 
			
		||||
| 
						 | 
				
			
			@ -1332,7 +1374,7 @@ var CBIValue = CBIAbstractValue.extend({
 | 
			
		|||
	},
 | 
			
		||||
 | 
			
		||||
	renderFrame: function(section_id, in_table, option_index, nodes) {
 | 
			
		||||
		var config_name = this.uciconfig || this.map.config,
 | 
			
		||||
		var config_name = this.uciconfig || this.section.uciconfig || this.map.config,
 | 
			
		||||
		    depend_list = this.transformDepList(section_id),
 | 
			
		||||
		    optionEl;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1558,6 +1600,29 @@ var CBIMultiValue = CBIDynamicList.extend({
 | 
			
		|||
	},
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
var CBITextValue = CBIValue.extend({
 | 
			
		||||
	__name__: 'CBI.TextValue',
 | 
			
		||||
 | 
			
		||||
	value: null,
 | 
			
		||||
 | 
			
		||||
	renderWidget: function(section_id, option_index, cfgvalue) {
 | 
			
		||||
		var value = (cfgvalue != null) ? cfgvalue : this.default;
 | 
			
		||||
 | 
			
		||||
		var widget = new ui.Textarea(value, {
 | 
			
		||||
			id: this.cbid(section_id),
 | 
			
		||||
			optional: this.optional || this.rmempty,
 | 
			
		||||
			placeholder: this.placeholder,
 | 
			
		||||
			monospace: this.monospace,
 | 
			
		||||
			cols: this.cols,
 | 
			
		||||
			rows: this.rows,
 | 
			
		||||
			wrap: this.wrap,
 | 
			
		||||
			validate: L.bind(this.validate, this, section_id)
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		return widget.render();
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
var CBIDummyValue = CBIValue.extend({
 | 
			
		||||
	__name__: 'CBI.DummyValue',
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1583,28 +1648,30 @@ var CBIButtonValue = CBIValue.extend({
 | 
			
		|||
	__name__: 'CBI.ButtonValue',
 | 
			
		||||
 | 
			
		||||
	renderWidget: function(section_id, option_index, cfgvalue) {
 | 
			
		||||
		var value = (cfgvalue != null) ? cfgvalue : this.default;
 | 
			
		||||
		var value = (cfgvalue != null) ? cfgvalue : this.default,
 | 
			
		||||
		    hiddenEl = new ui.Hiddenfield(value, { id: this.cbid(section_id) }),
 | 
			
		||||
		    outputEl = E('div'),
 | 
			
		||||
		    btn_title = this.titleFn('inputtitle', section_id) || this.titleFn('title', section_id);
 | 
			
		||||
 | 
			
		||||
		if (value !== false)
 | 
			
		||||
			return E([
 | 
			
		||||
				E('input', {
 | 
			
		||||
					'type': 'hidden',
 | 
			
		||||
					'id': this.cbid(section_id)
 | 
			
		||||
				}),
 | 
			
		||||
			L.dom.content(outputEl, [
 | 
			
		||||
				E('input', {
 | 
			
		||||
					'class': 'cbi-button cbi-button-%s'.format(this.inputstyle || 'button'),
 | 
			
		||||
					'type': 'submit',
 | 
			
		||||
					//'id': this.cbid(section_id),
 | 
			
		||||
					//'name': this.cbid(section_id),
 | 
			
		||||
					'value': this.inputtitle || this.title,
 | 
			
		||||
					'click': L.bind(function(ev) {
 | 
			
		||||
					'type': 'button',
 | 
			
		||||
					'value': btn_title,
 | 
			
		||||
					'click': L.bind(this.onclick || function(ev) {
 | 
			
		||||
						ev.target.previousElementSibling.value = ev.target.value;
 | 
			
		||||
						this.map.save();
 | 
			
		||||
					}, this)
 | 
			
		||||
				})
 | 
			
		||||
			]);
 | 
			
		||||
		else
 | 
			
		||||
			return document.createTextNode(' - ');
 | 
			
		||||
			L.dom.content(outputEl, ' - ');
 | 
			
		||||
 | 
			
		||||
		return E([
 | 
			
		||||
			outputEl,
 | 
			
		||||
			hiddenEl.render()
 | 
			
		||||
		]);
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1645,7 +1712,7 @@ var CBISectionValue = CBIValue.extend({
 | 
			
		|||
 | 
			
		||||
	checkDepends: function(section_id) {
 | 
			
		||||
		this.subsection.checkDepends();
 | 
			
		||||
		return this.super('checkDepends');
 | 
			
		||||
		return CBIValue.prototype.checkDepends.apply(this, [ section_id ]);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	write: function() {},
 | 
			
		||||
| 
						 | 
				
			
			@ -1669,6 +1736,7 @@ return L.Class.extend({
 | 
			
		|||
	ListValue: CBIListValue,
 | 
			
		||||
	Flag: CBIFlagValue,
 | 
			
		||||
	MultiValue: CBIMultiValue,
 | 
			
		||||
	TextValue: CBITextValue,
 | 
			
		||||
	DummyValue: CBIDummyValue,
 | 
			
		||||
	Button: CBIButtonValue,
 | 
			
		||||
	HiddenValue: CBIHiddenValue,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -559,6 +559,7 @@
 | 
			
		|||
	    domParser = null,
 | 
			
		||||
	    originalCBIInit = null,
 | 
			
		||||
	    rpcBaseURL = null,
 | 
			
		||||
	    sysFeatures = null,
 | 
			
		||||
	    classes = {};
 | 
			
		||||
 | 
			
		||||
	var LuCI = Class.extend({
 | 
			
		||||
| 
						 | 
				
			
			@ -797,6 +798,43 @@
 | 
			
		|||
			return Promise.resolve(rpcBaseURL);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		probeSystemFeatures: function() {
 | 
			
		||||
			if (sysFeatures == null) {
 | 
			
		||||
				try {
 | 
			
		||||
					sysFeatures = JSON.parse(window.sessionStorage.getItem('sysFeatures'));
 | 
			
		||||
				}
 | 
			
		||||
				catch (e) {}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (!this.isObject(sysFeatures)) {
 | 
			
		||||
				sysFeatures = classes.rpc.declare({
 | 
			
		||||
					object: 'luci',
 | 
			
		||||
					method: 'getFeatures',
 | 
			
		||||
					expect: { '': {} }
 | 
			
		||||
				})().then(function(features) {
 | 
			
		||||
					try {
 | 
			
		||||
						window.sessionStorage.setItem('sysFeatures', JSON.stringify(features));
 | 
			
		||||
					}
 | 
			
		||||
					catch (e) {}
 | 
			
		||||
 | 
			
		||||
					sysFeatures = features;
 | 
			
		||||
 | 
			
		||||
					return features;
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return Promise.resolve(sysFeatures);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		hasSystemFeature: function() {
 | 
			
		||||
			var ft = sysFeatures[arguments[0]];
 | 
			
		||||
 | 
			
		||||
			if (arguments.length == 2)
 | 
			
		||||
				return this.isObject(ft) ? ft[arguments[1]] : null;
 | 
			
		||||
 | 
			
		||||
			return (ft != null && ft != false);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		setupDOM: function(res) {
 | 
			
		||||
			var domEv = res[0],
 | 
			
		||||
			    uiClass = res[1],
 | 
			
		||||
| 
						 | 
				
			
			@ -828,10 +866,12 @@
 | 
			
		|||
				throw 'Session expired';
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			return this.probeSystemFeatures().finally(this.initDOM);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		initDOM: function() {
 | 
			
		||||
			originalCBIInit();
 | 
			
		||||
 | 
			
		||||
			Poll.start();
 | 
			
		||||
 | 
			
		||||
			document.dispatchEvent(new CustomEvent('luci-loaded'));
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -51,18 +51,19 @@ var callNetworkWirelessStatus = rpc.declare({
 | 
			
		|||
 | 
			
		||||
var callLuciNetdevs = rpc.declare({
 | 
			
		||||
	object: 'luci',
 | 
			
		||||
	method: 'netdevs'
 | 
			
		||||
	method: 'getNetworkDevices',
 | 
			
		||||
	expect: { '': {} }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
var callLuciIfaddrs = rpc.declare({
 | 
			
		||||
	object: 'luci',
 | 
			
		||||
	method: 'ifaddrs',
 | 
			
		||||
	method: 'getIfaddrs',
 | 
			
		||||
	expect: { result: [] }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
var callLuciBoardjson = rpc.declare({
 | 
			
		||||
	object: 'luci',
 | 
			
		||||
	method: 'boardjson'
 | 
			
		||||
	method: 'getBoardJSON'
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
var callIwinfoInfo = rpc.declare({
 | 
			
		||||
| 
						 | 
				
			
			@ -83,95 +84,104 @@ var callNetworkDeviceStatus = rpc.declare({
 | 
			
		|||
	expect: { '': {} }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
var _cache = {},
 | 
			
		||||
var callGetProtoHandlers = rpc.declare({
 | 
			
		||||
	object: 'network',
 | 
			
		||||
	method: 'get_proto_handlers',
 | 
			
		||||
	expect: { '': {} }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
var _init = null,
 | 
			
		||||
    _state = null,
 | 
			
		||||
    _protocols = {};
 | 
			
		||||
    _protocols = {},
 | 
			
		||||
    _protospecs = {};
 | 
			
		||||
 | 
			
		||||
function getWifiState() {
 | 
			
		||||
	if (_cache.wifi == null)
 | 
			
		||||
		return callNetworkWirelessStatus().then(function(state) {
 | 
			
		||||
			if (!L.isObject(state))
 | 
			
		||||
				throw !1;
 | 
			
		||||
			return (_cache.wifi = state);
 | 
			
		||||
		}).catch(function() {
 | 
			
		||||
			return (_cache.wifi = {});
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
	return Promise.resolve(_cache.wifi);
 | 
			
		||||
function getWifiState(cache) {
 | 
			
		||||
	return callNetworkWirelessStatus().then(function(state) {
 | 
			
		||||
		if (!L.isObject(state))
 | 
			
		||||
			throw !1;
 | 
			
		||||
		return state;
 | 
			
		||||
	}).catch(function() {
 | 
			
		||||
		return {};
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getInterfaceState() {
 | 
			
		||||
	if (_cache.interfacedump == null)
 | 
			
		||||
		return callNetworkInterfaceStatus().then(function(state) {
 | 
			
		||||
			if (!Array.isArray(state))
 | 
			
		||||
				throw !1;
 | 
			
		||||
			return (_cache.interfacedump = state);
 | 
			
		||||
		}).catch(function() {
 | 
			
		||||
			return (_cache.interfacedump = []);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
	return Promise.resolve(_cache.interfacedump);
 | 
			
		||||
function getInterfaceState(cache) {
 | 
			
		||||
	return callNetworkInterfaceStatus().then(function(state) {
 | 
			
		||||
		if (!Array.isArray(state))
 | 
			
		||||
			throw !1;
 | 
			
		||||
		return state;
 | 
			
		||||
	}).catch(function() {
 | 
			
		||||
		return [];
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getDeviceState() {
 | 
			
		||||
	if (_cache.devicedump == null)
 | 
			
		||||
		return callNetworkDeviceStatus().then(function(state) {
 | 
			
		||||
			if (!L.isObject(state))
 | 
			
		||||
				throw !1;
 | 
			
		||||
			return (_cache.devicedump = state);
 | 
			
		||||
		}).catch(function() {
 | 
			
		||||
			return (_cache.devicedump = {});
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
	return Promise.resolve(_cache.devicedump);
 | 
			
		||||
function getDeviceState(cache) {
 | 
			
		||||
	return callNetworkDeviceStatus().then(function(state) {
 | 
			
		||||
		if (!L.isObject(state))
 | 
			
		||||
			throw !1;
 | 
			
		||||
		return state;
 | 
			
		||||
	}).catch(function() {
 | 
			
		||||
		return {};
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getIfaddrState() {
 | 
			
		||||
	if (_cache.ifaddrs == null)
 | 
			
		||||
		return callLuciIfaddrs().then(function(addrs) {
 | 
			
		||||
			if (!Array.isArray(addrs))
 | 
			
		||||
				throw !1;
 | 
			
		||||
			return (_cache.ifaddrs = addrs);
 | 
			
		||||
		}).catch(function() {
 | 
			
		||||
			return (_cache.ifaddrs = []);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
	return Promise.resolve(_cache.ifaddrs);
 | 
			
		||||
function getIfaddrState(cache) {
 | 
			
		||||
	return callLuciIfaddrs().then(function(addrs) {
 | 
			
		||||
		if (!Array.isArray(addrs))
 | 
			
		||||
			throw !1;
 | 
			
		||||
		return addrs;
 | 
			
		||||
	}).catch(function() {
 | 
			
		||||
		return [];
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getNetdevState() {
 | 
			
		||||
	if (_cache.devices == null)
 | 
			
		||||
		return callLuciNetdevs().then(function(state) {
 | 
			
		||||
			if (!L.isObject(state))
 | 
			
		||||
				throw !1;
 | 
			
		||||
			return (_cache.devices = state);
 | 
			
		||||
		}).catch(function() {
 | 
			
		||||
			return (_cache.devices = {});
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
	return Promise.resolve(_cache.devices);
 | 
			
		||||
function getNetdevState(cache) {
 | 
			
		||||
	return callLuciNetdevs().then(function(state) {
 | 
			
		||||
		if (!L.isObject(state))
 | 
			
		||||
			throw !1;
 | 
			
		||||
		return state;
 | 
			
		||||
	}).catch(function() {
 | 
			
		||||
		return {};
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getBoardState() {
 | 
			
		||||
	if (_cache.board == null)
 | 
			
		||||
		return callLuciBoardjson().then(function(state) {
 | 
			
		||||
			if (!L.isObject(state))
 | 
			
		||||
				throw !1;
 | 
			
		||||
			return (_cache.board = state);
 | 
			
		||||
		}).catch(function() {
 | 
			
		||||
			return (_cache.board = {});
 | 
			
		||||
		});
 | 
			
		||||
function getBoardState(cache) {
 | 
			
		||||
	return callLuciBoardjson().then(function(state) {
 | 
			
		||||
		if (!L.isObject(state))
 | 
			
		||||
			throw !1;
 | 
			
		||||
		return state;
 | 
			
		||||
	}).catch(function() {
 | 
			
		||||
		return {};
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	return Promise.resolve(_cache.board);
 | 
			
		||||
function getProtocolHandlers(cache) {
 | 
			
		||||
	return callGetProtoHandlers().then(function(protos) {
 | 
			
		||||
		if (!L.isObject(protos))
 | 
			
		||||
			throw !1;
 | 
			
		||||
 | 
			
		||||
		Object.assign(_protospecs, protos);
 | 
			
		||||
 | 
			
		||||
		return Promise.all(Object.keys(protos).map(function(p) {
 | 
			
		||||
			return Promise.resolve(L.require('protocol.%s'.format(p))).catch(function(err) {
 | 
			
		||||
				if (L.isObject(err) && err.name != 'NetworkError')
 | 
			
		||||
					L.error(err);
 | 
			
		||||
			});
 | 
			
		||||
		})).then(function() {
 | 
			
		||||
			return protos;
 | 
			
		||||
		});
 | 
			
		||||
	}).catch(function() {
 | 
			
		||||
		return {};
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getWifiStateBySid(sid) {
 | 
			
		||||
	var s = uci.get('wireless', sid);
 | 
			
		||||
 | 
			
		||||
	if (s != null && s['.type'] == 'wifi-iface') {
 | 
			
		||||
		for (var radioname in _cache.wifi) {
 | 
			
		||||
			for (var i = 0; i < _cache.wifi[radioname].interfaces.length; i++) {
 | 
			
		||||
				var netstate = _cache.wifi[radioname].interfaces[i];
 | 
			
		||||
		for (var radioname in _state.radios) {
 | 
			
		||||
			for (var i = 0; i < _state.radios[radioname].interfaces.length; i++) {
 | 
			
		||||
				var netstate = _state.radios[radioname].interfaces[i];
 | 
			
		||||
 | 
			
		||||
				if (typeof(netstate.section) != 'string')
 | 
			
		||||
					continue;
 | 
			
		||||
| 
						 | 
				
			
			@ -179,7 +189,7 @@ function getWifiStateBySid(sid) {
 | 
			
		|||
				var s2 = uci.get('wireless', netstate.section);
 | 
			
		||||
 | 
			
		||||
				if (s2 != null && s['.type'] == s2['.type'] && s['.name'] == s2['.name'])
 | 
			
		||||
					return [ radioname, _cache.wifi[radioname], netstate ];
 | 
			
		||||
					return [ radioname, _state.radios[radioname], netstate ];
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -188,15 +198,15 @@ function getWifiStateBySid(sid) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
function getWifiStateByIfname(ifname) {
 | 
			
		||||
	for (var radioname in _cache.wifi) {
 | 
			
		||||
		for (var i = 0; i < _cache.wifi[radioname].interfaces.length; i++) {
 | 
			
		||||
			var netstate = _cache.wifi[radioname].interfaces[i];
 | 
			
		||||
	for (var radioname in _state.radios) {
 | 
			
		||||
		for (var i = 0; i < _state.radios[radioname].interfaces.length; i++) {
 | 
			
		||||
			var netstate = _state.radios[radioname].interfaces[i];
 | 
			
		||||
 | 
			
		||||
			if (typeof(netstate.ifname) != 'string')
 | 
			
		||||
				continue;
 | 
			
		||||
 | 
			
		||||
			if (netstate.ifname == ifname)
 | 
			
		||||
				return [ radioname, _cache.wifi[radioname], netstate ];
 | 
			
		||||
				return [ radioname, _state.radios[radioname], netstate ];
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -336,7 +346,7 @@ function appendValue(config, section, option, value) {
 | 
			
		|||
	    rv = false;
 | 
			
		||||
 | 
			
		||||
	if (isArray == false)
 | 
			
		||||
		values = String(values || '').split(/\s+/);
 | 
			
		||||
		values = L.toArray(values);
 | 
			
		||||
 | 
			
		||||
	if (values.indexOf(value) == -1) {
 | 
			
		||||
		values.push(value);
 | 
			
		||||
| 
						 | 
				
			
			@ -354,7 +364,7 @@ function removeValue(config, section, option, value) {
 | 
			
		|||
	    rv = false;
 | 
			
		||||
 | 
			
		||||
	if (isArray == false)
 | 
			
		||||
		values = String(values || '').split(/\s+/);
 | 
			
		||||
		values = L.toArray(values);
 | 
			
		||||
 | 
			
		||||
	for (var i = values.length - 1; i >= 0; i--) {
 | 
			
		||||
		if (values[i] == value) {
 | 
			
		||||
| 
						 | 
				
			
			@ -413,17 +423,19 @@ function maskToPrefix(mask, v6) {
 | 
			
		|||
	return bits;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function initNetworkState() {
 | 
			
		||||
	if (_state == null)
 | 
			
		||||
		return (_state = Promise.all([
 | 
			
		||||
function initNetworkState(refresh) {
 | 
			
		||||
	if (_state == null || refresh) {
 | 
			
		||||
		_init = _init || Promise.all([
 | 
			
		||||
			getInterfaceState(), getDeviceState(), getBoardState(),
 | 
			
		||||
			getWifiState(), getIfaddrState(), getNetdevState(),
 | 
			
		||||
			getWifiState(), getIfaddrState(), getNetdevState(), getProtocolHandlers(),
 | 
			
		||||
			uci.load('network'), uci.load('wireless'), uci.load('luci')
 | 
			
		||||
		]).finally(function() {
 | 
			
		||||
			var ifaddrs = _cache.ifaddrs,
 | 
			
		||||
			    devices = _cache.devices,
 | 
			
		||||
			    board = _cache.board,
 | 
			
		||||
			    s = { isTunnel: {}, isBridge: {}, isSwitch: {}, isWifi: {}, interfaces: {}, bridges: {}, switches: {} };
 | 
			
		||||
		]).then(function(data) {
 | 
			
		||||
			var board = data[2], ifaddrs = data[4], devices = data[5];
 | 
			
		||||
			var s = {
 | 
			
		||||
				isTunnel: {}, isBridge: {}, isSwitch: {}, isWifi: {},
 | 
			
		||||
				ifaces: data[0], devices: data[1], radios: data[3],
 | 
			
		||||
				netdevs: {}, bridges: {}, switches: {}
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			for (var i = 0, a; (a = ifaddrs[i]) != null; i++) {
 | 
			
		||||
				var name = a.name.replace(/:.+$/, '');
 | 
			
		||||
| 
						 | 
				
			
			@ -432,7 +444,7 @@ function initNetworkState() {
 | 
			
		|||
					s.isTunnel[name] = true;
 | 
			
		||||
 | 
			
		||||
				if (s.isTunnel[name] || !(isIgnoredIfname(name) || isVirtualIfname(name))) {
 | 
			
		||||
					s.interfaces[name] = s.interfaces[name] || {
 | 
			
		||||
					s.netdevs[name] = s.netdevs[name] || {
 | 
			
		||||
						idx:      a.ifindex || i,
 | 
			
		||||
						name:     name,
 | 
			
		||||
						rawname:  a.name,
 | 
			
		||||
| 
						 | 
				
			
			@ -442,15 +454,15 @@ function initNetworkState() {
 | 
			
		|||
					};
 | 
			
		||||
 | 
			
		||||
					if (a.family == 'packet') {
 | 
			
		||||
						s.interfaces[name].flags   = a.flags;
 | 
			
		||||
						s.interfaces[name].stats   = a.data;
 | 
			
		||||
						s.interfaces[name].macaddr = a.addr;
 | 
			
		||||
						s.netdevs[name].flags   = a.flags;
 | 
			
		||||
						s.netdevs[name].stats   = a.data;
 | 
			
		||||
						s.netdevs[name].macaddr = a.addr;
 | 
			
		||||
					}
 | 
			
		||||
					else if (a.family == 'inet') {
 | 
			
		||||
						s.interfaces[name].ipaddrs.push(a.addr + '/' + a.netmask);
 | 
			
		||||
						s.netdevs[name].ipaddrs.push(a.addr + '/' + a.netmask);
 | 
			
		||||
					}
 | 
			
		||||
					else if (a.family == 'inet6') {
 | 
			
		||||
						s.interfaces[name].ip6addrs.push(a.addr + '/' + a.netmask);
 | 
			
		||||
						s.netdevs[name].ip6addrs.push(a.addr + '/' + a.netmask);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			@ -467,7 +479,7 @@ function initNetworkState() {
 | 
			
		|||
					};
 | 
			
		||||
 | 
			
		||||
					for (var i = 0; dev.ports && i < dev.ports.length; i++) {
 | 
			
		||||
						var subdev = s.interfaces[dev.ports[i]];
 | 
			
		||||
						var subdev = s.netdevs[dev.ports[i]];
 | 
			
		||||
 | 
			
		||||
						if (subdev == null)
 | 
			
		||||
							continue;
 | 
			
		||||
| 
						 | 
				
			
			@ -477,6 +489,16 @@ function initNetworkState() {
 | 
			
		|||
					}
 | 
			
		||||
 | 
			
		||||
					s.bridges[devname] = b;
 | 
			
		||||
					s.isBridge[devname] = true;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (s.netdevs.hasOwnProperty(devname)) {
 | 
			
		||||
					Object.assign(s.netdevs[devname], {
 | 
			
		||||
						macaddr: dev.mac,
 | 
			
		||||
						type:    dev.type,
 | 
			
		||||
						mtu:     dev.mtu,
 | 
			
		||||
						qlen:    dev.qlen
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -544,17 +566,28 @@ function initNetworkState() {
 | 
			
		|||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return (_state = s);
 | 
			
		||||
		}));
 | 
			
		||||
			if (L.isObject(board.dsl) && L.isObject(board.dsl.modem)) {
 | 
			
		||||
				s.hasDSLModem = board.dsl.modem;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
	return Promise.resolve(_state);
 | 
			
		||||
			_init = null;
 | 
			
		||||
 | 
			
		||||
			return (_state = s);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return (_state != null ? Promise.resolve(_state) : _init);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function ifnameOf(obj) {
 | 
			
		||||
	if (obj instanceof Interface)
 | 
			
		||||
		return obj.name();
 | 
			
		||||
	else if (obj instanceof Protocol)
 | 
			
		||||
		return obj.ifname();
 | 
			
		||||
	if (obj instanceof Protocol)
 | 
			
		||||
		return obj.getIfname();
 | 
			
		||||
	else if (obj instanceof Device)
 | 
			
		||||
		return obj.getName();
 | 
			
		||||
	else if (obj instanceof WifiDevice)
 | 
			
		||||
		return obj.getName();
 | 
			
		||||
	else if (obj instanceof WifiNetwork)
 | 
			
		||||
		return obj.getIfname();
 | 
			
		||||
	else if (typeof(obj) == 'string')
 | 
			
		||||
		return obj.replace(/:.+$/, '');
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -580,10 +613,18 @@ function deviceSort(a, b) {
 | 
			
		|||
var Network, Protocol, Device, WifiDevice, WifiNetwork;
 | 
			
		||||
 | 
			
		||||
Network = L.Class.extend({
 | 
			
		||||
	prefixToMask: prefixToMask,
 | 
			
		||||
	maskToPrefix: maskToPrefix,
 | 
			
		||||
 | 
			
		||||
	flushCache: function() {
 | 
			
		||||
		initNetworkState(true);
 | 
			
		||||
		return _init;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	getProtocol: function(protoname, netname) {
 | 
			
		||||
		var v = _protocols[protoname];
 | 
			
		||||
		if (v != null)
 | 
			
		||||
			return v(netname || '__dummy__');
 | 
			
		||||
			return new v(netname || '__dummy__');
 | 
			
		||||
 | 
			
		||||
		return null;
 | 
			
		||||
	},
 | 
			
		||||
| 
						 | 
				
			
			@ -592,18 +633,35 @@ Network = L.Class.extend({
 | 
			
		|||
		var rv = [];
 | 
			
		||||
 | 
			
		||||
		for (var protoname in _protocols)
 | 
			
		||||
			rv.push(_protocols[protoname]('__dummy__'));
 | 
			
		||||
			rv.push(new _protocols[protoname]('__dummy__'));
 | 
			
		||||
 | 
			
		||||
		return rv;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	registerProtocol: function(protoname, methods) {
 | 
			
		||||
		var proto = Protocol.extend(Object.assign({}, methods, {
 | 
			
		||||
		var spec = L.isObject(_protospecs) ? _protospecs[protoname] : null;
 | 
			
		||||
		var proto = Protocol.extend(Object.assign({
 | 
			
		||||
			getI18n: function() {
 | 
			
		||||
				return protoname;
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
			isFloating: function() {
 | 
			
		||||
				return false;
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
			isVirtual: function() {
 | 
			
		||||
				return (L.isObject(spec) && spec.no_device == true);
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
			renderFormOptions: function(section) {
 | 
			
		||||
 | 
			
		||||
			}
 | 
			
		||||
		}, methods, {
 | 
			
		||||
			__init__: function(name) {
 | 
			
		||||
				this.sid = name;
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
			proto: function() {
 | 
			
		||||
			getProtocol: function() {
 | 
			
		||||
				return protoname;
 | 
			
		||||
			}
 | 
			
		||||
		}));
 | 
			
		||||
| 
						 | 
				
			
			@ -620,7 +678,7 @@ Network = L.Class.extend({
 | 
			
		|||
	registerErrorCode: function(code, message) {
 | 
			
		||||
		if (typeof(code) == 'string' &&
 | 
			
		||||
		    typeof(message) == 'string' &&
 | 
			
		||||
		    proto_errors.hasOwnProperty(code)) {
 | 
			
		||||
		    !proto_errors.hasOwnProperty(code)) {
 | 
			
		||||
			proto_errors[code] = message;
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -661,9 +719,9 @@ Network = L.Class.extend({
 | 
			
		|||
				return this.instantiateNetwork(name);
 | 
			
		||||
			}
 | 
			
		||||
			else if (name != null) {
 | 
			
		||||
				for (var i = 0; i < _cache.interfacedump.length; i++)
 | 
			
		||||
					if (_cache.interfacedump[i].interface == name)
 | 
			
		||||
						return this.instantiateNetwork(name, _cache.interfacedump[i].proto);
 | 
			
		||||
				for (var i = 0; i < _state.ifaces.length; i++)
 | 
			
		||||
					if (_state.ifaces[i].interface == name)
 | 
			
		||||
						return this.instantiateNetwork(name, _state.ifaces[i].proto);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return null;
 | 
			
		||||
| 
						 | 
				
			
			@ -678,10 +736,10 @@ Network = L.Class.extend({
 | 
			
		|||
			for (var i = 0; i < uciInterfaces.length; i++)
 | 
			
		||||
				networks[uciInterfaces[i]['.name']] = this.instantiateNetwork(uciInterfaces[i]['.name']);
 | 
			
		||||
 | 
			
		||||
			for (var i = 0; i < _cache.interfacedump.length; i++)
 | 
			
		||||
				if (networks[_cache.interfacedump[i].interface] == null)
 | 
			
		||||
					networks[_cache.interfacedump[i].interface] =
 | 
			
		||||
						this.instantiateNetwork(_cache.interfacedump[i].interface, _cache.interfacedump[i].proto);
 | 
			
		||||
			for (var i = 0; i < _state.ifaces.length; i++)
 | 
			
		||||
				if (networks[_state.ifaces[i].interface] == null)
 | 
			
		||||
					networks[_state.ifaces[i].interface] =
 | 
			
		||||
						this.instantiateNetwork(_state.ifaces[i].interface, _state.ifaces[i].proto);
 | 
			
		||||
 | 
			
		||||
			var rv = [];
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -795,7 +853,7 @@ Network = L.Class.extend({
 | 
			
		|||
			if (name == null)
 | 
			
		||||
				return null;
 | 
			
		||||
 | 
			
		||||
			if (_state.interfaces.hasOwnProperty(name) || isWifiIfname(name))
 | 
			
		||||
			if (_state.netdevs.hasOwnProperty(name) || isWifiIfname(name))
 | 
			
		||||
				return this.instantiateDevice(name);
 | 
			
		||||
 | 
			
		||||
			var netid = getWifiNetidBySid(name);
 | 
			
		||||
| 
						 | 
				
			
			@ -826,7 +884,7 @@ Network = L.Class.extend({
 | 
			
		|||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for (var ifname in _state.interfaces) {
 | 
			
		||||
			for (var ifname in _state.netdevs) {
 | 
			
		||||
				if (devices.hasOwnProperty(ifname))
 | 
			
		||||
					continue;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1017,7 +1075,7 @@ Network = L.Class.extend({
 | 
			
		|||
			var radioname = existingDevice['.name'],
 | 
			
		||||
			    netid = getWifiNetidBySid(sid) || [];
 | 
			
		||||
 | 
			
		||||
			return this.instantiateWifiNetwork(sid, radioname, _cache.wifi[radioname], netid[0], null, { ifname: netid });
 | 
			
		||||
			return this.instantiateWifiNetwork(sid, radioname, _state.radios[radioname], netid[0], null, { ifname: netid });
 | 
			
		||||
		}, this));
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1037,20 +1095,24 @@ Network = L.Class.extend({
 | 
			
		|||
		return initNetworkState().then(L.bind(function() {
 | 
			
		||||
			var rv = [];
 | 
			
		||||
 | 
			
		||||
			for (var i = 0; i < _state.interfacedump.length; i++) {
 | 
			
		||||
				if (!Array.isArray(_state.interfacedump[i].route))
 | 
			
		||||
			for (var i = 0; i < _state.ifaces.length; i++) {
 | 
			
		||||
				if (!Array.isArray(_state.ifaces[i].route))
 | 
			
		||||
					continue;
 | 
			
		||||
 | 
			
		||||
				for (var j = 0; j < _state.interfacedump[i].route.length; j++) {
 | 
			
		||||
					if (typeof(_state.interfacedump[i].route[j]) != 'object' ||
 | 
			
		||||
					    typeof(_state.interfacedump[i].route[j].target) != 'string' ||
 | 
			
		||||
					    typeof(_state.interfacedump[i].route[j].mask) != 'number')
 | 
			
		||||
				for (var j = 0; j < _state.ifaces[i].route.length; j++) {
 | 
			
		||||
					if (typeof(_state.ifaces[i].route[j]) != 'object' ||
 | 
			
		||||
					    typeof(_state.ifaces[i].route[j].target) != 'string' ||
 | 
			
		||||
					    typeof(_state.ifaces[i].route[j].mask) != 'number')
 | 
			
		||||
					    continue;
 | 
			
		||||
 | 
			
		||||
					if (_state.interfacedump[i].route[j].table)
 | 
			
		||||
					if (_state.ifaces[i].route[j].table)
 | 
			
		||||
						continue;
 | 
			
		||||
 | 
			
		||||
					rv.push(_state.interfacedump[i]);
 | 
			
		||||
					if (_state.ifaces[i].route[j].target != addr ||
 | 
			
		||||
					    _state.ifaces[i].route[j].mask != mask)
 | 
			
		||||
					    continue;
 | 
			
		||||
 | 
			
		||||
					rv.push(_state.ifaces[i]);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1062,25 +1124,25 @@ Network = L.Class.extend({
 | 
			
		|||
		return initNetworkState().then(L.bind(function() {
 | 
			
		||||
			var rv = [];
 | 
			
		||||
 | 
			
		||||
			for (var i = 0; i < _state.interfacedump.length; i++) {
 | 
			
		||||
				if (Array.isArray(_state.interfacedump[i]['ipv4-address']))
 | 
			
		||||
					for (var j = 0; j < _state.interfacedump[i]['ipv4-address'].length; j++)
 | 
			
		||||
						if (typeof(_state.interfacedump[i]['ipv4-address'][j]) == 'object' &&
 | 
			
		||||
						    _state.interfacedump[i]['ipv4-address'][j].address == addr)
 | 
			
		||||
							return _state.interfacedump[i];
 | 
			
		||||
			for (var i = 0; i < _state.ifaces.length; i++) {
 | 
			
		||||
				if (Array.isArray(_state.ifaces[i]['ipv4-address']))
 | 
			
		||||
					for (var j = 0; j < _state.ifaces[i]['ipv4-address'].length; j++)
 | 
			
		||||
						if (typeof(_state.ifaces[i]['ipv4-address'][j]) == 'object' &&
 | 
			
		||||
						    _state.ifaces[i]['ipv4-address'][j].address == addr)
 | 
			
		||||
							return _state.ifaces[i];
 | 
			
		||||
 | 
			
		||||
				if (Array.isArray(_state.interfacedump[i]['ipv6-address']))
 | 
			
		||||
					for (var j = 0; j < _state.interfacedump[i]['ipv6-address'].length; j++)
 | 
			
		||||
						if (typeof(_state.interfacedump[i]['ipv6-address'][j]) == 'object' &&
 | 
			
		||||
						    _state.interfacedump[i]['ipv6-address'][j].address == addr)
 | 
			
		||||
							return _state.interfacedump[i];
 | 
			
		||||
				if (Array.isArray(_state.ifaces[i]['ipv6-address']))
 | 
			
		||||
					for (var j = 0; j < _state.ifaces[i]['ipv6-address'].length; j++)
 | 
			
		||||
						if (typeof(_state.ifaces[i]['ipv6-address'][j]) == 'object' &&
 | 
			
		||||
						    _state.ifaces[i]['ipv6-address'][j].address == addr)
 | 
			
		||||
							return _state.ifaces[i];
 | 
			
		||||
 | 
			
		||||
				if (Array.isArray(_state.interfacedump[i]['ipv6-prefix-assignment']))
 | 
			
		||||
					for (var j = 0; j < _state.interfacedump[i]['ipv6-prefix-assignment'].length; j++)
 | 
			
		||||
						if (typeof(_state.interfacedump[i]['ipv6-prefix-assignment'][j]) == 'object' &&
 | 
			
		||||
							typeof(_state.interfacedump[i]['ipv6-prefix-assignment'][j]['local-address']) == 'object' &&
 | 
			
		||||
						    _state.interfacedump[i]['ipv6-prefix-assignment'][j]['local-address'].address == addr)
 | 
			
		||||
							return _state.interfacedump[i];
 | 
			
		||||
				if (Array.isArray(_state.ifaces[i]['ipv6-prefix-assignment']))
 | 
			
		||||
					for (var j = 0; j < _state.ifaces[i]['ipv6-prefix-assignment'].length; j++)
 | 
			
		||||
						if (typeof(_state.ifaces[i]['ipv6-prefix-assignment'][j]) == 'object' &&
 | 
			
		||||
							typeof(_state.ifaces[i]['ipv6-prefix-assignment'][j]['local-address']) == 'object' &&
 | 
			
		||||
						    _state.ifaces[i]['ipv6-prefix-assignment'][j]['local-address'].address == addr)
 | 
			
		||||
							return _state.ifaces[i];
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return null;
 | 
			
		||||
| 
						 | 
				
			
			@ -1135,6 +1197,16 @@ Network = L.Class.extend({
 | 
			
		|||
 | 
			
		||||
	instantiateWifiNetwork: function(sid, radioname, radiostate, netid, netstate, iwinfo) {
 | 
			
		||||
		return new WifiNetwork(sid, radioname, radiostate, netid, netstate, iwinfo);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	getIfnameOf: function(obj) {
 | 
			
		||||
		return ifnameOf(obj);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	getDSLModemType: function() {
 | 
			
		||||
		return initNetworkState().then(function() {
 | 
			
		||||
			return _state.hasDSLModem ? _state.hasDSLModem.type : null;
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1153,11 +1225,11 @@ Protocol = L.Class.extend({
 | 
			
		|||
	},
 | 
			
		||||
 | 
			
		||||
	_ubus: function(field) {
 | 
			
		||||
		for (var i = 0; i < _cache.interfacedump.length; i++) {
 | 
			
		||||
			if (_cache.interfacedump[i].interface != this.sid)
 | 
			
		||||
		for (var i = 0; i < _state.ifaces.length; i++) {
 | 
			
		||||
			if (_state.ifaces[i].interface != this.sid)
 | 
			
		||||
				continue;
 | 
			
		||||
 | 
			
		||||
			return (field != null ? _cache.interfacedump[i][field] : _cache.interfacedump[i]);
 | 
			
		||||
			return (field != null ? _state.ifaces[i][field] : _state.ifaces[i]);
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1175,7 +1247,7 @@ Protocol = L.Class.extend({
 | 
			
		|||
		if (this.isFloating())
 | 
			
		||||
			ifname = this._ubus('l3_device');
 | 
			
		||||
		else
 | 
			
		||||
			ifname = this._ubus('device');
 | 
			
		||||
			ifname = this._ubus('device') || this._ubus('l3_device');
 | 
			
		||||
 | 
			
		||||
		if (ifname != null)
 | 
			
		||||
			return ifname;
 | 
			
		||||
| 
						 | 
				
			
			@ -1185,7 +1257,7 @@ Protocol = L.Class.extend({
 | 
			
		|||
	},
 | 
			
		||||
 | 
			
		||||
	getProtocol: function() {
 | 
			
		||||
		return 'none';
 | 
			
		||||
		return null;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	getI18n: function() {
 | 
			
		||||
| 
						 | 
				
			
			@ -1455,11 +1527,6 @@ Protocol = L.Class.extend({
 | 
			
		|||
			return new Device(ifname, this);
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			var ifname = this._ubus('l3_device') || this._ubus('device');
 | 
			
		||||
 | 
			
		||||
			if (ifname != null)
 | 
			
		||||
				return L.network.instantiateDevice(ifname, this);
 | 
			
		||||
 | 
			
		||||
			var ifnames = L.toArray(uci.get('network', this.sid, 'ifname'));
 | 
			
		||||
 | 
			
		||||
			for (var i = 0; i < ifnames.length; i++) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1473,6 +1540,16 @@ Protocol = L.Class.extend({
 | 
			
		|||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	getL2Device: function() {
 | 
			
		||||
		var ifname = this._ubus('device');
 | 
			
		||||
		return (ifname != null ? L.network.instantiateDevice(ifname, this) : null);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	getL3Device: function() {
 | 
			
		||||
		var ifname = this._ubus('l3_device');
 | 
			
		||||
		return (ifname != null ? L.network.instantiateDevice(ifname, this) : null);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	getDevices: function() {
 | 
			
		||||
		var rv = [];
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1559,12 +1636,12 @@ Device = L.Class.extend({
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		this.ifname  = this.ifname || ifname;
 | 
			
		||||
		this.dev     = _state.interfaces[this.ifname];
 | 
			
		||||
		this.dev     = _state.netdevs[this.ifname];
 | 
			
		||||
		this.network = network;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	_ubus: function(field) {
 | 
			
		||||
		var dump = _cache.devicedump[this.ifname] || {};
 | 
			
		||||
		var dump = _state.devices[this.ifname] || {};
 | 
			
		||||
 | 
			
		||||
		return (field != null ? dump[field] : dump);
 | 
			
		||||
	},
 | 
			
		||||
| 
						 | 
				
			
			@ -1574,7 +1651,15 @@ Device = L.Class.extend({
 | 
			
		|||
	},
 | 
			
		||||
 | 
			
		||||
	getMAC: function() {
 | 
			
		||||
		return this._ubus('macaddr');
 | 
			
		||||
		var mac = (this.dev != null ? this.dev.macaddr : null);
 | 
			
		||||
		if (mac == null)
 | 
			
		||||
			mac = this._ubus('macaddr');
 | 
			
		||||
 | 
			
		||||
		return mac ? mac.toUpperCase() : null;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	getMTU: function() {
 | 
			
		||||
		return this.dev ? this.dev.mtu : null;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	getIPAddrs: function() {
 | 
			
		||||
| 
						 | 
				
			
			@ -1655,7 +1740,9 @@ Device = L.Class.extend({
 | 
			
		|||
			return null;
 | 
			
		||||
 | 
			
		||||
		for (var i = 0; i < br.ifnames.length; i++)
 | 
			
		||||
			rv.push(L.network.instantiateDevice(br.ifnames[i]));
 | 
			
		||||
			rv.push(L.network.instantiateDevice(br.ifnames[i].name));
 | 
			
		||||
 | 
			
		||||
		rv.sort(deviceSort);
 | 
			
		||||
 | 
			
		||||
		return rv;
 | 
			
		||||
	},
 | 
			
		||||
| 
						 | 
				
			
			@ -1786,8 +1873,8 @@ WifiDevice = L.Class.extend({
 | 
			
		|||
	},
 | 
			
		||||
 | 
			
		||||
	isUp: function() {
 | 
			
		||||
		if (L.isObject(_cache.wifi[this.sid]))
 | 
			
		||||
			return (_cache.wifi[this.sid].up == true);
 | 
			
		||||
		if (L.isObject(_state.radios[this.sid]))
 | 
			
		||||
			return (_state.radios[this.sid].up == true);
 | 
			
		||||
 | 
			
		||||
		return false;
 | 
			
		||||
	},
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -358,7 +358,7 @@ var CBINetworkSelect = form.ListValue.extend({
 | 
			
		|||
			var network = this.networks[i],
 | 
			
		||||
			    name = network.getName();
 | 
			
		||||
 | 
			
		||||
			if (name == 'loopback' || !this.filter(section_id, name))
 | 
			
		||||
			if (name == 'loopback' || name == this.exclude || !this.filter(section_id, name))
 | 
			
		||||
				continue;
 | 
			
		||||
 | 
			
		||||
			if (this.novirtual && network.isVirtual())
 | 
			
		||||
| 
						 | 
				
			
			@ -420,8 +420,12 @@ var CBIDeviceSelect = form.ListValue.extend({
 | 
			
		|||
	__name__: 'CBI.DeviceSelect',
 | 
			
		||||
 | 
			
		||||
	load: function(section_id) {
 | 
			
		||||
		return network.getDevices().then(L.bind(function(devices) {
 | 
			
		||||
			this.devices = devices;
 | 
			
		||||
		return Promise.all([
 | 
			
		||||
			network.getDevices(),
 | 
			
		||||
			this.noaliases ? null : network.getNetworks()
 | 
			
		||||
		]).then(L.bind(function(data) {
 | 
			
		||||
			this.devices = data[0];
 | 
			
		||||
			this.networks = data[1];
 | 
			
		||||
 | 
			
		||||
			return this.super('load', section_id);
 | 
			
		||||
		}, this));
 | 
			
		||||
| 
						 | 
				
			
			@ -483,6 +487,35 @@ var CBIDeviceSelect = form.ListValue.extend({
 | 
			
		|||
			order.push(name);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (this.networks != null) {
 | 
			
		||||
			for (var i = 0; i < this.networks.length; i++) {
 | 
			
		||||
				var net = this.networks[i],
 | 
			
		||||
				    device = network.instantiateDevice('@%s'.format(net.getName()), net),
 | 
			
		||||
				    name = device.getName();
 | 
			
		||||
 | 
			
		||||
				if (name == '@loopback' || name == this.exclude || !this.filter(section_id, name))
 | 
			
		||||
					continue;
 | 
			
		||||
 | 
			
		||||
				if (this.noinactive && net.isUp() == false)
 | 
			
		||||
					continue;
 | 
			
		||||
 | 
			
		||||
				var item = E([
 | 
			
		||||
					E('img', {
 | 
			
		||||
						'title': device.getI18n(),
 | 
			
		||||
						'src': L.resource('icons/alias%s.png'.format(net.isUp() ? '' : '_disabled'))
 | 
			
		||||
					}),
 | 
			
		||||
					E('span', { 'class': 'hide-open' }, [ name ]),
 | 
			
		||||
					E('span', { 'class': 'hide-close'}, [ device.getI18n() ])
 | 
			
		||||
				]);
 | 
			
		||||
 | 
			
		||||
				if (checked[name])
 | 
			
		||||
					values.push(name);
 | 
			
		||||
 | 
			
		||||
				choices[name] = item;
 | 
			
		||||
				order.push(name);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (!this.nocreate) {
 | 
			
		||||
			var keys = Object.keys(checked).sort();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -146,6 +146,61 @@ var UITextfield = UIElement.extend({
 | 
			
		|||
	}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
var UITextarea = UIElement.extend({
 | 
			
		||||
	__init__: function(value, options) {
 | 
			
		||||
		this.value = value;
 | 
			
		||||
		this.options = Object.assign({
 | 
			
		||||
		    optional: true,
 | 
			
		||||
		    wrap: false,
 | 
			
		||||
		    cols: null,
 | 
			
		||||
		    rows: null
 | 
			
		||||
		}, options);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	render: function() {
 | 
			
		||||
		var frameEl = E('div', { 'id': this.options.id }),
 | 
			
		||||
		    value = (this.value != null) ? String(this.value) : '';
 | 
			
		||||
 | 
			
		||||
		frameEl.appendChild(E('textarea', {
 | 
			
		||||
		    'id': this.options.id ? 'widget.' + this.options.id : null,
 | 
			
		||||
		    'name': this.options.name,
 | 
			
		||||
		    'class': 'cbi-input-textarea',
 | 
			
		||||
		    'readonly': this.options.readonly ? '' : null,
 | 
			
		||||
		    'placeholder': this.options.placeholder,
 | 
			
		||||
		    'style': !this.options.cols ? 'width:100%' : null,
 | 
			
		||||
		    'cols': this.options.cols,
 | 
			
		||||
		    'rows': this.options.rows,
 | 
			
		||||
		    'wrap': this.options.wrap ? '' : null
 | 
			
		||||
		}, [ value ]));
 | 
			
		||||
 | 
			
		||||
		if (this.options.monospace)
 | 
			
		||||
		    frameEl.firstElementChild.style.fontFamily = 'monospace';
 | 
			
		||||
 | 
			
		||||
		return this.bind(frameEl);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	bind: function(frameEl) {
 | 
			
		||||
		var inputEl = frameEl.firstElementChild;
 | 
			
		||||
 | 
			
		||||
		this.node = frameEl;
 | 
			
		||||
 | 
			
		||||
		this.setUpdateEvents(inputEl, 'keyup', 'blur');
 | 
			
		||||
		this.setChangeEvents(inputEl, 'change');
 | 
			
		||||
 | 
			
		||||
		L.dom.bindClassInstance(frameEl, this);
 | 
			
		||||
 | 
			
		||||
		return frameEl;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	getValue: function() {
 | 
			
		||||
		return this.node.firstElementChild.value;
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	setValue: function(value) {
 | 
			
		||||
		this.node.firstElementChild.value = value;
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
var UICheckbox = UIElement.extend({
 | 
			
		||||
	__init__: function(value, options) {
 | 
			
		||||
		this.value = value;
 | 
			
		||||
| 
						 | 
				
			
			@ -207,7 +262,7 @@ var UICheckbox = UIElement.extend({
 | 
			
		|||
 | 
			
		||||
var UISelect = UIElement.extend({
 | 
			
		||||
	__init__: function(value, choices, options) {
 | 
			
		||||
		if (typeof(choices) != 'object')
 | 
			
		||||
		if (!L.isObject(choices))
 | 
			
		||||
			choices = {};
 | 
			
		||||
 | 
			
		||||
		if (!Array.isArray(value))
 | 
			
		||||
| 
						 | 
				
			
			@ -1199,7 +1254,7 @@ var UIDynamicList = UIElement.extend({
 | 
			
		|||
					'name': this.options.name,
 | 
			
		||||
					'value': value })]);
 | 
			
		||||
 | 
			
		||||
		dl.querySelectorAll('.item, .add-item').forEach(function(item) {
 | 
			
		||||
		dl.querySelectorAll('.item').forEach(function(item) {
 | 
			
		||||
			if (exists)
 | 
			
		||||
				return;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1210,10 +1265,13 @@ var UIDynamicList = UIElement.extend({
 | 
			
		|||
 | 
			
		||||
			if (hidden && hidden.value === value)
 | 
			
		||||
				exists = true;
 | 
			
		||||
			else if (!hidden || hidden.value >= value)
 | 
			
		||||
				exists = !!item.parentNode.insertBefore(new_item, item);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		if (!exists) {
 | 
			
		||||
			var ai = dl.querySelector('.add-item');
 | 
			
		||||
			ai.parentNode.insertBefore(new_item, ai);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		dl.dispatchEvent(new CustomEvent('cbi-dynlist-change', {
 | 
			
		||||
			bubbles: true,
 | 
			
		||||
			detail: {
 | 
			
		||||
| 
						 | 
				
			
			@ -1553,9 +1611,6 @@ return L.Class.extend({
 | 
			
		|||
			document.addEventListener('dependency-update', this.updateTabs.bind(this));
 | 
			
		||||
 | 
			
		||||
			this.updateTabs();
 | 
			
		||||
 | 
			
		||||
			if (!groups.length)
 | 
			
		||||
				this.setActiveTabId(-1, -1);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		initTabGroup: function(panes) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1573,6 +1628,7 @@ return L.Class.extend({
 | 
			
		|||
				    active = pane.getAttribute('data-tab-active') === 'true';
 | 
			
		||||
 | 
			
		||||
				menu.appendChild(E('li', {
 | 
			
		||||
					'style': L.dom.isEmpty(pane) ? 'display:none' : null,
 | 
			
		||||
					'class': active ? 'cbi-tab' : 'cbi-tab-disabled',
 | 
			
		||||
					'data-tab': name
 | 
			
		||||
				}, E('a', {
 | 
			
		||||
| 
						 | 
				
			
			@ -1587,7 +1643,7 @@ return L.Class.extend({
 | 
			
		|||
			group.parentNode.insertBefore(menu, group);
 | 
			
		||||
 | 
			
		||||
			if (selected === null) {
 | 
			
		||||
				selected = this.getActiveTabId(groupId);
 | 
			
		||||
				selected = this.getActiveTabId(panes[0]);
 | 
			
		||||
 | 
			
		||||
				if (selected < 0 || selected >= panes.length || L.dom.isEmpty(panes[selected])) {
 | 
			
		||||
					for (var i = 0; i < panes.length; i++) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1602,32 +1658,51 @@ return L.Class.extend({
 | 
			
		|||
				menu.childNodes[selected].classList.remove('cbi-tab-disabled');
 | 
			
		||||
				panes[selected].setAttribute('data-tab-active', 'true');
 | 
			
		||||
 | 
			
		||||
				this.setActiveTabId(groupId, selected);
 | 
			
		||||
				this.setActiveTabId(panes[selected], selected);
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		getPathForPane: function(pane) {
 | 
			
		||||
			var path = [], node = null;
 | 
			
		||||
 | 
			
		||||
			for (node = pane ? pane.parentNode : null;
 | 
			
		||||
			     node != null && node.hasAttribute != null;
 | 
			
		||||
			     node = node.parentNode)
 | 
			
		||||
			{
 | 
			
		||||
				if (node.hasAttribute('data-tab'))
 | 
			
		||||
					path.unshift(node.getAttribute('data-tab'));
 | 
			
		||||
				else if (node.hasAttribute('data-section-id'))
 | 
			
		||||
					path.unshift(node.getAttribute('data-section-id'));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return path.join('/');
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		getActiveTabState: function() {
 | 
			
		||||
			var page = document.body.getAttribute('data-page');
 | 
			
		||||
 | 
			
		||||
			try {
 | 
			
		||||
				var val = JSON.parse(window.sessionStorage.getItem('tab'));
 | 
			
		||||
				if (val.page === page && Array.isArray(val.groups))
 | 
			
		||||
				if (val.page === page && L.isObject(val.paths))
 | 
			
		||||
					return val;
 | 
			
		||||
			}
 | 
			
		||||
			catch(e) {}
 | 
			
		||||
 | 
			
		||||
			window.sessionStorage.removeItem('tab');
 | 
			
		||||
			return { page: page, groups: [] };
 | 
			
		||||
			return { page: page, paths: {} };
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		getActiveTabId: function(groupId) {
 | 
			
		||||
			return +this.getActiveTabState().groups[groupId] || 0;
 | 
			
		||||
		getActiveTabId: function(pane) {
 | 
			
		||||
			var path = this.getPathForPane(pane);
 | 
			
		||||
			return +this.getActiveTabState().paths[path] || 0;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		setActiveTabId: function(groupId, tabIndex) {
 | 
			
		||||
		setActiveTabId: function(pane, tabIndex) {
 | 
			
		||||
			var path = this.getPathForPane(pane);
 | 
			
		||||
 | 
			
		||||
			try {
 | 
			
		||||
				var state = this.getActiveTabState();
 | 
			
		||||
				    state.groups[groupId] = tabIndex;
 | 
			
		||||
				    state.paths[path] = tabIndex;
 | 
			
		||||
 | 
			
		||||
			    window.sessionStorage.setItem('tab', JSON.stringify(state));
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			@ -1639,9 +1714,12 @@ return L.Class.extend({
 | 
			
		|||
		updateTabs: function(ev, root) {
 | 
			
		||||
			(root || document).querySelectorAll('[data-tab-title]').forEach(function(pane) {
 | 
			
		||||
				var menu = pane.parentNode.previousElementSibling,
 | 
			
		||||
				    tab = menu.querySelector('[data-tab="%s"]'.format(pane.getAttribute('data-tab'))),
 | 
			
		||||
				    tab = menu ? menu.querySelector('[data-tab="%s"]'.format(pane.getAttribute('data-tab'))) : null,
 | 
			
		||||
				    n_errors = pane.querySelectorAll('.cbi-input-invalid').length;
 | 
			
		||||
 | 
			
		||||
				if (!menu || !tab)
 | 
			
		||||
					return;
 | 
			
		||||
 | 
			
		||||
				if (L.dom.isEmpty(pane)) {
 | 
			
		||||
					tab.style.display = 'none';
 | 
			
		||||
					tab.classList.remove('flash');
 | 
			
		||||
| 
						 | 
				
			
			@ -1687,7 +1765,7 @@ return L.Class.extend({
 | 
			
		|||
				if (L.dom.matches(pane, '[data-tab]')) {
 | 
			
		||||
					if (pane.getAttribute('data-tab') === name) {
 | 
			
		||||
						pane.setAttribute('data-tab-active', 'true');
 | 
			
		||||
						L.ui.tabs.setActiveTabId(groupId, index);
 | 
			
		||||
						L.ui.tabs.setActiveTabId(pane, index);
 | 
			
		||||
					}
 | 
			
		||||
					else {
 | 
			
		||||
						pane.setAttribute('data-tab-active', 'false');
 | 
			
		||||
| 
						 | 
				
			
			@ -2058,8 +2136,32 @@ return L.Class.extend({
 | 
			
		|||
		catch (e) { }
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	createHandlerFn: function(ctx, fn /*, ... */) {
 | 
			
		||||
		if (typeof(fn) == 'string')
 | 
			
		||||
			fn = ctx[fn];
 | 
			
		||||
 | 
			
		||||
		if (typeof(fn) != 'function')
 | 
			
		||||
			return null;
 | 
			
		||||
 | 
			
		||||
		return Function.prototype.bind.apply(function() {
 | 
			
		||||
			var t = arguments[arguments.length - 1].target;
 | 
			
		||||
 | 
			
		||||
			t.classList.add('spinning');
 | 
			
		||||
			t.disabled = true;
 | 
			
		||||
 | 
			
		||||
			if (t.blur)
 | 
			
		||||
				t.blur();
 | 
			
		||||
 | 
			
		||||
			Promise.resolve(fn.apply(ctx, arguments)).then(function() {
 | 
			
		||||
				t.classList.remove('spinning');
 | 
			
		||||
				t.disabled = false;
 | 
			
		||||
			});
 | 
			
		||||
		}, this.varargs(arguments, 2, ctx));
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	/* Widgets */
 | 
			
		||||
	Textfield: UITextfield,
 | 
			
		||||
	Textarea: UITextarea,
 | 
			
		||||
	Checkbox: UICheckbox,
 | 
			
		||||
	Select: UISelect,
 | 
			
		||||
	Dropdown: UIDropdown,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,7 @@ local function readfile(path)
 | 
			
		|||
end
 | 
			
		||||
 | 
			
		||||
local methods = {
 | 
			
		||||
	initList = {
 | 
			
		||||
	getInitList = {
 | 
			
		||||
		args = { name = "name" },
 | 
			
		||||
		call = function(args)
 | 
			
		||||
			local sys = require "luci.sys"
 | 
			
		||||
| 
						 | 
				
			
			@ -22,11 +22,11 @@ local methods = {
 | 
			
		|||
					return { error = "No such init script" }
 | 
			
		||||
				end
 | 
			
		||||
			end
 | 
			
		||||
			return { result = scripts }
 | 
			
		||||
			return scripts
 | 
			
		||||
		end
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	initCall = {
 | 
			
		||||
	setInitAction = {
 | 
			
		||||
		args = { name = "name", action = "action" },
 | 
			
		||||
		call = function(args)
 | 
			
		||||
			local sys = require "luci.sys"
 | 
			
		||||
| 
						 | 
				
			
			@ -39,7 +39,7 @@ local methods = {
 | 
			
		|||
 | 
			
		||||
	getLocaltime = {
 | 
			
		||||
		call = function(args)
 | 
			
		||||
			return { localtime = os.time() }
 | 
			
		||||
			return { result = os.time() }
 | 
			
		||||
		end
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -52,11 +52,11 @@ local methods = {
 | 
			
		|||
				sys.call("date -s '%04d-%02d-%02d %02d:%02d:%02d' >/dev/null" %{ date.year, date.month, date.day, date.hour, date.min, date.sec })
 | 
			
		||||
				sys.call("/etc/init.d/sysfixtime restart >/dev/null")
 | 
			
		||||
			end
 | 
			
		||||
			return { localtime = args.localtime }
 | 
			
		||||
			return { result = args.localtime }
 | 
			
		||||
		end
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	timezone = {
 | 
			
		||||
	getTimezones = {
 | 
			
		||||
		call = function(args)
 | 
			
		||||
			local util  = require "luci.util"
 | 
			
		||||
			local zones = require "luci.sys.zoneinfo"
 | 
			
		||||
| 
						 | 
				
			
			@ -76,11 +76,11 @@ local methods = {
 | 
			
		|||
					active = (res and res.value == zone[1]) and true or nil
 | 
			
		||||
				}
 | 
			
		||||
			end
 | 
			
		||||
			return { result = result }
 | 
			
		||||
			return result
 | 
			
		||||
		end
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	leds = {
 | 
			
		||||
	getLEDs = {
 | 
			
		||||
		call = function()
 | 
			
		||||
			local iter   = fs.dir("/sys/class/leds")
 | 
			
		||||
			local result = { }
 | 
			
		||||
| 
						 | 
				
			
			@ -115,7 +115,7 @@ local methods = {
 | 
			
		|||
		end
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	usb = {
 | 
			
		||||
	getUSBDevices = {
 | 
			
		||||
		call = function()
 | 
			
		||||
			local fs     = require "nixio.fs"
 | 
			
		||||
			local iter   = fs.glob("/sys/bus/usb/devices/[0-9]*/manufacturer")
 | 
			
		||||
| 
						 | 
				
			
			@ -126,7 +126,7 @@ local methods = {
 | 
			
		|||
 | 
			
		||||
				local p
 | 
			
		||||
				for p in iter do
 | 
			
		||||
					local id = p:match("%d+-%d+")
 | 
			
		||||
					local id = p:match("/([^/]+)/manufacturer$")
 | 
			
		||||
 | 
			
		||||
					result.devices[#result.devices+1] = {
 | 
			
		||||
						id      = id,
 | 
			
		||||
| 
						 | 
				
			
			@ -139,18 +139,19 @@ local methods = {
 | 
			
		|||
				end
 | 
			
		||||
			end
 | 
			
		||||
 | 
			
		||||
			iter = fs.glob("/sys/bus/usb/devices/*/usb[0-9]*-port[0-9]*")
 | 
			
		||||
			iter = fs.glob("/sys/bus/usb/devices/*/*-port[0-9]*")
 | 
			
		||||
 | 
			
		||||
			if iter then
 | 
			
		||||
				result.ports = {}
 | 
			
		||||
 | 
			
		||||
				local p
 | 
			
		||||
				for p in iter do
 | 
			
		||||
					local bus, port = p:match("usb(%d+)-port(%d+)")
 | 
			
		||||
					local port = p:match("([^/]+)$")
 | 
			
		||||
					local link = fs.readlink(p.."/device")
 | 
			
		||||
 | 
			
		||||
					result.ports[#result.ports+1] = {
 | 
			
		||||
						hub  = tonumber(bus),
 | 
			
		||||
						port = tonumber(port)
 | 
			
		||||
						port   = port,
 | 
			
		||||
						device = link and fs.basename(link)
 | 
			
		||||
					}
 | 
			
		||||
				end
 | 
			
		||||
			end
 | 
			
		||||
| 
						 | 
				
			
			@ -159,20 +160,20 @@ local methods = {
 | 
			
		|||
		end
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	ifaddrs = {
 | 
			
		||||
	getIfaddrs = {
 | 
			
		||||
		call = function()
 | 
			
		||||
			return { result = nixio.getifaddrs() }
 | 
			
		||||
		end
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	host_hints = {
 | 
			
		||||
	getHostHints = {
 | 
			
		||||
		call = function()
 | 
			
		||||
			local sys = require "luci.sys"
 | 
			
		||||
			return sys.net.host_hints()
 | 
			
		||||
		end
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	duid_hints = {
 | 
			
		||||
	getDUIDHints = {
 | 
			
		||||
		call = function()
 | 
			
		||||
			local fp = io.open('/var/hosts/odhcpd')
 | 
			
		||||
			local result = { }
 | 
			
		||||
| 
						 | 
				
			
			@ -192,7 +193,7 @@ local methods = {
 | 
			
		|||
		end
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	leases = {
 | 
			
		||||
	getDHCPLeases = {
 | 
			
		||||
		args = { family = 0 },
 | 
			
		||||
		call = function(args)
 | 
			
		||||
			local s = require "luci.tools.status"
 | 
			
		||||
| 
						 | 
				
			
			@ -210,7 +211,7 @@ local methods = {
 | 
			
		|||
		end
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	netdevs = {
 | 
			
		||||
	getNetworkDevices = {
 | 
			
		||||
		call = function(args)
 | 
			
		||||
			local dir = fs.dir("/sys/class/net")
 | 
			
		||||
			local result = { }
 | 
			
		||||
| 
						 | 
				
			
			@ -273,52 +274,138 @@ local methods = {
 | 
			
		|||
		end
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	boardjson = {
 | 
			
		||||
	getBoardJSON = {
 | 
			
		||||
		call = function(args)
 | 
			
		||||
			local jsc = require "luci.jsonc"
 | 
			
		||||
			return jsc.parse(fs.readfile("/etc/board.json") or "")
 | 
			
		||||
		end
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	offload_support = {
 | 
			
		||||
	getConntrackHelpers = {
 | 
			
		||||
		call = function()
 | 
			
		||||
			local fs = require "nixio.fs"
 | 
			
		||||
			return { offload_support = not not fs.access("/sys/module/xt_FLOWOFFLOAD/refcnt") }
 | 
			
		||||
			local ok, fd = pcall(io.open, "/usr/share/fw3/helpers.conf", "r")
 | 
			
		||||
			local rv = {}
 | 
			
		||||
 | 
			
		||||
			if ok then
 | 
			
		||||
				local entry
 | 
			
		||||
 | 
			
		||||
				while true do
 | 
			
		||||
					local 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
 | 
			
		||||
 | 
			
		||||
				fd:close()
 | 
			
		||||
			end
 | 
			
		||||
 | 
			
		||||
			return { result = rv }
 | 
			
		||||
		end
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	conntrack_helpers = {
 | 
			
		||||
	getFeatures = {
 | 
			
		||||
		call = function()
 | 
			
		||||
			local fd = io.open("/usr/share/fw3/helpers.conf", "r")
 | 
			
		||||
			local fs = require "nixio.fs"
 | 
			
		||||
			local rv = {}
 | 
			
		||||
			local ok, fd
 | 
			
		||||
 | 
			
		||||
			local line, entry
 | 
			
		||||
			while true do
 | 
			
		||||
				line = fd:read("*l")
 | 
			
		||||
				if not line then
 | 
			
		||||
					break
 | 
			
		||||
				end
 | 
			
		||||
			rv.firewall      = fs.access("/sbin/fw3")
 | 
			
		||||
			rv.opkg          = fs.access("/bin/opkg")
 | 
			
		||||
			rv.offloading    = fs.access("/sys/module/xt_FLOWOFFLOAD/refcnt")
 | 
			
		||||
			rv.br2684ctl     = fs.access("/usr/sbin/br2684ctl")
 | 
			
		||||
			rv.swconfig      = fs.access("/sbin/swconfig")
 | 
			
		||||
			rv.odhcpd        = fs.access("/usr/sbin/odhcpd")
 | 
			
		||||
			rv.zram          = fs.access("/sys/class/zram-control")
 | 
			
		||||
			rv.sysntpd       = fs.readlink("/usr/sbin/ntpd") and true
 | 
			
		||||
 | 
			
		||||
				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
 | 
			
		||||
			local wifi_features = { "eap", "11n", "11ac", "11r", "11w", "acs", "sae", "owe", "suiteb192" }
 | 
			
		||||
 | 
			
		||||
			if fs.access("/usr/sbin/hostapd") then
 | 
			
		||||
				rv.hostapd = {}
 | 
			
		||||
 | 
			
		||||
				local _, feature
 | 
			
		||||
				for _, feature in ipairs(wifi_features) do
 | 
			
		||||
					rv.hostapd[feature] =
 | 
			
		||||
						(os.execute(string.format("/usr/sbin/hostapd -v%s >/dev/null 2>/dev/null", feature)) == 0)
 | 
			
		||||
				end
 | 
			
		||||
			end
 | 
			
		||||
			if entry then
 | 
			
		||||
				rv[#rv+1] = entry
 | 
			
		||||
 | 
			
		||||
			if fs.access("/usr/sbin/wpa_supplicant") then
 | 
			
		||||
				rv.wpasupplicant = {}
 | 
			
		||||
 | 
			
		||||
				local _, feature
 | 
			
		||||
				for _, feature in ipairs(wifi_features) do
 | 
			
		||||
					rv.wpasupplicant[feature] =
 | 
			
		||||
						(os.execute(string.format("/usr/sbin/wpa_supplicant -v%s >/dev/null 2>/dev/null", feature)) == 0)
 | 
			
		||||
				end
 | 
			
		||||
			end
 | 
			
		||||
 | 
			
		||||
			return { helpers = rv }
 | 
			
		||||
			ok, fd = pcall(io.popen, "dnsmasq --version 2>/dev/null")
 | 
			
		||||
			if ok then
 | 
			
		||||
				rv.dnsmasq = {}
 | 
			
		||||
 | 
			
		||||
				while true do
 | 
			
		||||
					local line = fd:read("*l")
 | 
			
		||||
					if not line then
 | 
			
		||||
						break
 | 
			
		||||
					end
 | 
			
		||||
 | 
			
		||||
					local opts = line:match("^Compile time options: (.+)$")
 | 
			
		||||
					if opts then
 | 
			
		||||
						local opt
 | 
			
		||||
						for opt in opts:gmatch("%S+") do
 | 
			
		||||
							local no = opt:match("^no%-(%S+)$")
 | 
			
		||||
							rv.dnsmasq[string.lower(no or opt)] = not no
 | 
			
		||||
						end
 | 
			
		||||
						break
 | 
			
		||||
					end
 | 
			
		||||
				end
 | 
			
		||||
 | 
			
		||||
				fd:close()
 | 
			
		||||
			end
 | 
			
		||||
 | 
			
		||||
			ok, fd = pcall(io.popen, "ipset --help 2>/dev/null")
 | 
			
		||||
			if ok then
 | 
			
		||||
				rv.ipset = {}
 | 
			
		||||
 | 
			
		||||
				local sets = false
 | 
			
		||||
 | 
			
		||||
				while true do
 | 
			
		||||
					local line = fd:read("*l")
 | 
			
		||||
					if not line then
 | 
			
		||||
						break
 | 
			
		||||
					elseif line:match("^Supported set types:") then
 | 
			
		||||
						sets = true
 | 
			
		||||
					elseif sets then
 | 
			
		||||
						local set, ver = line:match("^%s+(%S+)%s+(%d+)")
 | 
			
		||||
						if set and not rv.ipset[set] then
 | 
			
		||||
							rv.ipset[set] = tonumber(ver)
 | 
			
		||||
						end
 | 
			
		||||
					end
 | 
			
		||||
				end
 | 
			
		||||
 | 
			
		||||
				fd:close()
 | 
			
		||||
			end
 | 
			
		||||
 | 
			
		||||
			return rv
 | 
			
		||||
		end
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,54 @@
 | 
			
		|||
{
 | 
			
		||||
	"user": "nobody",
 | 
			
		||||
	"access": {
 | 
			
		||||
		"system": {
 | 
			
		||||
			"methods": [ "board", "info" ]
 | 
			
		||||
	"unauthenticated": {
 | 
			
		||||
		"description": "Allow system feature probing",
 | 
			
		||||
		"read": {
 | 
			
		||||
			"ubus": {
 | 
			
		||||
				"luci": [ "getFeatures" ]
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	"uci-access": {
 | 
			
		||||
		"description": "Grant uci write access to all configurations",
 | 
			
		||||
		"read": {
 | 
			
		||||
			"uci": [ "*" ]
 | 
			
		||||
		},
 | 
			
		||||
		"write": {
 | 
			
		||||
			"uci": [ "*" ]
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	"luci-access": {
 | 
			
		||||
		"description": "Grant access to basic LuCI procedures",
 | 
			
		||||
		"read": {
 | 
			
		||||
			"ubus": {
 | 
			
		||||
				"iwinfo": [ "info" ],
 | 
			
		||||
				"luci": [ "getBoardJSON", "getDUIDHints", "getHostHints", "getIfaddrs", "getInitList", "getLocaltime", "getTimezones", "getDHCPLeases", "getLEDs", "getNetworkDevices", "getUSBDevices" ],
 | 
			
		||||
				"network.device": [ "status" ],
 | 
			
		||||
				"network.interface": [ "dump" ],
 | 
			
		||||
				"network.wireless": [ "status" ],
 | 
			
		||||
				"network": [ "get_proto_handlers" ],
 | 
			
		||||
				"uci": [ "changes", "get" ]
 | 
			
		||||
			},
 | 
			
		||||
			"uci": [ "*" ]
 | 
			
		||||
		},
 | 
			
		||||
		"write": {
 | 
			
		||||
			"ubus": {
 | 
			
		||||
				"luci": [ "setInitAction", "setLocaltime" ],
 | 
			
		||||
				"uci": [ "add", "apply", "confirm", "delete", "order", "set" ]
 | 
			
		||||
			},
 | 
			
		||||
			"uci": [ "*" ]
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	"luci-app-firewall": {
 | 
			
		||||
		"description": "Grant access to firewall procedures",
 | 
			
		||||
		"read": {
 | 
			
		||||
			"ubus": {
 | 
			
		||||
				"luci": [ "getConntrackHelpers" ]
 | 
			
		||||
			},
 | 
			
		||||
			"uci": [ "firewall" ]
 | 
			
		||||
		},
 | 
			
		||||
		"write": {
 | 
			
		||||
			"uci": [ "firewall" ]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue