2015-01-16 22:38:38 +00:00
|
|
|
-- Copyright 2008 Steven Barth <steven@midlink.org>
|
2015-10-06 13:53:35 +00:00
|
|
|
-- Copyright 2008-2015 Jo-Philipp Wich <jow@openwrt.org>
|
2015-01-16 22:38:38 +00:00
|
|
|
-- Licensed to the public under the Apache License 2.0.
|
2008-07-29 20:32:02 +00:00
|
|
|
|
2009-07-19 00:24:58 +00:00
|
|
|
local fs = require "nixio.fs"
|
2008-08-29 23:26:01 +00:00
|
|
|
local sys = require "luci.sys"
|
|
|
|
local util = require "luci.util"
|
|
|
|
local http = require "luci.http"
|
2009-06-21 13:42:26 +00:00
|
|
|
local nixio = require "nixio", require "nixio.util"
|
2008-03-02 21:52:58 +00:00
|
|
|
|
2008-08-29 23:26:01 +00:00
|
|
|
module("luci.dispatcher", package.seeall)
|
2008-11-29 21:58:39 +00:00
|
|
|
context = util.threadlocal()
|
2009-11-10 16:02:48 +00:00
|
|
|
uci = require "luci.model.uci"
|
2009-11-29 13:46:04 +00:00
|
|
|
i18n = require "luci.i18n"
|
2009-11-14 18:41:16 +00:00
|
|
|
_M.fs = fs
|
2008-03-29 18:22:21 +00:00
|
|
|
|
2008-05-26 12:16:16 +00:00
|
|
|
-- Index table
|
2008-06-14 14:12:12 +00:00
|
|
|
local index = nil
|
2008-05-26 12:16:16 +00:00
|
|
|
|
2020-01-30 11:19:27 +00:00
|
|
|
local function check_fs_depends(spec)
|
2019-12-01 19:06:43 +00:00
|
|
|
local fs = require "nixio.fs"
|
|
|
|
|
2020-01-30 11:19:27 +00:00
|
|
|
for path, kind in pairs(spec) do
|
2019-12-01 19:06:43 +00:00
|
|
|
if kind == "directory" then
|
|
|
|
local empty = true
|
|
|
|
for entry in (fs.dir(path) or function() end) do
|
|
|
|
empty = false
|
|
|
|
break
|
|
|
|
end
|
|
|
|
if empty then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
elseif kind == "executable" then
|
|
|
|
if fs.stat(path, "type") ~= "reg" or not fs.access(path, "x") then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
elseif kind == "file" then
|
|
|
|
if fs.stat(path, "type") ~= "reg" then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
local function check_uci_depends_options(conf, s, opts)
|
|
|
|
local uci = require "luci.model.uci"
|
|
|
|
|
|
|
|
if type(opts) == "string" then
|
|
|
|
return (s[".type"] == opts)
|
|
|
|
elseif opts == true then
|
|
|
|
for option, value in pairs(s) do
|
|
|
|
if option:byte(1) ~= 46 then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
elseif type(opts) == "table" then
|
|
|
|
for option, value in pairs(opts) do
|
|
|
|
local sval = s[option]
|
|
|
|
if type(sval) == "table" then
|
|
|
|
local found = false
|
|
|
|
for _, v in ipairs(sval) do
|
|
|
|
if v == value then
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if not found then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
elseif value == true then
|
|
|
|
if sval == nil then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if sval ~= value then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
local function check_uci_depends_section(conf, sect)
|
|
|
|
local uci = require "luci.model.uci"
|
|
|
|
|
|
|
|
for section, options in pairs(sect) do
|
|
|
|
local stype = section:match("^@([A-Za-z0-9_%-]+)$")
|
|
|
|
if stype then
|
|
|
|
local found = false
|
|
|
|
uci:foreach(conf, stype, function(s)
|
|
|
|
if check_uci_depends_options(conf, s, options) then
|
|
|
|
found = true
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
if not found then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
else
|
|
|
|
local s = uci:get_all(conf, section)
|
|
|
|
if not s or not check_uci_depends_options(conf, s, options) then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
local function check_uci_depends(conf)
|
|
|
|
local uci = require "luci.model.uci"
|
|
|
|
|
|
|
|
for config, values in pairs(conf) do
|
|
|
|
if values == true then
|
|
|
|
local found = false
|
|
|
|
uci:foreach(config, nil, function(s)
|
|
|
|
found = true
|
|
|
|
return false
|
|
|
|
end)
|
|
|
|
if not found then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
elseif type(values) == "table" then
|
|
|
|
if not check_uci_depends_section(config, values) then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
local function check_depends(spec)
|
|
|
|
if type(spec.depends) ~= "table" then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2020-02-05 16:42:58 +00:00
|
|
|
if type(spec.depends.fs) == "table" then
|
2019-12-01 19:06:43 +00:00
|
|
|
local satisfied = false
|
|
|
|
local alternatives = (#spec.depends.fs > 0) and spec.depends.fs or { spec.depends.fs }
|
|
|
|
for _, alternative in ipairs(alternatives) do
|
|
|
|
if check_fs_depends(alternative) then
|
|
|
|
satisfied = true
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if not satisfied then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if type(spec.depends.uci) == "table" then
|
|
|
|
local satisfied = false
|
|
|
|
local alternatives = (#spec.depends.uci > 0) and spec.depends.uci or { spec.depends.uci }
|
|
|
|
for _, alternative in ipairs(alternatives) do
|
|
|
|
if check_uci_depends(alternative) then
|
|
|
|
satisfied = true
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if not satisfied then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
local function target_to_json(target, module)
|
|
|
|
local action
|
|
|
|
|
|
|
|
if target.type == "call" then
|
|
|
|
action = {
|
|
|
|
["type"] = "call",
|
|
|
|
["module"] = module,
|
|
|
|
["function"] = target.name,
|
|
|
|
["parameters"] = target.argv
|
|
|
|
}
|
|
|
|
elseif target.type == "view" then
|
|
|
|
action = {
|
|
|
|
["type"] = "view",
|
|
|
|
["path"] = target.view
|
|
|
|
}
|
|
|
|
elseif target.type == "template" then
|
|
|
|
action = {
|
|
|
|
["type"] = "template",
|
|
|
|
["path"] = target.view
|
|
|
|
}
|
|
|
|
elseif target.type == "cbi" then
|
|
|
|
action = {
|
|
|
|
["type"] = "cbi",
|
2020-02-18 11:00:01 +00:00
|
|
|
["path"] = target.model,
|
|
|
|
["config"] = target.config
|
2019-12-01 19:06:43 +00:00
|
|
|
}
|
|
|
|
elseif target.type == "form" then
|
|
|
|
action = {
|
|
|
|
["type"] = "form",
|
|
|
|
["path"] = target.model
|
|
|
|
}
|
|
|
|
elseif target.type == "firstchild" then
|
|
|
|
action = {
|
|
|
|
["type"] = "firstchild"
|
|
|
|
}
|
|
|
|
elseif target.type == "firstnode" then
|
|
|
|
action = {
|
|
|
|
["type"] = "firstchild",
|
|
|
|
["recurse"] = true
|
|
|
|
}
|
|
|
|
elseif target.type == "arcombine" then
|
|
|
|
if type(target.targets) == "table" then
|
|
|
|
action = {
|
|
|
|
["type"] = "arcombine",
|
|
|
|
["targets"] = {
|
|
|
|
target_to_json(target.targets[1], module),
|
|
|
|
target_to_json(target.targets[2], module)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
end
|
|
|
|
elseif target.type == "alias" then
|
|
|
|
action = {
|
|
|
|
["type"] = "alias",
|
|
|
|
["path"] = table.concat(target.req, "/")
|
|
|
|
}
|
|
|
|
elseif target.type == "rewrite" then
|
|
|
|
action = {
|
|
|
|
["type"] = "rewrite",
|
|
|
|
["path"] = table.concat(target.req, "/"),
|
|
|
|
["remove"] = target.n
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
if target.post and action then
|
|
|
|
action.post = target.post
|
|
|
|
end
|
|
|
|
|
|
|
|
return action
|
|
|
|
end
|
|
|
|
|
|
|
|
local function tree_to_json(node, json)
|
|
|
|
local fs = require "nixio.fs"
|
|
|
|
local util = require "luci.util"
|
|
|
|
|
|
|
|
if type(node.nodes) == "table" then
|
|
|
|
for subname, subnode in pairs(node.nodes) do
|
|
|
|
local spec = {
|
|
|
|
title = util.striptags(subnode.title),
|
|
|
|
order = subnode.order
|
|
|
|
}
|
|
|
|
|
|
|
|
if subnode.leaf then
|
|
|
|
spec.wildcard = true
|
|
|
|
end
|
|
|
|
|
|
|
|
if subnode.cors then
|
|
|
|
spec.cors = true
|
|
|
|
end
|
|
|
|
|
|
|
|
if subnode.setuser then
|
|
|
|
spec.setuser = subnode.setuser
|
|
|
|
end
|
|
|
|
|
|
|
|
if subnode.setgroup then
|
|
|
|
spec.setgroup = subnode.setgroup
|
|
|
|
end
|
|
|
|
|
|
|
|
if type(subnode.target) == "table" then
|
|
|
|
spec.action = target_to_json(subnode.target, subnode.module)
|
|
|
|
end
|
|
|
|
|
|
|
|
if type(subnode.file_depends) == "table" then
|
|
|
|
for _, v in ipairs(subnode.file_depends) do
|
|
|
|
spec.depends = spec.depends or {}
|
|
|
|
spec.depends.fs = spec.depends.fs or {}
|
|
|
|
|
|
|
|
local ft = fs.stat(v, "type")
|
|
|
|
if ft == "dir" then
|
|
|
|
spec.depends.fs[v] = "directory"
|
|
|
|
elseif v:match("/s?bin/") then
|
|
|
|
spec.depends.fs[v] = "executable"
|
|
|
|
else
|
|
|
|
spec.depends.fs[v] = "file"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if type(subnode.uci_depends) == "table" then
|
|
|
|
for k, v in pairs(subnode.uci_depends) do
|
|
|
|
spec.depends = spec.depends or {}
|
|
|
|
spec.depends.uci = spec.depends.uci or {}
|
|
|
|
spec.depends.uci[k] = v
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if (subnode.sysauth_authenticator ~= nil) or
|
|
|
|
(subnode.sysauth ~= nil and subnode.sysauth ~= false)
|
|
|
|
then
|
|
|
|
if subnode.sysauth_authenticator == "htmlauth" then
|
|
|
|
spec.auth = {
|
|
|
|
login = true,
|
|
|
|
methods = { "cookie:sysauth" }
|
|
|
|
}
|
|
|
|
elseif subname == "rpc" and subnode.module == "luci.controller.rpc" then
|
|
|
|
spec.auth = {
|
|
|
|
login = false,
|
2020-01-07 08:08:49 +00:00
|
|
|
methods = { "query:auth", "cookie:sysauth" }
|
2019-12-01 19:06:43 +00:00
|
|
|
}
|
|
|
|
elseif subnode.module == "luci.controller.admin.uci" then
|
|
|
|
spec.auth = {
|
|
|
|
login = false,
|
|
|
|
methods = { "param:sid" }
|
|
|
|
}
|
|
|
|
end
|
|
|
|
elseif subnode.sysauth == false then
|
|
|
|
spec.auth = {}
|
|
|
|
end
|
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
if not spec.action then
|
|
|
|
spec.title = nil
|
2019-12-01 19:06:43 +00:00
|
|
|
end
|
2019-12-04 18:19:45 +00:00
|
|
|
|
|
|
|
spec.satisfied = check_depends(spec)
|
|
|
|
json.children = json.children or {}
|
|
|
|
json.children[subname] = tree_to_json(subnode, spec)
|
2019-12-01 19:06:43 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return json
|
|
|
|
end
|
|
|
|
|
2008-05-29 13:51:32 +00:00
|
|
|
function build_url(...)
|
2008-12-15 10:40:45 +00:00
|
|
|
local path = {...}
|
2010-11-13 13:50:54 +00:00
|
|
|
local url = { http.getenv("SCRIPT_NAME") or "" }
|
|
|
|
|
|
|
|
local p
|
|
|
|
for _, p in ipairs(path) do
|
2010-11-13 20:50:20 +00:00
|
|
|
if p:match("^[a-zA-Z0-9_%-%.%%/,;]+$") then
|
2010-11-13 13:50:54 +00:00
|
|
|
url[#url+1] = "/"
|
|
|
|
url[#url+1] = p
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-10-21 14:45:48 +00:00
|
|
|
if #path == 0 then
|
|
|
|
url[#url+1] = "/"
|
|
|
|
end
|
|
|
|
|
2010-11-13 13:50:54 +00:00
|
|
|
return table.concat(url, "")
|
2008-03-02 21:52:58 +00:00
|
|
|
end
|
|
|
|
|
2011-10-26 00:24:17 +00:00
|
|
|
|
2008-03-02 21:52:58 +00:00
|
|
|
function error404(message)
|
2015-01-15 15:32:03 +00:00
|
|
|
http.status(404, "Not Found")
|
2008-03-02 21:52:58 +00:00
|
|
|
message = message or "Not Found"
|
2008-05-24 22:58:45 +00:00
|
|
|
|
2018-04-10 10:03:15 +00:00
|
|
|
local function render()
|
|
|
|
local template = require "luci.template"
|
|
|
|
template.render("error404")
|
|
|
|
end
|
|
|
|
|
|
|
|
if not util.copcall(render) then
|
2015-01-15 15:32:03 +00:00
|
|
|
http.prepare_content("text/plain")
|
|
|
|
http.write(message)
|
2008-03-02 21:52:58 +00:00
|
|
|
end
|
2018-04-10 10:03:15 +00:00
|
|
|
|
2008-05-24 22:58:45 +00:00
|
|
|
return false
|
2008-03-02 21:52:58 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function error500(message)
|
2015-01-15 15:32:03 +00:00
|
|
|
util.perror(message)
|
2009-02-09 13:17:26 +00:00
|
|
|
if not context.template_header_sent then
|
2015-01-15 15:32:03 +00:00
|
|
|
http.status(500, "Internal Server Error")
|
|
|
|
http.prepare_content("text/plain")
|
|
|
|
http.write(message)
|
2009-02-09 13:17:26 +00:00
|
|
|
else
|
|
|
|
require("luci.template")
|
2015-01-15 15:32:03 +00:00
|
|
|
if not util.copcall(luci.template.render, "error500", {message=message}) then
|
|
|
|
http.prepare_content("text/plain")
|
|
|
|
http.write(message)
|
2009-02-09 13:17:26 +00:00
|
|
|
end
|
2008-03-02 21:52:58 +00:00
|
|
|
end
|
2008-05-24 22:58:45 +00:00
|
|
|
return false
|
2008-03-02 21:52:58 +00:00
|
|
|
end
|
|
|
|
|
2019-12-04 18:00:27 +00:00
|
|
|
local function determine_request_language()
|
|
|
|
local conf = require "luci.config"
|
|
|
|
assert(conf.main, "/etc/config/luci seems to be corrupt, unable to find section 'main'")
|
|
|
|
|
|
|
|
local lang = conf.main.lang or "auto"
|
|
|
|
if lang == "auto" then
|
|
|
|
local aclang = http.getenv("HTTP_ACCEPT_LANGUAGE") or ""
|
|
|
|
for aclang in aclang:gmatch("[%w_-]+") do
|
|
|
|
local country, culture = aclang:match("^([a-z][a-z])[_-]([a-zA-Z][a-zA-Z])$")
|
|
|
|
if country and culture then
|
|
|
|
local cc = "%s_%s" %{ country, culture:lower() }
|
|
|
|
if conf.languages[cc] then
|
|
|
|
lang = cc
|
|
|
|
break
|
|
|
|
elseif conf.languages[country] then
|
|
|
|
lang = country
|
|
|
|
break
|
|
|
|
end
|
|
|
|
elseif conf.languages[aclang] then
|
|
|
|
lang = aclang
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if lang == "auto" then
|
|
|
|
lang = i18n.default
|
|
|
|
end
|
|
|
|
|
|
|
|
i18n.setlanguage(lang)
|
|
|
|
end
|
|
|
|
|
2009-07-25 12:45:38 +00:00
|
|
|
function httpdispatch(request, prefix)
|
2015-01-15 15:32:03 +00:00
|
|
|
http.context.request = request
|
2009-07-25 12:45:38 +00:00
|
|
|
|
|
|
|
local r = {}
|
|
|
|
context.request = r
|
2011-10-26 00:24:17 +00:00
|
|
|
|
2009-01-20 19:40:14 +00:00
|
|
|
local pathinfo = http.urldecode(request:getenv("PATH_INFO") or "", true)
|
2008-05-24 22:58:45 +00:00
|
|
|
|
2009-07-25 12:45:38 +00:00
|
|
|
if prefix then
|
|
|
|
for _, node in ipairs(prefix) do
|
|
|
|
r[#r+1] = node
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-04-10 09:38:29 +00:00
|
|
|
local node
|
|
|
|
for node in pathinfo:gmatch("[^/%z]+") do
|
2015-10-20 22:31:27 +00:00
|
|
|
r[#r+1] = node
|
2008-03-02 21:52:58 +00:00
|
|
|
end
|
2008-05-24 22:58:45 +00:00
|
|
|
|
2019-12-04 18:00:27 +00:00
|
|
|
determine_request_language()
|
|
|
|
|
2009-02-26 16:45:01 +00:00
|
|
|
local stat, err = util.coxpcall(function()
|
2009-07-24 15:45:29 +00:00
|
|
|
dispatch(context.request)
|
2009-02-26 16:45:01 +00:00
|
|
|
end, error500)
|
2008-09-23 00:10:51 +00:00
|
|
|
|
2015-01-15 15:32:03 +00:00
|
|
|
http.close()
|
2008-09-05 20:32:20 +00:00
|
|
|
|
|
|
|
--context._disable_memtrace()
|
2008-05-04 20:53:31 +00:00
|
|
|
end
|
|
|
|
|
2019-10-09 07:55:44 +00:00
|
|
|
local function require_post_security(target, args)
|
|
|
|
if type(target) == "table" and target.type == "arcombine" and type(target.targets) == "table" then
|
|
|
|
return require_post_security((type(args) == "table" and #args > 0) and target.targets[2] or target.targets[1], args)
|
|
|
|
end
|
|
|
|
|
2015-10-20 18:58:30 +00:00
|
|
|
if type(target) == "table" then
|
|
|
|
if type(target.post) == "table" then
|
|
|
|
local param_name, required_val, request_val
|
|
|
|
|
|
|
|
for param_name, required_val in pairs(target.post) do
|
|
|
|
request_val = http.formvalue(param_name)
|
|
|
|
|
|
|
|
if (type(required_val) == "string" and
|
|
|
|
request_val ~= required_val) or
|
2018-04-09 05:04:38 +00:00
|
|
|
(required_val == true and request_val == nil)
|
2015-10-20 18:58:30 +00:00
|
|
|
then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
return (target.post == true)
|
|
|
|
end
|
|
|
|
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2015-10-22 06:30:29 +00:00
|
|
|
function test_post_security()
|
|
|
|
if http.getenv("REQUEST_METHOD") ~= "POST" then
|
|
|
|
http.status(405, "Method Not Allowed")
|
|
|
|
http.header("Allow", "POST")
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
if http.formvalue("token") ~= context.authtoken then
|
|
|
|
http.status(403, "Forbidden")
|
|
|
|
luci.template.render("csrftoken")
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2017-07-09 19:26:49 +00:00
|
|
|
local function session_retrieve(sid, allowed_users)
|
|
|
|
local sdat = util.ubus("session", "get", { ubus_rpc_session = sid })
|
|
|
|
|
|
|
|
if type(sdat) == "table" and
|
|
|
|
type(sdat.values) == "table" and
|
|
|
|
type(sdat.values.token) == "string" and
|
2017-07-11 12:12:50 +00:00
|
|
|
(not allowed_users or
|
|
|
|
util.contains(allowed_users, sdat.values.username))
|
2017-07-09 19:26:49 +00:00
|
|
|
then
|
2018-04-24 19:26:01 +00:00
|
|
|
uci:set_session_id(sid)
|
2017-07-09 19:26:49 +00:00
|
|
|
return sid, sdat.values
|
|
|
|
end
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
end
|
|
|
|
|
2017-07-11 12:12:50 +00:00
|
|
|
local function session_setup(user, pass, allowed_users)
|
|
|
|
if util.contains(allowed_users, user) then
|
|
|
|
local login = util.ubus("session", "login", {
|
|
|
|
username = user,
|
|
|
|
password = pass,
|
|
|
|
timeout = tonumber(luci.config.sauth.sessiontime)
|
2017-07-09 19:26:49 +00:00
|
|
|
})
|
|
|
|
|
2018-01-17 17:49:08 +00:00
|
|
|
local rp = context.requestpath
|
|
|
|
and table.concat(context.requestpath, "/") or ""
|
|
|
|
|
2017-07-11 12:12:50 +00:00
|
|
|
if type(login) == "table" and
|
|
|
|
type(login.ubus_rpc_session) == "string"
|
|
|
|
then
|
|
|
|
util.ubus("session", "set", {
|
|
|
|
ubus_rpc_session = login.ubus_rpc_session,
|
|
|
|
values = { token = sys.uniqueid(16) }
|
|
|
|
})
|
|
|
|
|
2018-01-17 17:49:08 +00:00
|
|
|
io.stderr:write("luci: accepted login on /%s for %s from %s\n"
|
|
|
|
%{ rp, user, http.getenv("REMOTE_ADDR") or "?" })
|
|
|
|
|
2017-07-11 12:12:50 +00:00
|
|
|
return session_retrieve(login.ubus_rpc_session)
|
|
|
|
end
|
2018-01-17 17:49:08 +00:00
|
|
|
|
|
|
|
io.stderr:write("luci: failed login on /%s for %s from %s\n"
|
|
|
|
%{ rp, user, http.getenv("REMOTE_ADDR") or "?" })
|
2017-07-09 19:26:49 +00:00
|
|
|
end
|
|
|
|
|
2017-07-11 12:12:50 +00:00
|
|
|
return nil, nil
|
2017-07-09 19:26:49 +00:00
|
|
|
end
|
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
local function check_authentication(method)
|
|
|
|
local auth_type, auth_param = method:match("^(%w+):(.+)$")
|
|
|
|
local sid, sdat
|
|
|
|
|
|
|
|
if auth_type == "cookie" then
|
|
|
|
sid = http.getcookie(auth_param)
|
|
|
|
elseif auth_type == "param" then
|
|
|
|
sid = http.formvalue(auth_param)
|
2020-01-07 08:08:49 +00:00
|
|
|
elseif auth_type == "query" then
|
|
|
|
sid = http.formvalue(auth_param, true)
|
2019-12-04 18:19:45 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
return session_retrieve(sid)
|
|
|
|
end
|
|
|
|
|
|
|
|
local function get_children(node)
|
|
|
|
local children = {}
|
|
|
|
|
|
|
|
if not node.wildcard and type(node.children) == "table" then
|
|
|
|
for name, child in pairs(node.children) do
|
|
|
|
children[#children+1] = {
|
|
|
|
name = name,
|
|
|
|
node = child,
|
|
|
|
order = child.order or 1000
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
table.sort(children, function(a, b)
|
|
|
|
if a.order == b.order then
|
|
|
|
return a.name < b.name
|
|
|
|
else
|
|
|
|
return a.order < b.order
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
return children
|
|
|
|
end
|
|
|
|
|
|
|
|
local function find_subnode(root, prefix, recurse, descended)
|
|
|
|
local children = get_children(root)
|
|
|
|
|
|
|
|
if #children > 0 and (not descended or recurse) then
|
|
|
|
local sub_path = { unpack(prefix) }
|
|
|
|
|
|
|
|
if recurse == false then
|
|
|
|
recurse = nil
|
|
|
|
end
|
|
|
|
|
|
|
|
for _, child in ipairs(children) do
|
|
|
|
sub_path[#prefix+1] = child.name
|
|
|
|
|
|
|
|
local res_path = find_subnode(child.node, sub_path, recurse, true)
|
|
|
|
|
|
|
|
if res_path then
|
|
|
|
return res_path
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if descended then
|
|
|
|
if not recurse or
|
|
|
|
root.action.type == "cbi" or
|
|
|
|
root.action.type == "form" or
|
|
|
|
root.action.type == "view" or
|
|
|
|
root.action.type == "template" or
|
|
|
|
root.action.type == "arcombine"
|
|
|
|
then
|
|
|
|
return prefix
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-12-06 20:19:02 +00:00
|
|
|
local function merge_trees(node_a, node_b)
|
|
|
|
for k, v in pairs(node_b) do
|
|
|
|
if k == "children" then
|
|
|
|
node_a.children = node_a.children or {}
|
|
|
|
|
|
|
|
for name, spec in pairs(v) do
|
|
|
|
node_a.children[name] = merge_trees(node_a.children[name] or {}, spec)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
node_a[k] = v
|
|
|
|
end
|
|
|
|
end
|
2020-01-10 21:12:03 +00:00
|
|
|
|
|
|
|
if type(node_a.action) == "table" and
|
|
|
|
node_a.action.type == "firstchild" and
|
|
|
|
node_a.children == nil
|
|
|
|
then
|
|
|
|
node_a.satisfied = false
|
|
|
|
end
|
|
|
|
|
2019-12-06 20:19:02 +00:00
|
|
|
return node_a
|
|
|
|
end
|
|
|
|
|
2019-12-01 19:06:43 +00:00
|
|
|
function menu_json()
|
|
|
|
local tree = context.tree or createtree()
|
2019-12-06 20:19:02 +00:00
|
|
|
local lua_tree = tree_to_json(tree, {
|
2019-12-01 19:06:43 +00:00
|
|
|
action = {
|
|
|
|
["type"] = "firstchild",
|
|
|
|
["recurse"] = true
|
|
|
|
}
|
|
|
|
})
|
2019-12-06 20:19:02 +00:00
|
|
|
|
|
|
|
local json_tree = createtree_json()
|
|
|
|
return merge_trees(lua_tree, json_tree)
|
2019-12-01 19:06:43 +00:00
|
|
|
end
|
|
|
|
|
2019-12-04 18:04:50 +00:00
|
|
|
local function init_template_engine(ctx)
|
|
|
|
local tpl = require "luci.template"
|
|
|
|
local media = luci.config.main.mediaurlbase
|
|
|
|
|
|
|
|
if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then
|
|
|
|
media = nil
|
|
|
|
for name, theme in pairs(luci.config.themes) do
|
|
|
|
if name:sub(1,1) ~= "." and pcall(tpl.Template,
|
|
|
|
"themes/%s/header" % fs.basename(theme)) then
|
|
|
|
media = theme
|
|
|
|
end
|
|
|
|
end
|
|
|
|
assert(media, "No valid theme found")
|
|
|
|
end
|
|
|
|
|
|
|
|
local function _ifattr(cond, key, val, noescape)
|
|
|
|
if cond then
|
|
|
|
local env = getfenv(3)
|
|
|
|
local scope = (type(env.self) == "table") and env.self
|
|
|
|
if type(val) == "table" then
|
|
|
|
if not next(val) then
|
|
|
|
return ''
|
|
|
|
else
|
|
|
|
val = util.serialize_json(val)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
val = tostring(val or
|
|
|
|
(type(env[key]) ~= "function" and env[key]) or
|
|
|
|
(scope and type(scope[key]) ~= "function" and scope[key]) or "")
|
|
|
|
|
|
|
|
if noescape ~= true then
|
|
|
|
val = util.pcdata(val)
|
|
|
|
end
|
|
|
|
|
|
|
|
return string.format(' %s="%s"', tostring(key), val)
|
|
|
|
else
|
|
|
|
return ''
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
tpl.context.viewns = setmetatable({
|
|
|
|
write = http.write;
|
|
|
|
include = function(name) tpl.Template(name):render(getfenv(2)) end;
|
|
|
|
translate = i18n.translate;
|
|
|
|
translatef = i18n.translatef;
|
|
|
|
export = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end;
|
|
|
|
striptags = util.striptags;
|
|
|
|
pcdata = util.pcdata;
|
|
|
|
media = media;
|
|
|
|
theme = fs.basename(media);
|
|
|
|
resource = luci.config.main.resourcebase;
|
|
|
|
ifattr = function(...) return _ifattr(...) end;
|
|
|
|
attr = function(...) return _ifattr(true, ...) end;
|
|
|
|
url = build_url;
|
|
|
|
}, {__index=function(tbl, key)
|
|
|
|
if key == "controller" then
|
|
|
|
return build_url()
|
|
|
|
elseif key == "REQUEST_URI" then
|
|
|
|
return build_url(unpack(ctx.requestpath))
|
|
|
|
elseif key == "FULL_REQUEST_URI" then
|
|
|
|
local url = { http.getenv("SCRIPT_NAME") or "", http.getenv("PATH_INFO") }
|
|
|
|
local query = http.getenv("QUERY_STRING")
|
|
|
|
if query and #query > 0 then
|
|
|
|
url[#url+1] = "?"
|
|
|
|
url[#url+1] = query
|
|
|
|
end
|
|
|
|
return table.concat(url, "")
|
|
|
|
elseif key == "token" then
|
|
|
|
return ctx.authtoken
|
|
|
|
else
|
|
|
|
return rawget(tbl, key) or _G[key]
|
|
|
|
end
|
|
|
|
end})
|
|
|
|
|
|
|
|
return tpl
|
|
|
|
end
|
|
|
|
|
2009-07-24 15:45:29 +00:00
|
|
|
function dispatch(request)
|
2009-07-23 11:32:22 +00:00
|
|
|
--context._disable_memtrace = require "luci.debug".trap_memtrace("l")
|
2008-08-29 23:26:01 +00:00
|
|
|
local ctx = context
|
2008-09-23 00:10:51 +00:00
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
local auth, cors, suid, sgid
|
|
|
|
local menu = menu_json()
|
|
|
|
local page = menu
|
2009-07-24 15:45:29 +00:00
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
local requested_path_full = {}
|
|
|
|
local requested_path_node = {}
|
|
|
|
local requested_path_args = {}
|
2008-05-24 22:58:45 +00:00
|
|
|
|
2008-05-22 14:04:03 +00:00
|
|
|
for i, s in ipairs(request) do
|
2019-12-04 18:19:45 +00:00
|
|
|
if type(page.children) ~= "table" or not page.children[s] then
|
|
|
|
page = nil
|
[PATCH] Allow smarter node creation based on visibility during createtree
As I've brought up on the mailing list thread "High latency caused by full tree creation", there is a large amount of delay per LuCI request which is spent building the node tree in createtree(). Most nodes created aren't
needed for the view presented to the user and only serve to consume memory and CPU time during a page load.
My idea is to provide an easy mechanism for index()ers to determine which needs to be created and what isn't. Due to the constraints of the standard LuCI web interface, this optimization needs to establish a few rules:
* The page requested must have its node created
* All parents of the page being requested must be created, so the children inherit the track
* All the top-level nodes "Status", "System", "Services", "Network" (and others added by extensions) must be created in order to have their top-level tabs in the UI
* All peers of second-level nodes need to be created as well for the same reason, to display their links on the subindexes
To make this easy to implement in each controller, the attached patch adds an "inreq" field to each created node to indicate if it lies on the request path. To satisfy the "top level node" requirement, we always
add the top level node, then check its inreq property if the top-level node is not "in request", then the controller can exit index() early.
2011-08-12 11:05:59 +00:00
|
|
|
break
|
2008-05-24 22:58:45 +00:00
|
|
|
end
|
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
if not page.children[s].satisfied then
|
|
|
|
page = nil
|
[PATCH] Allow smarter node creation based on visibility during createtree
As I've brought up on the mailing list thread "High latency caused by full tree creation", there is a large amount of delay per LuCI request which is spent building the node tree in createtree(). Most nodes created aren't
needed for the view presented to the user and only serve to consume memory and CPU time during a page load.
My idea is to provide an easy mechanism for index()ers to determine which needs to be created and what isn't. Due to the constraints of the standard LuCI web interface, this optimization needs to establish a few rules:
* The page requested must have its node created
* All parents of the page being requested must be created, so the children inherit the track
* All the top-level nodes "Status", "System", "Services", "Network" (and others added by extensions) must be created in order to have their top-level tabs in the UI
* All peers of second-level nodes need to be created as well for the same reason, to display their links on the subindexes
To make this easy to implement in each controller, the attached patch adds an "inreq" field to each created node to indicate if it lies on the request path. To satisfy the "top level node" requirement, we always
add the top level node, then check its inreq property if the top-level node is not "in request", then the controller can exit index() early.
2011-08-12 11:05:59 +00:00
|
|
|
break
|
2008-08-22 21:52:36 +00:00
|
|
|
end
|
2008-05-24 22:58:45 +00:00
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
page = page.children[s]
|
|
|
|
auth = page.auth or auth
|
|
|
|
cors = page.cors or cors
|
|
|
|
suid = page.setuser or suid
|
|
|
|
sgid = page.setgroup or sgid
|
2008-08-07 19:03:25 +00:00
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
requested_path_full[i] = s
|
|
|
|
requested_path_node[i] = s
|
2008-12-14 21:43:10 +00:00
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
if page.wildcard then
|
|
|
|
for j = i + 1, #request do
|
|
|
|
requested_path_args[j - i] = request[j]
|
|
|
|
requested_path_full[j] = request[j]
|
|
|
|
end
|
|
|
|
break
|
|
|
|
end
|
2008-07-17 16:02:29 +00:00
|
|
|
end
|
2008-09-23 00:10:51 +00:00
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
local tpl = init_template_engine(ctx)
|
2008-09-23 00:10:51 +00:00
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
ctx.args = requested_path_args
|
|
|
|
ctx.path = requested_path_node
|
|
|
|
ctx.dispatched = page
|
2008-12-14 21:43:10 +00:00
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
ctx.requestpath = ctx.requestpath or requested_path_full
|
|
|
|
ctx.requestargs = ctx.requestargs or requested_path_args
|
|
|
|
ctx.requested = ctx.requested or page
|
2017-07-11 12:12:50 +00:00
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
if type(auth) == "table" and type(auth.methods) == "table" and #auth.methods > 0 then
|
|
|
|
local sid, sdat
|
|
|
|
for _, method in ipairs(auth.methods) do
|
|
|
|
sid, sdat = check_authentication(method)
|
2008-09-23 00:10:51 +00:00
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
if sid and sdat then
|
|
|
|
break
|
|
|
|
end
|
2017-07-11 12:12:50 +00:00
|
|
|
end
|
2015-01-15 09:55:53 +00:00
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
if not (sid and sdat) and auth.login then
|
2017-07-11 12:12:50 +00:00
|
|
|
local user = http.getenv("HTTP_AUTH_USER")
|
|
|
|
local pass = http.getenv("HTTP_AUTH_PASS")
|
2015-02-09 15:30:11 +00:00
|
|
|
|
2017-07-11 12:12:50 +00:00
|
|
|
if user == nil and pass == nil then
|
|
|
|
user = http.formvalue("luci_username")
|
|
|
|
pass = http.formvalue("luci_password")
|
|
|
|
end
|
2017-07-09 19:26:49 +00:00
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
sid, sdat = session_setup(user, pass, { "root" })
|
2017-07-11 12:12:50 +00:00
|
|
|
|
|
|
|
if not sid then
|
|
|
|
context.path = {}
|
|
|
|
|
|
|
|
http.status(403, "Forbidden")
|
2018-11-16 18:33:39 +00:00
|
|
|
http.header("X-LuCI-Login-Required", "yes")
|
2017-07-11 12:12:50 +00:00
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
return tpl.render("sysauth", { duser = "root", fuser = user })
|
2008-06-28 16:03:54 +00:00
|
|
|
end
|
2017-07-09 19:26:49 +00:00
|
|
|
|
2020-01-29 08:07:51 +00:00
|
|
|
http.header("Set-Cookie", 'sysauth=%s; path=%s; SameSite=Strict; HttpOnly%s' %{
|
2018-05-13 09:55:01 +00:00
|
|
|
sid, build_url(), http.getenv("HTTPS") == "on" and "; secure" or ""
|
|
|
|
})
|
2019-12-04 18:19:45 +00:00
|
|
|
|
2017-07-11 12:12:50 +00:00
|
|
|
http.redirect(build_url(unpack(ctx.requestpath)))
|
2019-12-04 18:19:45 +00:00
|
|
|
return
|
2008-06-28 16:03:54 +00:00
|
|
|
end
|
2017-07-09 19:26:49 +00:00
|
|
|
|
|
|
|
if not sid or not sdat then
|
|
|
|
http.status(403, "Forbidden")
|
2018-11-16 18:33:39 +00:00
|
|
|
http.header("X-LuCI-Login-Required", "yes")
|
2017-07-09 19:26:49 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
ctx.authsession = sid
|
|
|
|
ctx.authtoken = sdat.token
|
|
|
|
ctx.authuser = sdat.username
|
2008-06-28 16:03:54 +00:00
|
|
|
end
|
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
local action = (page and type(page.action) == "table") and page.action or {}
|
|
|
|
|
|
|
|
if action.type == "arcombine" then
|
|
|
|
action = (#requested_path_args > 0) and action.targets[2] or action.targets[1]
|
|
|
|
end
|
|
|
|
|
|
|
|
if cors and http.getenv("REQUEST_METHOD") == "OPTIONS" then
|
2018-04-24 18:32:47 +00:00
|
|
|
luci.http.status(200, "OK")
|
|
|
|
luci.http.header("Access-Control-Allow-Origin", http.getenv("HTTP_ORIGIN") or "*")
|
|
|
|
luci.http.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
if require_post_security(action) then
|
|
|
|
if not test_post_security() then
|
2015-10-06 13:53:35 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
if sgid then
|
|
|
|
sys.process.setgroup(sgid)
|
2008-06-28 16:03:54 +00:00
|
|
|
end
|
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
if suid then
|
|
|
|
sys.process.setuser(suid)
|
2008-06-28 16:03:54 +00:00
|
|
|
end
|
2008-05-24 22:58:45 +00:00
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
if action.type == "view" then
|
|
|
|
tpl.render("view", { view = action.path })
|
|
|
|
|
|
|
|
elseif action.type == "call" then
|
|
|
|
local ok, mod = util.copcall(require, action.module)
|
|
|
|
if not ok then
|
|
|
|
error500(mod)
|
|
|
|
return
|
2009-01-04 15:45:57 +00:00
|
|
|
end
|
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
local func = mod[action["function"]]
|
2008-10-11 11:30:43 +00:00
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
assert(func ~= nil,
|
|
|
|
'Cannot resolve function "' .. action["function"] .. '". Is it misspelled or local?')
|
2008-10-11 11:30:43 +00:00
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
assert(type(func) == "function",
|
|
|
|
'The symbol "' .. action["function"] .. '" does not refer to a function but data ' ..
|
|
|
|
'of type "' .. type(func) .. '".')
|
|
|
|
|
|
|
|
local argv = (type(action.parameters) == "table" and #action.parameters > 0) and { unpack(action.parameters) } or {}
|
|
|
|
for _, s in ipairs(requested_path_args) do
|
|
|
|
argv[#argv + 1] = s
|
2008-10-10 14:37:53 +00:00
|
|
|
end
|
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
local ok, err = util.copcall(func, unpack(argv))
|
|
|
|
if not ok then
|
|
|
|
error500(err)
|
|
|
|
end
|
2008-09-23 00:10:51 +00:00
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
elseif action.type == "firstchild" then
|
|
|
|
local sub_request = find_subnode(page, requested_path_full, action.recurse)
|
|
|
|
if sub_request then
|
|
|
|
dispatch(sub_request)
|
|
|
|
else
|
|
|
|
tpl.render("empty_node_placeholder", getfenv(1))
|
|
|
|
end
|
2008-09-05 18:35:09 +00:00
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
elseif action.type == "alias" then
|
|
|
|
local sub_request = {}
|
|
|
|
for name in action.path:gmatch("[^/]+") do
|
|
|
|
sub_request[#sub_request + 1] = name
|
|
|
|
end
|
2008-09-23 00:10:51 +00:00
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
for _, s in ipairs(requested_path_args) do
|
|
|
|
sub_request[#sub_request + 1] = s
|
2009-01-04 15:45:57 +00:00
|
|
|
end
|
2019-12-04 18:19:45 +00:00
|
|
|
|
|
|
|
dispatch(sub_request)
|
|
|
|
|
|
|
|
elseif action.type == "rewrite" then
|
|
|
|
local sub_request = { unpack(request) }
|
|
|
|
for i = 1, action.remove do
|
|
|
|
table.remove(sub_request, 1)
|
2018-05-31 15:41:40 +00:00
|
|
|
end
|
2019-12-04 18:19:45 +00:00
|
|
|
|
|
|
|
local n = 1
|
|
|
|
for s in action.path:gmatch("[^/]+") do
|
|
|
|
table.insert(sub_request, n, s)
|
|
|
|
n = n + 1
|
|
|
|
end
|
|
|
|
|
|
|
|
for _, s in ipairs(requested_path_args) do
|
|
|
|
sub_request[#sub_request + 1] = s
|
|
|
|
end
|
|
|
|
|
|
|
|
dispatch(sub_request)
|
|
|
|
|
|
|
|
elseif action.type == "template" then
|
|
|
|
tpl.render(action.path, getfenv(1))
|
|
|
|
|
|
|
|
elseif action.type == "cbi" then
|
|
|
|
_cbi({ config = action.config, model = action.path }, unpack(requested_path_args))
|
|
|
|
|
|
|
|
elseif action.type == "form" then
|
|
|
|
_form({ model = action.path }, unpack(requested_path_args))
|
|
|
|
|
2008-03-21 19:30:53 +00:00
|
|
|
else
|
2019-12-04 18:19:45 +00:00
|
|
|
local root = find_subnode(menu, {}, true)
|
|
|
|
if not root then
|
2011-07-20 23:57:32 +00:00
|
|
|
error404("No root node was registered, this usually happens if no module was installed.\n" ..
|
2011-10-21 17:22:48 +00:00
|
|
|
"Install luci-mod-admin-full and retry. " ..
|
2011-07-20 23:57:32 +00:00
|
|
|
"If the module is already installed, try removing the /tmp/luci-indexcache file.")
|
|
|
|
else
|
2019-12-04 18:19:45 +00:00
|
|
|
error404("No page is registered at '/" .. table.concat(requested_path_full, "/") .. "'.\n" ..
|
2011-07-20 23:57:32 +00:00
|
|
|
"If this url belongs to an extension, make sure it is properly installed.\n" ..
|
|
|
|
"If the extension was recently installed, try removing the /tmp/luci-indexcache file.")
|
|
|
|
end
|
2008-03-02 21:52:58 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2008-05-22 14:04:03 +00:00
|
|
|
function createindex()
|
2015-01-15 15:32:03 +00:00
|
|
|
local controllers = { }
|
|
|
|
local base = "%s/controller/" % util.libpath()
|
|
|
|
local _, path
|
2008-09-23 00:10:51 +00:00
|
|
|
|
2015-01-15 15:32:03 +00:00
|
|
|
for path in (fs.glob("%s*.lua" % base) or function() end) do
|
|
|
|
controllers[#controllers+1] = path
|
2008-05-26 09:45:12 +00:00
|
|
|
end
|
|
|
|
|
2015-01-15 15:32:03 +00:00
|
|
|
for path in (fs.glob("%s*/*.lua" % base) or function() end) do
|
|
|
|
controllers[#controllers+1] = path
|
2009-04-04 22:54:16 +00:00
|
|
|
end
|
2008-11-05 14:10:02 +00:00
|
|
|
|
2008-08-29 23:26:01 +00:00
|
|
|
if indexcache then
|
2009-07-19 00:24:58 +00:00
|
|
|
local cachedate = fs.stat(indexcache, "mtime")
|
2008-11-05 14:10:02 +00:00
|
|
|
if cachedate then
|
|
|
|
local realdate = 0
|
|
|
|
for _, obj in ipairs(controllers) do
|
2011-07-18 14:50:39 +00:00
|
|
|
local omtime = fs.stat(obj, "mtime")
|
2008-11-05 14:10:02 +00:00
|
|
|
realdate = (omtime and omtime > realdate) and omtime or realdate
|
|
|
|
end
|
2008-09-01 16:05:34 +00:00
|
|
|
|
2015-01-15 15:32:03 +00:00
|
|
|
if cachedate > realdate and sys.process.info("uid") == 0 then
|
2008-11-05 14:10:02 +00:00
|
|
|
assert(
|
|
|
|
sys.process.info("uid") == fs.stat(indexcache, "uid")
|
2009-06-21 13:42:26 +00:00
|
|
|
and fs.stat(indexcache, "modestr") == "rw-------",
|
2008-11-05 14:10:02 +00:00
|
|
|
"Fatal: Indexcache is not sane!"
|
|
|
|
)
|
2008-09-01 16:05:34 +00:00
|
|
|
|
2008-11-05 14:10:02 +00:00
|
|
|
index = loadfile(indexcache)()
|
|
|
|
return index
|
|
|
|
end
|
2008-09-23 00:10:51 +00:00
|
|
|
end
|
2008-08-29 23:26:01 +00:00
|
|
|
end
|
2008-09-23 00:10:51 +00:00
|
|
|
|
2009-07-24 15:45:29 +00:00
|
|
|
index = {}
|
2008-06-02 15:36:13 +00:00
|
|
|
|
2015-01-15 15:32:03 +00:00
|
|
|
for _, path in ipairs(controllers) do
|
|
|
|
local modname = "luci.controller." .. path:sub(#base+1, #path-4):gsub("/", ".")
|
2010-12-12 20:16:13 +00:00
|
|
|
local mod = require(modname)
|
2011-07-21 01:04:53 +00:00
|
|
|
assert(mod ~= true,
|
|
|
|
"Invalid controller file found\n" ..
|
2015-01-15 15:32:03 +00:00
|
|
|
"The file '" .. path .. "' contains an invalid module line.\n" ..
|
2011-07-21 01:04:53 +00:00
|
|
|
"Please verify whether the module name is set to '" .. modname ..
|
|
|
|
"' - It must correspond to the file path!")
|
2011-10-26 00:24:17 +00:00
|
|
|
|
2008-08-29 23:26:01 +00:00
|
|
|
local idx = mod.index
|
2019-12-06 20:19:02 +00:00
|
|
|
if type(idx) == "function" then
|
|
|
|
index[modname] = idx
|
|
|
|
end
|
2008-08-29 23:26:01 +00:00
|
|
|
end
|
2008-09-23 00:10:51 +00:00
|
|
|
|
2008-08-29 23:26:01 +00:00
|
|
|
if indexcache then
|
2009-06-21 13:42:26 +00:00
|
|
|
local f = nixio.open(indexcache, "w", 600)
|
|
|
|
f:writeall(util.get_bytecode(index))
|
|
|
|
f:close()
|
2008-06-02 15:36:05 +00:00
|
|
|
end
|
2008-03-21 19:30:53 +00:00
|
|
|
end
|
|
|
|
|
2019-12-06 20:19:02 +00:00
|
|
|
function createtree_json()
|
|
|
|
local json = require "luci.jsonc"
|
|
|
|
local tree = {}
|
|
|
|
|
|
|
|
local schema = {
|
|
|
|
action = "table",
|
|
|
|
auth = "table",
|
|
|
|
cors = "boolean",
|
|
|
|
depends = "table",
|
|
|
|
order = "number",
|
|
|
|
setgroup = "string",
|
|
|
|
setuser = "string",
|
|
|
|
title = "string",
|
|
|
|
wildcard = "boolean"
|
|
|
|
}
|
|
|
|
|
|
|
|
local files = {}
|
|
|
|
local fprint = {}
|
|
|
|
local cachefile
|
|
|
|
|
|
|
|
for file in (fs.glob("/usr/share/luci/menu.d/*.json") or function() end) do
|
|
|
|
files[#files+1] = file
|
|
|
|
|
|
|
|
if indexcache then
|
|
|
|
local st = fs.stat(file)
|
|
|
|
if st then
|
|
|
|
fprint[#fprint+1] = '%x' % st.ino
|
|
|
|
fprint[#fprint+1] = '%x' % st.mtime
|
|
|
|
fprint[#fprint+1] = '%x' % st.size
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if indexcache then
|
|
|
|
cachefile = "%s.%s.json" %{
|
|
|
|
indexcache,
|
|
|
|
nixio.crypt(table.concat(fprint, "|"), "$1$"):sub(5):gsub("/", ".")
|
|
|
|
}
|
|
|
|
|
|
|
|
local res = json.parse(fs.readfile(cachefile) or "")
|
|
|
|
if res then
|
|
|
|
return res
|
|
|
|
end
|
|
|
|
|
|
|
|
for file in (fs.glob("%s.*.json" % indexcache) or function() end) do
|
|
|
|
fs.unlink(file)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for _, file in ipairs(files) do
|
|
|
|
local data = json.parse(fs.readfile(file) or "")
|
|
|
|
if type(data) == "table" then
|
|
|
|
for path, spec in pairs(data) do
|
|
|
|
if type(spec) == "table" then
|
|
|
|
local node = tree
|
|
|
|
|
|
|
|
for s in path:gmatch("[^/]+") do
|
|
|
|
if s == "*" then
|
|
|
|
node.wildcard = true
|
|
|
|
break
|
|
|
|
end
|
|
|
|
|
|
|
|
node.children = node.children or {}
|
|
|
|
node.children[s] = node.children[s] or {}
|
|
|
|
node = node.children[s]
|
|
|
|
end
|
|
|
|
|
|
|
|
if node ~= tree then
|
|
|
|
for k, t in pairs(schema) do
|
|
|
|
if type(spec[k]) == t then
|
|
|
|
node[k] = spec[k]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
node.satisfied = check_depends(spec)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if cachefile then
|
|
|
|
fs.writefile(cachefile, json.stringify(tree))
|
|
|
|
end
|
|
|
|
|
|
|
|
return tree
|
|
|
|
end
|
|
|
|
|
2008-07-29 20:32:02 +00:00
|
|
|
-- Build the index before if it does not exist yet.
|
2008-05-26 12:16:16 +00:00
|
|
|
function createtree()
|
2009-07-24 15:45:29 +00:00
|
|
|
if not index then
|
|
|
|
createindex()
|
2009-07-23 03:25:27 +00:00
|
|
|
end
|
2008-09-23 00:10:51 +00:00
|
|
|
|
2009-07-24 15:45:29 +00:00
|
|
|
local ctx = context
|
[PATCH] Allow smarter node creation based on visibility during createtree
As I've brought up on the mailing list thread "High latency caused by full tree creation", there is a large amount of delay per LuCI request which is spent building the node tree in createtree(). Most nodes created aren't
needed for the view presented to the user and only serve to consume memory and CPU time during a page load.
My idea is to provide an easy mechanism for index()ers to determine which needs to be created and what isn't. Due to the constraints of the standard LuCI web interface, this optimization needs to establish a few rules:
* The page requested must have its node created
* All parents of the page being requested must be created, so the children inherit the track
* All the top-level nodes "Status", "System", "Services", "Network" (and others added by extensions) must be created in order to have their top-level tabs in the UI
* All peers of second-level nodes need to be created as well for the same reason, to display their links on the subindexes
To make this easy to implement in each controller, the attached patch adds an "inreq" field to each created node to indicate if it lies on the request path. To satisfy the "top level node" requirement, we always
add the top level node, then check its inreq property if the top-level node is not "in request", then the controller can exit index() early.
2011-08-12 11:05:59 +00:00
|
|
|
local tree = {nodes={}, inreq=true}
|
2009-07-24 15:45:29 +00:00
|
|
|
|
|
|
|
ctx.treecache = setmetatable({}, {__mode="v"})
|
|
|
|
ctx.tree = tree
|
2008-09-23 00:10:51 +00:00
|
|
|
|
2008-09-05 19:17:48 +00:00
|
|
|
local scope = setmetatable({}, {__index = luci.dispatcher})
|
2008-05-26 12:16:16 +00:00
|
|
|
|
2009-07-24 15:45:29 +00:00
|
|
|
for k, v in pairs(index) do
|
2008-05-31 08:04:49 +00:00
|
|
|
scope._NAME = k
|
|
|
|
setfenv(v, scope)
|
2009-07-24 15:45:29 +00:00
|
|
|
v()
|
2008-05-26 12:16:16 +00:00
|
|
|
end
|
2008-09-23 00:10:51 +00:00
|
|
|
|
2009-07-24 15:45:29 +00:00
|
|
|
return tree
|
2008-05-26 12:16:16 +00:00
|
|
|
end
|
|
|
|
|
2008-06-02 20:16:05 +00:00
|
|
|
function assign(path, clone, title, order)
|
2008-06-29 14:43:06 +00:00
|
|
|
local obj = node(unpack(path))
|
2008-06-02 20:16:05 +00:00
|
|
|
obj.nodes = nil
|
|
|
|
obj.module = nil
|
2008-09-23 00:10:51 +00:00
|
|
|
|
2008-06-02 20:16:05 +00:00
|
|
|
obj.title = title
|
|
|
|
obj.order = order
|
2008-06-06 15:50:21 +00:00
|
|
|
|
2008-09-15 16:50:55 +00:00
|
|
|
setmetatable(obj, {__index = _create_node(clone)})
|
2008-09-23 00:10:51 +00:00
|
|
|
|
2008-06-02 20:16:05 +00:00
|
|
|
return obj
|
|
|
|
end
|
2008-05-22 17:21:30 +00:00
|
|
|
|
2008-06-02 20:16:05 +00:00
|
|
|
function entry(path, target, title, order)
|
2008-06-29 14:42:53 +00:00
|
|
|
local c = node(unpack(path))
|
2008-09-23 00:10:51 +00:00
|
|
|
|
2008-05-22 17:21:30 +00:00
|
|
|
c.target = target
|
|
|
|
c.title = title
|
|
|
|
c.order = order
|
2008-05-27 20:39:48 +00:00
|
|
|
c.module = getfenv(2)._NAME
|
2008-05-24 22:58:45 +00:00
|
|
|
|
2008-05-22 17:21:30 +00:00
|
|
|
return c
|
|
|
|
end
|
2008-05-04 20:53:31 +00:00
|
|
|
|
2009-02-26 17:08:41 +00:00
|
|
|
-- enabling the node.
|
|
|
|
function get(...)
|
|
|
|
return _create_node({...})
|
|
|
|
end
|
|
|
|
|
2008-05-22 14:04:03 +00:00
|
|
|
function node(...)
|
2008-09-15 16:50:55 +00:00
|
|
|
local c = _create_node({...})
|
2008-05-24 22:58:45 +00:00
|
|
|
|
2008-06-06 15:50:21 +00:00
|
|
|
c.module = getfenv(2)._NAME
|
2008-07-17 16:02:29 +00:00
|
|
|
c.auto = nil
|
2008-06-04 22:41:58 +00:00
|
|
|
|
2008-05-22 14:04:03 +00:00
|
|
|
return c
|
|
|
|
end
|
|
|
|
|
2018-04-05 19:58:41 +00:00
|
|
|
function lookup(...)
|
|
|
|
local i, path = nil, {}
|
|
|
|
for i = 1, select('#', ...) do
|
|
|
|
local name, arg = nil, tostring(select(i, ...))
|
|
|
|
for name in arg:gmatch("[^/]+") do
|
|
|
|
path[#path+1] = name
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for i = #path, 1, -1 do
|
|
|
|
local node = context.treecache[table.concat(path, ".", 1, i)]
|
|
|
|
if node and (i == #path or node.leaf) then
|
|
|
|
return node, build_url(unpack(path))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-08-12 11:04:42 +00:00
|
|
|
function _create_node(path)
|
2008-08-29 23:26:01 +00:00
|
|
|
if #path == 0 then
|
|
|
|
return context.tree
|
|
|
|
end
|
2008-09-23 00:10:51 +00:00
|
|
|
|
2008-08-29 23:26:01 +00:00
|
|
|
local name = table.concat(path, ".")
|
2011-08-12 11:04:42 +00:00
|
|
|
local c = context.treecache[name]
|
2008-09-23 00:10:51 +00:00
|
|
|
|
2008-08-29 23:26:01 +00:00
|
|
|
if not c then
|
|
|
|
local last = table.remove(path)
|
2011-08-12 11:04:42 +00:00
|
|
|
local parent = _create_node(path)
|
2008-11-16 13:52:50 +00:00
|
|
|
|
2018-06-29 15:46:56 +00:00
|
|
|
c = {nodes={}, auto=true, inreq=true}
|
|
|
|
|
2011-08-12 11:04:42 +00:00
|
|
|
parent.nodes[last] = c
|
|
|
|
context.treecache[name] = c
|
2008-08-29 23:26:01 +00:00
|
|
|
end
|
2018-06-29 15:46:56 +00:00
|
|
|
|
2011-08-12 11:04:42 +00:00
|
|
|
return c
|
2008-08-29 23:26:01 +00:00
|
|
|
end
|
|
|
|
|
2008-05-22 14:04:03 +00:00
|
|
|
-- Subdispatchers --
|
2008-07-29 20:32:02 +00:00
|
|
|
|
2011-10-25 22:48:43 +00:00
|
|
|
function firstchild()
|
2019-12-04 18:19:45 +00:00
|
|
|
return { type = "firstchild" }
|
2018-09-19 17:58:45 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function firstnode()
|
2019-12-04 18:19:45 +00:00
|
|
|
return { type = "firstnode" }
|
2008-03-21 19:30:53 +00:00
|
|
|
end
|
|
|
|
|
2019-12-01 19:06:43 +00:00
|
|
|
function alias(...)
|
2019-12-04 18:19:45 +00:00
|
|
|
return { type = "alias", req = { ... } }
|
2008-05-29 19:18:49 +00:00
|
|
|
end
|
|
|
|
|
2019-12-01 19:06:43 +00:00
|
|
|
function rewrite(n, ...)
|
2019-12-04 18:19:45 +00:00
|
|
|
return { type = "rewrite", n = n, req = { ... } }
|
2009-01-04 15:45:57 +00:00
|
|
|
end
|
|
|
|
|
2008-06-14 14:12:12 +00:00
|
|
|
function call(name, ...)
|
2019-12-04 18:19:45 +00:00
|
|
|
return { type = "call", argv = {...}, name = name }
|
2009-01-04 15:45:57 +00:00
|
|
|
end
|
|
|
|
|
2015-10-20 18:58:30 +00:00
|
|
|
function post_on(params, name, ...)
|
2015-10-06 13:53:35 +00:00
|
|
|
return {
|
|
|
|
type = "call",
|
2015-10-20 18:58:30 +00:00
|
|
|
post = params,
|
2015-10-06 13:53:35 +00:00
|
|
|
argv = { ... },
|
2019-12-04 18:19:45 +00:00
|
|
|
name = name
|
2015-10-06 13:53:35 +00:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2015-10-20 18:58:30 +00:00
|
|
|
function post(...)
|
|
|
|
return post_on(true, ...)
|
|
|
|
end
|
|
|
|
|
2009-01-04 15:45:57 +00:00
|
|
|
|
2008-05-22 14:04:03 +00:00
|
|
|
function template(name)
|
2019-12-04 18:19:45 +00:00
|
|
|
return { type = "template", view = name }
|
2019-04-01 13:38:00 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function view(name)
|
2019-12-04 18:19:45 +00:00
|
|
|
return { type = "view", view = name }
|
2019-04-01 13:38:00 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
function _cbi(self, ...)
|
2009-01-04 15:45:57 +00:00
|
|
|
local cbi = require "luci.cbi"
|
|
|
|
local tpl = require "luci.template"
|
|
|
|
local http = require "luci.http"
|
2008-05-24 22:58:45 +00:00
|
|
|
|
2009-01-04 15:45:57 +00:00
|
|
|
local config = self.config or {}
|
|
|
|
local maps = cbi.load(self.model, ...)
|
2008-10-20 22:35:11 +00:00
|
|
|
|
2009-01-04 15:45:57 +00:00
|
|
|
local state = nil
|
2008-05-24 22:58:45 +00:00
|
|
|
|
2018-04-06 10:07:01 +00:00
|
|
|
local i, res
|
2009-01-04 15:45:57 +00:00
|
|
|
for i, res in ipairs(maps) do
|
2018-04-06 10:07:01 +00:00
|
|
|
if util.instanceof(res, cbi.SimpleForm) then
|
|
|
|
io.stderr:write("Model %s returns SimpleForm but is dispatched via cbi(),\n"
|
|
|
|
% self.model)
|
|
|
|
|
|
|
|
io.stderr:write("please change %s to use the form() action instead.\n"
|
|
|
|
% table.concat(context.request, "/"))
|
|
|
|
end
|
|
|
|
|
2009-04-03 18:08:25 +00:00
|
|
|
res.flow = config
|
2009-01-04 15:45:57 +00:00
|
|
|
local cstate = res:parse()
|
2009-03-07 16:28:27 +00:00
|
|
|
if cstate and (not state or cstate < state) then
|
2009-01-04 15:45:57 +00:00
|
|
|
state = cstate
|
2008-11-05 21:12:19 +00:00
|
|
|
end
|
2009-01-04 15:45:57 +00:00
|
|
|
end
|
2008-11-05 21:12:19 +00:00
|
|
|
|
2009-06-20 07:14:36 +00:00
|
|
|
local function _resolve_path(path)
|
|
|
|
return type(path) == "table" and build_url(unpack(path)) or path
|
|
|
|
end
|
|
|
|
|
2009-01-04 15:45:57 +00:00
|
|
|
if config.on_valid_to and state and state > 0 and state < 2 then
|
2009-06-20 07:14:36 +00:00
|
|
|
http.redirect(_resolve_path(config.on_valid_to))
|
2009-01-04 15:45:57 +00:00
|
|
|
return
|
|
|
|
end
|
2008-11-02 13:26:41 +00:00
|
|
|
|
2009-01-04 15:45:57 +00:00
|
|
|
if config.on_changed_to and state and state > 1 then
|
2009-06-20 07:14:36 +00:00
|
|
|
http.redirect(_resolve_path(config.on_changed_to))
|
2009-01-04 15:45:57 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
if config.on_success_to and state and state > 0 then
|
2009-06-20 07:14:36 +00:00
|
|
|
http.redirect(_resolve_path(config.on_success_to))
|
2009-01-04 15:45:57 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
if config.state_handler then
|
|
|
|
if not config.state_handler(state, maps) then
|
|
|
|
return
|
2008-11-01 18:32:02 +00:00
|
|
|
end
|
2009-01-04 15:45:57 +00:00
|
|
|
end
|
2008-11-01 18:32:02 +00:00
|
|
|
|
2009-01-04 15:45:57 +00:00
|
|
|
http.header("X-CBI-State", state or 0)
|
2010-11-22 00:32:54 +00:00
|
|
|
|
2009-03-27 00:10:17 +00:00
|
|
|
if not config.noheader then
|
|
|
|
tpl.render("cbi/header", {state = state})
|
|
|
|
end
|
2010-11-22 00:32:54 +00:00
|
|
|
|
|
|
|
local redirect
|
2010-11-27 18:17:15 +00:00
|
|
|
local messages
|
2010-11-22 00:32:54 +00:00
|
|
|
local applymap = false
|
|
|
|
local pageaction = true
|
|
|
|
local parsechain = { }
|
|
|
|
|
2009-01-04 15:45:57 +00:00
|
|
|
for i, res in ipairs(maps) do
|
2010-11-22 00:32:54 +00:00
|
|
|
if res.apply_needed and res.parsechain then
|
|
|
|
local c
|
|
|
|
for _, c in ipairs(res.parsechain) do
|
|
|
|
parsechain[#parsechain+1] = c
|
|
|
|
end
|
|
|
|
applymap = true
|
2008-07-15 13:17:28 +00:00
|
|
|
end
|
2010-11-22 00:32:54 +00:00
|
|
|
|
2010-11-21 00:14:03 +00:00
|
|
|
if res.redirect then
|
|
|
|
redirect = redirect or res.redirect
|
|
|
|
end
|
2010-11-22 00:32:54 +00:00
|
|
|
|
|
|
|
if res.pageaction == false then
|
|
|
|
pageaction = false
|
|
|
|
end
|
2010-11-27 18:17:15 +00:00
|
|
|
|
|
|
|
if res.message then
|
|
|
|
messages = messages or { }
|
|
|
|
messages[#messages+1] = res.message
|
|
|
|
end
|
2008-03-02 21:52:58 +00:00
|
|
|
end
|
2010-11-22 00:32:54 +00:00
|
|
|
|
|
|
|
for i, res in ipairs(maps) do
|
|
|
|
res:render({
|
|
|
|
firstmap = (i == 1),
|
|
|
|
redirect = redirect,
|
2010-11-27 18:17:15 +00:00
|
|
|
messages = messages,
|
2010-11-22 00:32:54 +00:00
|
|
|
pageaction = pageaction,
|
|
|
|
parsechain = parsechain
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
2009-03-27 00:10:17 +00:00
|
|
|
if not config.nofooter then
|
2010-11-21 00:14:03 +00:00
|
|
|
tpl.render("cbi/footer", {
|
2018-07-26 20:12:45 +00:00
|
|
|
flow = config,
|
|
|
|
pageaction = pageaction,
|
|
|
|
redirect = redirect,
|
|
|
|
state = state,
|
|
|
|
autoapply = config.autoapply,
|
|
|
|
trigger_apply = applymap
|
2010-11-21 00:14:03 +00:00
|
|
|
})
|
2009-03-27 00:10:17 +00:00
|
|
|
end
|
2008-05-24 22:58:45 +00:00
|
|
|
end
|
2008-08-09 14:14:04 +00:00
|
|
|
|
2009-01-04 15:45:57 +00:00
|
|
|
function cbi(model, config)
|
2015-10-20 18:58:30 +00:00
|
|
|
return {
|
|
|
|
type = "cbi",
|
2018-04-04 22:15:22 +00:00
|
|
|
post = { ["cbi.submit"] = true },
|
2015-10-20 18:58:30 +00:00
|
|
|
config = config,
|
2019-12-04 18:19:45 +00:00
|
|
|
model = model
|
2015-10-20 18:58:30 +00:00
|
|
|
}
|
2009-01-04 15:45:57 +00:00
|
|
|
end
|
2008-08-29 20:36:45 +00:00
|
|
|
|
2008-08-09 14:14:04 +00:00
|
|
|
|
2009-01-04 15:45:57 +00:00
|
|
|
function arcombine(trg1, trg2)
|
2019-12-04 18:19:45 +00:00
|
|
|
return {
|
|
|
|
type = "arcombine",
|
|
|
|
env = getfenv(),
|
|
|
|
targets = {trg1, trg2}
|
|
|
|
}
|
2009-01-04 15:45:57 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
2019-12-04 18:19:45 +00:00
|
|
|
function _form(self, ...)
|
2009-01-04 15:45:57 +00:00
|
|
|
local cbi = require "luci.cbi"
|
|
|
|
local tpl = require "luci.template"
|
|
|
|
local http = require "luci.http"
|
2008-08-09 14:14:04 +00:00
|
|
|
|
2009-01-04 15:45:57 +00:00
|
|
|
local maps = luci.cbi.load(self.model, ...)
|
|
|
|
local state = nil
|
|
|
|
|
2018-04-06 10:07:01 +00:00
|
|
|
local i, res
|
2009-01-04 15:45:57 +00:00
|
|
|
for i, res in ipairs(maps) do
|
|
|
|
local cstate = res:parse()
|
2009-03-07 16:28:27 +00:00
|
|
|
if cstate and (not state or cstate < state) then
|
2009-01-04 15:45:57 +00:00
|
|
|
state = cstate
|
2008-08-09 14:14:04 +00:00
|
|
|
end
|
|
|
|
end
|
2009-01-04 15:45:57 +00:00
|
|
|
|
|
|
|
http.header("X-CBI-State", state or 0)
|
|
|
|
tpl.render("header")
|
|
|
|
for i, res in ipairs(maps) do
|
|
|
|
res:render()
|
|
|
|
end
|
|
|
|
tpl.render("footer")
|
|
|
|
end
|
|
|
|
|
|
|
|
function form(model)
|
2015-10-20 18:58:30 +00:00
|
|
|
return {
|
2019-12-01 19:06:43 +00:00
|
|
|
type = "form",
|
2018-04-04 22:15:22 +00:00
|
|
|
post = { ["cbi.submit"] = true },
|
2019-12-04 18:19:45 +00:00
|
|
|
model = model
|
2015-10-20 18:58:30 +00:00
|
|
|
}
|
2008-08-09 14:14:04 +00:00
|
|
|
end
|
2011-08-12 11:13:39 +00:00
|
|
|
|
|
|
|
translate = i18n.translate
|
2011-08-12 13:11:29 +00:00
|
|
|
|
|
|
|
-- This function does not actually translate the given argument but
|
|
|
|
-- is used by build/i18n-scan.pl to find translatable entries.
|
|
|
|
function _(text)
|
|
|
|
return text
|
|
|
|
end
|