summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/forwards.js3
-rw-r--r--applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/rules.js3
-rw-r--r--applications/luci-app-firewall/htdocs/luci-static/resources/view/firewall/zones.js24
-rw-r--r--applications/luci-app-statistics/luasrc/statistics/rrdtool/definitions/cpufreq.lua4
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/firewall.js2
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/form.js158
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/luci.js44
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/network.js403
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/tools/widgets.js37
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/ui.js80
-rw-r--r--modules/luci-base/luasrc/controller/admin/index.lua18
-rwxr-xr-xmodules/luci-base/root/usr/libexec/rpcd/luci173
-rw-r--r--modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json16
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js9
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/hosts.js3
-rw-r--r--modules/luci-mod-system/htdocs/luci-static/resources/view/system/leds.js34
-rw-r--r--modules/luci-mod-system/htdocs/luci-static/resources/view/system/system.js15
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;