diff options
17 files changed, 696 insertions, 330 deletions
diff --git a/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/forwards.js b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/forwards.js index 63af69f8a9..80938711e9 100644 --- a/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/forwards.js +++ b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/forwards.js @@ -75,7 +75,8 @@ function forward_via_txt(s) { return L.view.extend({ callHostHints: rpc.declare({ object: 'luci', - method: 'host_hints' + method: 'getHostHints', + expect: { '': {} } }), load: function() { diff --git a/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/rules.js b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/rules.js index a7924b1076..6df3bc7f85 100644 --- a/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/rules.js +++ b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/rules.js @@ -112,7 +112,8 @@ function rule_target_txt(s) { return L.view.extend({ callHostHints: rpc.declare({ object: 'luci', - method: 'host_hints' + method: 'getHostHints', + expect: { '': {} } }), load: function() { diff --git a/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/zones.js b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/zones.js index 4d13752b3a..62b792da1f 100644 --- a/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/zones.js +++ b/applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/zones.js @@ -7,28 +7,22 @@ 'require tools.widgets as widgets'; return L.view.extend({ - callOffloadSupport: rpc.declare({ - object: 'luci', - method: 'offload_support', - expect: { offload_support: false } - }), - callConntrackHelpers: rpc.declare({ object: 'luci', - method: 'conntrack_helpers', - expect: { helpers: [] } + method: 'getConntrackHelpers', + expect: { result: [] } }), load: function() { return Promise.all([ - this.callOffloadSupport(), - this.callConntrackHelpers() + this.callConntrackHelpers(), + firewall.getDefaults() ]); }, render: function(data) { - var hasOffloading = data[0], - ctHelpers = data[1], + var ctHelpers = data[0], + fwDefaults = data[1], m, s, o, inp, out; m = new form.Map('firewall', _('Firewall - Zone Settings'), @@ -55,7 +49,7 @@ return L.view.extend({ /* Netfilter flow offload support */ - if (hasOffloading) { + if (L.hasSystemFeature('offloading')) { s = m.section(form.TypedSection, 'defaults', _('Routing/NAT Offloading'), _('Experimental feature. Not fully compatible with QoS/SQM.')); @@ -126,6 +120,10 @@ return L.view.extend({ p[i].editable = true; } + p[0].default = fwDefaults.getInput(); + p[1].default = fwDefaults.getOutput(); + p[2].default = fwDefaults.getForward(); + o = s.taboption('general', form.Flag, 'masq', _('Masquerading')); o.editable = true; diff --git a/applications/luci-app-statistics/luasrc/statistics/rrdtool/definitions/cpufreq.lua b/applications/luci-app-statistics/luasrc/statistics/rrdtool/definitions/cpufreq.lua index d3596637b2..cb7ae53afa 100644 --- a/applications/luci-app-statistics/luasrc/statistics/rrdtool/definitions/cpufreq.lua +++ b/applications/luci-app-statistics/luasrc/statistics/rrdtool/definitions/cpufreq.lua @@ -48,12 +48,12 @@ function rrdargs( graph, plugin, plugin_instance, dtype ) data = { types = { "percent" }, options = { - percent = { title = "%di Hz", negweight = true }, + percent = { title = "%di kHz", negweight = true }, } } } - return { cpufreq, transitions, percentage } + return { cpufreq, percentage, transitions } else return { cpufreq } end diff --git a/modules/luci-base/htdocs/luci-static/resources/firewall.js b/modules/luci-base/htdocs/luci-static/resources/firewall.js index 9ae14e16d9..8f6be1813d 100644 --- a/modules/luci-base/htdocs/luci-static/resources/firewall.js +++ b/modules/luci-base/htdocs/luci-static/resources/firewall.js @@ -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(), diff --git a/modules/luci-base/htdocs/luci-static/resources/form.js b/modules/luci-base/htdocs/luci-static/resources/form.js index ab0998943c..44a2df22f1 100644 --- a/modules/luci-base/htdocs/luci-static/resources/form.js +++ b/modules/luci-base/htdocs/luci-static/resources/form.js @@ -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) || '')); - else if (!this.anonymous) - name = section_id; + s.tabs = this.tabs; + s.tab_names = this.tab_names; - if (name) - title += ' - ' + name; + 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) + 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; @@ -1583,28 +1625,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 +1689,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() {}, diff --git a/modules/luci-base/htdocs/luci-static/resources/luci.js b/modules/luci-base/htdocs/luci-static/resources/luci.js index 66f32d7223..d72764b114 100644 --- a/modules/luci-base/htdocs/luci-static/resources/luci.js +++ b/modules/luci-base/htdocs/luci-static/resources/luci.js @@ -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'; }); - originalCBIInit(); + return this.probeSystemFeatures().finally(this.initDOM); + }, + initDOM: function() { + originalCBIInit(); Poll.start(); - document.dispatchEvent(new CustomEvent('luci-loaded')); }, diff --git a/modules/luci-base/htdocs/luci-static/resources/network.js b/modules/luci-base/htdocs/luci-static/resources/network.js index d3d9a1cf57..d6a97408a2 100644 --- a/modules/luci-base/htdocs/luci-static/resources/network.js +++ b/modules/luci-base/htdocs/luci-static/resources/network.js @@ -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 = {}, - _state = null, - _protocols = {}; - -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 = {}); - }); +var callGetProtoHandlers = rpc.declare({ + object: 'network', + method: 'get_proto_handlers', + expect: { '': {} } +}); - return Promise.resolve(_cache.wifi); +var _init = null, + _state = null, + _protocols = {}, + _protospecs = {}; + +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 = {}); - }); +function getDeviceState(cache) { + return callNetworkDeviceStatus().then(function(state) { + if (!L.isObject(state)) + throw !1; + return state; + }).catch(function() { + return {}; + }); +} - return Promise.resolve(_cache.devicedump); +function getIfaddrState(cache) { + return callLuciIfaddrs().then(function(addrs) { + if (!Array.isArray(addrs)) + throw !1; + return addrs; + }).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 = []); - }); +function getNetdevState(cache) { + return callLuciNetdevs().then(function(state) { + if (!L.isObject(state)) + throw !1; + return state; + }).catch(function() { + return {}; + }); +} - return Promise.resolve(_cache.ifaddrs); +function getBoardState(cache) { + return callLuciBoardjson().then(function(state) { + if (!L.isObject(state)) + throw !1; + return state; + }).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 = {}); - }); +function getProtocolHandlers(cache) { + return callGetProtoHandlers().then(function(protos) { + if (!L.isObject(protos)) + throw !1; - return Promise.resolve(_cache.devices); -} + Object.assign(_protospecs, protos); -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 = {}); + 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; }); - - return Promise.resolve(_cache.board); + }).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() { } } + if (L.isObject(board.dsl) && L.isObject(board.dsl.modem)) { + s.hasDSLModem = board.dsl.modem; + } + + _init = null; + return (_state = s); - })); + }); + } - return Promise.resolve(_state); + 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; } })); @@ -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]; - - 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.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]; + 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.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.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; }, diff --git a/modules/luci-base/htdocs/luci-static/resources/tools/widgets.js b/modules/luci-base/htdocs/luci-static/resources/tools/widgets.js index 39e5aa1655..2da9e7435f 100644 --- a/modules/luci-base/htdocs/luci-static/resources/tools/widgets.js +++ b/modules/luci-base/htdocs/luci-static/resources/tools/widgets.js @@ -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(); diff --git a/modules/luci-base/htdocs/luci-static/resources/ui.js b/modules/luci-base/htdocs/luci-static/resources/ui.js index 29233dec02..e008681710 100644 --- a/modules/luci-base/htdocs/luci-static/resources/ui.js +++ b/modules/luci-base/htdocs/luci-static/resources/ui.js @@ -207,7 +207,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 +1199,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 +1210,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 +1556,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 +1573,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 +1588,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,8 +1603,24 @@ 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() { @@ -1611,23 +1628,26 @@ return L.Class.extend({ 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 +1659,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 +1710,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,6 +2081,29 @@ 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, Checkbox: UICheckbox, diff --git a/modules/luci-base/luasrc/controller/admin/index.lua b/modules/luci-base/luasrc/controller/admin/index.lua index 3f2b465879..b0427d6c05 100644 --- a/modules/luci-base/luasrc/controller/admin/index.lua +++ b/modules/luci-base/luasrc/controller/admin/index.lua @@ -96,6 +96,7 @@ function index() page.leaf = true page = entry({"admin", "ubus"}, call("action_ubus"), nil) + page.sysauth = false page.leaf = true -- Logout is last @@ -165,6 +166,17 @@ local ubus_types = { "double" } +local function ubus_access(sid, obj, fun) + local res, code = luci.util.ubus("session", "access", { + ubus_rpc_session = sid, + scope = "ubus", + object = obj, + ["function"] = fun + }) + + return (type(res) == "table" and res.access == true) +end + local function ubus_request(req) if type(req) ~= "table" or type(req.method) ~= "string" or type(req.params) ~= "table" or #req.params < 2 or req.jsonrpc ~= "2.0" or req.id == nil then @@ -177,10 +189,14 @@ local function ubus_request(req) return ubus_reply(req.id, nil, -32602, "Invalid parameters") end - if sid == "00000000000000000000000000000000" then + if sid == "00000000000000000000000000000000" and luci.dispatcher.context.authsession then sid = luci.dispatcher.context.authsession end + if not ubus_access(sid, obj, fun) then + return ubus_reply(req.id, nil, -32002, "Access denied") + end + arg.ubus_rpc_session = sid local res, code = luci.util.ubus(obj, fun, arg) diff --git a/modules/luci-base/root/usr/libexec/rpcd/luci b/modules/luci-base/root/usr/libexec/rpcd/luci index 7644745efd..b895013736 100755 --- a/modules/luci-base/root/usr/libexec/rpcd/luci +++ b/modules/luci-base/root/usr/libexec/rpcd/luci @@ -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 + + 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 + + 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 - local line, entry - while true do - line = fd:read("*l") - if not line then - break + 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 - if line:match("^%s*config%s") then - if entry then - rv[#rv+1] = entry + 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 - 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 + + 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 - if entry then - rv[#rv+1] = entry + 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 { helpers = rv } + return rv end } } diff --git a/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json b/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json index de145ce784..5ffcbdc2e6 100644 --- a/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json +++ b/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json @@ -1,4 +1,13 @@ { + "unauthenticated": { + "description": "Allow system feature probing", + "read": { + "ubus": { + "luci": [ "getFeatures" ] + } + } + }, + "uci-access": { "description": "Grant uci write access to all configurations", "read": { @@ -13,17 +22,18 @@ "read": { "ubus": { "iwinfo": [ "info" ], - "luci": [ "boardjson", "duid_hints", "host_hints", "ifaddrs", "initList", "getLocaltime", "leases", "leds", "netdevs", "usb" ], + "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": [ "initCall", "setLocaltime", "timezone" ], + "luci": [ "setInitAction", "setLocaltime" ], "uci": [ "add", "apply", "confirm", "delete", "order", "set" ] }, "uci": [ "*" ] @@ -33,7 +43,7 @@ "description": "Grant access to firewall procedures", "read": { "ubus": { - "luci": [ "conntrack_helpers", "offload_support" ] + "luci": [ "getConntrackHelpers" ] }, "uci": [ "firewall" ] }, diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js index 7035dc4769..1e9c402e0c 100644 --- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js +++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js @@ -7,17 +7,19 @@ var callHostHints, callDUIDHints, callDHCPLeases, CBILeaseStatus; callHostHints = rpc.declare({ object: 'luci', - method: 'host_hints' + method: 'getHostHints', + expect: { '': {} } }); callDUIDHints = rpc.declare({ object: 'luci', - method: 'duid_hints' + method: 'getDUIDHints', + expect: { '': {} } }); callDHCPLeases = rpc.declare({ object: 'luci', - method: 'leases', + method: 'getDHCPLeases', params: [ 'family' ], expect: { dhcp_leases: [] } }); @@ -57,7 +59,6 @@ return L.view.extend({ m, s, o, ss, so; m = new form.Map('dhcp', _('DHCP and DNS'), _('Dnsmasq is a combined <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr>-Server and <abbr title="Domain Name System">DNS</abbr>-Forwarder for <abbr title="Network Address Translation">NAT</abbr> firewalls')); - m.tabbed = true; s = m.section(form.TypedSection, 'dnsmasq', _('Server Settings')); s.anonymous = true; diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/hosts.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/hosts.js index 2a49b04817..3cdea8adbe 100644 --- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/hosts.js +++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/hosts.js @@ -5,7 +5,8 @@ return L.view.extend({ callHostHints: rpc.declare({ object: 'luci', - method: 'host_hints' + method: 'getHostHints', + expect: { '': {} } }), load: function() { diff --git a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/leds.js b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/leds.js index c1109b5d64..a5bda05761 100644 --- a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/leds.js +++ b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/leds.js @@ -3,28 +3,23 @@ 'require rpc'; 'require form'; -var callInitAction, callLeds, callUSB, callNetdevs; - -callInitAction = rpc.declare({ - object: 'luci', - method: 'initCall', - params: [ 'name', 'action' ], - expect: { result: false } -}); +var callLeds, callUSB, callNetdevs; callLeds = rpc.declare({ object: 'luci', - method: 'leds' + method: 'getLEDs', + expect: { '': {} } }); callUSB = rpc.declare({ object: 'luci', - method: 'usb' + method: 'getUSBDevices', + expect: { '': {} } }); callNetdevs = rpc.declare({ object: 'luci', - method: 'ifaddrs', + method: 'getIfaddrs', expect: { result: [] }, filter: function(res) { var devs = {}; @@ -130,16 +125,23 @@ return L.view.extend({ value = String(value || '').split(/\s+/); for (var i = 0; i < value.length; i++) - if (value[i].match(/^usb(\d+)-port(\d+)$/)) - ports.push(value[i]); - else if (value[i].match(/^(\d+)-(\d+)$/)) + if (value[i].match(/^(\d+)-(\d+)$/)) ports.push('usb%d-port%d'.format(Regexp.$1, Regexp.$2)); + else + ports.push(value[i]); return ports; }; usb.ports.forEach(function(usbport) { - o.value('usb%d-port%d'.format(usbport.hub, usbport.port), - 'Hub %d, Port %d'.format(usbport.hub, usbport.port)); + var dev = (usbport.device && Array.isArray(usb.devices)) + ? usb.devices.filter(function(d) { return d.id == usbport.device })[0] : null; + + var label = _('Port %s').format(usbport.port); + + if (dev) + label += ' (%s - %s)'.format(dev.vendor || '?', dev.product || '?'); + + o.value(usbport.port, label); }); } diff --git a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/system.js b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/system.js index 6db973a8df..1ed8f64d8f 100644 --- a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/system.js +++ b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/system.js @@ -8,9 +8,9 @@ var callInitList, callInitAction, callTimezone, callInitList = rpc.declare({ object: 'luci', - method: 'initList', + method: 'getInitList', params: [ 'name' ], - expect: { result: {} }, + expect: { '': {} }, filter: function(res) { for (var k in res) return +res[k].enabled; @@ -20,7 +20,7 @@ callInitList = rpc.declare({ callInitAction = rpc.declare({ object: 'luci', - method: 'initCall', + method: 'setInitAction', params: [ 'name', 'action' ], expect: { result: false } }); @@ -28,20 +28,20 @@ callInitAction = rpc.declare({ callGetLocaltime = rpc.declare({ object: 'luci', method: 'getLocaltime', - expect: { localtime: 0 } + expect: { result: 0 } }); callSetLocaltime = rpc.declare({ object: 'luci', method: 'setLocaltime', params: [ 'localtime' ], - expect: { localtime: 0 } + expect: { result: 0 } }); callTimezone = rpc.declare({ object: 'luci', - method: 'timezone', - expect: { result: {} } + method: 'getTimezones', + expect: { '': {} } }); CBILocalTime = form.DummyValue.extend({ @@ -103,7 +103,6 @@ return L.view.extend({ _('Here you can configure the basic aspects of your device like its hostname or the timezone.')); m.chain('luci'); - m.tabbed = true; s = m.section(form.TypedSection, 'system', _('System Properties')); s.anonymous = true; |