summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/luci.js147
-rw-r--r--modules/luci-base/luasrc/controller/admin/index.lua112
2 files changed, 227 insertions, 32 deletions
diff --git a/modules/luci-base/htdocs/luci-static/resources/luci.js b/modules/luci-base/htdocs/luci-static/resources/luci.js
index 1653bcfce2..07e8d8c887 100644
--- a/modules/luci-base/htdocs/luci-static/resources/luci.js
+++ b/modules/luci-base/htdocs/luci-static/resources/luci.js
@@ -433,8 +433,6 @@
__init__: function(env) {
Object.assign(this.env, env);
- document.addEventListener('DOMContentLoaded', this.setupDOM.bind(this));
-
document.addEventListener('poll-start', function(ev) {
document.querySelectorAll('[id^="xhr_poll_status"]').forEach(function(e) {
e.style.display = (e.id == 'xhr_poll_status_off') ? 'none' : '';
@@ -447,6 +445,17 @@
});
});
+ var domReady = new Promise(function(resolveFn, rejectFn) {
+ document.addEventListener('DOMContentLoaded', resolveFn);
+ });
+
+ Promise.all([
+ domReady,
+ this.require('ui')
+ ]).then(this.setupDOM.bind(this)).catch(function(error) {
+ alert('LuCI class loading error:\n' + error);
+ });
+
originalCBIInit = window.cbi_init;
window.cbi_init = function() {};
},
@@ -582,38 +591,32 @@
/* DOM setup */
setupDOM: function(ev) {
- Promise.all([
- L.require('ui')
- ]).then(function() {
- Request.addInterceptor(function(res) {
- if (res.status != 403 || res.headers.get('X-LuCI-Login-Required') != 'yes')
- return;
+ Request.addInterceptor(function(res) {
+ if (res.status != 403 || res.headers.get('X-LuCI-Login-Required') != 'yes')
+ return;
+
+ Request.poll.stop();
+
+ L.ui.showModal(_('Session expired'), [
+ E('div', { class: 'alert-message warning' },
+ _('A new login is required since the authentication session expired.')),
+ E('div', { class: 'right' },
+ E('div', {
+ class: 'btn primary',
+ click: function() {
+ var loc = window.location;
+ window.location = loc.protocol + '//' + loc.host + loc.pathname + loc.search;
+ }
+ }, _('To login…')))
+ ]);
- Request.poll.stop();
-
- L.ui.showModal(_('Session expired'), [
- E('div', { class: 'alert-message warning' },
- _('A new login is required since the authentication session expired.')),
- E('div', { class: 'right' },
- E('div', {
- class: 'btn primary',
- click: function() {
- var loc = window.location;
- window.location = loc.protocol + '//' + loc.host + loc.pathname + loc.search;
- }
- }, _('To login…')))
- ]);
-
- L.error('AuthenticationError', 'Session expired');
- });
+ L.error('AuthenticationError', 'Session expired');
+ });
- originalCBIInit();
- Request.poll.start();
+ originalCBIInit();
+ Request.poll.start();
- document.dispatchEvent(new CustomEvent('luci-loaded'));
- }).catch(function(error) {
- alert('LuCI class loading error:\n' + error);
- });
+ document.dispatchEvent(new CustomEvent('luci-loaded'));
},
env: {},
@@ -923,7 +926,87 @@
}),
Class: Class,
- Request: Request
+ Request: Request,
+
+ view: Class.extend({
+ __name__: 'LuCI.View',
+
+ __init__: function() {
+ var mc = document.getElementById('maincontent');
+
+ L.dom.content(mc, E('div', { 'class': 'spinning' }, _('Loading view…')));
+
+ return Promise.resolve(this.load())
+ .then(L.bind(this.render, this))
+ .then(L.bind(function(nodes) {
+ var mc = document.getElementById('maincontent');
+
+ L.dom.content(mc, nodes);
+ L.dom.append(mc, this.addFooter());
+ }, this));
+ },
+
+ load: function() {},
+ render: function() {},
+
+ handleSave: function(ev) {
+ var tasks = [];
+
+ document.getElementById('maincontent')
+ .querySelectorAll('.cbi-map').forEach(function(map) {
+ tasks.push(L.dom.callClassMethod(map, 'save'));
+ });
+
+ return Promise.all(tasks);
+ },
+
+ handleSaveApply: function(ev) {
+ return this.handleSave(ev).then(function() {
+ L.ui.changes.apply(true);
+ });
+ },
+
+ handleReset: function(ev) {
+ var tasks = [];
+
+ document.getElementById('maincontent')
+ .querySelectorAll('.cbi-map').forEach(function(map) {
+ tasks.push(L.dom.callClassMethod(map, 'reset'));
+ });
+
+ return Promise.all(tasks);
+ },
+
+ addFooter: function() {
+ var footer = E([]),
+ mc = document.getElementById('maincontent');
+
+ if (mc.querySelector('.cbi-map')) {
+ footer.appendChild(E('div', { 'class': 'cbi-page-actions' }, [
+ E('input', {
+ 'class': 'cbi-button cbi-button-apply',
+ 'type': 'button',
+ 'value': _('Save & Apply'),
+ 'click': L.bind(this.handleSaveApply, this)
+ }), ' ',
+ E('input', {
+ 'class': 'cbi-button cbi-button-save',
+ 'type': 'submit',
+ 'value': _('Save'),
+ 'click': L.bind(this.handleSave, this)
+ }), ' ',
+ E('input', {
+ 'class': 'cbi-button cbi-button-reset',
+ 'type': 'button',
+ 'value': _('Reset'),
+ 'click': L.bind(this.handleReset, this)
+ })
+ ]));
+ }
+
+ return footer;
+ }
+ })
});
XHR = Class.extend({
diff --git a/modules/luci-base/luasrc/controller/admin/index.lua b/modules/luci-base/luasrc/controller/admin/index.lua
index 1f7db0cb38..259c34eee8 100644
--- a/modules/luci-base/luasrc/controller/admin/index.lua
+++ b/modules/luci-base/luasrc/controller/admin/index.lua
@@ -88,6 +88,9 @@ function index()
page = entry({"admin", "translations"}, call("action_translations"), nil)
page.leaf = true
+ page = entry({"admin", "ubus"}, call("action_ubus"), nil)
+ page.leaf = true
+
-- Logout is last
entry({"admin", "logout"}, call("action_logout"), _("Logout"), 999)
end
@@ -129,6 +132,115 @@ function action_translations(lang)
http.write_json(i18n.dump())
end
+local function ubus_reply(id, data, code, errmsg)
+ local reply = { jsonrpc = "2.0", id = id }
+ if errmsg then
+ reply.error = {
+ code = code,
+ message = errmsg
+ }
+ else
+ reply.result = { code, data }
+ end
+
+ return reply
+end
+
+local ubus_types = {
+ nil,
+ "array",
+ "object",
+ "string",
+ nil, -- INT64
+ "number",
+ nil, -- INT16,
+ "boolean",
+ "double"
+}
+
+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
+ return ubus_reply(req.id, nil, -32600, "Invalid request")
+
+ elseif req.method == "call" then
+ local sid, obj, fun, arg =
+ req.params[1], req.params[2], req.params[3], req.params[4] or {}
+ if type(arg) ~= "table" or arg.ubus_rpc_session ~= nil then
+ return ubus_reply(req.id, nil, -32602, "Invalid parameters")
+ end
+
+ if sid == "00000000000000000000000000000000" then
+ sid = luci.dispatcher.context.authsession
+ end
+
+ arg.ubus_rpc_session = sid
+
+ local res, code = luci.util.ubus(obj, fun, arg)
+ return ubus_reply(req.id, res, code or 0)
+
+ elseif req.method == "list" then
+ if type(params) ~= "table" or #params == 0 then
+ local objs = { luci.util.ubus() }
+ return ubus_reply(req.id, objs, 0)
+ else
+ local n, rv = nil, {}
+ for n = 1, #params do
+ if type(params[n]) ~= "string" then
+ return ubus_reply(req.id, nil, -32602, "Invalid parameters")
+ end
+
+ local sig = luci.util.ubus(params[n])
+ if sig and type(sig) == "table" then
+ rv[params[n]] = {}
+
+ local m, p
+ for m, p in pairs(sig) do
+ if type(p) == "table" then
+ rv[params[n]][m] = {}
+
+ local pn, pt
+ for pn, pt in pairs(p) do
+ rv[params[n]][m][pn] = ubus_types[pt] or "unknown"
+ end
+ end
+ end
+ end
+ end
+ return ubus_reply(req.id, rv, 0)
+ end
+ end
+
+ return ubus_reply(req.id, nil, -32601, "Method not found")
+end
+
+function action_ubus()
+ local parser = require "luci.jsonc".new()
+ luci.http.context.request:setfilehandler(function(_, s) parser:parse(s or "") end)
+ luci.http.context.request:content()
+
+ local json = parser:get()
+ if json == nil or type(json) ~= "table" then
+ luci.http.prepare_content("application/json")
+ luci.http.write_json(ubus_reply(nil, nil, -32700, "Parse error"))
+ return
+ end
+
+ local response
+ if #json == 0 then
+ response = ubus_request(json)
+ else
+ response = {}
+
+ local _, request
+ for _, request in ipairs(json) do
+ response[_] = ubus_request(request)
+ end
+ end
+
+ luci.http.prepare_content("application/json")
+ luci.http.write_json(response)
+end
function lease_status()
local s = require "luci.tools.status"