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)
627 if type(node_a.action) == "table" and
628 node_a.action.type == "firstchild" and
629 node_a.children == nil
631 node_a.satisfied = false
638 local tree = context.tree or createtree()
639 local lua_tree = tree_to_json(tree, {
641 ["type"] = "firstchild",
646 local json_tree = createtree_json()
647 return merge_trees(lua_tree, json_tree)
650 local function init_template_engine(ctx)
651 local tpl = require "luci.template"
652 local media = luci.config.main.mediaurlbase
654 if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then
656 for name, theme in pairs(luci.config.themes) do
657 if name:sub(1,1) ~= "." and pcall(tpl.Template,
658 "themes/%s/header" % fs.basename(theme)) then
662 assert(media, "No valid theme found")
665 local function _ifattr(cond, key, val, noescape)
667 local env = getfenv(3)
668 local scope = (type(env.self) == "table") and env.self
669 if type(val) == "table" then
670 if not next(val) then
673 val = util.serialize_json(val)
677 val = tostring(val or
678 (type(env[key]) ~= "function" and env[key]) or
679 (scope and type(scope[key]) ~= "function" and scope[key]) or "")
681 if noescape ~= true then
682 val = util.pcdata(val)
685 return string.format(' %s="%s"', tostring(key), val)
691 tpl.context.viewns = setmetatable({
693 include = function(name) tpl.Template(name):render(getfenv(2)) end;
694 translate = i18n.translate;
695 translatef = i18n.translatef;
696 export = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end;
697 striptags = util.striptags;
698 pcdata = util.pcdata;
700 theme = fs.basename(media);
701 resource = luci.config.main.resourcebase;
702 ifattr = function(...) return _ifattr(...) end;
703 attr = function(...) return _ifattr(true, ...) end;
705 }, {__index=function(tbl, key)
706 if key == "controller" then
708 elseif key == "REQUEST_URI" then
709 return build_url(unpack(ctx.requestpath))
710 elseif key == "FULL_REQUEST_URI" then
711 local url = { http.getenv("SCRIPT_NAME") or "", http.getenv("PATH_INFO") }
712 local query = http.getenv("QUERY_STRING")
713 if query and #query > 0 then
717 return table.concat(url, "")
718 elseif key == "token" then
721 return rawget(tbl, key) or _G[key]
728 function dispatch(request)
729 --context._disable_memtrace = require "luci.debug".trap_memtrace("l")
732 local auth, cors, suid, sgid
733 local menu = menu_json()
736 local requested_path_full = {}
737 local requested_path_node = {}
738 local requested_path_args = {}
740 for i, s in ipairs(request) do
741 if type(page.children) ~= "table" or not page.children[s] then
746 if not page.children[s].satisfied then
751 page = page.children[s]
752 auth = page.auth or auth
753 cors = page.cors or cors
754 suid = page.setuser or suid
755 sgid = page.setgroup or sgid
757 requested_path_full[i] = s
758 requested_path_node[i] = s
760 if page.wildcard then
761 for j = i + 1, #request do
762 requested_path_args[j - i] = request[j]
763 requested_path_full[j] = request[j]
769 local tpl = init_template_engine(ctx)
771 ctx.args = requested_path_args
772 ctx.path = requested_path_node
773 ctx.dispatched = page
775 ctx.requestpath = ctx.requestpath or requested_path_full
776 ctx.requestargs = ctx.requestargs or requested_path_args
777 ctx.requested = ctx.requested or page
779 if type(auth) == "table" and type(auth.methods) == "table" and #auth.methods > 0 then
781 for _, method in ipairs(auth.methods) do
782 sid, sdat = check_authentication(method)
789 if not (sid and sdat) and auth.login then
790 local user = http.getenv("HTTP_AUTH_USER")
791 local pass = http.getenv("HTTP_AUTH_PASS")
793 if user == nil and pass == nil then
794 user = http.formvalue("luci_username")
795 pass = http.formvalue("luci_password")
798 sid, sdat = session_setup(user, pass, { "root" })
803 http.status(403, "Forbidden")
804 http.header("X-LuCI-Login-Required", "yes")
806 return tpl.render("sysauth", { duser = "root", fuser = user })
809 http.header("Set-Cookie", 'sysauth=%s; path=%s; HttpOnly%s' %{
810 sid, build_url(), http.getenv("HTTPS") == "on" and "; secure" or ""
813 http.redirect(build_url(unpack(ctx.requestpath)))
817 if not sid or not sdat then
818 http.status(403, "Forbidden")
819 http.header("X-LuCI-Login-Required", "yes")
823 ctx.authsession = sid
824 ctx.authtoken = sdat.token
825 ctx.authuser = sdat.username
828 local action = (page and type(page.action) == "table") and page.action or {}
830 if action.type == "arcombine" then
831 action = (#requested_path_args > 0) and action.targets[2] or action.targets[1]
834 if cors and http.getenv("REQUEST_METHOD") == "OPTIONS" then
835 luci.http.status(200, "OK")
836 luci.http.header("Access-Control-Allow-Origin", http.getenv("HTTP_ORIGIN") or "*")
837 luci.http.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
841 if require_post_security(action) then
842 if not test_post_security() then
848 sys.process.setgroup(sgid)
852 sys.process.setuser(suid)
855 if action.type == "view" then
856 tpl.render("view", { view = action.path })
858 elseif action.type == "call" then
859 local ok, mod = util.copcall(require, action.module)
865 local func = mod[action["function"]]
868 'Cannot resolve function "' .. action["function"] .. '". Is it misspelled or local?')
870 assert(type(func) == "function",
871 'The symbol "' .. action["function"] .. '" does not refer to a function but data ' ..
872 'of type "' .. type(func) .. '".')
874 local argv = (type(action.parameters) == "table" and #action.parameters > 0) and { unpack(action.parameters) } or {}
875 for _, s in ipairs(requested_path_args) do
879 local ok, err = util.copcall(func, unpack(argv))
884 elseif action.type == "firstchild" then
885 local sub_request = find_subnode(page, requested_path_full, action.recurse)
887 dispatch(sub_request)
889 tpl.render("empty_node_placeholder", getfenv(1))
892 elseif action.type == "alias" then
893 local sub_request = {}
894 for name in action.path:gmatch("[^/]+") do
895 sub_request[#sub_request + 1] = name
898 for _, s in ipairs(requested_path_args) do
899 sub_request[#sub_request + 1] = s
902 dispatch(sub_request)
904 elseif action.type == "rewrite" then
905 local sub_request = { unpack(request) }
906 for i = 1, action.remove do
907 table.remove(sub_request, 1)
911 for s in action.path:gmatch("[^/]+") do
912 table.insert(sub_request, n, s)
916 for _, s in ipairs(requested_path_args) do
917 sub_request[#sub_request + 1] = s
920 dispatch(sub_request)
922 elseif action.type == "template" then
923 tpl.render(action.path, getfenv(1))
925 elseif action.type == "cbi" then
926 _cbi({ config = action.config, model = action.path }, unpack(requested_path_args))
928 elseif action.type == "form" then
929 _form({ model = action.path }, unpack(requested_path_args))
932 local root = find_subnode(menu, {}, true)
934 error404("No root node was registered, this usually happens if no module was installed.\n" ..
935 "Install luci-mod-admin-full and retry. " ..
936 "If the module is already installed, try removing the /tmp/luci-indexcache file.")
938 error404("No page is registered at '/" .. table.concat(requested_path_full, "/") .. "'.\n" ..
939 "If this url belongs to an extension, make sure it is properly installed.\n" ..
940 "If the extension was recently installed, try removing the /tmp/luci-indexcache file.")
945 function createindex()
946 local controllers = { }
947 local base = "%s/controller/" % util.libpath()
950 for path in (fs.glob("%s*.lua" % base) or function() end) do
951 controllers[#controllers+1] = path
954 for path in (fs.glob("%s*/*.lua" % base) or function() end) do
955 controllers[#controllers+1] = path
959 local cachedate = fs.stat(indexcache, "mtime")
962 for _, obj in ipairs(controllers) do
963 local omtime = fs.stat(obj, "mtime")
964 realdate = (omtime and omtime > realdate) and omtime or realdate
967 if cachedate > realdate and sys.process.info("uid") == 0 then
969 sys.process.info("uid") == fs.stat(indexcache, "uid")
970 and fs.stat(indexcache, "modestr") == "rw-------",
971 "Fatal: Indexcache is not sane!"
974 index = loadfile(indexcache)()
982 for _, path in ipairs(controllers) do
983 local modname = "luci.controller." .. path:sub(#base+1, #path-4):gsub("/", ".")
984 local mod = require(modname)
986 "Invalid controller file found\n" ..
987 "The file '" .. path .. "' contains an invalid module line.\n" ..
988 "Please verify whether the module name is set to '" .. modname ..
989 "' - It must correspond to the file path!")
991 local idx = mod.index
992 if type(idx) == "function" then
998 local f = nixio.open(indexcache, "w", 600)
999 f:writeall(util.get_bytecode(index))
1004 function createtree_json()
1005 local json = require "luci.jsonc"
1014 setgroup = "string",
1017 wildcard = "boolean"
1024 for file in (fs.glob("/usr/share/luci/menu.d/*.json") or function() end) do
1025 files[#files+1] = file
1028 local st = fs.stat(file)
1030 fprint[#fprint+1] = '%x' % st.ino
1031 fprint[#fprint+1] = '%x' % st.mtime
1032 fprint[#fprint+1] = '%x' % st.size
1038 cachefile = "%s.%s.json" %{
1040 nixio.crypt(table.concat(fprint, "|"), "$1$"):sub(5):gsub("/", ".")
1043 local res = json.parse(fs.readfile(cachefile) or "")
1048 for file in (fs.glob("%s.*.json" % indexcache) or function() end) do
1053 for _, file in ipairs(files) do
1054 local data = json.parse(fs.readfile(file) or "")
1055 if type(data) == "table" then
1056 for path, spec in pairs(data) do
1057 if type(spec) == "table" then
1060 for s in path:gmatch("[^/]+") do
1062 node.wildcard = true
1066 node.children = node.children or {}
1067 node.children[s] = node.children[s] or {}
1068 node = node.children[s]
1071 if node ~= tree then
1072 for k, t in pairs(schema) do
1073 if type(spec[k]) == t then
1078 node.satisfied = check_depends(spec)
1086 fs.writefile(cachefile, json.stringify(tree))
1092 -- Build the index before if it does not exist yet.
1093 function createtree()
1099 local tree = {nodes={}, inreq=true}
1101 ctx.treecache = setmetatable({}, {__mode="v"})
1104 local scope = setmetatable({}, {__index = luci.dispatcher})
1106 for k, v in pairs(index) do
1115 function assign(path, clone, title, order)
1116 local obj = node(unpack(path))
1123 setmetatable(obj, {__index = _create_node(clone)})
1128 function entry(path, target, title, order)
1129 local c = node(unpack(path))
1134 c.module = getfenv(2)._NAME
1139 -- enabling the node.
1141 return _create_node({...})
1145 local c = _create_node({...})
1147 c.module = getfenv(2)._NAME
1153 function lookup(...)
1154 local i, path = nil, {}
1155 for i = 1, select('#', ...) do
1156 local name, arg = nil, tostring(select(i, ...))
1157 for name in arg:gmatch("[^/]+") do
1158 path[#path+1] = name
1162 for i = #path, 1, -1 do
1163 local node = context.treecache[table.concat(path, ".", 1, i)]
1164 if node and (i == #path or node.leaf) then
1165 return node, build_url(unpack(path))
1170 function _create_node(path)
1175 local name = table.concat(path, ".")
1176 local c = context.treecache[name]
1179 local last = table.remove(path)
1180 local parent = _create_node(path)
1182 c = {nodes={}, auto=true, inreq=true}
1184 parent.nodes[last] = c
1185 context.treecache[name] = c
1191 -- Subdispatchers --
1193 function firstchild()
1194 return { type = "firstchild" }
1197 function firstnode()
1198 return { type = "firstnode" }
1202 return { type = "alias", req = { ... } }
1205 function rewrite(n, ...)
1206 return { type = "rewrite", n = n, req = { ... } }
1209 function call(name, ...)
1210 return { type = "call", argv = {...}, name = name }
1213 function post_on(params, name, ...)
1223 return post_on(true, ...)
1227 function template(name)
1228 return { type = "template", view = name }
1232 return { type = "view", view = name }
1236 function _cbi(self, ...)
1237 local cbi = require "luci.cbi"
1238 local tpl = require "luci.template"
1239 local http = require "luci.http"
1241 local config = self.config or {}
1242 local maps = cbi.load(self.model, ...)
1247 for i, res in ipairs(maps) do
1248 if util.instanceof(res, cbi.SimpleForm) then
1249 io.stderr:write("Model %s returns SimpleForm but is dispatched via cbi(),\n"
1252 io.stderr:write("please change %s to use the form() action instead.\n"
1253 % table.concat(context.request, "/"))
1257 local cstate = res:parse()
1258 if cstate and (not state or cstate < state) then
1263 local function _resolve_path(path)
1264 return type(path) == "table" and build_url(unpack(path)) or path
1267 if config.on_valid_to and state and state > 0 and state < 2 then
1268 http.redirect(_resolve_path(config.on_valid_to))
1272 if config.on_changed_to and state and state > 1 then
1273 http.redirect(_resolve_path(config.on_changed_to))
1277 if config.on_success_to and state and state > 0 then
1278 http.redirect(_resolve_path(config.on_success_to))
1282 if config.state_handler then
1283 if not config.state_handler(state, maps) then
1288 http.header("X-CBI-State", state or 0)
1290 if not config.noheader then
1291 tpl.render("cbi/header", {state = state})
1296 local applymap = false
1297 local pageaction = true
1298 local parsechain = { }
1300 for i, res in ipairs(maps) do
1301 if res.apply_needed and res.parsechain then
1303 for _, c in ipairs(res.parsechain) do
1304 parsechain[#parsechain+1] = c
1309 if res.redirect then
1310 redirect = redirect or res.redirect
1313 if res.pageaction == false then
1318 messages = messages or { }
1319 messages[#messages+1] = res.message
1323 for i, res in ipairs(maps) do
1325 firstmap = (i == 1),
1326 redirect = redirect,
1327 messages = messages,
1328 pageaction = pageaction,
1329 parsechain = parsechain
1333 if not config.nofooter then
1334 tpl.render("cbi/footer", {
1336 pageaction = pageaction,
1337 redirect = redirect,
1339 autoapply = config.autoapply,
1340 trigger_apply = applymap
1345 function cbi(model, config)
1348 post = { ["cbi.submit"] = true },
1355 function arcombine(trg1, trg2)
1359 targets = {trg1, trg2}
1364 function _form(self, ...)
1365 local cbi = require "luci.cbi"
1366 local tpl = require "luci.template"
1367 local http = require "luci.http"
1369 local maps = luci.cbi.load(self.model, ...)
1373 for i, res in ipairs(maps) do
1374 local cstate = res:parse()
1375 if cstate and (not state or cstate < state) then
1380 http.header("X-CBI-State", state or 0)
1381 tpl.render("header")
1382 for i, res in ipairs(maps) do
1385 tpl.render("footer")
1388 function form(model)
1391 post = { ["cbi.submit"] = true },
1396 translate = i18n.translate
1398 -- This function does not actually translate the given argument but
1399 -- is used by build/i18n-scan.pl to find translatable entries.