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 = { "query: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)
549 elseif auth_type == "query" then
550 sid = http.formvalue(auth_param, true)
553 return session_retrieve(sid)
556 local function get_children(node)
559 if not node.wildcard and type(node.children) == "table" then
560 for name, child in pairs(node.children) do
561 children[#children+1] = {
564 order = child.order or 1000
568 table.sort(children, function(a, b)
569 if a.order == b.order then
570 return a.name < b.name
572 return a.order < b.order
580 local function find_subnode(root, prefix, recurse, descended)
581 local children = get_children(root)
583 if #children > 0 and (not descended or recurse) then
584 local sub_path = { unpack(prefix) }
586 if recurse == false then
590 for _, child in ipairs(children) do
591 sub_path[#prefix+1] = child.name
593 local res_path = find_subnode(child.node, sub_path, recurse, true)
603 root.action.type == "cbi" or
604 root.action.type == "form" or
605 root.action.type == "view" or
606 root.action.type == "template" or
607 root.action.type == "arcombine"
614 local function merge_trees(node_a, node_b)
615 for k, v in pairs(node_b) do
616 if k == "children" then
617 node_a.children = node_a.children or {}
619 for name, spec in pairs(v) do
620 node_a.children[name] = merge_trees(node_a.children[name] or {}, spec)
630 local tree = context.tree or createtree()
631 local lua_tree = tree_to_json(tree, {
633 ["type"] = "firstchild",
638 local json_tree = createtree_json()
639 return merge_trees(lua_tree, json_tree)
642 local function init_template_engine(ctx)
643 local tpl = require "luci.template"
644 local media = luci.config.main.mediaurlbase
646 if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then
648 for name, theme in pairs(luci.config.themes) do
649 if name:sub(1,1) ~= "." and pcall(tpl.Template,
650 "themes/%s/header" % fs.basename(theme)) then
654 assert(media, "No valid theme found")
657 local function _ifattr(cond, key, val, noescape)
659 local env = getfenv(3)
660 local scope = (type(env.self) == "table") and env.self
661 if type(val) == "table" then
662 if not next(val) then
665 val = util.serialize_json(val)
669 val = tostring(val or
670 (type(env[key]) ~= "function" and env[key]) or
671 (scope and type(scope[key]) ~= "function" and scope[key]) or "")
673 if noescape ~= true then
674 val = util.pcdata(val)
677 return string.format(' %s="%s"', tostring(key), val)
683 tpl.context.viewns = setmetatable({
685 include = function(name) tpl.Template(name):render(getfenv(2)) end;
686 translate = i18n.translate;
687 translatef = i18n.translatef;
688 export = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end;
689 striptags = util.striptags;
690 pcdata = util.pcdata;
692 theme = fs.basename(media);
693 resource = luci.config.main.resourcebase;
694 ifattr = function(...) return _ifattr(...) end;
695 attr = function(...) return _ifattr(true, ...) end;
697 }, {__index=function(tbl, key)
698 if key == "controller" then
700 elseif key == "REQUEST_URI" then
701 return build_url(unpack(ctx.requestpath))
702 elseif key == "FULL_REQUEST_URI" then
703 local url = { http.getenv("SCRIPT_NAME") or "", http.getenv("PATH_INFO") }
704 local query = http.getenv("QUERY_STRING")
705 if query and #query > 0 then
709 return table.concat(url, "")
710 elseif key == "token" then
713 return rawget(tbl, key) or _G[key]
720 function dispatch(request)
721 --context._disable_memtrace = require "luci.debug".trap_memtrace("l")
724 local auth, cors, suid, sgid
725 local menu = menu_json()
728 local requested_path_full = {}
729 local requested_path_node = {}
730 local requested_path_args = {}
732 for i, s in ipairs(request) do
733 if type(page.children) ~= "table" or not page.children[s] then
738 if not page.children[s].satisfied then
743 page = page.children[s]
744 auth = page.auth or auth
745 cors = page.cors or cors
746 suid = page.setuser or suid
747 sgid = page.setgroup or sgid
749 requested_path_full[i] = s
750 requested_path_node[i] = s
752 if page.wildcard then
753 for j = i + 1, #request do
754 requested_path_args[j - i] = request[j]
755 requested_path_full[j] = request[j]
761 local tpl = init_template_engine(ctx)
763 ctx.args = requested_path_args
764 ctx.path = requested_path_node
765 ctx.dispatched = page
767 ctx.requestpath = ctx.requestpath or requested_path_full
768 ctx.requestargs = ctx.requestargs or requested_path_args
769 ctx.requested = ctx.requested or page
771 if type(auth) == "table" and type(auth.methods) == "table" and #auth.methods > 0 then
773 for _, method in ipairs(auth.methods) do
774 sid, sdat = check_authentication(method)
781 if not (sid and sdat) and auth.login then
782 local user = http.getenv("HTTP_AUTH_USER")
783 local pass = http.getenv("HTTP_AUTH_PASS")
785 if user == nil and pass == nil then
786 user = http.formvalue("luci_username")
787 pass = http.formvalue("luci_password")
790 sid, sdat = session_setup(user, pass, { "root" })
795 http.status(403, "Forbidden")
796 http.header("X-LuCI-Login-Required", "yes")
798 return tpl.render("sysauth", { duser = "root", fuser = user })
801 http.header("Set-Cookie", 'sysauth=%s; path=%s; HttpOnly%s' %{
802 sid, build_url(), http.getenv("HTTPS") == "on" and "; secure" or ""
805 http.redirect(build_url(unpack(ctx.requestpath)))
809 if not sid or not sdat then
810 http.status(403, "Forbidden")
811 http.header("X-LuCI-Login-Required", "yes")
815 ctx.authsession = sid
816 ctx.authtoken = sdat.token
817 ctx.authuser = sdat.username
820 local action = (page and type(page.action) == "table") and page.action or {}
822 if action.type == "arcombine" then
823 action = (#requested_path_args > 0) and action.targets[2] or action.targets[1]
826 if cors and http.getenv("REQUEST_METHOD") == "OPTIONS" then
827 luci.http.status(200, "OK")
828 luci.http.header("Access-Control-Allow-Origin", http.getenv("HTTP_ORIGIN") or "*")
829 luci.http.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
833 if require_post_security(action) then
834 if not test_post_security() then
840 sys.process.setgroup(sgid)
844 sys.process.setuser(suid)
847 if action.type == "view" then
848 tpl.render("view", { view = action.path })
850 elseif action.type == "call" then
851 local ok, mod = util.copcall(require, action.module)
857 local func = mod[action["function"]]
860 'Cannot resolve function "' .. action["function"] .. '". Is it misspelled or local?')
862 assert(type(func) == "function",
863 'The symbol "' .. action["function"] .. '" does not refer to a function but data ' ..
864 'of type "' .. type(func) .. '".')
866 local argv = (type(action.parameters) == "table" and #action.parameters > 0) and { unpack(action.parameters) } or {}
867 for _, s in ipairs(requested_path_args) do
871 local ok, err = util.copcall(func, unpack(argv))
876 elseif action.type == "firstchild" then
877 local sub_request = find_subnode(page, requested_path_full, action.recurse)
879 dispatch(sub_request)
881 tpl.render("empty_node_placeholder", getfenv(1))
884 elseif action.type == "alias" then
885 local sub_request = {}
886 for name in action.path:gmatch("[^/]+") do
887 sub_request[#sub_request + 1] = name
890 for _, s in ipairs(requested_path_args) do
891 sub_request[#sub_request + 1] = s
894 dispatch(sub_request)
896 elseif action.type == "rewrite" then
897 local sub_request = { unpack(request) }
898 for i = 1, action.remove do
899 table.remove(sub_request, 1)
903 for s in action.path:gmatch("[^/]+") do
904 table.insert(sub_request, n, s)
908 for _, s in ipairs(requested_path_args) do
909 sub_request[#sub_request + 1] = s
912 dispatch(sub_request)
914 elseif action.type == "template" then
915 tpl.render(action.path, getfenv(1))
917 elseif action.type == "cbi" then
918 _cbi({ config = action.config, model = action.path }, unpack(requested_path_args))
920 elseif action.type == "form" then
921 _form({ model = action.path }, unpack(requested_path_args))
924 local root = find_subnode(menu, {}, true)
926 error404("No root node was registered, this usually happens if no module was installed.\n" ..
927 "Install luci-mod-admin-full and retry. " ..
928 "If the module is already installed, try removing the /tmp/luci-indexcache file.")
930 error404("No page is registered at '/" .. table.concat(requested_path_full, "/") .. "'.\n" ..
931 "If this url belongs to an extension, make sure it is properly installed.\n" ..
932 "If the extension was recently installed, try removing the /tmp/luci-indexcache file.")
937 function createindex()
938 local controllers = { }
939 local base = "%s/controller/" % util.libpath()
942 for path in (fs.glob("%s*.lua" % base) or function() end) do
943 controllers[#controllers+1] = path
946 for path in (fs.glob("%s*/*.lua" % base) or function() end) do
947 controllers[#controllers+1] = path
951 local cachedate = fs.stat(indexcache, "mtime")
954 for _, obj in ipairs(controllers) do
955 local omtime = fs.stat(obj, "mtime")
956 realdate = (omtime and omtime > realdate) and omtime or realdate
959 if cachedate > realdate and sys.process.info("uid") == 0 then
961 sys.process.info("uid") == fs.stat(indexcache, "uid")
962 and fs.stat(indexcache, "modestr") == "rw-------",
963 "Fatal: Indexcache is not sane!"
966 index = loadfile(indexcache)()
974 for _, path in ipairs(controllers) do
975 local modname = "luci.controller." .. path:sub(#base+1, #path-4):gsub("/", ".")
976 local mod = require(modname)
978 "Invalid controller file found\n" ..
979 "The file '" .. path .. "' contains an invalid module line.\n" ..
980 "Please verify whether the module name is set to '" .. modname ..
981 "' - It must correspond to the file path!")
983 local idx = mod.index
984 if type(idx) == "function" then
990 local f = nixio.open(indexcache, "w", 600)
991 f:writeall(util.get_bytecode(index))
996 function createtree_json()
997 local json = require "luci.jsonc"
1006 setgroup = "string",
1009 wildcard = "boolean"
1016 for file in (fs.glob("/usr/share/luci/menu.d/*.json") or function() end) do
1017 files[#files+1] = file
1020 local st = fs.stat(file)
1022 fprint[#fprint+1] = '%x' % st.ino
1023 fprint[#fprint+1] = '%x' % st.mtime
1024 fprint[#fprint+1] = '%x' % st.size
1030 cachefile = "%s.%s.json" %{
1032 nixio.crypt(table.concat(fprint, "|"), "$1$"):sub(5):gsub("/", ".")
1035 local res = json.parse(fs.readfile(cachefile) or "")
1040 for file in (fs.glob("%s.*.json" % indexcache) or function() end) do
1045 for _, file in ipairs(files) do
1046 local data = json.parse(fs.readfile(file) or "")
1047 if type(data) == "table" then
1048 for path, spec in pairs(data) do
1049 if type(spec) == "table" then
1052 for s in path:gmatch("[^/]+") do
1054 node.wildcard = true
1058 node.children = node.children or {}
1059 node.children[s] = node.children[s] or {}
1060 node = node.children[s]
1063 if node ~= tree then
1064 for k, t in pairs(schema) do
1065 if type(spec[k]) == t then
1070 node.satisfied = check_depends(spec)
1078 fs.writefile(cachefile, json.stringify(tree))
1084 -- Build the index before if it does not exist yet.
1085 function createtree()
1091 local tree = {nodes={}, inreq=true}
1093 ctx.treecache = setmetatable({}, {__mode="v"})
1096 local scope = setmetatable({}, {__index = luci.dispatcher})
1098 for k, v in pairs(index) do
1107 function assign(path, clone, title, order)
1108 local obj = node(unpack(path))
1115 setmetatable(obj, {__index = _create_node(clone)})
1120 function entry(path, target, title, order)
1121 local c = node(unpack(path))
1126 c.module = getfenv(2)._NAME
1131 -- enabling the node.
1133 return _create_node({...})
1137 local c = _create_node({...})
1139 c.module = getfenv(2)._NAME
1145 function lookup(...)
1146 local i, path = nil, {}
1147 for i = 1, select('#', ...) do
1148 local name, arg = nil, tostring(select(i, ...))
1149 for name in arg:gmatch("[^/]+") do
1150 path[#path+1] = name
1154 for i = #path, 1, -1 do
1155 local node = context.treecache[table.concat(path, ".", 1, i)]
1156 if node and (i == #path or node.leaf) then
1157 return node, build_url(unpack(path))
1162 function _create_node(path)
1167 local name = table.concat(path, ".")
1168 local c = context.treecache[name]
1171 local last = table.remove(path)
1172 local parent = _create_node(path)
1174 c = {nodes={}, auto=true, inreq=true}
1176 parent.nodes[last] = c
1177 context.treecache[name] = c
1183 -- Subdispatchers --
1185 function firstchild()
1186 return { type = "firstchild" }
1189 function firstnode()
1190 return { type = "firstnode" }
1194 return { type = "alias", req = { ... } }
1197 function rewrite(n, ...)
1198 return { type = "rewrite", n = n, req = { ... } }
1201 function call(name, ...)
1202 return { type = "call", argv = {...}, name = name }
1205 function post_on(params, name, ...)
1215 return post_on(true, ...)
1219 function template(name)
1220 return { type = "template", view = name }
1224 return { type = "view", view = name }
1228 function _cbi(self, ...)
1229 local cbi = require "luci.cbi"
1230 local tpl = require "luci.template"
1231 local http = require "luci.http"
1233 local config = self.config or {}
1234 local maps = cbi.load(self.model, ...)
1239 for i, res in ipairs(maps) do
1240 if util.instanceof(res, cbi.SimpleForm) then
1241 io.stderr:write("Model %s returns SimpleForm but is dispatched via cbi(),\n"
1244 io.stderr:write("please change %s to use the form() action instead.\n"
1245 % table.concat(context.request, "/"))
1249 local cstate = res:parse()
1250 if cstate and (not state or cstate < state) then
1255 local function _resolve_path(path)
1256 return type(path) == "table" and build_url(unpack(path)) or path
1259 if config.on_valid_to and state and state > 0 and state < 2 then
1260 http.redirect(_resolve_path(config.on_valid_to))
1264 if config.on_changed_to and state and state > 1 then
1265 http.redirect(_resolve_path(config.on_changed_to))
1269 if config.on_success_to and state and state > 0 then
1270 http.redirect(_resolve_path(config.on_success_to))
1274 if config.state_handler then
1275 if not config.state_handler(state, maps) then
1280 http.header("X-CBI-State", state or 0)
1282 if not config.noheader then
1283 tpl.render("cbi/header", {state = state})
1288 local applymap = false
1289 local pageaction = true
1290 local parsechain = { }
1292 for i, res in ipairs(maps) do
1293 if res.apply_needed and res.parsechain then
1295 for _, c in ipairs(res.parsechain) do
1296 parsechain[#parsechain+1] = c
1301 if res.redirect then
1302 redirect = redirect or res.redirect
1305 if res.pageaction == false then
1310 messages = messages or { }
1311 messages[#messages+1] = res.message
1315 for i, res in ipairs(maps) do
1317 firstmap = (i == 1),
1318 redirect = redirect,
1319 messages = messages,
1320 pageaction = pageaction,
1321 parsechain = parsechain
1325 if not config.nofooter then
1326 tpl.render("cbi/footer", {
1328 pageaction = pageaction,
1329 redirect = redirect,
1331 autoapply = config.autoapply,
1332 trigger_apply = applymap
1337 function cbi(model, config)
1340 post = { ["cbi.submit"] = true },
1347 function arcombine(trg1, trg2)
1351 targets = {trg1, trg2}
1356 function _form(self, ...)
1357 local cbi = require "luci.cbi"
1358 local tpl = require "luci.template"
1359 local http = require "luci.http"
1361 local maps = luci.cbi.load(self.model, ...)
1365 for i, res in ipairs(maps) do
1366 local cstate = res:parse()
1367 if cstate and (not state or cstate < state) then
1372 http.header("X-CBI-State", state or 0)
1373 tpl.render("header")
1374 for i, res in ipairs(maps) do
1377 tpl.render("footer")
1380 function form(model)
1383 post = { ["cbi.submit"] = true },
1388 translate = i18n.translate
1390 -- This function does not actually translate the given argument but
1391 -- is used by build/i18n-scan.pl to find translatable entries.