diff options
Diffstat (limited to 'modules/luci-base/htdocs/luci-static')
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, |