diff options
-rw-r--r-- | modules/luci-base/htdocs/luci-static/resources/luci.js | 147 | ||||
-rw-r--r-- | modules/luci-base/luasrc/controller/admin/index.lua | 112 |
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" |