1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Copyright 2008-2015 Jo-Philipp Wich <jow@openwrt.org>
3 -- Licensed to the public under the Apache License 2.0.
5 local fs = require "nixio.fs"
6 local sys = require "luci.sys"
7 local util = require "luci.util"
8 local http = require "luci.http"
9 local nixio = require "nixio", require "nixio.util"
11 module("luci.dispatcher", package.seeall)
12 context = util.threadlocal()
13 uci = require "luci.model.uci"
14 i18n = require "luci.i18n"
20 local function check_fs_depends(fs)
21 local fs = require "nixio.fs"
23 for path, kind in pairs(fs) do
24 if kind == "directory" then
26 for entry in (fs.dir(path) or function() end) do
33 elseif kind == "executable" then
34 if fs.stat(path, "type") ~= "reg" or not fs.access(path, "x") then
37 elseif kind == "file" then
38 if fs.stat(path, "type") ~= "reg" then
47 local function check_uci_depends_options(conf, s, opts)
48 local uci = require "luci.model.uci"
50 if type(opts) == "string" then
51 return (s[".type"] == opts)
52 elseif opts == true then
53 for option, value in pairs(s) do
54 if option:byte(1) ~= 46 then
58 elseif type(opts) == "table" then
59 for option, value in pairs(opts) do
60 local sval = s[option]
61 if type(sval) == "table" then
63 for _, v in ipairs(sval) do
72 elseif value == true then
87 local function check_uci_depends_section(conf, sect)
88 local uci = require "luci.model.uci"
90 for section, options in pairs(sect) do
91 local stype = section:match("^@([A-Za-z0-9_%-]+)$")
94 uci:foreach(conf, stype, function(s)
95 if check_uci_depends_options(conf, s, options) then
104 local s = uci:get_all(conf, section)
105 if not s or not check_uci_depends_options(conf, s, options) then
114 local function check_uci_depends(conf)
115 local uci = require "luci.model.uci"
117 for config, values in pairs(conf) do
118 if values == true then
120 uci:foreach(config, nil, function(s)
127 elseif type(values) == "table" then
128 if not check_uci_depends_section(config, values) then
137 local function check_depends(spec)
138 if type(spec.depends) ~= "table" then
142 if type(spec.depends.fs) == "table" and not check_fs_depends(spec.depends.fs) then
143 local satisfied = false
144 local alternatives = (#spec.depends.fs > 0) and spec.depends.fs or { spec.depends.fs }
145 for _, alternative in ipairs(alternatives) do
146 if check_fs_depends(alternative) then
151 if not satisfied then
156 if type(spec.depends.uci) == "table" then
157 local satisfied = false
158 local alternatives = (#spec.depends.uci > 0) and spec.depends.uci or { spec.depends.uci }
159 for _, alternative in ipairs(alternatives) do
160 if check_uci_depends(alternative) then
165 if not satisfied then
173 local function target_to_json(target, module)
176 if target.type == "call" then
180 ["function"] = target.name,
181 ["parameters"] = target.argv
183 elseif target.type == "view" then
186 ["path"] = target.view
188 elseif target.type == "template" then
190 ["type"] = "template",
191 ["path"] = target.view
193 elseif target.type == "cbi" then
196 ["path"] = target.model
198 elseif target.type == "form" then
201 ["path"] = target.model
203 elseif target.type == "firstchild" then
205 ["type"] = "firstchild"
207 elseif target.type == "firstnode" then
209 ["type"] = "firstchild",
212 elseif target.type == "arcombine" then
213 if type(target.targets) == "table" then
215 ["type"] = "arcombine",
217 target_to_json(target.targets[1], module),
218 target_to_json(target.targets[2], module)
222 elseif target.type == "alias" then
225 ["path"] = table.concat(target.req, "/")
227 elseif target.type == "rewrite" then
229 ["type"] = "rewrite",
230 ["path"] = table.concat(target.req, "/"),
231 ["remove"] = target.n
235 if target.post and action then
236 action.post = target.post
242 local function tree_to_json(node, json)
243 local fs = require "nixio.fs"
244 local util = require "luci.util"
246 if type(node.nodes) == "table" then
247 for subname, subnode in pairs(node.nodes) do
249 title = util.striptags(subnode.title),
250 order = subnode.order
261 if subnode.setuser then
262 spec.setuser = subnode.setuser
265 if subnode.setgroup then
266 spec.setgroup = subnode.setgroup
269 if type(subnode.target) == "table" then
270 spec.action = target_to_json(subnode.target, subnode.module)
273 if type(subnode.file_depends) == "table" then
274 for _, v in ipairs(subnode.file_depends) do
275 spec.depends = spec.depends or {}
276 spec.depends.fs = spec.depends.fs or {}
278 local ft = fs.stat(v, "type")
280 spec.depends.fs[v] = "directory"
281 elseif v:match("/s?bin/") then
282 spec.depends.fs[v] = "executable"
284 spec.depends.fs[v] = "file"
289 if type(subnode.uci_depends) == "table" then
290 for k, v in pairs(subnode.uci_depends) do
291 spec.depends = spec.depends or {}
292 spec.depends.uci = spec.depends.uci or {}
293 spec.depends.uci[k] = v
297 if (subnode.sysauth_authenticator ~= nil) or
298 (subnode.sysauth ~= nil and subnode.sysauth ~= false)
300 if subnode.sysauth_authenticator == "htmlauth" then
303 methods = { "cookie:sysauth" }
305 elseif subname == "rpc" and subnode.module == "luci.controller.rpc" then
308 methods = { "param:auth", "cookie:sysauth" }
310 elseif subnode.module == "luci.controller.admin.uci" then
313 methods = { "param:sid" }
316 elseif subnode.sysauth == false then
320 if not spec.action then
324 spec.satisfied = check_depends(spec)
325 json.children = json.children or {}
326 json.children[subname] = tree_to_json(subnode, spec)
333 function build_url(...)
335 local url = { http.getenv("SCRIPT_NAME") or "" }
338 for _, p in ipairs(path) do
339 if p:match("^[a-zA-Z0-9_%-%.%%/,;]+$") then
349 return table.concat(url, "")
353 function error404(message)
354 http.status(404, "Not Found")
355 message = message or "Not Found"
357 local function render()
358 local template = require "luci.template"
359 template.render("error404")
362 if not util.copcall(render) then
363 http.prepare_content("text/plain")
370 function error500(message)
372 if not context.template_header_sent then
373 http.status(500, "Internal Server Error")
374 http.prepare_content("text/plain")
377 require("luci.template")
378 if not util.copcall(luci.template.render, "error500", {message=message}) then
379 http.prepare_content("text/plain")
386 local function determine_request_language()
387 local conf = require "luci.config"
388 assert(conf.main, "/etc/config/luci seems to be corrupt, unable to find section 'main'")
390 local lang = conf.main.lang or "auto"
391 if lang == "auto" then
392 local aclang = http.getenv("HTTP_ACCEPT_LANGUAGE") or ""
393 for aclang in aclang:gmatch("[%w_-]+") do
394 local country, culture = aclang:match("^([a-z][a-z])[_-]([a-zA-Z][a-zA-Z])$")
395 if country and culture then
396 local cc = "%s_%s" %{ country, culture:lower() }
397 if conf.languages[cc] then
400 elseif conf.languages[country] then
404 elseif conf.languages[aclang] then
411 if lang == "auto" then
415 i18n.setlanguage(lang)
418 function httpdispatch(request, prefix)
419 http.context.request = request
424 local pathinfo = http.urldecode(request:getenv("PATH_INFO") or "", true)
427 for _, node in ipairs(prefix) do
433 for node in pathinfo:gmatch("[^/%z]+") do
437 determine_request_language()
439 local stat, err = util.coxpcall(function()
440 dispatch(context.request)
445 --context._disable_memtrace()
448 local function require_post_security(target, args)
449 if type(target) == "table" and target.type == "arcombine" and type(target.targets) == "table" then
450 return require_post_security((type(args) == "table" and #args > 0) and target.targets[2] or target.targets[1], args)
453 if type(target) == "table" then
454 if type(target.post) == "table" then
455 local param_name, required_val, request_val
457 for param_name, required_val in pairs(target.post) do
458 request_val = http.formvalue(param_name)
460 if (type(required_val) == "string" and
461 request_val ~= required_val) or
462 (required_val == true and request_val == nil)
471 return (target.post == true)
477 function test_post_security()
478 if http.getenv("REQUEST_METHOD") ~= "POST" then
479 http.status(405, "Method Not Allowed")
480 http.header("Allow", "POST")
484 if http.formvalue("token") ~= context.authtoken then
485 http.status(403, "Forbidden")
486 luci.template.render("csrftoken")
493 local function session_retrieve(sid, allowed_users)
494 local sdat = util.ubus("session", "get", { ubus_rpc_session = sid })
496 if type(sdat) == "table" and
497 type(sdat.values) == "table" and
498 type(sdat.values.token) == "string" and
499 (not allowed_users or
500 util.contains(allowed_users, sdat.values.username))
502 uci:set_session_id(sid)
503 return sid, sdat.values
509 local function session_setup(user, pass, allowed_users)
510 if util.contains(allowed_users, user) then
511 local login = util.ubus("session", "login", {
514 timeout = tonumber(luci.config.sauth.sessiontime)
517 local rp = context.requestpath
518 and table.concat(context.requestpath, "/") or ""
520 if type(login) == "table" and
521 type(login.ubus_rpc_session) == "string"
523 util.ubus("session", "set", {
524 ubus_rpc_session = login.ubus_rpc_session,
525 values = { token = sys.uniqueid(16) }
528 io.stderr:write("luci: accepted login on /%s for %s from %s\n"
529 %{ rp, user, http.getenv("REMOTE_ADDR") or "?" })
531 return session_retrieve(login.ubus_rpc_session)
534 io.stderr:write("luci: failed login on /%s for %s from %s\n"
535 %{ rp, user, http.getenv("REMOTE_ADDR") or "?" })
541 local function check_authentication(method)
542 local auth_type, auth_param = method:match("^(%w+):(.+)$")
545 if auth_type == "cookie" then
546 sid = http.getcookie(auth_param)
547 elseif auth_type == "param" then
548 sid = http.formvalue(auth_param)
551 return session_retrieve(sid)
554 local function get_children(node)
557 if not node.wildcard and type(node.children) == "table" then
558 for name, child in pairs(node.children) do
559 children[#children+1] = {
562 order = child.order or 1000
566 table.sort(children, function(a, b)
567 if a.order == b.order then
568 return a.name < b.name
570 return a.order < b.order
578 local function find_subnode(root, prefix, recurse, descended)
579 local children = get_children(root)
581 if #children > 0 and (not descended or recurse) then
582 local sub_path = { unpack(prefix) }
584 if recurse == false then
588 for _, child in ipairs(children) do
589 sub_path[#prefix+1] = child.name
591 local res_path = find_subnode(child.node, sub_path, recurse, true)
601 root.action.type == "cbi" or
602 root.action.type == "form" or
603 root.action.type == "view" or
604 root.action.type == "template" or
605 root.action.type == "arcombine"
612 local function merge_trees(node_a, node_b)
613 for k, v in pairs(node_b) do
614 if k == "children" then
615 node_a.children = node_a.children or {}
617 for name, spec in pairs(v) do
618 node_a.children[name] = merge_trees(node_a.children[name] or {}, spec)
628 local tree = context.tree or createtree()
629 local lua_tree = tree_to_json(tree, {
631 ["type"] = "firstchild",
636 local json_tree = createtree_json()
637 return merge_trees(lua_tree, json_tree)
640 local function init_template_engine(ctx)
641 local tpl = require "luci.template"
642 local media = luci.config.main.mediaurlbase
644 if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then
646 for name, theme in pairs(luci.config.themes) do
647 if name:sub(1,1) ~= "." and pcall(tpl.Template,
648 "themes/%s/header" % fs.basename(theme)) then
652 assert(media, "No valid theme found")
655 local function _ifattr(cond, key, val, noescape)
657 local env = getfenv(3)
658 local scope = (type(env.self) == "table") and env.self
659 if type(val) == "table" then
660 if not next(val) then
663 val = util.serialize_json(val)
667 val = tostring(val or
668 (type(env[key]) ~= "function" and env[key]) or
669 (scope and type(scope[key]) ~= "function" and scope[key]) or "")
671 if noescape ~= true then
672 val = util.pcdata(val)
675 return string.format(' %s="%s"', tostring(key), val)
681 tpl.context.viewns = setmetatable({
683 include = function(name) tpl.Template(name):render(getfenv(2)) end;
684 translate = i18n.translate;
685 translatef = i18n.translatef;
686 export = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end;
687 striptags = util.striptags;
688 pcdata = util.pcdata;
690 theme = fs.basename(media);
691 resource = luci.config.main.resourcebase;
692 ifattr = function(...) return _ifattr(...) end;
693 attr = function(...) return _ifattr(true, ...) end;
695 }, {__index=function(tbl, key)
696 if key == "controller" then
698 elseif key == "REQUEST_URI" then
699 return build_url(unpack(ctx.requestpath))
700 elseif key == "FULL_REQUEST_URI" then
701 local url = { http.getenv("SCRIPT_NAME") or "", http.getenv("PATH_INFO") }
702 local query = http.getenv("QUERY_STRING")
703 if query and #query > 0 then
707 return table.concat(url, "")
708 elseif key == "token" then
711 return rawget(tbl, key) or _G[key]
718 function dispatch(request)
719 --context._disable_memtrace = require "luci.debug".trap_memtrace("l")
722 local auth, cors, suid, sgid
723 local menu = menu_json()
726 local requested_path_full = {}
727 local requested_path_node = {}
728 local requested_path_args = {}
730 for i, s in ipairs(request) do
731 if type(page.children) ~= "table" or not page.children[s] then
736 if not page.children[s].satisfied then
741 page = page.children[s]
742 auth = page.auth or auth
743 cors = page.cors or cors
744 suid = page.setuser or suid
745 sgid = page.setgroup or sgid
747 requested_path_full[i] = s
748 requested_path_node[i] = s
750 if page.wildcard then
751 for j = i + 1, #request do
752 requested_path_args[j - i] = request[j]
753 requested_path_full[j] = request[j]
759 local tpl = init_template_engine(ctx)
761 ctx.args = requested_path_args
762 ctx.path = requested_path_node
763 ctx.dispatched = page
765 ctx.requestpath = ctx.requestpath or requested_path_full
766 ctx.requestargs = ctx.requestargs or requested_path_args
767 ctx.requested = ctx.requested or page
769 if type(auth) == "table" and type(auth.methods) == "table" and #auth.methods > 0 then
771 for _, method in ipairs(auth.methods) do
772 sid, sdat = check_authentication(method)
779 if not (sid and sdat) and auth.login then
780 local user = http.getenv("HTTP_AUTH_USER")
781 local pass = http.getenv("HTTP_AUTH_PASS")
783 if user == nil and pass == nil then
784 user = http.formvalue("luci_username")
785 pass = http.formvalue("luci_password")
788 sid, sdat = session_setup(user, pass, { "root" })
793 http.status(403, "Forbidden")
794 http.header("X-LuCI-Login-Required", "yes")
796 return tpl.render("sysauth", { duser = "root", fuser = user })
799 http.header("Set-Cookie", 'sysauth=%s; path=%s; HttpOnly%s' %{
800 sid, build_url(), http.getenv("HTTPS") == "on" and "; secure" or ""
803 http.redirect(build_url(unpack(ctx.requestpath)))
807 if not sid or not sdat then
808 http.status(403, "Forbidden")
809 http.header("X-LuCI-Login-Required", "yes")
813 ctx.authsession = sid
814 ctx.authtoken = sdat.token
815 ctx.authuser = sdat.username
818 local action = (page and type(page.action) == "table") and page.action or {}
820 if action.type == "arcombine" then
821 action = (#requested_path_args > 0) and action.targets[2] or action.targets[1]
824 if cors and http.getenv("REQUEST_METHOD") == "OPTIONS" then
825 luci.http.status(200, "OK")
826 luci.http.header("Access-Control-Allow-Origin", http.getenv("HTTP_ORIGIN") or "*")
827 luci.http.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
831 if require_post_security(action) then
832 if not test_post_security() then
838 sys.process.setgroup(sgid)
842 sys.process.setuser(suid)
845 if action.type == "view" then
846 tpl.render("view", { view = action.path })
848 elseif action.type == "call" then
849 local ok, mod = util.copcall(require, action.module)
855 local func = mod[action["function"]]
858 'Cannot resolve function "' .. action["function"] .. '". Is it misspelled or local?')
860 assert(type(func) == "function",
861 'The symbol "' .. action["function"] .. '" does not refer to a function but data ' ..
862 'of type "' .. type(func) .. '".')
864 local argv = (type(action.parameters) == "table" and #action.parameters > 0) and { unpack(action.parameters) } or {}
865 for _, s in ipairs(requested_path_args) do
869 local ok, err = util.copcall(func, unpack(argv))
874 elseif action.type == "firstchild" then
875 local sub_request = find_subnode(page, requested_path_full, action.recurse)
877 dispatch(sub_request)
879 tpl.render("empty_node_placeholder", getfenv(1))
882 elseif action.type == "alias" then
883 local sub_request = {}
884 for name in action.path:gmatch("[^/]+") do
885 sub_request[#sub_request + 1] = name
888 for _, s in ipairs(requested_path_args) do
889 sub_request[#sub_request + 1] = s
892 dispatch(sub_request)
894 elseif action.type == "rewrite" then
895 local sub_request = { unpack(request) }
896 for i = 1, action.remove do
897 table.remove(sub_request, 1)
901 for s in action.path:gmatch("[^/]+") do
902 table.insert(sub_request, n, s)
906 for _, s in ipairs(requested_path_args) do
907 sub_request[#sub_request + 1] = s
910 dispatch(sub_request)
912 elseif action.type == "template" then
913 tpl.render(action.path, getfenv(1))
915 elseif action.type == "cbi" then
916 _cbi({ config = action.config, model = action.path }, unpack(requested_path_args))
918 elseif action.type == "form" then
919 _form({ model = action.path }, unpack(requested_path_args))
922 local root = find_subnode(menu, {}, true)
924 error404("No root node was registered, this usually happens if no module was installed.\n" ..
925 "Install luci-mod-admin-full and retry. " ..
926 "If the module is already installed, try removing the /tmp/luci-indexcache file.")
928 error404("No page is registered at '/" .. table.concat(requested_path_full, "/") .. "'.\n" ..
929 "If this url belongs to an extension, make sure it is properly installed.\n" ..
930 "If the extension was recently installed, try removing the /tmp/luci-indexcache file.")
935 function createindex()
936 local controllers = { }
937 local base = "%s/controller/" % util.libpath()
940 for path in (fs.glob("%s*.lua" % base) or function() end) do
941 controllers[#controllers+1] = path
944 for path in (fs.glob("%s*/*.lua" % base) or function() end) do
945 controllers[#controllers+1] = path
949 local cachedate = fs.stat(indexcache, "mtime")
952 for _, obj in ipairs(controllers) do
953 local omtime = fs.stat(obj, "mtime")
954 realdate = (omtime and omtime > realdate) and omtime or realdate
957 if cachedate > realdate and sys.process.info("uid") == 0 then
959 sys.process.info("uid") == fs.stat(indexcache, "uid")
960 and fs.stat(indexcache, "modestr") == "rw-------",
961 "Fatal: Indexcache is not sane!"
964 index = loadfile(indexcache)()
972 for _, path in ipairs(controllers) do
973 local modname = "luci.controller." .. path:sub(#base+1, #path-4):gsub("/", ".")
974 local mod = require(modname)
976 "Invalid controller file found\n" ..
977 "The file '" .. path .. "' contains an invalid module line.\n" ..
978 "Please verify whether the module name is set to '" .. modname ..
979 "' - It must correspond to the file path!")
981 local idx = mod.index
982 if type(idx) == "function" then
988 local f = nixio.open(indexcache, "w", 600)
989 f:writeall(util.get_bytecode(index))
994 function createtree_json()
995 local json = require "luci.jsonc"
1004 setgroup = "string",
1007 wildcard = "boolean"
1014 for file in (fs.glob("/usr/share/luci/menu.d/*.json") or function() end) do
1015 files[#files+1] = file
1018 local st = fs.stat(file)
1020 fprint[#fprint+1] = '%x' % st.ino
1021 fprint[#fprint+1] = '%x' % st.mtime
1022 fprint[#fprint+1] = '%x' % st.size
1028 cachefile = "%s.%s.json" %{
1030 nixio.crypt(table.concat(fprint, "|"), "$1$"):sub(5):gsub("/", ".")
1033 local res = json.parse(fs.readfile(cachefile) or "")
1038 for file in (fs.glob("%s.*.json" % indexcache) or function() end) do
1043 for _, file in ipairs(files) do
1044 local data = json.parse(fs.readfile(file) or "")
1045 if type(data) == "table" then
1046 for path, spec in pairs(data) do
1047 if type(spec) == "table" then
1050 for s in path:gmatch("[^/]+") do
1052 node.wildcard = true
1056 node.children = node.children or {}
1057 node.children[s] = node.children[s] or {}
1058 node = node.children[s]
1061 if node ~= tree then
1062 for k, t in pairs(schema) do
1063 if type(spec[k]) == t then
1068 node.satisfied = check_depends(spec)
1076 fs.writefile(cachefile, json.stringify(tree))
1082 -- Build the index before if it does not exist yet.
1083 function createtree()
1089 local tree = {nodes={}, inreq=true}
1091 ctx.treecache = setmetatable({}, {__mode="v"})
1094 local scope = setmetatable({}, {__index = luci.dispatcher})
1096 for k, v in pairs(index) do
1105 function assign(path, clone, title, order)
1106 local obj = node(unpack(path))
1113 setmetatable(obj, {__index = _create_node(clone)})
1118 function entry(path, target, title, order)
1119 local c = node(unpack(path))
1124 c.module = getfenv(2)._NAME
1129 -- enabling the node.
1131 return _create_node({...})
1135 local c = _create_node({...})
1137 c.module = getfenv(2)._NAME
1143 function lookup(...)
1144 local i, path = nil, {}
1145 for i = 1, select('#', ...) do
1146 local name, arg = nil, tostring(select(i, ...))
1147 for name in arg:gmatch("[^/]+") do
1148 path[#path+1] = name
1152 for i = #path, 1, -1 do
1153 local node = context.treecache[table.concat(path, ".", 1, i)]
1154 if node and (i == #path or node.leaf) then
1155 return node, build_url(unpack(path))
1160 function _create_node(path)
1165 local name = table.concat(path, ".")
1166 local c = context.treecache[name]
1169 local last = table.remove(path)
1170 local parent = _create_node(path)
1172 c = {nodes={}, auto=true, inreq=true}
1174 parent.nodes[last] = c
1175 context.treecache[name] = c
1181 -- Subdispatchers --
1183 function firstchild()
1184 return { type = "firstchild" }
1187 function firstnode()
1188 return { type = "firstnode" }
1192 return { type = "alias", req = { ... } }
1195 function rewrite(n, ...)
1196 return { type = "rewrite", n = n, req = { ... } }
1199 function call(name, ...)
1200 return { type = "call", argv = {...}, name = name }
1203 function post_on(params, name, ...)
1213 return post_on(true, ...)
1217 function template(name)
1218 return { type = "template", view = name }
1222 return { type = "view", view = name }
1226 function _cbi(self, ...)
1227 local cbi = require "luci.cbi"
1228 local tpl = require "luci.template"
1229 local http = require "luci.http"
1231 local config = self.config or {}
1232 local maps = cbi.load(self.model, ...)
1237 for i, res in ipairs(maps) do
1238 if util.instanceof(res, cbi.SimpleForm) then
1239 io.stderr:write("Model %s returns SimpleForm but is dispatched via cbi(),\n"
1242 io.stderr:write("please change %s to use the form() action instead.\n"
1243 % table.concat(context.request, "/"))
1247 local cstate = res:parse()
1248 if cstate and (not state or cstate < state) then
1253 local function _resolve_path(path)
1254 return type(path) == "table" and build_url(unpack(path)) or path
1257 if config.on_valid_to and state and state > 0 and state < 2 then
1258 http.redirect(_resolve_path(config.on_valid_to))
1262 if config.on_changed_to and state and state > 1 then
1263 http.redirect(_resolve_path(config.on_changed_to))
1267 if config.on_success_to and state and state > 0 then
1268 http.redirect(_resolve_path(config.on_success_to))
1272 if config.state_handler then
1273 if not config.state_handler(state, maps) then
1278 http.header("X-CBI-State", state or 0)
1280 if not config.noheader then
1281 tpl.render("cbi/header", {state = state})
1286 local applymap = false
1287 local pageaction = true
1288 local parsechain = { }
1290 for i, res in ipairs(maps) do
1291 if res.apply_needed and res.parsechain then
1293 for _, c in ipairs(res.parsechain) do
1294 parsechain[#parsechain+1] = c
1299 if res.redirect then
1300 redirect = redirect or res.redirect
1303 if res.pageaction == false then
1308 messages = messages or { }
1309 messages[#messages+1] = res.message
1313 for i, res in ipairs(maps) do
1315 firstmap = (i == 1),
1316 redirect = redirect,
1317 messages = messages,
1318 pageaction = pageaction,
1319 parsechain = parsechain
1323 if not config.nofooter then
1324 tpl.render("cbi/footer", {
1326 pageaction = pageaction,
1327 redirect = redirect,
1329 autoapply = config.autoapply,
1330 trigger_apply = applymap
1335 function cbi(model, config)
1338 post = { ["cbi.submit"] = true },
1345 function arcombine(trg1, trg2)
1349 targets = {trg1, trg2}
1354 function _form(self, ...)
1355 local cbi = require "luci.cbi"
1356 local tpl = require "luci.template"
1357 local http = require "luci.http"
1359 local maps = luci.cbi.load(self.model, ...)
1363 for i, res in ipairs(maps) do
1364 local cstate = res:parse()
1365 if cstate and (not state or cstate < state) then
1370 http.header("X-CBI-State", state or 0)
1371 tpl.render("header")
1372 for i, res in ipairs(maps) do
1375 tpl.render("footer")
1378 function form(model)
1381 post = { ["cbi.submit"] = true },
1386 translate = i18n.translate
1388 -- This function does not actually translate the given argument but
1389 -- is used by build/i18n-scan.pl to find translatable entries.