1
0
Fork 0
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:
Ycarus (Yannick Chabanois) 2019-08-27 22:20:52 +02:00
parent c3dc257a80
commit e7ae8eed52
8 changed files with 761 additions and 290 deletions

View file

@ -99,7 +99,7 @@ Firewall = L.Class.extend({
if (name == null || !/^[a-zA-Z0-9_]+$/.test(name)) if (name == null || !/^[a-zA-Z0-9_]+$/.test(name))
return null; return null;
if (this.getZone(name) != null) if (lookupZone(name) != null)
return null; return null;
var d = new Defaults(), var d = new Defaults(),
@ -375,6 +375,14 @@ Zone = AbstractFirewallItem.extend({
this.set('network', ' '); this.set('network', ' ');
}, },
getDevices: function() {
return L.toArray(this.get('device'));
},
getSubnets: function() {
return L.toArray(this.get('subnet'));
},
getForwardingsBy: function(what) { getForwardingsBy: function(what) {
var sections = uci.sections('firewall', 'forwarding'), var sections = uci.sections('firewall', 'forwarding'),
forwards = []; forwards = [];

View file

@ -57,6 +57,23 @@ var CBINode = Class.extend({
var x = E('div', {}, s); var x = E('div', {}, s);
return x.textContent || x.innerText || ''; 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); return Promise.all(tasks);
}, },
save: function() { save: function(cb) {
this.checkDepends(); this.checkDepends();
return this.parse() return this.parse()
.then(cb)
.then(uci.save.bind(uci)) .then(uci.save.bind(uci))
.then(this.load.bind(this)) .then(this.load.bind(this))
.then(this.renderContents.bind(this)) .then(this.renderContents.bind(this))
@ -156,7 +174,10 @@ var CBIMap = CBINode.extend({
if (this.description != null && this.description != '') if (this.description != null && this.description != '')
mapEl.appendChild(E('div', { 'class': 'cbi-map-descr' }, 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) { if (!initialRender) {
mapEl.classList.remove('flash'); mapEl.classList.remove('flash');
@ -168,17 +189,22 @@ var CBIMap = CBINode.extend({
this.checkDepends(); 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; return mapEl;
}, this)); }, this));
}, },
lookupOption: function(name, section_id) { lookupOption: function(name, section_id, config_name) {
var id, elem, sid, inst; var id, elem, sid, inst;
if (name.indexOf('.') > -1) if (name.indexOf('.') > -1)
id = 'cbid.%s'.format(name); id = 'cbid.%s'.format(name);
else 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); elem = this.findElement('data-field', id);
sid = elem ? id.split(/\./)[2] : null; sid = elem ? id.split(/\./)[2] : null;
@ -437,7 +463,11 @@ var CBIAbstractValue = CBINode.extend({
else if (k.indexOf('.') !== -1) else if (k.indexOf('.') !== -1)
dep['cbid.%s'.format(k)] = list[i][k]; dep['cbid.%s'.format(k)] = list[i][k];
else 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; istat = false;
} }
else { 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; val = res ? res[0].formvalue(res[1]) : null;
istat = (istat && isEqual(val, this.deps[i][dep])); istat = (istat && isEqual(val, this.deps[i][dep]));
@ -502,7 +533,9 @@ var CBIAbstractValue = CBINode.extend({
if (section_id == null) if (section_id == null)
L.error('TypeError', 'Section ID required'); 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) { load: function(section_id) {
@ -510,7 +543,7 @@ var CBIAbstractValue = CBINode.extend({
L.error('TypeError', 'Section ID required'); L.error('TypeError', 'Section ID required');
return uci.get( return uci.get(
this.uciconfig || this.map.config, this.uciconfig || this.section.uciconfig || this.map.config,
this.ucisection || section_id, this.ucisection || section_id,
this.ucioption || this.option); this.ucioption || this.option);
}, },
@ -598,7 +631,7 @@ var CBIAbstractValue = CBINode.extend({
write: function(section_id, formvalue) { write: function(section_id, formvalue) {
return uci.set( return uci.set(
this.uciconfig || this.map.config, this.uciconfig || this.section.uciconfig || this.map.config,
this.ucisection || section_id, this.ucisection || section_id,
this.ucioption || this.option, this.ucioption || this.option,
formvalue); formvalue);
@ -606,7 +639,7 @@ var CBIAbstractValue = CBINode.extend({
remove: function(section_id) { remove: function(section_id) {
return uci.unset( return uci.unset(
this.uciconfig || this.map.config, this.uciconfig || this.section.uciconfig || this.map.config,
this.ucisection || section_id, this.ucisection || section_id,
this.ucioption || this.option); this.ucioption || this.option);
} }
@ -640,7 +673,8 @@ var CBITypedSection = CBIAbstractSection.extend({
return E([]); return E([]);
var createEl = E('div', { 'class': 'cbi-section-create' }), 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) if (extra_class != null)
createEl.classList.add(extra_class); createEl.classList.add(extra_class);
@ -649,8 +683,8 @@ var CBITypedSection = CBIAbstractSection.extend({
createEl.appendChild(E('input', { createEl.appendChild(E('input', {
'type': 'submit', 'type': 'submit',
'class': 'cbi-button cbi-button-add', 'class': 'cbi-button cbi-button-add',
'value': _('Add'), 'value': btn_title || _('Add'),
'title': _('Add'), 'title': btn_title || _('Add'),
'click': L.bind(this.handleAdd, this) 'click': L.bind(this.handleAdd, this)
})); }));
} }
@ -665,8 +699,8 @@ var CBITypedSection = CBIAbstractSection.extend({
E('input', { E('input', {
'class': 'cbi-button cbi-button-add', 'class': 'cbi-button cbi-button-add',
'type': 'submit', 'type': 'submit',
'value': _('Add'), 'value': btn_title || _('Add'),
'title': _('Add'), 'title': btn_title || _('Add'),
'click': L.bind(function(ev) { 'click': L.bind(function(ev) {
if (nameEl.classList.contains('cbi-input-invalid')) if (nameEl.classList.contains('cbi-input-invalid'))
return; return;
@ -682,12 +716,21 @@ var CBITypedSection = CBIAbstractSection.extend({
return createEl; return createEl;
}, },
renderSectionPlaceholder: function() {
return E([
E('em', _('This section contains no values yet')),
E('br'), E('br')
]);
},
renderContents: function(cfgsections, nodes) { renderContents: function(cfgsections, nodes) {
var section_id = null, var section_id = null,
config_name = this.uciconfig || this.map.config, config_name = this.uciconfig || this.map.config,
sectionEl = E('div', { sectionEl = E('div', {
'id': 'cbi-%s-%s'.format(config_name, this.sectiontype), '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 != '') if (this.title != null && this.title != '')
@ -716,18 +759,13 @@ var CBITypedSection = CBIAbstractSection.extend({
sectionEl.appendChild(E('div', { sectionEl.appendChild(E('div', {
'id': 'cbi-%s-%s'.format(config_name, cfgsections[i]), 'id': 'cbi-%s-%s'.format(config_name, cfgsections[i]),
'class': this.tabs '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])); }, nodes[i]));
if (this.tabs)
ui.tabs.initTabGroup(sectionEl.lastChild.childNodes);
} }
if (nodes.length == 0) if (nodes.length == 0)
L.dom.append(sectionEl, [ sectionEl.appendChild(this.renderSectionPlaceholder());
E('em', _('This section contains no values yet')),
E('br'), E('br')
]);
sectionEl.appendChild(this.renderSectionAdd()); sectionEl.appendChild(this.renderSectionAdd());
@ -761,7 +799,9 @@ var CBITableSection = CBITypedSection.extend({
has_more = max_cols < this.children.length, has_more = max_cols < this.children.length,
sectionEl = E('div', { sectionEl = E('div', {
'id': 'cbi-%s-%s'.format(config_name, this.sectiontype), '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', { tableEl = E('div', {
'class': 'table cbi-section-table' 'class': 'table cbi-section-table'
@ -776,8 +816,7 @@ var CBITableSection = CBITypedSection.extend({
tableEl.appendChild(this.renderHeaderRows(max_cols)); tableEl.appendChild(this.renderHeaderRows(max_cols));
for (var i = 0; i < nodes.length; i++) { for (var i = 0; i < nodes.length; i++) {
var sectionname = this.stripTags((typeof(this.sectiontitle) == 'function') var sectionname = this.titleFn('sectiontitle', cfgsections[i]);
? String(this.sectiontitle(cfgsections[i]) || '') : cfgsections[i]).trim();
var trEl = E('div', { var trEl = E('div', {
'id': 'cbi-%s-%s'.format(config_name, cfgsections[i]), '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, 'dragleave': this.sortable ? L.bind(this.handleDragLeave, this) : null,
'dragend': this.sortable ? L.bind(this.handleDragEnd, this) : null, 'dragend': this.sortable ? L.bind(this.handleDragEnd, this) : null,
'drop': this.sortable ? L.bind(this.handleDrop, 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) if (this.extedit || this.rowcolors)
@ -954,11 +994,13 @@ var CBITableSection = CBITypedSection.extend({
} }
if (this.addremove) { if (this.addremove) {
var btn_title = this.titleFn('removebtntitle', section_id);
L.dom.append(tdEl.lastElementChild, L.dom.append(tdEl.lastElementChild,
E('input', { E('input', {
'type': 'submit', 'type': 'submit',
'value': _('Delete'), 'value': btn_title || _('Delete'),
'title': _('Delete'), 'title': btn_title || _('Delete'),
'class': 'cbi-button cbi-button-remove', 'class': 'cbi-button cbi-button-remove',
'click': L.bind(function(sid, ev) { 'click': L.bind(function(sid, ev) {
uci.remove(config_name, sid); uci.remove(config_name, sid);
@ -1077,16 +1119,16 @@ var CBITableSection = CBITypedSection.extend({
name = null, name = null,
m = new CBIMap(this.map.config, null, null), m = new CBIMap(this.map.config, null, null),
s = m.section(CBINamedSection, section_id, this.sectiontype); s = m.section(CBINamedSection, section_id, this.sectiontype);
s.tabs = this.tabs;
s.tab_names = this.tab_names;
if (typeof(this.sectiontitle) == 'function') s.tabs = this.tabs;
name = this.stripTags(String(this.sectiontitle(section_id) || '')); 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) else if (!this.anonymous)
name = section_id; title = '%s - %s'.format(parent.title, section_id);
if (name)
title += ' - ' + name;
for (var i = 0; i < this.children.length; i++) { for (var i = 0; i < this.children.length; i++) {
var o1 = this.children[i]; var o1 = this.children[i];
@ -1258,7 +1300,9 @@ var CBINamedSection = CBIAbstractSection.extend({
config_name = this.uciconfig || this.map.config, config_name = this.uciconfig || this.map.config,
sectionEl = E('div', { sectionEl = E('div', {
'id': ucidata ? null : 'cbi-%s-%s'.format(config_name, section_id), '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 !== '') if (typeof(this.title) === 'string' && this.title !== '')
@ -1282,11 +1326,9 @@ var CBINamedSection = CBIAbstractSection.extend({
sectionEl.appendChild(E('div', { sectionEl.appendChild(E('div', {
'id': 'cbi-%s-%s'.format(config_name, section_id), 'id': 'cbi-%s-%s'.format(config_name, section_id),
'class': this.tabs '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)); }, nodes));
if (this.tabs)
ui.tabs.initTabGroup(sectionEl.lastChild.childNodes);
} }
else if (this.addremove) { else if (this.addremove) {
sectionEl.appendChild( sectionEl.appendChild(
@ -1332,7 +1374,7 @@ var CBIValue = CBIAbstractValue.extend({
}, },
renderFrame: function(section_id, in_table, option_index, nodes) { 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), depend_list = this.transformDepList(section_id),
optionEl; 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({ var CBIDummyValue = CBIValue.extend({
__name__: 'CBI.DummyValue', __name__: 'CBI.DummyValue',
@ -1583,28 +1648,30 @@ var CBIButtonValue = CBIValue.extend({
__name__: 'CBI.ButtonValue', __name__: 'CBI.ButtonValue',
renderWidget: function(section_id, option_index, cfgvalue) { 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) if (value !== false)
return E([ L.dom.content(outputEl, [
E('input', {
'type': 'hidden',
'id': this.cbid(section_id)
}),
E('input', { E('input', {
'class': 'cbi-button cbi-button-%s'.format(this.inputstyle || 'button'), 'class': 'cbi-button cbi-button-%s'.format(this.inputstyle || 'button'),
'type': 'submit', 'type': 'button',
//'id': this.cbid(section_id), 'value': btn_title,
//'name': this.cbid(section_id), 'click': L.bind(this.onclick || function(ev) {
'value': this.inputtitle || this.title,
'click': L.bind(function(ev) {
ev.target.previousElementSibling.value = ev.target.value; ev.target.previousElementSibling.value = ev.target.value;
this.map.save(); this.map.save();
}, this) }, this)
}) })
]); ]);
else 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) { checkDepends: function(section_id) {
this.subsection.checkDepends(); this.subsection.checkDepends();
return this.super('checkDepends'); return CBIValue.prototype.checkDepends.apply(this, [ section_id ]);
}, },
write: function() {}, write: function() {},
@ -1669,6 +1736,7 @@ return L.Class.extend({
ListValue: CBIListValue, ListValue: CBIListValue,
Flag: CBIFlagValue, Flag: CBIFlagValue,
MultiValue: CBIMultiValue, MultiValue: CBIMultiValue,
TextValue: CBITextValue,
DummyValue: CBIDummyValue, DummyValue: CBIDummyValue,
Button: CBIButtonValue, Button: CBIButtonValue,
HiddenValue: CBIHiddenValue, HiddenValue: CBIHiddenValue,

View file

@ -559,6 +559,7 @@
domParser = null, domParser = null,
originalCBIInit = null, originalCBIInit = null,
rpcBaseURL = null, rpcBaseURL = null,
sysFeatures = null,
classes = {}; classes = {};
var LuCI = Class.extend({ var LuCI = Class.extend({
@ -797,6 +798,43 @@
return Promise.resolve(rpcBaseURL); 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) { setupDOM: function(res) {
var domEv = res[0], var domEv = res[0],
uiClass = res[1], uiClass = res[1],
@ -828,10 +866,12 @@
throw 'Session expired'; throw 'Session expired';
}); });
return this.probeSystemFeatures().finally(this.initDOM);
},
initDOM: function() {
originalCBIInit(); originalCBIInit();
Poll.start(); Poll.start();
document.dispatchEvent(new CustomEvent('luci-loaded')); document.dispatchEvent(new CustomEvent('luci-loaded'));
}, },

View file

@ -51,18 +51,19 @@ var callNetworkWirelessStatus = rpc.declare({
var callLuciNetdevs = rpc.declare({ var callLuciNetdevs = rpc.declare({
object: 'luci', object: 'luci',
method: 'netdevs' method: 'getNetworkDevices',
expect: { '': {} }
}); });
var callLuciIfaddrs = rpc.declare({ var callLuciIfaddrs = rpc.declare({
object: 'luci', object: 'luci',
method: 'ifaddrs', method: 'getIfaddrs',
expect: { result: [] } expect: { result: [] }
}); });
var callLuciBoardjson = rpc.declare({ var callLuciBoardjson = rpc.declare({
object: 'luci', object: 'luci',
method: 'boardjson' method: 'getBoardJSON'
}); });
var callIwinfoInfo = rpc.declare({ var callIwinfoInfo = rpc.declare({
@ -83,95 +84,104 @@ var callNetworkDeviceStatus = rpc.declare({
expect: { '': {} } expect: { '': {} }
}); });
var _cache = {}, var callGetProtoHandlers = rpc.declare({
object: 'network',
method: 'get_proto_handlers',
expect: { '': {} }
});
var _init = null,
_state = null, _state = null,
_protocols = {}; _protocols = {},
_protospecs = {};
function getWifiState() { function getWifiState(cache) {
if (_cache.wifi == null) return callNetworkWirelessStatus().then(function(state) {
return callNetworkWirelessStatus().then(function(state) { if (!L.isObject(state))
if (!L.isObject(state)) throw !1;
throw !1; return state;
return (_cache.wifi = state); }).catch(function() {
}).catch(function() { return {};
return (_cache.wifi = {}); });
});
return Promise.resolve(_cache.wifi);
} }
function getInterfaceState() { function getInterfaceState(cache) {
if (_cache.interfacedump == null) return callNetworkInterfaceStatus().then(function(state) {
return callNetworkInterfaceStatus().then(function(state) { if (!Array.isArray(state))
if (!Array.isArray(state)) throw !1;
throw !1; return state;
return (_cache.interfacedump = state); }).catch(function() {
}).catch(function() { return [];
return (_cache.interfacedump = []); });
});
return Promise.resolve(_cache.interfacedump);
} }
function getDeviceState() { function getDeviceState(cache) {
if (_cache.devicedump == null) return callNetworkDeviceStatus().then(function(state) {
return callNetworkDeviceStatus().then(function(state) { if (!L.isObject(state))
if (!L.isObject(state)) throw !1;
throw !1; return state;
return (_cache.devicedump = state); }).catch(function() {
}).catch(function() { return {};
return (_cache.devicedump = {}); });
});
return Promise.resolve(_cache.devicedump);
} }
function getIfaddrState() { function getIfaddrState(cache) {
if (_cache.ifaddrs == null) return callLuciIfaddrs().then(function(addrs) {
return callLuciIfaddrs().then(function(addrs) { if (!Array.isArray(addrs))
if (!Array.isArray(addrs)) throw !1;
throw !1; return addrs;
return (_cache.ifaddrs = addrs); }).catch(function() {
}).catch(function() { return [];
return (_cache.ifaddrs = []); });
});
return Promise.resolve(_cache.ifaddrs);
} }
function getNetdevState() { function getNetdevState(cache) {
if (_cache.devices == null) return callLuciNetdevs().then(function(state) {
return callLuciNetdevs().then(function(state) { if (!L.isObject(state))
if (!L.isObject(state)) throw !1;
throw !1; return state;
return (_cache.devices = state); }).catch(function() {
}).catch(function() { return {};
return (_cache.devices = {}); });
});
return Promise.resolve(_cache.devices);
} }
function getBoardState() { function getBoardState(cache) {
if (_cache.board == null) return callLuciBoardjson().then(function(state) {
return callLuciBoardjson().then(function(state) { if (!L.isObject(state))
if (!L.isObject(state)) throw !1;
throw !1; return state;
return (_cache.board = state); }).catch(function() {
}).catch(function() { return {};
return (_cache.board = {}); });
}); }
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) { function getWifiStateBySid(sid) {
var s = uci.get('wireless', sid); var s = uci.get('wireless', sid);
if (s != null && s['.type'] == 'wifi-iface') { if (s != null && s['.type'] == 'wifi-iface') {
for (var radioname in _cache.wifi) { for (var radioname in _state.radios) {
for (var i = 0; i < _cache.wifi[radioname].interfaces.length; i++) { for (var i = 0; i < _state.radios[radioname].interfaces.length; i++) {
var netstate = _cache.wifi[radioname].interfaces[i]; var netstate = _state.radios[radioname].interfaces[i];
if (typeof(netstate.section) != 'string') if (typeof(netstate.section) != 'string')
continue; continue;
@ -179,7 +189,7 @@ function getWifiStateBySid(sid) {
var s2 = uci.get('wireless', netstate.section); var s2 = uci.get('wireless', netstate.section);
if (s2 != null && s['.type'] == s2['.type'] && s['.name'] == s2['.name']) 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) { function getWifiStateByIfname(ifname) {
for (var radioname in _cache.wifi) { for (var radioname in _state.radios) {
for (var i = 0; i < _cache.wifi[radioname].interfaces.length; i++) { for (var i = 0; i < _state.radios[radioname].interfaces.length; i++) {
var netstate = _cache.wifi[radioname].interfaces[i]; var netstate = _state.radios[radioname].interfaces[i];
if (typeof(netstate.ifname) != 'string') if (typeof(netstate.ifname) != 'string')
continue; continue;
if (netstate.ifname == ifname) 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; rv = false;
if (isArray == false) if (isArray == false)
values = String(values || '').split(/\s+/); values = L.toArray(values);
if (values.indexOf(value) == -1) { if (values.indexOf(value) == -1) {
values.push(value); values.push(value);
@ -354,7 +364,7 @@ function removeValue(config, section, option, value) {
rv = false; rv = false;
if (isArray == false) if (isArray == false)
values = String(values || '').split(/\s+/); values = L.toArray(values);
for (var i = values.length - 1; i >= 0; i--) { for (var i = values.length - 1; i >= 0; i--) {
if (values[i] == value) { if (values[i] == value) {
@ -413,17 +423,19 @@ function maskToPrefix(mask, v6) {
return bits; return bits;
} }
function initNetworkState() { function initNetworkState(refresh) {
if (_state == null) if (_state == null || refresh) {
return (_state = Promise.all([ _init = _init || Promise.all([
getInterfaceState(), getDeviceState(), getBoardState(), getInterfaceState(), getDeviceState(), getBoardState(),
getWifiState(), getIfaddrState(), getNetdevState(), getWifiState(), getIfaddrState(), getNetdevState(), getProtocolHandlers(),
uci.load('network'), uci.load('wireless'), uci.load('luci') uci.load('network'), uci.load('wireless'), uci.load('luci')
]).finally(function() { ]).then(function(data) {
var ifaddrs = _cache.ifaddrs, var board = data[2], ifaddrs = data[4], devices = data[5];
devices = _cache.devices, var s = {
board = _cache.board, isTunnel: {}, isBridge: {}, isSwitch: {}, isWifi: {},
s = { isTunnel: {}, isBridge: {}, isSwitch: {}, isWifi: {}, interfaces: {}, bridges: {}, switches: {} }; ifaces: data[0], devices: data[1], radios: data[3],
netdevs: {}, bridges: {}, switches: {}
};
for (var i = 0, a; (a = ifaddrs[i]) != null; i++) { for (var i = 0, a; (a = ifaddrs[i]) != null; i++) {
var name = a.name.replace(/:.+$/, ''); var name = a.name.replace(/:.+$/, '');
@ -432,7 +444,7 @@ function initNetworkState() {
s.isTunnel[name] = true; s.isTunnel[name] = true;
if (s.isTunnel[name] || !(isIgnoredIfname(name) || isVirtualIfname(name))) { if (s.isTunnel[name] || !(isIgnoredIfname(name) || isVirtualIfname(name))) {
s.interfaces[name] = s.interfaces[name] || { s.netdevs[name] = s.netdevs[name] || {
idx: a.ifindex || i, idx: a.ifindex || i,
name: name, name: name,
rawname: a.name, rawname: a.name,
@ -442,15 +454,15 @@ function initNetworkState() {
}; };
if (a.family == 'packet') { if (a.family == 'packet') {
s.interfaces[name].flags = a.flags; s.netdevs[name].flags = a.flags;
s.interfaces[name].stats = a.data; s.netdevs[name].stats = a.data;
s.interfaces[name].macaddr = a.addr; s.netdevs[name].macaddr = a.addr;
} }
else if (a.family == 'inet') { 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') { 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++) { 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) if (subdev == null)
continue; continue;
@ -477,6 +489,16 @@ function initNetworkState() {
} }
s.bridges[devname] = b; 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) { function ifnameOf(obj) {
if (obj instanceof Interface) if (obj instanceof Protocol)
return obj.name(); return obj.getIfname();
else if (obj instanceof Protocol) else if (obj instanceof Device)
return obj.ifname(); return obj.getName();
else if (obj instanceof WifiDevice)
return obj.getName();
else if (obj instanceof WifiNetwork)
return obj.getIfname();
else if (typeof(obj) == 'string') else if (typeof(obj) == 'string')
return obj.replace(/:.+$/, ''); return obj.replace(/:.+$/, '');
@ -580,10 +613,18 @@ function deviceSort(a, b) {
var Network, Protocol, Device, WifiDevice, WifiNetwork; var Network, Protocol, Device, WifiDevice, WifiNetwork;
Network = L.Class.extend({ Network = L.Class.extend({
prefixToMask: prefixToMask,
maskToPrefix: maskToPrefix,
flushCache: function() {
initNetworkState(true);
return _init;
},
getProtocol: function(protoname, netname) { getProtocol: function(protoname, netname) {
var v = _protocols[protoname]; var v = _protocols[protoname];
if (v != null) if (v != null)
return v(netname || '__dummy__'); return new v(netname || '__dummy__');
return null; return null;
}, },
@ -592,18 +633,35 @@ Network = L.Class.extend({
var rv = []; var rv = [];
for (var protoname in _protocols) for (var protoname in _protocols)
rv.push(_protocols[protoname]('__dummy__')); rv.push(new _protocols[protoname]('__dummy__'));
return rv; return rv;
}, },
registerProtocol: function(protoname, methods) { 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) { __init__: function(name) {
this.sid = name; this.sid = name;
}, },
proto: function() { getProtocol: function() {
return protoname; return protoname;
} }
})); }));
@ -620,7 +678,7 @@ Network = L.Class.extend({
registerErrorCode: function(code, message) { registerErrorCode: function(code, message) {
if (typeof(code) == 'string' && if (typeof(code) == 'string' &&
typeof(message) == 'string' && typeof(message) == 'string' &&
proto_errors.hasOwnProperty(code)) { !proto_errors.hasOwnProperty(code)) {
proto_errors[code] = message; proto_errors[code] = message;
return true; return true;
} }
@ -661,9 +719,9 @@ Network = L.Class.extend({
return this.instantiateNetwork(name); return this.instantiateNetwork(name);
} }
else if (name != null) { else if (name != null) {
for (var i = 0; i < _cache.interfacedump.length; i++) for (var i = 0; i < _state.ifaces.length; i++)
if (_cache.interfacedump[i].interface == name) if (_state.ifaces[i].interface == name)
return this.instantiateNetwork(name, _cache.interfacedump[i].proto); return this.instantiateNetwork(name, _state.ifaces[i].proto);
} }
return null; return null;
@ -678,10 +736,10 @@ Network = L.Class.extend({
for (var i = 0; i < uciInterfaces.length; i++) for (var i = 0; i < uciInterfaces.length; i++)
networks[uciInterfaces[i]['.name']] = this.instantiateNetwork(uciInterfaces[i]['.name']); networks[uciInterfaces[i]['.name']] = this.instantiateNetwork(uciInterfaces[i]['.name']);
for (var i = 0; i < _cache.interfacedump.length; i++) for (var i = 0; i < _state.ifaces.length; i++)
if (networks[_cache.interfacedump[i].interface] == null) if (networks[_state.ifaces[i].interface] == null)
networks[_cache.interfacedump[i].interface] = networks[_state.ifaces[i].interface] =
this.instantiateNetwork(_cache.interfacedump[i].interface, _cache.interfacedump[i].proto); this.instantiateNetwork(_state.ifaces[i].interface, _state.ifaces[i].proto);
var rv = []; var rv = [];
@ -795,7 +853,7 @@ Network = L.Class.extend({
if (name == null) if (name == null)
return null; return null;
if (_state.interfaces.hasOwnProperty(name) || isWifiIfname(name)) if (_state.netdevs.hasOwnProperty(name) || isWifiIfname(name))
return this.instantiateDevice(name); return this.instantiateDevice(name);
var netid = getWifiNetidBySid(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)) if (devices.hasOwnProperty(ifname))
continue; continue;
@ -1017,7 +1075,7 @@ Network = L.Class.extend({
var radioname = existingDevice['.name'], var radioname = existingDevice['.name'],
netid = getWifiNetidBySid(sid) || []; 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)); }, this));
}, },
@ -1037,20 +1095,24 @@ Network = L.Class.extend({
return initNetworkState().then(L.bind(function() { return initNetworkState().then(L.bind(function() {
var rv = []; var rv = [];
for (var i = 0; i < _state.interfacedump.length; i++) { for (var i = 0; i < _state.ifaces.length; i++) {
if (!Array.isArray(_state.interfacedump[i].route)) if (!Array.isArray(_state.ifaces[i].route))
continue; continue;
for (var j = 0; j < _state.interfacedump[i].route.length; j++) { for (var j = 0; j < _state.ifaces[i].route.length; j++) {
if (typeof(_state.interfacedump[i].route[j]) != 'object' || if (typeof(_state.ifaces[i].route[j]) != 'object' ||
typeof(_state.interfacedump[i].route[j].target) != 'string' || typeof(_state.ifaces[i].route[j].target) != 'string' ||
typeof(_state.interfacedump[i].route[j].mask) != 'number') typeof(_state.ifaces[i].route[j].mask) != 'number')
continue; continue;
if (_state.interfacedump[i].route[j].table) if (_state.ifaces[i].route[j].table)
continue; 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() { return initNetworkState().then(L.bind(function() {
var rv = []; var rv = [];
for (var i = 0; i < _state.interfacedump.length; i++) { for (var i = 0; i < _state.ifaces.length; i++) {
if (Array.isArray(_state.interfacedump[i]['ipv4-address'])) if (Array.isArray(_state.ifaces[i]['ipv4-address']))
for (var j = 0; j < _state.interfacedump[i]['ipv4-address'].length; j++) for (var j = 0; j < _state.ifaces[i]['ipv4-address'].length; j++)
if (typeof(_state.interfacedump[i]['ipv4-address'][j]) == 'object' && if (typeof(_state.ifaces[i]['ipv4-address'][j]) == 'object' &&
_state.interfacedump[i]['ipv4-address'][j].address == addr) _state.ifaces[i]['ipv4-address'][j].address == addr)
return _state.interfacedump[i]; return _state.ifaces[i];
if (Array.isArray(_state.interfacedump[i]['ipv6-address'])) if (Array.isArray(_state.ifaces[i]['ipv6-address']))
for (var j = 0; j < _state.interfacedump[i]['ipv6-address'].length; j++) for (var j = 0; j < _state.ifaces[i]['ipv6-address'].length; j++)
if (typeof(_state.interfacedump[i]['ipv6-address'][j]) == 'object' && if (typeof(_state.ifaces[i]['ipv6-address'][j]) == 'object' &&
_state.interfacedump[i]['ipv6-address'][j].address == addr) _state.ifaces[i]['ipv6-address'][j].address == addr)
return _state.interfacedump[i]; return _state.ifaces[i];
if (Array.isArray(_state.interfacedump[i]['ipv6-prefix-assignment'])) if (Array.isArray(_state.ifaces[i]['ipv6-prefix-assignment']))
for (var j = 0; j < _state.interfacedump[i]['ipv6-prefix-assignment'].length; j++) for (var j = 0; j < _state.ifaces[i]['ipv6-prefix-assignment'].length; j++)
if (typeof(_state.interfacedump[i]['ipv6-prefix-assignment'][j]) == 'object' && if (typeof(_state.ifaces[i]['ipv6-prefix-assignment'][j]) == 'object' &&
typeof(_state.interfacedump[i]['ipv6-prefix-assignment'][j]['local-address']) == 'object' && typeof(_state.ifaces[i]['ipv6-prefix-assignment'][j]['local-address']) == 'object' &&
_state.interfacedump[i]['ipv6-prefix-assignment'][j]['local-address'].address == addr) _state.ifaces[i]['ipv6-prefix-assignment'][j]['local-address'].address == addr)
return _state.interfacedump[i]; return _state.ifaces[i];
} }
return null; return null;
@ -1135,6 +1197,16 @@ Network = L.Class.extend({
instantiateWifiNetwork: function(sid, radioname, radiostate, netid, netstate, iwinfo) { instantiateWifiNetwork: function(sid, radioname, radiostate, netid, netstate, iwinfo) {
return new WifiNetwork(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) { _ubus: function(field) {
for (var i = 0; i < _cache.interfacedump.length; i++) { for (var i = 0; i < _state.ifaces.length; i++) {
if (_cache.interfacedump[i].interface != this.sid) if (_state.ifaces[i].interface != this.sid)
continue; 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()) if (this.isFloating())
ifname = this._ubus('l3_device'); ifname = this._ubus('l3_device');
else else
ifname = this._ubus('device'); ifname = this._ubus('device') || this._ubus('l3_device');
if (ifname != null) if (ifname != null)
return ifname; return ifname;
@ -1185,7 +1257,7 @@ Protocol = L.Class.extend({
}, },
getProtocol: function() { getProtocol: function() {
return 'none'; return null;
}, },
getI18n: function() { getI18n: function() {
@ -1455,11 +1527,6 @@ Protocol = L.Class.extend({
return new Device(ifname, this); return new Device(ifname, this);
} }
else { 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')); var ifnames = L.toArray(uci.get('network', this.sid, 'ifname'));
for (var i = 0; i < ifnames.length; i++) { 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() { getDevices: function() {
var rv = []; var rv = [];
@ -1559,12 +1636,12 @@ Device = L.Class.extend({
} }
this.ifname = this.ifname || ifname; this.ifname = this.ifname || ifname;
this.dev = _state.interfaces[this.ifname]; this.dev = _state.netdevs[this.ifname];
this.network = network; this.network = network;
}, },
_ubus: function(field) { _ubus: function(field) {
var dump = _cache.devicedump[this.ifname] || {}; var dump = _state.devices[this.ifname] || {};
return (field != null ? dump[field] : dump); return (field != null ? dump[field] : dump);
}, },
@ -1574,7 +1651,15 @@ Device = L.Class.extend({
}, },
getMAC: function() { 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() { getIPAddrs: function() {
@ -1655,7 +1740,9 @@ Device = L.Class.extend({
return null; return null;
for (var i = 0; i < br.ifnames.length; i++) 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; return rv;
}, },
@ -1786,8 +1873,8 @@ WifiDevice = L.Class.extend({
}, },
isUp: function() { isUp: function() {
if (L.isObject(_cache.wifi[this.sid])) if (L.isObject(_state.radios[this.sid]))
return (_cache.wifi[this.sid].up == true); return (_state.radios[this.sid].up == true);
return false; return false;
}, },

View file

@ -358,7 +358,7 @@ var CBINetworkSelect = form.ListValue.extend({
var network = this.networks[i], var network = this.networks[i],
name = network.getName(); name = network.getName();
if (name == 'loopback' || !this.filter(section_id, name)) if (name == 'loopback' || name == this.exclude || !this.filter(section_id, name))
continue; continue;
if (this.novirtual && network.isVirtual()) if (this.novirtual && network.isVirtual())
@ -420,8 +420,12 @@ var CBIDeviceSelect = form.ListValue.extend({
__name__: 'CBI.DeviceSelect', __name__: 'CBI.DeviceSelect',
load: function(section_id) { load: function(section_id) {
return network.getDevices().then(L.bind(function(devices) { return Promise.all([
this.devices = devices; 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); return this.super('load', section_id);
}, this)); }, this));
@ -483,6 +487,35 @@ var CBIDeviceSelect = form.ListValue.extend({
order.push(name); 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) { if (!this.nocreate) {
var keys = Object.keys(checked).sort(); var keys = Object.keys(checked).sort();

View file

@ -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({ var UICheckbox = UIElement.extend({
__init__: function(value, options) { __init__: function(value, options) {
this.value = value; this.value = value;
@ -207,7 +262,7 @@ var UICheckbox = UIElement.extend({
var UISelect = UIElement.extend({ var UISelect = UIElement.extend({
__init__: function(value, choices, options) { __init__: function(value, choices, options) {
if (typeof(choices) != 'object') if (!L.isObject(choices))
choices = {}; choices = {};
if (!Array.isArray(value)) if (!Array.isArray(value))
@ -1199,7 +1254,7 @@ var UIDynamicList = UIElement.extend({
'name': this.options.name, 'name': this.options.name,
'value': value })]); 'value': value })]);
dl.querySelectorAll('.item, .add-item').forEach(function(item) { dl.querySelectorAll('.item').forEach(function(item) {
if (exists) if (exists)
return; return;
@ -1210,10 +1265,13 @@ var UIDynamicList = UIElement.extend({
if (hidden && hidden.value === value) if (hidden && hidden.value === value)
exists = true; 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', { dl.dispatchEvent(new CustomEvent('cbi-dynlist-change', {
bubbles: true, bubbles: true,
detail: { detail: {
@ -1553,9 +1611,6 @@ return L.Class.extend({
document.addEventListener('dependency-update', this.updateTabs.bind(this)); document.addEventListener('dependency-update', this.updateTabs.bind(this));
this.updateTabs(); this.updateTabs();
if (!groups.length)
this.setActiveTabId(-1, -1);
}, },
initTabGroup: function(panes) { initTabGroup: function(panes) {
@ -1573,6 +1628,7 @@ return L.Class.extend({
active = pane.getAttribute('data-tab-active') === 'true'; active = pane.getAttribute('data-tab-active') === 'true';
menu.appendChild(E('li', { menu.appendChild(E('li', {
'style': L.dom.isEmpty(pane) ? 'display:none' : null,
'class': active ? 'cbi-tab' : 'cbi-tab-disabled', 'class': active ? 'cbi-tab' : 'cbi-tab-disabled',
'data-tab': name 'data-tab': name
}, E('a', { }, E('a', {
@ -1587,7 +1643,7 @@ return L.Class.extend({
group.parentNode.insertBefore(menu, group); group.parentNode.insertBefore(menu, group);
if (selected === null) { if (selected === null) {
selected = this.getActiveTabId(groupId); selected = this.getActiveTabId(panes[0]);
if (selected < 0 || selected >= panes.length || L.dom.isEmpty(panes[selected])) { if (selected < 0 || selected >= panes.length || L.dom.isEmpty(panes[selected])) {
for (var i = 0; i < panes.length; i++) { for (var i = 0; i < panes.length; i++) {
@ -1602,32 +1658,51 @@ return L.Class.extend({
menu.childNodes[selected].classList.remove('cbi-tab-disabled'); menu.childNodes[selected].classList.remove('cbi-tab-disabled');
panes[selected].setAttribute('data-tab-active', 'true'); 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() { getActiveTabState: function() {
var page = document.body.getAttribute('data-page'); var page = document.body.getAttribute('data-page');
try { try {
var val = JSON.parse(window.sessionStorage.getItem('tab')); 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; return val;
} }
catch(e) {} catch(e) {}
window.sessionStorage.removeItem('tab'); window.sessionStorage.removeItem('tab');
return { page: page, groups: [] }; return { page: page, paths: {} };
}, },
getActiveTabId: function(groupId) { getActiveTabId: function(pane) {
return +this.getActiveTabState().groups[groupId] || 0; 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 { try {
var state = this.getActiveTabState(); var state = this.getActiveTabState();
state.groups[groupId] = tabIndex; state.paths[path] = tabIndex;
window.sessionStorage.setItem('tab', JSON.stringify(state)); window.sessionStorage.setItem('tab', JSON.stringify(state));
} }
@ -1639,9 +1714,12 @@ return L.Class.extend({
updateTabs: function(ev, root) { updateTabs: function(ev, root) {
(root || document).querySelectorAll('[data-tab-title]').forEach(function(pane) { (root || document).querySelectorAll('[data-tab-title]').forEach(function(pane) {
var menu = pane.parentNode.previousElementSibling, 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; n_errors = pane.querySelectorAll('.cbi-input-invalid').length;
if (!menu || !tab)
return;
if (L.dom.isEmpty(pane)) { if (L.dom.isEmpty(pane)) {
tab.style.display = 'none'; tab.style.display = 'none';
tab.classList.remove('flash'); tab.classList.remove('flash');
@ -1687,7 +1765,7 @@ return L.Class.extend({
if (L.dom.matches(pane, '[data-tab]')) { if (L.dom.matches(pane, '[data-tab]')) {
if (pane.getAttribute('data-tab') === name) { if (pane.getAttribute('data-tab') === name) {
pane.setAttribute('data-tab-active', 'true'); pane.setAttribute('data-tab-active', 'true');
L.ui.tabs.setActiveTabId(groupId, index); L.ui.tabs.setActiveTabId(pane, index);
} }
else { else {
pane.setAttribute('data-tab-active', 'false'); pane.setAttribute('data-tab-active', 'false');
@ -2058,8 +2136,32 @@ return L.Class.extend({
catch (e) { } 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 */ /* Widgets */
Textfield: UITextfield, Textfield: UITextfield,
Textarea: UITextarea,
Checkbox: UICheckbox, Checkbox: UICheckbox,
Select: UISelect, Select: UISelect,
Dropdown: UIDropdown, Dropdown: UIDropdown,

View file

@ -9,7 +9,7 @@ local function readfile(path)
end end
local methods = { local methods = {
initList = { getInitList = {
args = { name = "name" }, args = { name = "name" },
call = function(args) call = function(args)
local sys = require "luci.sys" local sys = require "luci.sys"
@ -22,11 +22,11 @@ local methods = {
return { error = "No such init script" } return { error = "No such init script" }
end end
end end
return { result = scripts } return scripts
end end
}, },
initCall = { setInitAction = {
args = { name = "name", action = "action" }, args = { name = "name", action = "action" },
call = function(args) call = function(args)
local sys = require "luci.sys" local sys = require "luci.sys"
@ -39,7 +39,7 @@ local methods = {
getLocaltime = { getLocaltime = {
call = function(args) call = function(args)
return { localtime = os.time() } return { result = os.time() }
end 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("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") sys.call("/etc/init.d/sysfixtime restart >/dev/null")
end end
return { localtime = args.localtime } return { result = args.localtime }
end end
}, },
timezone = { getTimezones = {
call = function(args) call = function(args)
local util = require "luci.util" local util = require "luci.util"
local zones = require "luci.sys.zoneinfo" local zones = require "luci.sys.zoneinfo"
@ -76,11 +76,11 @@ local methods = {
active = (res and res.value == zone[1]) and true or nil active = (res and res.value == zone[1]) and true or nil
} }
end end
return { result = result } return result
end end
}, },
leds = { getLEDs = {
call = function() call = function()
local iter = fs.dir("/sys/class/leds") local iter = fs.dir("/sys/class/leds")
local result = { } local result = { }
@ -115,7 +115,7 @@ local methods = {
end end
}, },
usb = { getUSBDevices = {
call = function() call = function()
local fs = require "nixio.fs" local fs = require "nixio.fs"
local iter = fs.glob("/sys/bus/usb/devices/[0-9]*/manufacturer") local iter = fs.glob("/sys/bus/usb/devices/[0-9]*/manufacturer")
@ -126,7 +126,7 @@ local methods = {
local p local p
for p in iter do for p in iter do
local id = p:match("%d+-%d+") local id = p:match("/([^/]+)/manufacturer$")
result.devices[#result.devices+1] = { result.devices[#result.devices+1] = {
id = id, id = id,
@ -139,18 +139,19 @@ local methods = {
end end
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 if iter then
result.ports = {} result.ports = {}
local p local p
for p in iter do 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] = { result.ports[#result.ports+1] = {
hub = tonumber(bus), port = port,
port = tonumber(port) device = link and fs.basename(link)
} }
end end
end end
@ -159,20 +160,20 @@ local methods = {
end end
}, },
ifaddrs = { getIfaddrs = {
call = function() call = function()
return { result = nixio.getifaddrs() } return { result = nixio.getifaddrs() }
end end
}, },
host_hints = { getHostHints = {
call = function() call = function()
local sys = require "luci.sys" local sys = require "luci.sys"
return sys.net.host_hints() return sys.net.host_hints()
end end
}, },
duid_hints = { getDUIDHints = {
call = function() call = function()
local fp = io.open('/var/hosts/odhcpd') local fp = io.open('/var/hosts/odhcpd')
local result = { } local result = { }
@ -192,7 +193,7 @@ local methods = {
end end
}, },
leases = { getDHCPLeases = {
args = { family = 0 }, args = { family = 0 },
call = function(args) call = function(args)
local s = require "luci.tools.status" local s = require "luci.tools.status"
@ -210,7 +211,7 @@ local methods = {
end end
}, },
netdevs = { getNetworkDevices = {
call = function(args) call = function(args)
local dir = fs.dir("/sys/class/net") local dir = fs.dir("/sys/class/net")
local result = { } local result = { }
@ -273,52 +274,138 @@ local methods = {
end end
}, },
boardjson = { getBoardJSON = {
call = function(args) call = function(args)
local jsc = require "luci.jsonc" local jsc = require "luci.jsonc"
return jsc.parse(fs.readfile("/etc/board.json") or "") return jsc.parse(fs.readfile("/etc/board.json") or "")
end end
}, },
offload_support = { getConntrackHelpers = {
call = function() call = function()
local fs = require "nixio.fs" local ok, fd = pcall(io.open, "/usr/share/fw3/helpers.conf", "r")
return { offload_support = not not fs.access("/sys/module/xt_FLOWOFFLOAD/refcnt") } 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 end
}, },
getFeatures = {
conntrack_helpers = {
call = function() call = function()
local fd = io.open("/usr/share/fw3/helpers.conf", "r") local fs = require "nixio.fs"
local rv = {} local rv = {}
local ok, fd
local line, entry rv.firewall = fs.access("/sbin/fw3")
while true do rv.opkg = fs.access("/bin/opkg")
line = fd:read("*l") rv.offloading = fs.access("/sys/module/xt_FLOWOFFLOAD/refcnt")
if not line then rv.br2684ctl = fs.access("/usr/sbin/br2684ctl")
break rv.swconfig = fs.access("/sbin/swconfig")
end 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 local wifi_features = { "eap", "11n", "11ac", "11r", "11w", "acs", "sae", "owe", "suiteb192" }
if entry then
rv[#rv+1] = entry if fs.access("/usr/sbin/hostapd") then
end rv.hostapd = {}
entry = {}
else local _, feature
local opt, val = line:match("^%s*option%s+(%S+)%s+(%S.*)$") for _, feature in ipairs(wifi_features) do
if opt and val then rv.hostapd[feature] =
opt = opt:gsub("^'(.+)'$", "%1"):gsub('^"(.+)"$', "%1") (os.execute(string.format("/usr/sbin/hostapd -v%s >/dev/null 2>/dev/null", feature)) == 0)
val = val:gsub("^'(.+)'$", "%1"):gsub('^"(.+)"$', "%1")
entry[opt] = val
end
end end
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 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 end
} }
} }

View file

@ -1,8 +1,54 @@
{ {
"user": "nobody", "unauthenticated": {
"access": { "description": "Allow system feature probing",
"system": { "read": {
"methods": [ "board", "info" ] "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" ]
} }
} }
} }