summaryrefslogtreecommitdiffhomepage
path: root/modules/luci-base/htdocs/luci-static
diff options
context:
space:
mode:
Diffstat (limited to 'modules/luci-base/htdocs/luci-static')
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/form.js23
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/fs.js95
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/network.js2
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/protocol/static.js2
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/ui.js104
5 files changed, 196 insertions, 30 deletions
diff --git a/modules/luci-base/htdocs/luci-static/resources/form.js b/modules/luci-base/htdocs/luci-static/resources/form.js
index 1c6f843041..180cd61a6d 100644
--- a/modules/luci-base/htdocs/luci-static/resources/form.js
+++ b/modules/luci-base/htdocs/luci-static/resources/form.js
@@ -765,6 +765,12 @@ var CBIAbstractValue = CBINode.extend({
this.ucioption || this.option);
},
+ getUIElement: function(section_id) {
+ var node = this.map.findElement('id', this.cbid(section_id)),
+ inst = node ? L.dom.findClassInstance(node) : null;
+ return (inst instanceof ui.AbstractElement) ? inst : null;
+ },
+
cfgvalue: function(section_id, set_value) {
if (section_id == null)
L.error('TypeError', 'Section ID required');
@@ -778,8 +784,8 @@ var CBIAbstractValue = CBINode.extend({
},
formvalue: function(section_id) {
- var node = this.map.findElement('id', this.cbid(section_id));
- return node ? L.dom.callClassMethod(node, 'getValue') : null;
+ var elem = this.getUIElement(section_id);
+ return elem ? elem.getValue() : null;
},
textvalue: function(section_id) {
@@ -796,8 +802,8 @@ var CBIAbstractValue = CBINode.extend({
},
isValid: function(section_id) {
- var node = this.map.findElement('id', this.cbid(section_id));
- return node ? L.dom.callClassMethod(node, 'isValid') : true;
+ var elem = this.getUIElement(section_id);
+ return elem ? elem.isValid() : true;
},
isActive: function(section_id) {
@@ -817,8 +823,8 @@ var CBIAbstractValue = CBINode.extend({
},
triggerValidation: function(section_id) {
- var node = this.map.findElement('id', this.cbid(section_id));
- return node ? L.dom.callClassMethod(node, 'triggerValidation') : true;
+ var elem = this.getUIElement(section_id);
+ return elem ? elem.triggerValidation() : true;
},
parse: function(section_id) {
@@ -1742,9 +1748,8 @@ var CBIFlagValue = CBIValue.extend({
},
formvalue: function(section_id) {
- var node = this.map.findElement('id', this.cbid(section_id)),
- checked = node ? L.dom.callClassMethod(node, 'isChecked') : false;
-
+ var elem = this.getUIElement(section_id),
+ checked = elem ? elem.isChecked() : false;
return checked ? this.enabled : this.disabled;
},
diff --git a/modules/luci-base/htdocs/luci-static/resources/fs.js b/modules/luci-base/htdocs/luci-static/resources/fs.js
index 8a96ea87e2..e1bf4f874a 100644
--- a/modules/luci-base/htdocs/luci-static/resources/fs.js
+++ b/modules/luci-base/htdocs/luci-static/resources/fs.js
@@ -108,6 +108,31 @@ function handleRpcReply(expect, rc) {
return rc;
}
+function handleCgiIoReply(res) {
+ if (!res.ok || res.status != 200) {
+ var e = new Error(res.statusText);
+ switch (res.status) {
+ case 400:
+ e.name = 'InvalidArgumentError';
+ break;
+
+ case 403:
+ e.name = 'PermissionError';
+ break;
+
+ case 404:
+ e.name = 'NotFoundError';
+ break;
+
+ default:
+ e.name = 'Error';
+ }
+ throw e;
+ }
+
+ return res.text();
+}
+
/**
* @class fs
* @memberof LuCI
@@ -293,6 +318,76 @@ var FileSystem = L.Class.extend(/** @lends LuCI.fs.prototype */ {
return lines;
});
+ },
+
+ /**
+ * Read the contents of the given file and return them, bypassing ubus.
+ *
+ * This function will read the requested file through the cgi-io
+ * helper applet at `/cgi-bin/cgi-download` which bypasses the ubus rpc
+ * transport. This is useful to fetch large file contents which might
+ * exceed the ubus message size limits or which contain binary data.
+ *
+ * The cgi-io helper will enforce the same access permission rules as
+ * the ubus based read call.
+ *
+ * @param {string} path
+ * The file path to read.
+ *
+ * @returns {Promise<string>}
+ * Returns a promise resolving to a string containing the file contents or
+ * rejecting with an error stating the failure reason.
+ */
+ read_direct: function(path) {
+ var postdata = 'sessionid=%s&path=%s'
+ .format(encodeURIComponent(L.env.sessionid), encodeURIComponent(path));
+
+ return L.Request.post('/cgi-bin/cgi-download', postdata, {
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+ }).then(handleCgiIoReply);
+ },
+
+ /**
+ * Execute the specified command, bypassing ubus.
+ *
+ * Note: The `command` must be either the path to an executable,
+ * or a basename without arguments in which case it will be searched
+ * in $PATH. If specified, the values given in `params` will be passed
+ * as arguments to the command.
+ *
+ * This function will invoke the requested commands through the cgi-io
+ * helper applet at `/cgi-bin/cgi-exec` which bypasses the ubus rpc
+ * transport. This is useful to fetch large command outputs which might
+ * exceed the ubus message size limits or which contain binary data.
+ *
+ * The cgi-io helper will enforce the same access permission rules as
+ * the ubus based exec call.
+ *
+ * @param {string} command
+ * The command to invoke.
+ *
+ * @param {string[]} [params]
+ * The arguments to pass to the command.
+ *
+ * @returns {Promise<string>}
+ * Returns a promise resolving to the gathered command stdout output or
+ * rejecting with an error stating the failure reason.
+ */
+ exec_direct: function(command, params) {
+ var cmdstr = String(command)
+ .replace(/\\/g, '\\\\').replace(/(\s)/g, '\\$1');
+
+ if (Array.isArray(params))
+ for (var i = 0; i < params.length; i++)
+ cmdstr += ' ' + String(params[i])
+ .replace(/\\/g, '\\\\').replace(/(\s)/g, '\\$1');
+
+ var postdata = 'sessionid=%s&command=%s'
+ .format(encodeURIComponent(L.env.sessionid), encodeURIComponent(cmdstr));
+
+ return L.Request.post('/cgi-bin/cgi-exec', postdata, {
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+ }).then(handleCgiIoReply);
}
});
diff --git a/modules/luci-base/htdocs/luci-static/resources/network.js b/modules/luci-base/htdocs/luci-static/resources/network.js
index 504f592978..68abb939fc 100644
--- a/modules/luci-base/htdocs/luci-static/resources/network.js
+++ b/modules/luci-base/htdocs/luci-static/resources/network.js
@@ -5,7 +5,7 @@
var proto_errors = {
CONNECT_FAILED: _('Connection attempt failed'),
- INVALID_ADDRESS: _('IP address in invalid'),
+ INVALID_ADDRESS: _('IP address is invalid'),
INVALID_GATEWAY: _('Gateway address is invalid'),
INVALID_LOCAL_ADDRESS: _('Local IP address is invalid'),
MISSING_ADDRESS: _('IP address is missing'),
diff --git a/modules/luci-base/htdocs/luci-static/resources/protocol/static.js b/modules/luci-base/htdocs/luci-static/resources/protocol/static.js
index 9039acd5f3..2d70ae681f 100644
--- a/modules/luci-base/htdocs/luci-static/resources/protocol/static.js
+++ b/modules/luci-base/htdocs/luci-static/resources/protocol/static.js
@@ -195,7 +195,7 @@ return network.registerProtocol('static', {
var n = parseInt(value, 16);
if (!/^(0x)?[0-9a-fA-F]+$/.test(value) || isNaN(n) || n >= 0xffffffff)
- return _('Expecting an hexadecimal assignment hint');
+ return _('Expecting a hexadecimal assignment hint');
return true;
};
diff --git a/modules/luci-base/htdocs/luci-static/resources/ui.js b/modules/luci-base/htdocs/luci-static/resources/ui.js
index 31f89339c4..5fa75c3f66 100644
--- a/modules/luci-base/htdocs/luci-static/resources/ui.js
+++ b/modules/luci-base/htdocs/luci-static/resources/ui.js
@@ -457,9 +457,9 @@ var UIDropdown = UIElement.extend({
'placeholder': this.options.custom_placeholder || this.options.placeholder
});
- if (this.options.datatype)
- L.ui.addValidator(createEl, this.options.datatype,
- true, null, 'blur', 'keyup');
+ if (this.options.datatype || this.options.validate)
+ L.ui.addValidator(createEl, this.options.datatype || 'string',
+ true, this.options.validate, 'blur', 'keyup');
sb.lastElementChild.appendChild(E('li', { 'data-value': '-' }, createEl));
}
@@ -917,6 +917,33 @@ var UIDropdown = UIElement.extend({
}
},
+ createChoiceElement: function(sb, value, label) {
+ var tpl = sb.querySelector(this.options.create_template),
+ markup = null;
+
+ if (tpl)
+ markup = (tpl.textContent || tpl.innerHTML || tpl.firstChild.data).replace(/^<!--|-->$/, '').trim();
+ else
+ markup = '<li data-value="{{value}}"><span data-label-placeholder="true" /></li>';
+
+ var new_item = E(markup.replace(/{{value}}/g, '%h'.format(value))),
+ placeholder = new_item.querySelector('[data-label-placeholder]');
+
+ if (placeholder) {
+ var content = E('span', {}, label || this.choices[value] || [ value ]);
+
+ while (content.firstChild)
+ placeholder.parentNode.insertBefore(content.firstChild, placeholder);
+
+ placeholder.parentNode.removeChild(placeholder);
+ }
+
+ if (this.options.multiple)
+ this.transformItem(sb, new_item);
+
+ return new_item;
+ },
+
createItems: function(sb, value) {
var sbox = this,
val = (value || '').trim(),
@@ -936,20 +963,9 @@ var UIDropdown = UIElement.extend({
});
if (!new_item) {
- var markup,
- tpl = sb.querySelector(sbox.options.create_template);
-
- if (tpl)
- markup = (tpl.textContent || tpl.innerHTML || tpl.firstChild.data).replace(/^<!--|-->$/, '').trim();
- else
- markup = '<li data-value="{{value}}">{{value}}</li>';
-
- new_item = E(markup.replace(/{{value}}/g, '%h'.format(item)));
+ new_item = sbox.createChoiceElement(sb, item);
- if (sbox.options.multiple) {
- sbox.transformItem(sb, new_item);
- }
- else {
+ if (!sbox.options.multiple) {
var old = ul.querySelector('li[created]');
if (old)
ul.removeChild(old);
@@ -965,6 +981,54 @@ var UIDropdown = UIElement.extend({
});
},
+ clearChoices: function(reset_value) {
+ var ul = this.node.querySelector('ul'),
+ lis = ul ? ul.querySelectorAll('li[data-value]') : [],
+ len = lis.length - (this.options.create ? 1 : 0),
+ val = reset_value ? null : this.getValue();
+
+ for (var i = 0; i < len; i++) {
+ var lival = lis[i].getAttribute('data-value');
+ if (val == null ||
+ (!this.options.multiple && val != lival) ||
+ (this.options.multiple && val.indexOf(lival) == -1))
+ ul.removeChild(lis[i]);
+ }
+
+ if (reset_value)
+ this.setValues(this.node, {});
+ },
+
+ addChoices: function(values, labels) {
+ var sb = this.node,
+ ul = sb.querySelector('ul'),
+ lis = ul ? ul.querySelectorAll('li[data-value]') : [];
+
+ if (!Array.isArray(values))
+ values = L.toArray(values);
+
+ if (!L.isObject(labels))
+ labels = {};
+
+ for (var i = 0; i < values.length; i++) {
+ var found = false;
+
+ for (var j = 0; j < lis.length; j++) {
+ if (lis[j].getAttribute('data-value') === values[i]) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found)
+ continue;
+
+ ul.insertBefore(
+ this.createChoiceElement(sb, values[i], labels[values[i]]),
+ ul.lastElementChild);
+ }
+ },
+
closeAllDropdowns: function() {
document.querySelectorAll('.cbi-dropdown[open]').forEach(function(s) {
s.dispatchEvent(new CustomEvent('cbi-dropdown-close', {}));
@@ -1270,9 +1334,9 @@ var UIDynamicList = UIElement.extend({
dl.lastElementChild.appendChild(inputEl);
dl.lastElementChild.appendChild(E('div', { 'class': 'cbi-button cbi-button-add' }, '+'));
- if (this.options.datatype)
- L.ui.addValidator(inputEl, this.options.datatype,
- true, null, 'blur', 'keyup');
+ if (this.options.datatype || this.options.validate)
+ L.ui.addValidator(inputEl, this.options.datatype || 'string',
+ true, this.options.validate, 'blur', 'keyup');
}
for (var i = 0; i < this.values.length; i++)
@@ -2779,6 +2843,8 @@ return L.Class.extend({
}, this.varargs(arguments, 2, ctx));
},
+ AbstractElement: UIElement,
+
/* Widgets */
Textfield: UITextfield,
Textarea: UITextarea,